about summary refs log tree commit diff
diff options
context:
space:
mode:
authorbors <bors@rust-lang.org>2023-10-04 18:54:49 +0000
committerbors <bors@rust-lang.org>2023-10-04 18:54:49 +0000
commiteea26141ec2bf07a825011af841463dec51a04c9 (patch)
treec9892ae6c0fe4d275bd8cd82abf1936dbb383653
parentf306362fb97fadcee44f68817a1d22292458138b (diff)
parentabece4faccbeb2f0985415e279bbc79ecc2c1380 (diff)
Auto merge of #115200 - rcvalle:rust-cfi-fix-115199, r=workingjubilee
Disable CFI for core and std CFI violations

Work around https://github.com/rust-lang/rust/issues/115199 by temporarily disabling CFI for core and std CFI violations to allow the user rebuild and use both core and std with CFI enabled using the Cargo build-std feature.
-rw-r--r--library/core/src/fmt/rt.rs4
-rw-r--r--library/core/src/lib.rs1
-rw-r--r--library/std/src/lib.rs2
-rw-r--r--library/std/src/sys/unix/thread_local_dtor.rs43
-rw-r--r--src/doc/unstable-book/src/compiler-flags/sanitizer.md161
5 files changed, 123 insertions, 88 deletions
diff --git a/library/core/src/fmt/rt.rs b/library/core/src/fmt/rt.rs
index d37888c27bd..5bf221b429f 100644
--- a/library/core/src/fmt/rt.rs
+++ b/library/core/src/fmt/rt.rs
@@ -133,6 +133,10 @@ impl<'a> Argument<'a> {
         Self::new(x, USIZE_MARKER)
     }
 
+    // FIXME: Transmuting formatter in new and indirectly branching to/calling
+    // it here is an explicit CFI violation.
+    #[allow(inline_no_sanitize)]
+    #[no_sanitize(cfi, kcfi)]
     #[inline(always)]
     pub(super) fn fmt(&self, f: &mut Formatter<'_>) -> Result {
         (self.formatter)(self.value, f)
diff --git a/library/core/src/lib.rs b/library/core/src/lib.rs
index ab8d9f33b08..7fa7e83a744 100644
--- a/library/core/src/lib.rs
+++ b/library/core/src/lib.rs
@@ -238,6 +238,7 @@
 #![feature(negative_impls)]
 #![feature(never_type)]
 #![feature(no_core)]
+#![feature(no_sanitize)]
 #![feature(platform_intrinsics)]
 #![feature(prelude_import)]
 #![feature(repr_simd)]
diff --git a/library/std/src/lib.rs b/library/std/src/lib.rs
index 6f78778f01a..02f4d5bc7ae 100644
--- a/library/std/src/lib.rs
+++ b/library/std/src/lib.rs
@@ -270,6 +270,7 @@
 #![feature(allow_internal_unstable)]
 #![feature(c_unwind)]
 #![feature(cfg_target_thread_local)]
+#![feature(cfi_encoding)]
 #![feature(concat_idents)]
 #![feature(const_mut_refs)]
 #![feature(const_trait_impl)]
@@ -292,6 +293,7 @@
 #![feature(needs_panic_runtime)]
 #![feature(negative_impls)]
 #![feature(never_type)]
+#![feature(no_sanitize)]
 #![feature(platform_intrinsics)]
 #![feature(prelude_import)]
 #![feature(rustc_attrs)]
diff --git a/library/std/src/sys/unix/thread_local_dtor.rs b/library/std/src/sys/unix/thread_local_dtor.rs
index fba2a676f28..e9d19766592 100644
--- a/library/std/src/sys/unix/thread_local_dtor.rs
+++ b/library/std/src/sys/unix/thread_local_dtor.rs
@@ -11,28 +11,47 @@
 // Note, however, that we run on lots older linuxes, as well as cross
 // compiling from a newer linux to an older linux, so we also have a
 // fallback implementation to use as well.
+#[allow(unexpected_cfgs)]
 #[cfg(any(target_os = "linux", target_os = "fuchsia", target_os = "redox", target_os = "hurd"))]
+// FIXME: The Rust compiler currently omits weakly function definitions (i.e.,
+// __cxa_thread_atexit_impl) and its metadata from LLVM IR.
+#[no_sanitize(cfi, kcfi)]
 pub unsafe fn register_dtor(t: *mut u8, dtor: unsafe extern "C" fn(*mut u8)) {
     use crate::mem;
     use crate::sys_common::thread_local_dtor::register_dtor_fallback;
 
+    /// This is necessary because the __cxa_thread_atexit_impl implementation
+    /// std links to by default may be a C or C++ implementation that was not
+    /// compiled using the Clang integer normalization option.
+    #[cfg(not(sanitizer_cfi_normalize_integers))]
+    #[cfi_encoding = "i"]
+    #[repr(transparent)]
+    pub struct c_int(pub libc::c_int);
+
     extern "C" {
         #[linkage = "extern_weak"]
         static __dso_handle: *mut u8;
         #[linkage = "extern_weak"]
-        static __cxa_thread_atexit_impl: *const libc::c_void;
+        static __cxa_thread_atexit_impl: Option<
+            extern "C" fn(
+                unsafe extern "C" fn(*mut libc::c_void),
+                *mut libc::c_void,
+                *mut libc::c_void,
+            ) -> c_int,
+        >;
     }
-    if !__cxa_thread_atexit_impl.is_null() {
-        type F = unsafe extern "C" fn(
-            dtor: unsafe extern "C" fn(*mut u8),
-            arg: *mut u8,
-            dso_handle: *mut u8,
-        ) -> libc::c_int;
-        mem::transmute::<*const libc::c_void, F>(__cxa_thread_atexit_impl)(
-            dtor,
-            t,
-            &__dso_handle as *const _ as *mut _,
-        );
+
+    if let Some(f) = __cxa_thread_atexit_impl {
+        unsafe {
+            f(
+                mem::transmute::<
+                    unsafe extern "C" fn(*mut u8),
+                    unsafe extern "C" fn(*mut libc::c_void),
+                >(dtor),
+                t.cast(),
+                &__dso_handle as *const _ as *mut _,
+            );
+        }
         return;
     }
     register_dtor_fallback(t, dtor);
diff --git a/src/doc/unstable-book/src/compiler-flags/sanitizer.md b/src/doc/unstable-book/src/compiler-flags/sanitizer.md
index 49389b28c8f..502853f39ae 100644
--- a/src/doc/unstable-book/src/compiler-flags/sanitizer.md
+++ b/src/doc/unstable-book/src/compiler-flags/sanitizer.md
@@ -197,22 +197,26 @@ Shadow byte legend (one shadow byte represents 8 application bytes):
 
 # ControlFlowIntegrity
 
-The LLVM Control Flow Integrity (CFI) support in the Rust compiler provides
-forward-edge control flow protection for both Rust-compiled code only and for C
-or C++ and Rust -compiled code mixed-language binaries, also known as “mixed
-binaries” (i.e., for when C or C++ and Rust -compiled code share the same
-virtual address space), by aggregating function pointers in groups identified by
-their return and parameter types.
-
-LLVM CFI can be enabled with `-Zsanitizer=cfi` and requires LTO (i.e., `-Clto`).
-Cross-language LLVM CFI can be enabled with `-Zsanitizer=cfi`, and requires the
-`-Zsanitizer-cfi-normalize-integers` option to be used with Clang
-`-fsanitize-cfi-icall-normalize-integers` for normalizing integer types, and
-proper (i.e., non-rustc) LTO (i.e., `-Clinker-plugin-lto`).
+The LLVM CFI support in the Rust compiler provides forward-edge control flow
+protection for both Rust-compiled code only and for C or C++ and Rust -compiled
+code mixed-language binaries, also known as “mixed binaries” (i.e., for when C
+or C++ and Rust -compiled code share the same virtual address space), by
+aggregating function pointers in groups identified by their return and parameter
+types.
+
+LLVM CFI can be enabled with `-Zsanitizer=cfi` and requires LTO (i.e.,
+`-Clinker-plugin-lto` or `-Clto`). Cross-language LLVM CFI can be enabled with
+`-Zsanitizer=cfi`, and requires the `-Zsanitizer-cfi-normalize-integers` option
+to be used with Clang `-fsanitize-cfi-icall-experimental-normalize-integers`
+option for cross-language LLVM CFI support, and proper (i.e., non-rustc) LTO
+(i.e., `-Clinker-plugin-lto`).
+
+It is recommended to rebuild the standard library with CFI enabled by using the
+Cargo build-std feature (i.e., `-Zbuild-std`) when enabling CFI.
 
 See the [Clang ControlFlowIntegrity documentation][clang-cfi] for more details.
 
-## Example
+## Example 1: Redirecting control flow using an indirect branch/call to an invalid destination
 
 ```rust,ignore (making doc tests pass cross-platform is hard)
 #![feature(naked_functions)]
@@ -239,7 +243,7 @@ pub extern "C" fn add_two(x: i32) {
              nop
              nop
              nop
-             lea eax, [edi+2]
+             lea eax, [rdi+2]
              ret
         ",
             options(noreturn)
@@ -258,8 +262,9 @@ fn main() {
 
     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).
+        // Offset 0 is a valid branch/call destination (i.e., the function entry
+        // point), but offsets 1-8 within the landing pad/nop block 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);
@@ -267,38 +272,40 @@ fn main() {
     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.
+Fig. 1. Redirecting control flow using an indirect branch/call to an invalid
+destination (i.e., within the body of the function).
 
 ```shell
 $ cargo run --release
    Compiling rust-cfi-1 v0.1.0 (/home/rcvalle/rust-cfi-1)
-    Finished release [optimized] target(s) in 0.76s
+    Finished release [optimized] target(s) in 0.42s
      Running `target/release/rust-cfi-1`
 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.
+Fig. 2. Build and execution of Fig. 1 with LLVM CFI disabled.
 
 ```shell
-$ RUSTFLAGS="-Zsanitizer=cfi -Cembed-bitcode=yes -Clto" cargo run --release
+$ RUSTFLAGS="-Clinker-plugin-lto -Clinker=clang -Clink-arg=-fuse-ld=lld -Zsanitizer=cfi" cargo run -Zbuild-std -Zbuild-std-features --release --target x86_64-unknown-linux-gnu
+   ...
    Compiling rust-cfi-1 v0.1.0 (/home/rcvalle/rust-cfi-1)
-    Finished release [optimized] target(s) in 3.39s
-     Running `target/release/rust-cfi-1`
+    Finished release [optimized] target(s) in 1m 08s
+     Running `target/x86_64-unknown-linux-gnu/release/rust-cfi-1`
 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.
+Fig. 3. Build and execution of Fig. 1 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).
 
+## Example 2: Redirecting control flow using an indirect branch/call to a function with a different number of parameters
+
 ```rust
 use std::mem;
 
@@ -327,39 +334,42 @@ fn main() {
     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.
+Fig. 4. Redirecting control flow using an indirect branch/call to a function
+with a different number of parameters than arguments intended/passed in the
+call/branch site.
 
 ```shell
 $ cargo run --release
    Compiling rust-cfi-2 v0.1.0 (/home/rcvalle/rust-cfi-2)
-    Finished release [optimized] target(s) in 0.76s
+    Finished release [optimized] target(s) in 0.43s
      Running `target/release/rust-cfi-2`
 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.
+Fig. 5. Build and execution of Fig. 4 with LLVM CFI disabled.
 
 ```shell
-$ RUSTFLAGS="-Cembed-bitcode=yes -Clto -Zsanitizer=cfi" cargo run --release
+$ RUSTFLAGS="-Clinker-plugin-lto -Clinker=clang -Clink-arg=-fuse-ld=lld -Zsanitizer=cfi" cargo run -Zbuild-std -Zbuild-std-features --release --target x86_64-unknown-linux-gnu
+   ...
    Compiling rust-cfi-2 v0.1.0 (/home/rcvalle/rust-cfi-2)
-    Finished release [optimized] target(s) in 3.38s
-     Running `target/release/rust-cfi-2`
+    Finished release [optimized] target(s) in 1m 08s
+     Running `target/x86_64-unknown-linux-gnu/release/rust-cfi-2`
 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.
+Fig. 6. Build and execution of Fig. 4 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
 parameters than arguments intended/passed in the call/branch site, the
 execution is also terminated (see Fig. 6).
 
+## Example 3: Redirecting control flow using an indirect branch/call to a function with different return and parameter types
+
 ```rust
 use std::mem;
 
@@ -388,42 +398,46 @@ fn main() {
     println!("The next answer is: {}", next_answer);
 }
 ```
-Fig. 7. Another modified example from the [Advanced Functions and
-Closures][rust-book-ch19-05] chapter of the [The Rust Programming
-Language][rust-book] book.
+Fig. 7. Redirecting control flow using an indirect branch/call to a function
+with different return and parameter types than the return type expected and
+arguments intended/passed at the call/branch site.
 
 ```shell
 $ cargo run --release
    Compiling rust-cfi-3 v0.1.0 (/home/rcvalle/rust-cfi-3)
-    Finished release [optimized] target(s) in 0.74s
+    Finished release [optimized] target(s) in 0.44s
      Running `target/release/rust-cfi-3`
 The answer is: 12
 With CFI enabled, you should not see the next answer
 The next answer is: 14
 $
 ```
-Fig. 8. Build and execution of the modified example with LLVM CFI disabled.
+Fig. 8. Build and execution of Fig. 7 with LLVM CFI disabled.
 
 ```shell
-$ RUSTFLAGS="-Cembed-bitcode=yes -Clto -Zsanitizer=cfi" cargo run --release
+$ RUSTFLAGS="-Clinker-plugin-lto -Clinker=clang -Clink-arg=-fuse-ld=lld -Zsanitizer=cfi" cargo run -Zbuild-std -Zbuild-std-features --release --target x86_64-unknown-linux-gnu
+   ...
    Compiling rust-cfi-3 v0.1.0 (/home/rcvalle/rust-cfi-3)
-    Finished release [optimized] target(s) in 3.40s
-     Running `target/release/rust-cfi-3`
+    Finished release [optimized] target(s) in 1m 07s
+     Running `target/x86_64-unknown-linux-gnu/release/rust-cfi-3`
 The answer is: 12
 With CFI enabled, you should not see the next answer
 Illegal instruction
 $
 ```
-Fig. 9. Build and execution of the modified example with LLVM CFI enabled.
+Fig. 9. Build and execution of Fig. 7 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 return and
 parameter types than the return type expected and arguments intended/passed in
 the call/branch site, the execution is also terminated (see Fig. 9).
 
+## Example 4: Redirecting control flow using an indirect branch/call to a function with different return and parameter types across the FFI boundary
+
 ```ignore (cannot-test-this-because-uses-custom-build)
 int
-do_twice(int (*fn)(int), int arg) {
+do_twice(int (*fn)(int), int arg)
+{
     return fn(arg) + fn(arg);
 }
 ```
@@ -459,54 +473,49 @@ fn main() {
     println!("The next answer is: {}", next_answer);
 }
 ```
-Fig. 11. Another modified example from the [Advanced Functions and
-Closures][rust-book-ch19-05] chapter of the [The Rust Programming
-Language][rust-book] book.
+Fig. 11. Redirecting control flow using an indirect branch/call to a function
+with different return and parameter types than the return type expected and
+arguments intended/passed in the call/branch site, across the FFI boundary.
 
 ```shell
 $ make
-mkdir -p target/debug
-clang -I. -Isrc -Wall -flto -fvisibility=hidden -c -emit-llvm src/foo.c -o target/debug/libfoo.bc
-llvm-ar rcs target/debug/libfoo.a target/debug/libfoo.bc
-RUSTFLAGS="-L./target/debug -Clinker-plugin-lto -Clinker=clang -Clink-arg=-fuse-ld=lld" cargo build
-   Compiling main v0.1.0 (/home/rcvalle/rust-cross-cfi-1)
-    Finished dev [unoptimized + debuginfo] target(s) in 0.45s
-$ ./target/debug/main
+mkdir -p target/release
+clang -I. -Isrc -Wall -c src/foo.c -o target/release/libfoo.o
+llvm-ar rcs target/release/libfoo.a target/release/libfoo.o
+RUSTFLAGS="-L./target/release -Clinker=clang -Clink-arg=-fuse-ld=lld" cargo build --release
+   Compiling rust-cfi-4 v0.1.0 (/home/rcvalle/rust-cfi-4)
+    Finished release [optimized] target(s) in 0.49s
+$ ./target/release/rust-cfi-4
 The answer is: 12
 With CFI enabled, you should not see the next answer
 The next answer is: 14
 $
 ```
-Fig. 12. Build and execution of the modified example with LLVM CFI disabled.
+Fig. 12. Build and execution of Figs. 10–11 with LLVM CFI disabled.
 
 ```shell
 $ make
-mkdir -p target/debug
-clang -I. -Isrc -Wall -flto -fvisibility=hidden -fsanitize=cfi -fsanitize-cfi-icall-normalize-integers -c -emit-llvm src/foo.c -o target/debug/libfoo.bc
-llvm-ar rcs target/debug/libfoo.a target/debug/libfoo.bc
-RUSTFLAGS="-L./target/debug -Clinker-plugin-lto -Clinker=clang -Clink-arg=-fuse-ld=lld -Zsanitizer=cfi -Zsanitizer-cfi-normalize-integers" cargo build
-   Compiling main v0.1.0 (/home/rcvalle/rust-cross-cfi-1)
-    Finished dev [unoptimized + debuginfo] target(s) in 0.45s
-$ ./target/debug/main
+mkdir -p target/release
+clang -I. -Isrc -Wall -flto -fsanitize=cfi -fsanitize-cfi-icall-experimental-normalize-integers -fvisibility=hidden -c -emit-llvm src/foo.c -o target/release/libfoo.bc
+llvm-ar rcs target/release/libfoo.a target/release/libfoo.bc
+RUSTFLAGS="-L./target/release -Clinker-plugin-lto -Clinker=clang -Clink-arg=-fuse-ld=lld -Zsanitizer=cfi -Zsanitizer-cfi-normalize-integers" cargo build -Zbuild-std -Zbuild-std-features --release --target x86_64-unknown-linux-gnu
+   ...
+   Compiling rust-cfi-4 v0.1.0 (/home/rcvalle/rust-cfi-4)
+    Finished release [optimized] target(s) in 1m 06s
+$ ./target/x86_64-unknown-linux-gnu/release/rust-cfi-4
 The answer is: 12
 With CFI enabled, you should not see the next answer
 Illegal instruction
 $
 ```
-Fig. 13. 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 return and
-parameter types than the return type expected and arguments intended/passed in
-the call/branch site, even across the FFI boundary and for extern "C" function
-types indirectly called (i.e., callbacks/function pointers) across the FFI
-boundary, in C or C++ and Rust -compiled code mixed-language binaries, also
-known as “mixed binaries” (i.e., for when C or C++ and Rust -compiled code share
-the same virtual address space), the execution is also terminated (see Fig. 13).
-
-
-[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
+Fig. 13. Build and execution of FIgs. 10–11 with LLVM CFI enabled.
+
+When LLVM CFI is enabled, if there are any attempts to redirect control flow
+using an indirect branch/call to a function with different return and parameter
+types than the return type expected and arguments intended/passed in the
+call/branch site, even across the FFI boundary and for extern "C" function types
+indirectly called (i.e., callbacks/function pointers) across the FFI boundary,
+the execution is also terminated (see Fig. 13).
 
 # HWAddressSanitizer