Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Disable CFI for core and std CFI violations #115200

Merged
merged 2 commits into from
Oct 4, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions library/core/src/fmt/rt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
1 change: 1 addition & 0 deletions library/core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand Down
2 changes: 2 additions & 0 deletions library/std/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand All @@ -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)]
Expand Down
43 changes: 31 additions & 12 deletions library/std/src/sys/unix/thread_local_dtor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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"]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why the explicit encoding? i would expect c_int to be an integer already as its repr(transparent).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the encoding of the C int type, which from a CFI perspective is distinct from i32 or any other rust type. int and long can be the same size and in that case would be the same rust type, but CFI does distinguish between them unless -Zcfi-normalize-integers is used to only record the integer type size rather than it's name.

#[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,
>;
workingjubilee marked this conversation as resolved.
Show resolved Hide resolved
}
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 _,
rcvalle marked this conversation as resolved.
Show resolved Hide resolved
);
}
return;
}
register_dtor_fallback(t, dtor);
Expand Down
161 changes: 85 additions & 76 deletions src/doc/unstable-book/src/compiler-flags/sanitizer.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand All @@ -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)
Expand All @@ -258,47 +262,50 @@ 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);

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;

Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -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);
}
```
Expand Down Expand Up @@ -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.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Fig. 13. Build and execution of FIgs. 10–11 with LLVM CFI enabled.
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

Expand Down
Loading