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.
This commit is contained in:
bors 2023-10-04 18:54:49 +00:00
commit eea26141ec
5 changed files with 121 additions and 86 deletions

View File

@ -133,6 +133,10 @@ impl<'a> Argument<'a> {
Self::new(x, USIZE_MARKER) 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)] #[inline(always)]
pub(super) fn fmt(&self, f: &mut Formatter<'_>) -> Result { pub(super) fn fmt(&self, f: &mut Formatter<'_>) -> Result {
(self.formatter)(self.value, f) (self.formatter)(self.value, f)

View File

@ -238,6 +238,7 @@
#![feature(negative_impls)] #![feature(negative_impls)]
#![feature(never_type)] #![feature(never_type)]
#![feature(no_core)] #![feature(no_core)]
#![feature(no_sanitize)]
#![feature(platform_intrinsics)] #![feature(platform_intrinsics)]
#![feature(prelude_import)] #![feature(prelude_import)]
#![feature(repr_simd)] #![feature(repr_simd)]

View File

@ -270,6 +270,7 @@
#![feature(allow_internal_unstable)] #![feature(allow_internal_unstable)]
#![feature(c_unwind)] #![feature(c_unwind)]
#![feature(cfg_target_thread_local)] #![feature(cfg_target_thread_local)]
#![feature(cfi_encoding)]
#![feature(concat_idents)] #![feature(concat_idents)]
#![feature(const_mut_refs)] #![feature(const_mut_refs)]
#![feature(const_trait_impl)] #![feature(const_trait_impl)]
@ -292,6 +293,7 @@
#![feature(needs_panic_runtime)] #![feature(needs_panic_runtime)]
#![feature(negative_impls)] #![feature(negative_impls)]
#![feature(never_type)] #![feature(never_type)]
#![feature(no_sanitize)]
#![feature(platform_intrinsics)] #![feature(platform_intrinsics)]
#![feature(prelude_import)] #![feature(prelude_import)]
#![feature(rustc_attrs)] #![feature(rustc_attrs)]

View File

@ -11,28 +11,47 @@
// Note, however, that we run on lots older linuxes, as well as cross // 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 // compiling from a newer linux to an older linux, so we also have a
// fallback implementation to use as well. // fallback implementation to use as well.
#[allow(unexpected_cfgs)]
#[cfg(any(target_os = "linux", target_os = "fuchsia", target_os = "redox", target_os = "hurd"))] #[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)) { pub unsafe fn register_dtor(t: *mut u8, dtor: unsafe extern "C" fn(*mut u8)) {
use crate::mem; use crate::mem;
use crate::sys_common::thread_local_dtor::register_dtor_fallback; 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" { extern "C" {
#[linkage = "extern_weak"] #[linkage = "extern_weak"]
static __dso_handle: *mut u8; static __dso_handle: *mut u8;
#[linkage = "extern_weak"] #[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( if let Some(f) = __cxa_thread_atexit_impl {
dtor: unsafe extern "C" fn(*mut u8), unsafe {
arg: *mut u8, f(
dso_handle: *mut u8, mem::transmute::<
) -> libc::c_int; unsafe extern "C" fn(*mut u8),
mem::transmute::<*const libc::c_void, F>(__cxa_thread_atexit_impl)( unsafe extern "C" fn(*mut libc::c_void),
dtor, >(dtor),
t, t.cast(),
&__dso_handle as *const _ as *mut _, &__dso_handle as *const _ as *mut _,
); );
}
return; return;
} }
register_dtor_fallback(t, dtor); register_dtor_fallback(t, dtor);

View File

@ -197,22 +197,26 @@ Shadow byte legend (one shadow byte represents 8 application bytes):
# ControlFlowIntegrity # ControlFlowIntegrity
The LLVM Control Flow Integrity (CFI) support in the Rust compiler provides The LLVM CFI support in the Rust compiler provides forward-edge control flow
forward-edge control flow protection for both Rust-compiled code only and for C protection for both Rust-compiled code only and for C or C++ and Rust -compiled
or C++ and Rust -compiled code mixed-language binaries, also known as “mixed code mixed-language binaries, also known as “mixed binaries” (i.e., for when C
binaries” (i.e., for when C or C++ and Rust -compiled code share the same or C++ and Rust -compiled code share the same virtual address space), by
virtual address space), by aggregating function pointers in groups identified by aggregating function pointers in groups identified by their return and parameter
their return and parameter types. types.
LLVM CFI can be enabled with `-Zsanitizer=cfi` and requires LTO (i.e., `-Clto`). LLVM CFI can be enabled with `-Zsanitizer=cfi` and requires LTO (i.e.,
Cross-language LLVM CFI can be enabled with `-Zsanitizer=cfi`, and requires the `-Clinker-plugin-lto` or `-Clto`). Cross-language LLVM CFI can be enabled with
`-Zsanitizer-cfi-normalize-integers` option to be used with Clang `-Zsanitizer=cfi`, and requires the `-Zsanitizer-cfi-normalize-integers` option
`-fsanitize-cfi-icall-normalize-integers` for normalizing integer types, and to be used with Clang `-fsanitize-cfi-icall-experimental-normalize-integers`
proper (i.e., non-rustc) LTO (i.e., `-Clinker-plugin-lto`). 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. 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) ```rust,ignore (making doc tests pass cross-platform is hard)
#![feature(naked_functions)] #![feature(naked_functions)]
@ -239,7 +243,7 @@ pub extern "C" fn add_two(x: i32) {
nop nop
nop nop
nop nop
lea eax, [edi+2] lea eax, [rdi+2]
ret ret
", ",
options(noreturn) options(noreturn)
@ -258,8 +262,9 @@ fn main() {
println!("With CFI enabled, you should not see the next answer"); println!("With CFI enabled, you should not see the next answer");
let f: fn(i32) -> i32 = unsafe { let f: fn(i32) -> i32 = unsafe {
// Offsets 0-8 make it land in the landing pad/nop block, and offsets 1-8 are // Offset 0 is a valid branch/call destination (i.e., the function entry
// invalid branch/call destinations (i.e., within the body of the function). // 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)) mem::transmute::<*const u8, fn(i32) -> i32>((add_two as *const u8).offset(5))
}; };
let next_answer = do_twice(f, 5); let next_answer = do_twice(f, 5);
@ -267,38 +272,40 @@ fn main() {
println!("The next answer is: {}", next_answer); println!("The next answer is: {}", next_answer);
} }
``` ```
Fig. 1.Modified example from the [Advanced Functions and Fig. 1.Redirecting control flow using an indirect branch/call to an invalid
Closures][rust-book-ch19-05] chapter of the [The Rust Programming destination (i.e., within the body of the function).
Language][rust-book] book.
```shell ```shell
$ cargo run --release $ cargo run --release
Compiling rust-cfi-1 v0.1.0 (/home/rcvalle/rust-cfi-1) 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` Running `target/release/rust-cfi-1`
The answer is: 12 The answer is: 12
With CFI enabled, you should not see the next answer With CFI enabled, you should not see the next answer
The next answer is: 14 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 ```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) Compiling rust-cfi-1 v0.1.0 (/home/rcvalle/rust-cfi-1)
Finished release [optimized] target(s) in 3.39s Finished release [optimized] target(s) in 1m 08s
Running `target/release/rust-cfi-1` Running `target/x86_64-unknown-linux-gnu/release/rust-cfi-1`
The answer is: 12 The answer is: 12
With CFI enabled, you should not see the next answer With CFI enabled, you should not see the next answer
Illegal instruction 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 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 flow using an indirect branch/call to an invalid destination, the execution is
terminated (see Fig. 3). terminated (see Fig. 3).
## Example 2: Redirecting control flow using an indirect branch/call to a function with a different number of parameters
```rust ```rust
use std::mem; use std::mem;
@ -327,39 +334,42 @@ fn main() {
println!("The next answer is: {}", next_answer); println!("The next answer is: {}", next_answer);
} }
``` ```
Fig. 4.Another modified example from the [Advanced Functions and Fig. 4.Redirecting control flow using an indirect branch/call to a function
Closures][rust-book-ch19-05] chapter of the [The Rust Programming with a different number of parameters than arguments intended/passed in the
Language][rust-book] book. call/branch site.
```shell ```shell
$ cargo run --release $ cargo run --release
Compiling rust-cfi-2 v0.1.0 (/home/rcvalle/rust-cfi-2) 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` Running `target/release/rust-cfi-2`
The answer is: 12 The answer is: 12
With CFI enabled, you should not see the next answer With CFI enabled, you should not see the next answer
The next answer is: 14 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 ```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) Compiling rust-cfi-2 v0.1.0 (/home/rcvalle/rust-cfi-2)
Finished release [optimized] target(s) in 3.38s Finished release [optimized] target(s) in 1m 08s
Running `target/release/rust-cfi-2` Running `target/x86_64-unknown-linux-gnu/release/rust-cfi-2`
The answer is: 12 The answer is: 12
With CFI enabled, you should not see the next answer With CFI enabled, you should not see the next answer
Illegal instruction 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 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 flow using an indirect branch/call to a function with different number of
parameters than arguments intended/passed in the call/branch site, the parameters than arguments intended/passed in the call/branch site, the
execution is also terminated (see Fig. 6). 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 ```rust
use std::mem; use std::mem;
@ -388,42 +398,46 @@ fn main() {
println!("The next answer is: {}", next_answer); println!("The next answer is: {}", next_answer);
} }
``` ```
Fig. 7.Another modified example from the [Advanced Functions and Fig. 7.Redirecting control flow using an indirect branch/call to a function
Closures][rust-book-ch19-05] chapter of the [The Rust Programming with different return and parameter types than the return type expected and
Language][rust-book] book. arguments intended/passed at the call/branch site.
```shell ```shell
$ cargo run --release $ cargo run --release
Compiling rust-cfi-3 v0.1.0 (/home/rcvalle/rust-cfi-3) 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` Running `target/release/rust-cfi-3`
The answer is: 12 The answer is: 12
With CFI enabled, you should not see the next answer With CFI enabled, you should not see the next answer
The next answer is: 14 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 ```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) Compiling rust-cfi-3 v0.1.0 (/home/rcvalle/rust-cfi-3)
Finished release [optimized] target(s) in 3.40s Finished release [optimized] target(s) in 1m 07s
Running `target/release/rust-cfi-3` Running `target/x86_64-unknown-linux-gnu/release/rust-cfi-3`
The answer is: 12 The answer is: 12
With CFI enabled, you should not see the next answer With CFI enabled, you should not see the next answer
Illegal instruction 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 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 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 parameter types than the return type expected and arguments intended/passed in
the call/branch site, the execution is also terminated (see Fig. 9). 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) ```ignore (cannot-test-this-because-uses-custom-build)
int int
do_twice(int (*fn)(int), int arg) { do_twice(int (*fn)(int), int arg)
{
return fn(arg) + fn(arg); return fn(arg) + fn(arg);
} }
``` ```
@ -459,54 +473,49 @@ fn main() {
println!("The next answer is: {}", next_answer); println!("The next answer is: {}", next_answer);
} }
``` ```
Fig. 11.Another modified example from the [Advanced Functions and Fig. 11.Redirecting control flow using an indirect branch/call to a function
Closures][rust-book-ch19-05] chapter of the [The Rust Programming with different return and parameter types than the return type expected and
Language][rust-book] book. arguments intended/passed in the call/branch site, across the FFI boundary.
```shell ```shell
$ make $ make
mkdir -p target/debug mkdir -p target/release
clang -I. -Isrc -Wall -flto -fvisibility=hidden -c -emit-llvm src/foo.c -o target/debug/libfoo.bc clang -I. -Isrc -Wall -c src/foo.c -o target/release/libfoo.o
llvm-ar rcs target/debug/libfoo.a target/debug/libfoo.bc llvm-ar rcs target/release/libfoo.a target/release/libfoo.o
RUSTFLAGS="-L./target/debug -Clinker-plugin-lto -Clinker=clang -Clink-arg=-fuse-ld=lld" cargo build RUSTFLAGS="-L./target/release -Clinker=clang -Clink-arg=-fuse-ld=lld" cargo build --release
Compiling main v0.1.0 (/home/rcvalle/rust-cross-cfi-1) Compiling rust-cfi-4 v0.1.0 (/home/rcvalle/rust-cfi-4)
Finished dev [unoptimized + debuginfo] target(s) in 0.45s Finished release [optimized] target(s) in 0.49s
$ ./target/debug/main $ ./target/release/rust-cfi-4
The answer is: 12 The answer is: 12
With CFI enabled, you should not see the next answer With CFI enabled, you should not see the next answer
The next answer is: 14 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. 1011 with LLVM CFI disabled.
```shell ```shell
$ make $ make
mkdir -p target/debug mkdir -p target/release
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 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/debug/libfoo.a target/debug/libfoo.bc llvm-ar rcs target/release/libfoo.a target/release/libfoo.bc
RUSTFLAGS="-L./target/debug -Clinker-plugin-lto -Clinker=clang -Clink-arg=-fuse-ld=lld -Zsanitizer=cfi -Zsanitizer-cfi-normalize-integers" cargo build 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 main v0.1.0 (/home/rcvalle/rust-cross-cfi-1) ...
Finished dev [unoptimized + debuginfo] target(s) in 0.45s Compiling rust-cfi-4 v0.1.0 (/home/rcvalle/rust-cfi-4)
$ ./target/debug/main Finished release [optimized] target(s) in 1m 06s
$ ./target/x86_64-unknown-linux-gnu/release/rust-cfi-4
The answer is: 12 The answer is: 12
With CFI enabled, you should not see the next answer With CFI enabled, you should not see the next answer
Illegal instruction Illegal instruction
$ $
``` ```
Fig. 13.Build and execution of the modified example with LLVM CFI enabled. Fig. 13.Build and execution of FIgs. 1011 with LLVM CFI enabled.
When LLVM CFI is enabled, if there are any attempts to change/hijack control When LLVM CFI is enabled, if there are any attempts to redirect control flow
flow using an indirect branch/call to a function with different return and using an indirect branch/call to a function with different return and parameter
parameter types than the return type expected and arguments intended/passed in types than the return type expected and arguments intended/passed in the
the call/branch site, even across the FFI boundary and for extern "C" function call/branch site, even across the FFI boundary and for extern "C" function types
types indirectly called (i.e., callbacks/function pointers) across the FFI indirectly called (i.e., callbacks/function pointers) across the FFI boundary,
boundary, in C or C++ and Rust -compiled code mixed-language binaries, also the execution is also terminated (see Fig. 13).
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
# HWAddressSanitizer # HWAddressSanitizer