diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/doc/rustc/src/exploit-mitigations.md | 40 | ||||
| -rw-r--r-- | src/doc/unstable-book/src/compiler-flags/sanitizer.md | 181 | ||||
| -rw-r--r-- | src/test/codegen/sanitizer_cfi_add_canonical_jump_tables_flag.rs | 14 | ||||
| -rw-r--r-- | src/test/codegen/sanitizer_cfi_emit_type_checks.rs | 24 | ||||
| -rw-r--r-- | src/test/codegen/sanitizer_cfi_emit_type_metadata.rs | 31 |
5 files changed, 271 insertions, 19 deletions
diff --git a/src/doc/rustc/src/exploit-mitigations.md b/src/doc/rustc/src/exploit-mitigations.md index 70df5170b21..fa38dd54d60 100644 --- a/src/doc/rustc/src/exploit-mitigations.md +++ b/src/doc/rustc/src/exploit-mitigations.md @@ -123,9 +123,9 @@ equivalent. <tr> <td>Forward-edge control flow protection </td> - <td>No + <td>Yes </td> - <td> + <td>Nightly </td> </tr> <tr> @@ -465,24 +465,27 @@ implementations such as [LLVM ControlFlowIntegrity commercially available [grsecurity/PaX Reuse Attack Protector (RAP)](https://grsecurity.net/rap_faq). -The Rust compiler does not support forward-edge control flow protection on -Linux<sup id="fnref:6" role="doc-noteref"><a href="#fn:6" -class="footnote">6</a></sup>. There is work currently ongoing to add support -for the [sanitizers](https://github.com/google/sanitizers)[40], which may or -may not include support for LLVM CFI. +The Rust compiler supports forward-edge control flow protection on nightly +builds[40]-[41] <sup id="fnref:6" role="doc-noteref"><a href="#fn:6" +class="footnote">6</a></sup>. ```text -$ readelf -s target/release/hello-rust | grep __cfi_init +$ readelf -s -W target/debug/rust-cfi | grep "\.cfi" + 12: 0000000000005170 46 FUNC LOCAL DEFAULT 14 _RNvCsjaOHoaNjor6_8rust_cfi7add_one.cfi + 15: 00000000000051a0 16 FUNC LOCAL DEFAULT 14 _RNvCsjaOHoaNjor6_8rust_cfi7add_two.cfi + 17: 0000000000005270 396 FUNC LOCAL DEFAULT 14 _RNvCsjaOHoaNjor6_8rust_cfi4main.cfi +... ``` -Fig. 15. Checking if LLVM CFI is enabled for a given binary. +Fig. 15. Checking if LLVM CFI is enabled for a given binary[41]. -The presence of the `__cfi_init` symbol (and references to `__cfi_check`) -indicates that LLVM CFI (i.e., forward-edge control flow protection) is -enabled for a given binary. Conversely, the absence of the `__cfi_init` -symbol (and references to `__cfi_check`) indicates that LLVM CFI is not -enabled for a given binary (see Fig. 15). +The presence of symbols suffixed with ".cfi" or the `__cfi_init` symbol (and +references to `__cfi_check`) indicates that LLVM CFI (i.e., forward-edge control +flow protection) is enabled for a given binary. Conversely, the absence of +symbols suffixed with ".cfi" or the `__cfi_init` symbol (and references to +`__cfi_check`) indicates that LLVM CFI is not enabled for a given binary (see +Fig. 15). -<small id="fn:6">6\. It supports Control Flow Guard (CFG) on Windows (see +<small id="fn:6">6\. It also supports Control Flow Guard (CFG) on Windows (see <https://github.com/rust-lang/rust/issues/68793>). <a href="#fnref:6" class="reversefootnote" role="doc-backlink">↩</a></small> @@ -689,5 +692,8 @@ defaults (unrelated to `READ_IMPLIES_EXEC`). 39. A. Crichton. “Remove the alloc\_jemalloc crate #55238.” GitHub. <https://github.com/rust-lang/rust/pull/55238>. -40. J. Aparicio. 2017. “Tracking issue for sanitizer support #39699.” - <https://github.com/rust-lang/rust/issues/39699>. +40. R. de C Valle. “Tracking Issue for LLVM Control Flow Integrity (CFI) Support + for Rust #89653.” GitHub. <https://github.com/rust-lang/rust/issues/89653>. + +41. “ControlFlowIntegrity.” The Rust Unstable Book. + <https://doc.rust-lang.org/beta/unstable-book/compiler-flags/sanitizer.html#controlflowintegrity>. diff --git a/src/doc/unstable-book/src/compiler-flags/sanitizer.md b/src/doc/unstable-book/src/compiler-flags/sanitizer.md index 29a267053b4..b3dbc9a9956 100644 --- a/src/doc/unstable-book/src/compiler-flags/sanitizer.md +++ b/src/doc/unstable-book/src/compiler-flags/sanitizer.md @@ -1,19 +1,24 @@ # `sanitizer` -The tracking issue for this feature is: [#39699](https://github.com/rust-lang/rust/issues/39699). +The tracking issues for this feature are: + +* [#39699](https://github.com/rust-lang/rust/issues/39699). +* [#89653](https://github.com/rust-lang/rust/issues/89653). ------------------------ This feature allows for use of one of following sanitizers: * [AddressSanitizer][clang-asan] a fast memory error detector. +* [ControlFlowIntegrity][clang-cfi] LLVM Control Flow Integrity (CFI) provides + forward-edge control flow protection. * [HWAddressSanitizer][clang-hwasan] a memory error detector similar to AddressSanitizer, but based on partial hardware assistance. * [LeakSanitizer][clang-lsan] a run-time memory leak detector. * [MemorySanitizer][clang-msan] a detector of uninitialized reads. * [ThreadSanitizer][clang-tsan] a fast data race detector. -To enable a sanitizer compile with `-Zsanitizer=address`, +To enable a sanitizer compile with `-Zsanitizer=address`,`-Zsanitizer=cfi`, `-Zsanitizer=hwaddress`, `-Zsanitizer=leak`, `-Zsanitizer=memory` or `-Zsanitizer=thread`. @@ -177,6 +182,176 @@ Shadow byte legend (one shadow byte represents 8 application bytes): ==39249==ABORTING ``` +# ControlFlowIntegrity + +The LLVM Control Flow Integrity (CFI) support in the Rust compiler initially +provides forward-edge control flow protection for Rust-compiled code only by +aggregating function pointers in groups identified by their number of arguments. + +Forward-edge control flow protection for C or C++ and Rust -compiled code "mixed +binaries" (i.e., for when C or C++ and Rust -compiled code share the same +virtual address space) will be provided in later work by defining and using +compatible type identifiers (see Type metadata in the design document in the +tracking issue [#89653](https://github.com/rust-lang/rust/issues/89653)). + +LLVM CFI can be enabled with -Zsanitizer=cfi and requires LTO (i.e., -Clto). + +## Example + +```text +#![feature(asm, naked_functions)] + +use std::mem; + +fn add_one(x: i32) -> i32 { + x + 1 +} + +#[naked] +pub extern "C" fn add_two(x: i32) { + // x + 2 preceeded by a landing pad/nop block + unsafe { + asm!( + " + nop + nop + nop + nop + nop + nop + nop + nop + nop + lea rax, [rdi+2] + ret + ", + options(noreturn) + ); + } +} + +fn do_twice(f: fn(i32) -> i32, arg: i32) -> i32 { + f(arg) + f(arg) +} + +fn main() { + let answer = do_twice(add_one, 5); + + println!("The answer is: {}", answer); + + println!("With CFI enabled, you should not see the next answer"); + let f: fn(i32) -> i32 = unsafe { + // Offsets 0-8 make it land in the landing pad/nop block, and offsets 1-8 are + // invalid branch/call destinations (i.e., within the body of the function). + mem::transmute::<*const u8, fn(i32) -> i32>((add_two as *const u8).offset(5)) + }; + let next_answer = do_twice(f, 5); + + println!("The next answer is: {}", next_answer); +} +``` +Fig. 1. Modified example from the [Advanced Functions and +Closures][rust-book-ch19-05] chapter of the [The Rust Programming +Language][rust-book] book. + +[//]: # (FIXME: Replace with output from cargo using nightly when #89652 is merged) + +```shell +$ rustc rust_cfi.rs -o rust_cfi +$ ./rust_cfi +The answer is: 12 +With CFI enabled, you should not see the next answer +The next answer is: 14 +$ +``` +Fig. 2. Build and execution of the modified example with LLVM CFI disabled. + +[//]: # (FIXME: Replace with output from cargo using nightly when #89652 is merged) + +```shell +$ rustc -Clto -Zsanitizer=cfi rust_cfi.rs -o rust_cfi +$ ./rust_cfi +The answer is: 12 +With CFI enabled, you should not see the next answer +Illegal instruction +$ +``` +Fig. 3. Build and execution of the modified example with LLVM CFI enabled. + +When LLVM CFI is enabled, if there are any attempts to change/hijack control +flow using an indirect branch/call to an invalid destination, the execution is +terminated (see Fig. 3). + +```rust +use std::mem; + +fn add_one(x: i32) -> i32 { + x + 1 +} + +fn add_two(x: i32, _y: i32) -> i32 { + x + 2 +} + +fn do_twice(f: fn(i32) -> i32, arg: i32) -> i32 { + f(arg) + f(arg) +} + +fn main() { + let answer = do_twice(add_one, 5); + + println!("The answer is: {}", answer); + + println!("With CFI enabled, you should not see the next answer"); + let f: fn(i32) -> i32 = + unsafe { mem::transmute::<*const u8, fn(i32) -> i32>(add_two as *const u8) }; + let next_answer = do_twice(f, 5); + + println!("The next answer is: {}", next_answer); +} +``` +Fig. 4. Another modified example from the [Advanced Functions and +Closures][rust-book-ch19-05] chapter of the [The Rust Programming +Language][rust-book] book. + +[//]: # (FIXME: Replace with output from cargo using nightly when #89652 is merged) + +```shell +$ rustc rust_cfi.rs -o rust_cfi +$ ./rust_cfi +The answer is: 12 +With CFI enabled, you should not see the next answer +The next answer is: 14 +$ +``` +Fig. 5. Build and execution of the modified example with LLVM CFI disabled. + +[//]: # (FIXME: Replace with output from cargo using nightly when #89652 is merged) + +```shell +$ rustc -Clto -Zsanitizer=cfi rust_cfi.rs -o rust_cfi +$ ./rust_cfi +The answer is: 12 +With CFI enabled, you should not see the next answer +Illegal instruction +$ +``` +Fig. 6. Build and execution of the modified example with LLVM CFI enabled. + +When LLVM CFI is enabled, if there are any attempts to change/hijack control +flow using an indirect branch/call to a function with different number of +arguments than intended/passed in the call/branch site, the execution is also +terminated (see Fig. 6). + +Forward-edge control flow protection not only by aggregating function pointers +in groups identified by their number of arguments, but also their argument +types, will also be provided in later work by defining and using compatible type +identifiers (see Type metadata in the design document in the tracking +issue [#89653](https://github.com/rust-lang/rust/issues/89653)). + +[rust-book-ch19-05]: https://doc.rust-lang.org/book/ch19-05-advanced-functions-and-closures.html +[rust-book]: https://doc.rust-lang.org/book/title-page.html + # HWAddressSanitizer HWAddressSanitizer is a newer variant of AddressSanitizer that consumes much @@ -404,12 +579,14 @@ Sanitizers produce symbolized stacktraces when llvm-symbolizer binary is in `PAT * [Sanitizers project page](https://github.com/google/sanitizers/wiki/) * [AddressSanitizer in Clang][clang-asan] +* [ControlFlowIntegrity in Clang][clang-cfi] * [HWAddressSanitizer in Clang][clang-hwasan] * [LeakSanitizer in Clang][clang-lsan] * [MemorySanitizer in Clang][clang-msan] * [ThreadSanitizer in Clang][clang-tsan] [clang-asan]: https://clang.llvm.org/docs/AddressSanitizer.html +[clang-cfi]: https://clang.llvm.org/docs/ControlFlowIntegrity.html [clang-hwasan]: https://clang.llvm.org/docs/HardwareAssistedAddressSanitizerDesign.html [clang-lsan]: https://clang.llvm.org/docs/LeakSanitizer.html [clang-msan]: https://clang.llvm.org/docs/MemorySanitizer.html diff --git a/src/test/codegen/sanitizer_cfi_add_canonical_jump_tables_flag.rs b/src/test/codegen/sanitizer_cfi_add_canonical_jump_tables_flag.rs new file mode 100644 index 00000000000..68f81808861 --- /dev/null +++ b/src/test/codegen/sanitizer_cfi_add_canonical_jump_tables_flag.rs @@ -0,0 +1,14 @@ +// Verifies that "CFI Canonical Jump Tables" module flag is added. +// +// ignore-windows +// needs-sanitizer-cfi +// only-aarch64 +// only-x86_64 +// compile-flags: -Clto -Zsanitizer=cfi + +#![crate_type="lib"] + +pub fn foo() { +} + +// CHECK: !{{[0-9]+}} = !{i32 2, !"CFI Canonical Jump Tables", i32 1} diff --git a/src/test/codegen/sanitizer_cfi_emit_type_checks.rs b/src/test/codegen/sanitizer_cfi_emit_type_checks.rs new file mode 100644 index 00000000000..9ed0422ceff --- /dev/null +++ b/src/test/codegen/sanitizer_cfi_emit_type_checks.rs @@ -0,0 +1,24 @@ +// Verifies that pointer type membership tests for indirect calls are emitted. +// +// ignore-windows +// needs-sanitizer-cfi +// only-aarch64 +// only-x86_64 +// compile-flags: -Clto -Cno-prepopulate-passes -Zsanitizer=cfi + +#![crate_type="lib"] + +pub fn foo(f: fn(i32) -> i32, arg: i32) -> i32 { + // CHECK-LABEL: define{{.*}}foo{{.*}}!type !{{[0-9]+}} + // CHECK: start: + // CHECK-NEXT: %0 = bitcast i32 (i32)* %f to i8* + // CHECK-NEXT: %1 = call i1 @llvm.type.test(i8* %0, metadata !"{{[[:print:]]+}}") + // CHECK-NEXT: br i1 %1, label %type_test.pass, label %type_test.fail + // CHECK: type_test.pass: + // CHECK-NEXT: %2 = call i32 %f(i32 %arg) + // CHECK-NEXT: br label %bb1 + // CHECK: type_test.fail: + // CHECK-NEXT: call void @llvm.trap() + // CHECK-NEXT: unreachable + f(arg) +} diff --git a/src/test/codegen/sanitizer_cfi_emit_type_metadata.rs b/src/test/codegen/sanitizer_cfi_emit_type_metadata.rs new file mode 100644 index 00000000000..96fced47e78 --- /dev/null +++ b/src/test/codegen/sanitizer_cfi_emit_type_metadata.rs @@ -0,0 +1,31 @@ +// Verifies that type metadata for functions are emitted. +// +// ignore-windows +// needs-sanitizer-cfi +// only-aarch64 +// only-x86_64 +// compile-flags: -Clto -Cno-prepopulate-passes -Zsanitizer=cfi + +#![crate_type="lib"] + +pub fn foo(f: fn(i32) -> i32, arg: i32) -> i32 { + // CHECK-LABEL: define{{.*}}foo{{.*}}!type !{{[0-9]+}} + // CHECK: %1 = call i1 @llvm.type.test(i8* %0, metadata !"typeid1") + f(arg) +} + +pub fn bar(f: fn(i32, i32) -> i32, arg1: i32, arg2: i32) -> i32 { + // CHECK-LABEL: define{{.*}}bar{{.*}}!type !{{[0-9]+}} + // CHECK: %1 = call i1 @llvm.type.test(i8* %0, metadata !"typeid2") + f(arg1, arg2) +} + +pub fn baz(f: fn(i32, i32, i32) -> i32, arg1: i32, arg2: i32, arg3: i32) -> i32 { + // CHECK-LABEL: define{{.*}}baz{{.*}}!type !{{[0-9]+}} + // CHECK: %1 = call i1 @llvm.type.test(i8* %0, metadata !"typeid3") + f(arg1, arg2, arg3) +} + +// CHECK: !{{[0-9]+}} = !{i64 0, !"typeid2"} +// CHECK: !{{[0-9]+}} = !{i64 0, !"typeid3"} +// CHECK: !{{[0-9]+}} = !{i64 0, !"typeid4"} |
