From 8928f0a54be8ff229ea7f0c3cb3562557c40b5c9 Mon Sep 17 00:00:00 2001 From: Ramon de C Valle Date: Thu, 30 Apr 2026 19:37:04 -0700 Subject: [PATCH] CFI: Fix LTO for `#![no_builtins]` crates with CFI Fixes LTO for `#![no_builtins]` crates with CFI enabled by using rustc's `EmitObj::Bitcode` path (and emitting LLVM bitcode in the `.o` for linker-based LTO). --- compiler/rustc_codegen_ssa/src/back/write.rs | 28 +++- .../sanitizer-cfi-build-std-clang/Cargo.toml | 10 ++ .../Cargo.toml | 8 + .../cross-lang-cfi-types-crate-abort/build.rs | 61 +++++++ .../src/foo.c | 5 + .../src/main.rs | 36 ++++ .../Cargo.toml | 8 + .../build.rs | 61 +++++++ .../src/foo.c | 23 +++ .../src/main.rs | 67 ++++++++ .../Cargo.toml | 11 ++ .../build.rs | 62 +++++++ .../src/foo.c | 5 + .../src/main.rs | 34 ++++ .../Cargo.toml | 11 ++ .../build.rs | 62 +++++++ .../src/foo.c | 23 +++ .../src/main.rs | 89 ++++++++++ .../indirect-arity-mismatch-abort/Cargo.toml | 4 + .../indirect-arity-mismatch-abort/src/main.rs | 30 ++++ .../indirect-type-mismatch-abort/Cargo.toml | 4 + .../indirect-type-mismatch-abort/src/main.rs | 30 ++++ .../invalid-branch-target-abort/Cargo.toml | 4 + .../invalid-branch-target-abort/src/main.rs | 50 ++++++ .../sanitizer-cfi-build-std-clang/rmake.rs | 154 ++++++++++++++++++ .../cfi/builds-with-no-builtins-crates.rs | 22 +++ 26 files changed, 893 insertions(+), 9 deletions(-) create mode 100644 tests/run-make-cargo/sanitizer-cfi-build-std-clang/Cargo.toml create mode 100644 tests/run-make-cargo/sanitizer-cfi-build-std-clang/cross-lang-cfi-types-crate-abort/Cargo.toml create mode 100644 tests/run-make-cargo/sanitizer-cfi-build-std-clang/cross-lang-cfi-types-crate-abort/build.rs create mode 100644 tests/run-make-cargo/sanitizer-cfi-build-std-clang/cross-lang-cfi-types-crate-abort/src/foo.c create mode 100644 tests/run-make-cargo/sanitizer-cfi-build-std-clang/cross-lang-cfi-types-crate-abort/src/main.rs create mode 100644 tests/run-make-cargo/sanitizer-cfi-build-std-clang/cross-lang-cfi-types-crate-not-abort/Cargo.toml create mode 100644 tests/run-make-cargo/sanitizer-cfi-build-std-clang/cross-lang-cfi-types-crate-not-abort/build.rs create mode 100644 tests/run-make-cargo/sanitizer-cfi-build-std-clang/cross-lang-cfi-types-crate-not-abort/src/foo.c create mode 100644 tests/run-make-cargo/sanitizer-cfi-build-std-clang/cross-lang-cfi-types-crate-not-abort/src/main.rs create mode 100644 tests/run-make-cargo/sanitizer-cfi-build-std-clang/cross-lang-integer-normalization-abort/Cargo.toml create mode 100644 tests/run-make-cargo/sanitizer-cfi-build-std-clang/cross-lang-integer-normalization-abort/build.rs create mode 100644 tests/run-make-cargo/sanitizer-cfi-build-std-clang/cross-lang-integer-normalization-abort/src/foo.c create mode 100644 tests/run-make-cargo/sanitizer-cfi-build-std-clang/cross-lang-integer-normalization-abort/src/main.rs create mode 100644 tests/run-make-cargo/sanitizer-cfi-build-std-clang/cross-lang-integer-normalization-not-abort/Cargo.toml create mode 100644 tests/run-make-cargo/sanitizer-cfi-build-std-clang/cross-lang-integer-normalization-not-abort/build.rs create mode 100644 tests/run-make-cargo/sanitizer-cfi-build-std-clang/cross-lang-integer-normalization-not-abort/src/foo.c create mode 100644 tests/run-make-cargo/sanitizer-cfi-build-std-clang/cross-lang-integer-normalization-not-abort/src/main.rs create mode 100644 tests/run-make-cargo/sanitizer-cfi-build-std-clang/indirect-arity-mismatch-abort/Cargo.toml create mode 100644 tests/run-make-cargo/sanitizer-cfi-build-std-clang/indirect-arity-mismatch-abort/src/main.rs create mode 100644 tests/run-make-cargo/sanitizer-cfi-build-std-clang/indirect-type-mismatch-abort/Cargo.toml create mode 100644 tests/run-make-cargo/sanitizer-cfi-build-std-clang/indirect-type-mismatch-abort/src/main.rs create mode 100644 tests/run-make-cargo/sanitizer-cfi-build-std-clang/invalid-branch-target-abort/Cargo.toml create mode 100644 tests/run-make-cargo/sanitizer-cfi-build-std-clang/invalid-branch-target-abort/src/main.rs create mode 100644 tests/run-make-cargo/sanitizer-cfi-build-std-clang/rmake.rs create mode 100644 tests/ui/sanitizer/cfi/builds-with-no-builtins-crates.rs diff --git a/compiler/rustc_codegen_ssa/src/back/write.rs b/compiler/rustc_codegen_ssa/src/back/write.rs index ff91a08de4de6..265a487c68781 100644 --- a/compiler/rustc_codegen_ssa/src/back/write.rs +++ b/compiler/rustc_codegen_ssa/src/back/write.rs @@ -136,7 +136,8 @@ impl ModuleConfig { let emit_obj = if !should_emit_obj { EmitObj::None } else if sess.target.obj_is_bitcode - || (sess.opts.cg.linker_plugin_lto.enabled() && !no_builtins) + || (sess.opts.cg.linker_plugin_lto.enabled() + && (!no_builtins || tcx.sess.is_sanitizer_cfi_enabled())) { // This case is selected if the target uses objects as bitcode, or // if linker plugin LTO is enabled. In the linker plugin LTO case @@ -144,14 +145,23 @@ impl ModuleConfig { // and convert it to object code. This may be done by either the // native linker or rustc itself. // - // Note, however, that the linker-plugin-lto requested here is - // explicitly ignored for `#![no_builtins]` crates. These crates are - // specifically ignored by rustc's LTO passes and wouldn't work if - // loaded into the linker. These crates define symbols that LLVM - // lowers intrinsics to, and these symbol dependencies aren't known - // until after codegen. As a result any crate marked - // `#![no_builtins]` is assumed to not participate in LTO and - // instead goes on to generate object code. + // By default this branch is skipped for `#![no_builtins]` crates so + // they emit native object files (machine code), not LLVM bitcode + // objects for the linker (see rust-lang/rust#146133). + // + // However, when LLVM CFI is enabled (`-Zsanitizer=cfi`), this + // breaks LLVM's expected pipeline: LLVM emits `llvm.type.test` + // intrinsics and related metadata that must be lowered by LLVM's + // `LowerTypeTests` pass before instruction selection during + // link-time LTO. Otherwise, `llvm.type.test` intrinsics and related + // metadata are not lowered by LLVM's `LowerTypeTests` pass before + // reaching the target backend, and LLVM may abort during codegen + // (for example in SelectionDAG type legalization) (see + // rust-lang/rust#142284). + // + // Therefore, with `-Clinker-plugin-lto` and `-Zsanitizer=cfi`, a + // `#![no_builtins]` crate must still use rustc's `EmitObj::Bitcode` + // path (and emit LLVM bitcode in the `.o` for linker-based LTO). EmitObj::Bitcode } else if need_bitcode_in_object(tcx) || sess.target.requires_lto { EmitObj::ObjectCode(BitcodeSection::Full) diff --git a/tests/run-make-cargo/sanitizer-cfi-build-std-clang/Cargo.toml b/tests/run-make-cargo/sanitizer-cfi-build-std-clang/Cargo.toml new file mode 100644 index 0000000000000..d7d0c24307924 --- /dev/null +++ b/tests/run-make-cargo/sanitizer-cfi-build-std-clang/Cargo.toml @@ -0,0 +1,10 @@ +# Workspace mirroring the examples in . +[workspace] +resolver = "2" +members = [ + "invalid-branch-target-abort", + "indirect-arity-mismatch-abort", + "indirect-type-mismatch-abort", + "cross-lang-cfi-types-crate-abort", + "cross-lang-cfi-types-crate-not-abort", +] diff --git a/tests/run-make-cargo/sanitizer-cfi-build-std-clang/cross-lang-cfi-types-crate-abort/Cargo.toml b/tests/run-make-cargo/sanitizer-cfi-build-std-clang/cross-lang-cfi-types-crate-abort/Cargo.toml new file mode 100644 index 0000000000000..af46166a44533 --- /dev/null +++ b/tests/run-make-cargo/sanitizer-cfi-build-std-clang/cross-lang-cfi-types-crate-abort/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "cross-lang-cfi-types-crate-abort" +version = "0.1.0" +edition = "2021" +build = "build.rs" + +[dependencies] +cfi-types = "0.0.5" diff --git a/tests/run-make-cargo/sanitizer-cfi-build-std-clang/cross-lang-cfi-types-crate-abort/build.rs b/tests/run-make-cargo/sanitizer-cfi-build-std-clang/cross-lang-cfi-types-crate-abort/build.rs new file mode 100644 index 0000000000000..42e90634afc04 --- /dev/null +++ b/tests/run-make-cargo/sanitizer-cfi-build-std-clang/cross-lang-cfi-types-crate-abort/build.rs @@ -0,0 +1,61 @@ +use std::env; +use std::path::{Path, PathBuf}; +use std::process::Command; + +fn llvm_ar_path() -> PathBuf { + if let Ok(d) = env::var("LLVM_BIN_DIR") { + let llvm_ar = Path::new(d.trim_end_matches('/')).join("llvm-ar"); + if llvm_ar.exists() { + return llvm_ar; + } + } + if let Ok(clang) = env::var("CLANG") { + let clang = Path::new(&clang); + if let Some(clang_dir) = clang.parent() { + let llvm_ar = clang_dir.join("llvm-ar"); + if llvm_ar.exists() { + return llvm_ar; + } + } + } + PathBuf::from("llvm-ar") +} + +fn main() { + let out_dir = env::var("OUT_DIR").expect("OUT_DIR"); + let manifest_dir = env::var("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR"); + let c_src = Path::new(&manifest_dir).join("src/foo.c"); + let bc_path = Path::new(&out_dir).join("foo.bc"); + let a_path = Path::new(&out_dir).join("libfoo.a"); + + let clang = + env::var("CC").or_else(|_| env::var("CLANG")).unwrap_or_else(|_| "clang".to_string()); + let llvm_ar = llvm_ar_path(); + + let st = Command::new(&clang) + .args([ + "-Wall", + "-flto=thin", + "-fsanitize=cfi", + "-fvisibility=hidden", + "-c", + "-emit-llvm", + "-o", + ]) + .arg(&bc_path) + .arg(&c_src) + .status() + .unwrap_or_else(|e| panic!("failed to spawn `{clang}`: {e}")); + assert!(st.success(), "`{clang}` failed with {st}"); + + let st = Command::new(&llvm_ar) + .args(["rcs", a_path.to_str().unwrap(), bc_path.to_str().unwrap()]) + .status() + .unwrap_or_else(|e| panic!("failed to spawn `{}`: {e}", llvm_ar.display())); + assert!(st.success(), "`{}` failed with {st}", llvm_ar.display()); + + println!("cargo:rustc-link-search=native={out_dir}"); + println!("cargo:rustc-link-lib=static=foo"); + println!("cargo:rerun-if-changed={}", c_src.display()); + println!("cargo:rerun-if-changed=build.rs"); +} diff --git a/tests/run-make-cargo/sanitizer-cfi-build-std-clang/cross-lang-cfi-types-crate-abort/src/foo.c b/tests/run-make-cargo/sanitizer-cfi-build-std-clang/cross-lang-cfi-types-crate-abort/src/foo.c new file mode 100644 index 0000000000000..9021075763bb3 --- /dev/null +++ b/tests/run-make-cargo/sanitizer-cfi-build-std-clang/cross-lang-cfi-types-crate-abort/src/foo.c @@ -0,0 +1,5 @@ +int +do_twice(int (*fn)(int), int arg) +{ + return fn(arg) + fn(arg); +} diff --git a/tests/run-make-cargo/sanitizer-cfi-build-std-clang/cross-lang-cfi-types-crate-abort/src/main.rs b/tests/run-make-cargo/sanitizer-cfi-build-std-clang/cross-lang-cfi-types-crate-abort/src/main.rs new file mode 100644 index 0000000000000..dc6490f6d3ff5 --- /dev/null +++ b/tests/run-make-cargo/sanitizer-cfi-build-std-clang/cross-lang-cfi-types-crate-abort/src/main.rs @@ -0,0 +1,36 @@ +// This example demonstrates 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, +// across the FFI boundary using the `cfi_types` crate for cross-language LLVM +// CFI. + +use std::mem; + +use cfi_types::{c_int, c_long}; + +#[link(name = "foo")] +unsafe extern "C" { + fn do_twice(f: unsafe extern "C" fn(c_int) -> c_int, arg: i32) -> i32; +} + +unsafe extern "C" fn add_one(x: c_int) -> c_int { + c_int(x.0 + 1) +} + +unsafe extern "C" fn add_two(x: c_long) -> c_long { + c_long(x.0 + 2) +} + +fn main() { + let answer = unsafe { do_twice(add_one, 5) }; + + println!("The answer is: {}", answer); + + println!("With CFI enabled, you should not see the next answer"); + let f: unsafe extern "C" fn(c_int) -> c_int = unsafe { + mem::transmute::<*const u8, unsafe extern "C" fn(c_int) -> c_int>(add_two as *const u8) + }; + let next_answer = unsafe { do_twice(f, 5) }; + + println!("The next answer is: {}", next_answer); +} diff --git a/tests/run-make-cargo/sanitizer-cfi-build-std-clang/cross-lang-cfi-types-crate-not-abort/Cargo.toml b/tests/run-make-cargo/sanitizer-cfi-build-std-clang/cross-lang-cfi-types-crate-not-abort/Cargo.toml new file mode 100644 index 0000000000000..d9c5d3502f473 --- /dev/null +++ b/tests/run-make-cargo/sanitizer-cfi-build-std-clang/cross-lang-cfi-types-crate-not-abort/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "cross-lang-cfi-types-crate-not-abort" +version = "0.1.0" +edition = "2021" +build = "build.rs" + +[dependencies] +cfi-types = "0.0.5" diff --git a/tests/run-make-cargo/sanitizer-cfi-build-std-clang/cross-lang-cfi-types-crate-not-abort/build.rs b/tests/run-make-cargo/sanitizer-cfi-build-std-clang/cross-lang-cfi-types-crate-not-abort/build.rs new file mode 100644 index 0000000000000..42e90634afc04 --- /dev/null +++ b/tests/run-make-cargo/sanitizer-cfi-build-std-clang/cross-lang-cfi-types-crate-not-abort/build.rs @@ -0,0 +1,61 @@ +use std::env; +use std::path::{Path, PathBuf}; +use std::process::Command; + +fn llvm_ar_path() -> PathBuf { + if let Ok(d) = env::var("LLVM_BIN_DIR") { + let llvm_ar = Path::new(d.trim_end_matches('/')).join("llvm-ar"); + if llvm_ar.exists() { + return llvm_ar; + } + } + if let Ok(clang) = env::var("CLANG") { + let clang = Path::new(&clang); + if let Some(clang_dir) = clang.parent() { + let llvm_ar = clang_dir.join("llvm-ar"); + if llvm_ar.exists() { + return llvm_ar; + } + } + } + PathBuf::from("llvm-ar") +} + +fn main() { + let out_dir = env::var("OUT_DIR").expect("OUT_DIR"); + let manifest_dir = env::var("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR"); + let c_src = Path::new(&manifest_dir).join("src/foo.c"); + let bc_path = Path::new(&out_dir).join("foo.bc"); + let a_path = Path::new(&out_dir).join("libfoo.a"); + + let clang = + env::var("CC").or_else(|_| env::var("CLANG")).unwrap_or_else(|_| "clang".to_string()); + let llvm_ar = llvm_ar_path(); + + let st = Command::new(&clang) + .args([ + "-Wall", + "-flto=thin", + "-fsanitize=cfi", + "-fvisibility=hidden", + "-c", + "-emit-llvm", + "-o", + ]) + .arg(&bc_path) + .arg(&c_src) + .status() + .unwrap_or_else(|e| panic!("failed to spawn `{clang}`: {e}")); + assert!(st.success(), "`{clang}` failed with {st}"); + + let st = Command::new(&llvm_ar) + .args(["rcs", a_path.to_str().unwrap(), bc_path.to_str().unwrap()]) + .status() + .unwrap_or_else(|e| panic!("failed to spawn `{}`: {e}", llvm_ar.display())); + assert!(st.success(), "`{}` failed with {st}", llvm_ar.display()); + + println!("cargo:rustc-link-search=native={out_dir}"); + println!("cargo:rustc-link-lib=static=foo"); + println!("cargo:rerun-if-changed={}", c_src.display()); + println!("cargo:rerun-if-changed=build.rs"); +} diff --git a/tests/run-make-cargo/sanitizer-cfi-build-std-clang/cross-lang-cfi-types-crate-not-abort/src/foo.c b/tests/run-make-cargo/sanitizer-cfi-build-std-clang/cross-lang-cfi-types-crate-not-abort/src/foo.c new file mode 100644 index 0000000000000..d02bbb285883d --- /dev/null +++ b/tests/run-make-cargo/sanitizer-cfi-build-std-clang/cross-lang-cfi-types-crate-not-abort/src/foo.c @@ -0,0 +1,23 @@ +#include +#include + +// This definition has the type id "_ZTSFvlE". +void +hello_from_c(long arg) +{ + printf("Hello from C!\n"); +} + +// This definition has the type id "_ZTSFvPFvlElE"--this can be ignored for the +// purposes of this example. +void +indirect_call_from_c(void (*fn)(long), long arg) +{ + // This call site tests whether the destination pointer is a member of the + // group derived from the same type id of the fn declaration, which has the + // type id "_ZTSFvlE". + // + // Notice that since the test is at the call site and generated by Clang, + // the type id used in the test is encoded by Clang. + fn(arg); +} diff --git a/tests/run-make-cargo/sanitizer-cfi-build-std-clang/cross-lang-cfi-types-crate-not-abort/src/main.rs b/tests/run-make-cargo/sanitizer-cfi-build-std-clang/cross-lang-cfi-types-crate-not-abort/src/main.rs new file mode 100644 index 0000000000000..a9d1326d6dc43 --- /dev/null +++ b/tests/run-make-cargo/sanitizer-cfi-build-std-clang/cross-lang-cfi-types-crate-not-abort/src/main.rs @@ -0,0 +1,67 @@ +use cfi_types::c_long; + +#[link(name = "foo")] +extern "C" { + // This declaration has the type id "_ZTSFvlE" because it uses the CFI types + // for cross-language LLVM CFI support. The cfi_types crate provides a new + // set of C types as user-defined types using the cfi_encoding attribute and + // repr(transparent) to be used for cross-language LLVM CFI support. This + // new set of C types allows the Rust compiler to identify and correctly + // encode C types in extern "C" function types indirectly called across the + // FFI boundary when CFI is enabled. + fn hello_from_c(_: c_long); + + // This declaration has the type id "_ZTSFvPFvlElE" because it uses the CFI + // types for cross-language LLVM CFI support--this can be ignored for the + // purposes of this example. + fn indirect_call_from_c(f: unsafe extern "C" fn(c_long), arg: c_long); +} + +// This definition has the type id "_ZTSFvlE" because it uses the CFI types for +// cross-language LLVM CFI support, similarly to the hello_from_c declaration +// above. +unsafe extern "C" fn hello_from_rust(_: c_long) { + println!("Hello, world!"); +} + +// This definition has the type id "_ZTSFvlE" because it uses the CFI types for +// cross-language LLVM CFI support, similarly to the hello_from_c declaration +// above. +unsafe extern "C" fn hello_from_rust_again(_: c_long) { + println!("Hello from Rust again!"); +} + +// This definition would also have the type id "_ZTSFvPFvlElE" because it uses +// the CFI types for cross-language LLVM CFI support, similarly to the +// hello_from_c declaration above--this can be ignored for the purposes of this +// example. +fn indirect_call(f: unsafe extern "C" fn(c_long), arg: c_long) { + // This indirect call site tests whether the destination pointer is a member + // of the group derived from the same type id of the f declaration, which + // has the type id "_ZTSFvlE" because it uses the CFI types for + // cross-language LLVM CFI support, similarly to the hello_from_c + // declaration above. + unsafe { f(arg) } +} + +// This definition has the type id "_ZTSFvvE"--this can be ignored for the +// purposes of this example. +fn main() { + // This demonstrates an indirect call within Rust-only code using the same + // encoding for hello_from_rust and the test at the indirect call site at + // indirect_call (i.e., "_ZTSFvlE"). + indirect_call(hello_from_rust, c_long(5)); + + // This demonstrates an indirect call across the FFI boundary with the Rust + // compiler and Clang using the same encoding for hello_from_c and the test + // at the indirect call site at indirect_call (i.e., "_ZTSFvlE"). + indirect_call(hello_from_c, c_long(5)); + + // This demonstrates an indirect call to a function passed as a callback + // across the FFI boundary with the Rust compiler and Clang the same + // encoding for the passed-callback declaration and the test at the indirect + // call site at indirect_call_from_c (i.e., "_ZTSFvlE"). + unsafe { + indirect_call_from_c(hello_from_rust_again, c_long(5)); + } +} diff --git a/tests/run-make-cargo/sanitizer-cfi-build-std-clang/cross-lang-integer-normalization-abort/Cargo.toml b/tests/run-make-cargo/sanitizer-cfi-build-std-clang/cross-lang-integer-normalization-abort/Cargo.toml new file mode 100644 index 0000000000000..6afe93fe224e1 --- /dev/null +++ b/tests/run-make-cargo/sanitizer-cfi-build-std-clang/cross-lang-integer-normalization-abort/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "cross-lang-integer-normalization-abort" +version = "0.1.0" +edition = "2021" +build = "build.rs" + +# Not a member of the parent `sanitizer-cfi-build-std-clang` workspace so it can +# be built with different `RUSTFLAGS` (i.e., integer normalization). +[workspace] +members = ["."] +resolver = "2" diff --git a/tests/run-make-cargo/sanitizer-cfi-build-std-clang/cross-lang-integer-normalization-abort/build.rs b/tests/run-make-cargo/sanitizer-cfi-build-std-clang/cross-lang-integer-normalization-abort/build.rs new file mode 100644 index 0000000000000..dbaaba395b439 --- /dev/null +++ b/tests/run-make-cargo/sanitizer-cfi-build-std-clang/cross-lang-integer-normalization-abort/build.rs @@ -0,0 +1,62 @@ +use std::env; +use std::path::{Path, PathBuf}; +use std::process::Command; + +fn llvm_ar_path() -> PathBuf { + if let Ok(d) = env::var("LLVM_BIN_DIR") { + let llvm_ar = Path::new(d.trim_end_matches('/')).join("llvm-ar"); + if llvm_ar.exists() { + return llvm_ar; + } + } + if let Ok(clang) = env::var("CLANG") { + let clang = Path::new(&clang); + if let Some(clang_dir) = clang.parent() { + let llvm_ar = clang_dir.join("llvm-ar"); + if llvm_ar.exists() { + return llvm_ar; + } + } + } + PathBuf::from("llvm-ar") +} + +fn main() { + let out_dir = env::var("OUT_DIR").expect("OUT_DIR"); + let manifest_dir = env::var("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR"); + let c_src = Path::new(&manifest_dir).join("src/foo.c"); + let bc_path = Path::new(&out_dir).join("foo.bc"); + let a_path = Path::new(&out_dir).join("libfoo.a"); + + let clang = + env::var("CC").or_else(|_| env::var("CLANG")).unwrap_or_else(|_| "clang".to_string()); + let llvm_ar = llvm_ar_path(); + + let st = Command::new(&clang) + .args([ + "-Wall", + "-flto=thin", + "-fsanitize=cfi", + "-fsanitize-cfi-icall-experimental-normalize-integers", + "-fvisibility=hidden", + "-c", + "-emit-llvm", + "-o", + ]) + .arg(&bc_path) + .arg(&c_src) + .status() + .unwrap_or_else(|e| panic!("failed to spawn `{clang}`: {e}")); + assert!(st.success(), "`{clang}` failed with {st}"); + + let st = Command::new(&llvm_ar) + .args(["rcs", a_path.to_str().unwrap(), bc_path.to_str().unwrap()]) + .status() + .unwrap_or_else(|e| panic!("failed to spawn `{}`: {e}", llvm_ar.display())); + assert!(st.success(), "`{}` failed with {st}", llvm_ar.display()); + + println!("cargo:rustc-link-search=native={out_dir}"); + println!("cargo:rustc-link-lib=static=foo"); + println!("cargo:rerun-if-changed={}", c_src.display()); + println!("cargo:rerun-if-changed=build.rs"); +} diff --git a/tests/run-make-cargo/sanitizer-cfi-build-std-clang/cross-lang-integer-normalization-abort/src/foo.c b/tests/run-make-cargo/sanitizer-cfi-build-std-clang/cross-lang-integer-normalization-abort/src/foo.c new file mode 100644 index 0000000000000..9021075763bb3 --- /dev/null +++ b/tests/run-make-cargo/sanitizer-cfi-build-std-clang/cross-lang-integer-normalization-abort/src/foo.c @@ -0,0 +1,5 @@ +int +do_twice(int (*fn)(int), int arg) +{ + return fn(arg) + fn(arg); +} diff --git a/tests/run-make-cargo/sanitizer-cfi-build-std-clang/cross-lang-integer-normalization-abort/src/main.rs b/tests/run-make-cargo/sanitizer-cfi-build-std-clang/cross-lang-integer-normalization-abort/src/main.rs new file mode 100644 index 0000000000000..ef5d1da6ca737 --- /dev/null +++ b/tests/run-make-cargo/sanitizer-cfi-build-std-clang/cross-lang-integer-normalization-abort/src/main.rs @@ -0,0 +1,34 @@ +// This example demonstrates 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, +// across the FFI boundary using integer normalization for cross-language LLVM +// CFI. + +use std::mem; + +#[link(name = "foo")] +extern "C" { + fn do_twice(f: unsafe extern "C" fn(i32) -> i32, arg: i32) -> i32; +} + +unsafe extern "C" fn add_one(x: i32) -> i32 { + x + 1 +} + +unsafe extern "C" fn add_two(x: i64) -> i64 { + x + 2 +} + +fn main() { + let answer = unsafe { do_twice(add_one, 5) }; + + println!("The answer is: {}", answer); + + println!("With CFI enabled, you should not see the next answer"); + let f: unsafe extern "C" fn(i32) -> i32 = unsafe { + mem::transmute::<*const u8, unsafe extern "C" fn(i32) -> i32>(add_two as *const u8) + }; + let next_answer = unsafe { do_twice(f, 5) }; + + println!("The next answer is: {}", next_answer); +} diff --git a/tests/run-make-cargo/sanitizer-cfi-build-std-clang/cross-lang-integer-normalization-not-abort/Cargo.toml b/tests/run-make-cargo/sanitizer-cfi-build-std-clang/cross-lang-integer-normalization-not-abort/Cargo.toml new file mode 100644 index 0000000000000..bb5ed37fa248a --- /dev/null +++ b/tests/run-make-cargo/sanitizer-cfi-build-std-clang/cross-lang-integer-normalization-not-abort/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "cross-lang-integer-normalization-not-abort" +version = "0.1.0" +edition = "2021" +build = "build.rs" + +# Not a member of the parent `sanitizer-cfi-build-std-clang` workspace so it can +# be built with different `RUSTFLAGS` (i.e., integer normalization). +[workspace] +members = ["."] +resolver = "2" diff --git a/tests/run-make-cargo/sanitizer-cfi-build-std-clang/cross-lang-integer-normalization-not-abort/build.rs b/tests/run-make-cargo/sanitizer-cfi-build-std-clang/cross-lang-integer-normalization-not-abort/build.rs new file mode 100644 index 0000000000000..dbaaba395b439 --- /dev/null +++ b/tests/run-make-cargo/sanitizer-cfi-build-std-clang/cross-lang-integer-normalization-not-abort/build.rs @@ -0,0 +1,62 @@ +use std::env; +use std::path::{Path, PathBuf}; +use std::process::Command; + +fn llvm_ar_path() -> PathBuf { + if let Ok(d) = env::var("LLVM_BIN_DIR") { + let llvm_ar = Path::new(d.trim_end_matches('/')).join("llvm-ar"); + if llvm_ar.exists() { + return llvm_ar; + } + } + if let Ok(clang) = env::var("CLANG") { + let clang = Path::new(&clang); + if let Some(clang_dir) = clang.parent() { + let llvm_ar = clang_dir.join("llvm-ar"); + if llvm_ar.exists() { + return llvm_ar; + } + } + } + PathBuf::from("llvm-ar") +} + +fn main() { + let out_dir = env::var("OUT_DIR").expect("OUT_DIR"); + let manifest_dir = env::var("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR"); + let c_src = Path::new(&manifest_dir).join("src/foo.c"); + let bc_path = Path::new(&out_dir).join("foo.bc"); + let a_path = Path::new(&out_dir).join("libfoo.a"); + + let clang = + env::var("CC").or_else(|_| env::var("CLANG")).unwrap_or_else(|_| "clang".to_string()); + let llvm_ar = llvm_ar_path(); + + let st = Command::new(&clang) + .args([ + "-Wall", + "-flto=thin", + "-fsanitize=cfi", + "-fsanitize-cfi-icall-experimental-normalize-integers", + "-fvisibility=hidden", + "-c", + "-emit-llvm", + "-o", + ]) + .arg(&bc_path) + .arg(&c_src) + .status() + .unwrap_or_else(|e| panic!("failed to spawn `{clang}`: {e}")); + assert!(st.success(), "`{clang}` failed with {st}"); + + let st = Command::new(&llvm_ar) + .args(["rcs", a_path.to_str().unwrap(), bc_path.to_str().unwrap()]) + .status() + .unwrap_or_else(|e| panic!("failed to spawn `{}`: {e}", llvm_ar.display())); + assert!(st.success(), "`{}` failed with {st}", llvm_ar.display()); + + println!("cargo:rustc-link-search=native={out_dir}"); + println!("cargo:rustc-link-lib=static=foo"); + println!("cargo:rerun-if-changed={}", c_src.display()); + println!("cargo:rerun-if-changed=build.rs"); +} diff --git a/tests/run-make-cargo/sanitizer-cfi-build-std-clang/cross-lang-integer-normalization-not-abort/src/foo.c b/tests/run-make-cargo/sanitizer-cfi-build-std-clang/cross-lang-integer-normalization-not-abort/src/foo.c new file mode 100644 index 0000000000000..d02bbb285883d --- /dev/null +++ b/tests/run-make-cargo/sanitizer-cfi-build-std-clang/cross-lang-integer-normalization-not-abort/src/foo.c @@ -0,0 +1,23 @@ +#include +#include + +// This definition has the type id "_ZTSFvlE". +void +hello_from_c(long arg) +{ + printf("Hello from C!\n"); +} + +// This definition has the type id "_ZTSFvPFvlElE"--this can be ignored for the +// purposes of this example. +void +indirect_call_from_c(void (*fn)(long), long arg) +{ + // This call site tests whether the destination pointer is a member of the + // group derived from the same type id of the fn declaration, which has the + // type id "_ZTSFvlE". + // + // Notice that since the test is at the call site and generated by Clang, + // the type id used in the test is encoded by Clang. + fn(arg); +} diff --git a/tests/run-make-cargo/sanitizer-cfi-build-std-clang/cross-lang-integer-normalization-not-abort/src/main.rs b/tests/run-make-cargo/sanitizer-cfi-build-std-clang/cross-lang-integer-normalization-not-abort/src/main.rs new file mode 100644 index 0000000000000..70a4d9a789e58 --- /dev/null +++ b/tests/run-make-cargo/sanitizer-cfi-build-std-clang/cross-lang-integer-normalization-not-abort/src/main.rs @@ -0,0 +1,89 @@ +use std::ffi::c_long; + +#[link(name = "foo")] +extern "C" { + // This declaration would have the type id "_ZTSFvlE", but at the time types + // are encoded, all type aliases are already resolved to their respective + // Rust aliased types, so this is encoded either as "_ZTSFvu3i32E" or + // "_ZTSFvu3i64E" depending to what type c_long type alias is resolved to, + // which currently uses the u vendor extended type + // encoding for the Rust integer types--this is the problem demonstrated in + // this example. + fn hello_from_c(_: c_long); + + // This declaration would have the type id "_ZTSFvPFvlElE", but is encoded + // either as "_ZTSFvPFvu3i32ES_E" (compressed) or "_ZTSFvPFvu3i64ES_E" + // (compressed), similarly to the hello_from_c declaration above--this can + // be ignored for the purposes of this example. + fn indirect_call_from_c(f: unsafe extern "C" fn(c_long), arg: c_long); +} + +// This definition would have the type id "_ZTSFvlE", but is encoded either as +// "_ZTSFvu3i32E" or "_ZTSFvu3i64E", similarly to the hello_from_c declaration +// above. +unsafe extern "C" fn hello_from_rust(_: c_long) { + println!("Hello, world!"); +} + +// This definition would have the type id "_ZTSFvlE", but is encoded either as +// "_ZTSFvu3i32E" or "_ZTSFvu3i64E", similarly to the hello_from_c declaration +// above. +unsafe extern "C" fn hello_from_rust_again(_: c_long) { + println!("Hello from Rust again!"); +} + +// This definition would also have the type id "_ZTSFvPFvlElE", but is encoded +// either as "_ZTSFvPFvu3i32ES_E" (compressed) or "_ZTSFvPFvu3i64ES_E" +// (compressed), similarly to the hello_from_c declaration above--this can be +// ignored for the purposes of this example. +fn indirect_call(f: unsafe extern "C" fn(c_long), arg: c_long) { + // This indirect call site tests whether the destination pointer is a member + // of the group derived from the same type id of the f declaration, which + // would have the type id "_ZTSFvlE", but is encoded either as + // "_ZTSFvu3i32E" or "_ZTSFvu3i64E", similarly to the hello_from_c + // declaration above. + // + // Notice that since the test is at the call site and generated by the Rust + // compiler, the type id used in the test is encoded by the Rust compiler. + unsafe { f(arg) } +} + +// This definition has the type id "_ZTSFvvE"--this can be ignored for the +// purposes of this example. +fn main() { + // This demonstrates an indirect call within Rust-only code using the same + // encoding for hello_from_rust and the test at the indirect call site at + // indirect_call (i.e., "_ZTSFvu3i32E" or "_ZTSFvu3i64E"). + indirect_call(hello_from_rust, 5); + + // This demonstrates an indirect call across the FFI boundary with the Rust + // compiler and Clang using different encodings for hello_from_c and the + // test at the indirect call site at indirect_call (i.e., "_ZTSFvu3i32E" or + // "_ZTSFvu3i64E" vs "_ZTSFvlE"). + // + // When using rustc LTO (i.e., -Clto), this works because the type id used + // is from the Rust-declared hello_from_c, which is encoded by the Rust + // compiler (i.e., "_ZTSFvu3i32E" or "_ZTSFvu3i64E"). + // + // When using (proper) LTO (i.e., -Clinker-plugin-lto), this does not work + // because the type id used is from the C-defined hello_from_c, which is + // encoded by Clang (i.e., "_ZTSFvlE"). + indirect_call(hello_from_c, 5); + + // This demonstrates an indirect call to a function passed as a callback + // across the FFI boundary with the Rust compiler and Clang using different + // encodings for the passed-callback declaration and the test at the + // indirect call site at indirect_call_from_c (i.e., "_ZTSFvu3i32E" or + // "_ZTSFvu3i64E" vs "_ZTSFvlE"). + // + // When Rust functions are passed as callbacks across the FFI boundary to be + // called back from C code, the tests are also at the call site but + // generated by Clang instead, so the type ids used in the tests are encoded + // by Clang, which will not match the type ids of declarations encoded by + // the Rust compiler (e.g., hello_from_rust_again). (The same happens the + // other way around for C functions passed as callbacks across the FFI + // boundary to be called back from Rust code.) + unsafe { + indirect_call_from_c(hello_from_rust_again, 5); + } +} diff --git a/tests/run-make-cargo/sanitizer-cfi-build-std-clang/indirect-arity-mismatch-abort/Cargo.toml b/tests/run-make-cargo/sanitizer-cfi-build-std-clang/indirect-arity-mismatch-abort/Cargo.toml new file mode 100644 index 0000000000000..8c7f2b33265f7 --- /dev/null +++ b/tests/run-make-cargo/sanitizer-cfi-build-std-clang/indirect-arity-mismatch-abort/Cargo.toml @@ -0,0 +1,4 @@ +[package] +name = "indirect-arity-mismatch-abort" +version = "0.1.0" +edition = "2021" diff --git a/tests/run-make-cargo/sanitizer-cfi-build-std-clang/indirect-arity-mismatch-abort/src/main.rs b/tests/run-make-cargo/sanitizer-cfi-build-std-clang/indirect-arity-mismatch-abort/src/main.rs new file mode 100644 index 0000000000000..8cc0dba58b201 --- /dev/null +++ b/tests/run-make-cargo/sanitizer-cfi-build-std-clang/indirect-arity-mismatch-abort/src/main.rs @@ -0,0 +1,30 @@ +// This example demonstrates redirecting control flow using an indirect +// branch/call to a function with a different number of parameters than +// arguments intended/passed at the call/branch site. + +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); +} diff --git a/tests/run-make-cargo/sanitizer-cfi-build-std-clang/indirect-type-mismatch-abort/Cargo.toml b/tests/run-make-cargo/sanitizer-cfi-build-std-clang/indirect-type-mismatch-abort/Cargo.toml new file mode 100644 index 0000000000000..e167b2acfbcf5 --- /dev/null +++ b/tests/run-make-cargo/sanitizer-cfi-build-std-clang/indirect-type-mismatch-abort/Cargo.toml @@ -0,0 +1,4 @@ +[package] +name = "indirect-type-mismatch-abort" +version = "0.1.0" +edition = "2021" diff --git a/tests/run-make-cargo/sanitizer-cfi-build-std-clang/indirect-type-mismatch-abort/src/main.rs b/tests/run-make-cargo/sanitizer-cfi-build-std-clang/indirect-type-mismatch-abort/src/main.rs new file mode 100644 index 0000000000000..0f40aa316152e --- /dev/null +++ b/tests/run-make-cargo/sanitizer-cfi-build-std-clang/indirect-type-mismatch-abort/src/main.rs @@ -0,0 +1,30 @@ +// This example demonstrates 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. + +use std::mem; + +fn add_one(x: i32) -> i32 { + x + 1 +} + +fn add_two(x: i64) -> i64 { + 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); +} diff --git a/tests/run-make-cargo/sanitizer-cfi-build-std-clang/invalid-branch-target-abort/Cargo.toml b/tests/run-make-cargo/sanitizer-cfi-build-std-clang/invalid-branch-target-abort/Cargo.toml new file mode 100644 index 0000000000000..e6af74f961e04 --- /dev/null +++ b/tests/run-make-cargo/sanitizer-cfi-build-std-clang/invalid-branch-target-abort/Cargo.toml @@ -0,0 +1,4 @@ +[package] +name = "invalid-branch-target-abort" +version = "0.1.0" +edition = "2021" diff --git a/tests/run-make-cargo/sanitizer-cfi-build-std-clang/invalid-branch-target-abort/src/main.rs b/tests/run-make-cargo/sanitizer-cfi-build-std-clang/invalid-branch-target-abort/src/main.rs new file mode 100644 index 0000000000000..0b047bf5fefb6 --- /dev/null +++ b/tests/run-make-cargo/sanitizer-cfi-build-std-clang/invalid-branch-target-abort/src/main.rs @@ -0,0 +1,50 @@ +// This example demonstrates redirecting control flow using an indirect +// branch/call to an invalid destination (i.e., within the body of the +// function). + +use std::mem; + +fn add_one(x: i32) -> i32 { + x + 1 +} + +#[unsafe(naked)] +pub extern "C" fn add_two(_x: i32) -> ! { + // x + 2 preceded by a landing pad/nop block + core::arch::naked_asm!( + r#" + nop + nop + nop + nop + nop + nop + nop + nop + nop + lea eax, [rdi + 2] + ret + "#, + ); +} + +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 { + // 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); +} diff --git a/tests/run-make-cargo/sanitizer-cfi-build-std-clang/rmake.rs b/tests/run-make-cargo/sanitizer-cfi-build-std-clang/rmake.rs new file mode 100644 index 0000000000000..77e97aaef5044 --- /dev/null +++ b/tests/run-make-cargo/sanitizer-cfi-build-std-clang/rmake.rs @@ -0,0 +1,154 @@ +//! Verifies that the examples in [rust-cfi-examples](https://github.com/rcvalle/rust-cfi-examples) +//! build and run with `-Zbuild-std` to prevent regressions such as [rust-lang/rust#142284]. + +//@ needs-sanitizer-cfi +//@ needs-force-clang-based-tests +//@ needs-rust-lld +//@ needs-target-std +//@ ignore-cross-compile +//@ only-x86_64-unknown-linux-gnu + +#![deny(warnings)] + +use std::path::Path; + +use run_make_support::run::cmd; +use run_make_support::{bin_name, cargo, path, target}; + +fn clang_path() -> String { + if let Ok(d) = std::env::var("LLVM_BIN_DIR") { + let clang = Path::new(d.trim_end_matches('/')).join("clang"); + if clang.exists() { + return clang.display().to_string(); + } + } + if let Ok(clang) = std::env::var("CLANG") { + let clang = Path::new(clang.trim_end_matches('/')); + if clang.exists() { + return clang.display().to_string(); + } + } + "clang".to_string() +} + +fn fuse_ld_path() -> String { + if let Ok(d) = std::env::var("LLVM_BIN_DIR") { + let llvm_bin_dir = Path::new(d.trim_end_matches('/')); + let gcc_ld_lld = llvm_bin_dir.join("gcc-ld").join("ld.lld"); + if gcc_ld_lld.exists() { + return gcc_ld_lld.display().to_string(); + } + let ld_lld = llvm_bin_dir.join("ld.lld"); + if ld_lld.exists() { + return ld_lld.display().to_string(); + } + } + if let Ok(clang) = std::env::var("CLANG") { + let clang = Path::new(clang.trim_end_matches('/')); + if let Some(clang_dir) = clang.parent() { + let gcc_ld_lld = clang_dir.join("gcc-ld").join("ld.lld"); + if gcc_ld_lld.exists() { + return gcc_ld_lld.display().to_string(); + } + let ld_lld = clang_dir.join("ld.lld"); + if ld_lld.exists() { + return ld_lld.display().to_string(); + } + } + } + if let Ok(rustc) = std::env::var("RUSTC") { + let rustc = Path::new(rustc.trim_end_matches('/')); + if let Some(rustc_bin_dir) = rustc.parent() + && let Some(sysroot_dir) = rustc_bin_dir.parent() + { + let target_bin_dir = sysroot_dir.join("lib").join("rustlib").join(target()).join("bin"); + let gcc_ld_lld = target_bin_dir.join("gcc-ld").join("ld.lld"); + if gcc_ld_lld.exists() { + return gcc_ld_lld.display().to_string(); + } + } + } + "ld.lld".to_string() +} + +fn run_and_expect_cfi_abort(target_dir: &Path, target: &str, binary: &str) { + let exe = target_dir.join(target).join("release").join(bin_name(binary)); + let output = cmd(&exe).run_fail(); + output + .assert_stdout_contains("With CFI enabled, you should not see the next answer") + .assert_stdout_not_contains("The next answer is:"); +} + +fn run_and_expect_cfi_not_abort(target_dir: &Path, target: &str, binary: &str) { + let exe = target_dir.join(target).join("release").join(bin_name(binary)); + let output = cmd(&exe).run(); + output.assert_stdout_contains("Hello from C!"); +} + +fn main() { + let clang = clang_path(); + let fuse_ld = fuse_ld_path(); + let tgt = target(); + let target_dir = path("target"); + let lib = std::env::var("LIB").unwrap_or_default(); + + let prior_rustflags = std::env::var("RUSTFLAGS").unwrap_or_default(); + + let rustflags = format!( + "{prior_rustflags} -Clinker-plugin-lto -Clinker={clang} \ + -Clink-arg=-fuse-ld={fuse_ld} -Zsanitizer=cfi \ + -Ctarget-feature=-crt-static" + ) + .trim() + .to_owned(); + + let rustflags_with_integer_normalization = + format!("{rustflags} -Zsanitizer-cfi-normalize-integers").trim().to_owned(); + + let run = |manifest: &Path, rustflags: &str, workspace: bool| { + let mut c = cargo(); + c.arg("build") + .arg("--manifest-path") + .arg(manifest) + .arg("--release") + .arg("-Zbuild-std") + .arg("--target") + .arg(&tgt); + if workspace { + c.arg("--workspace"); + } + c.env("RUSTFLAGS", rustflags) + .env("CC", &clang) + .env("CARGO_TARGET_DIR", &target_dir) + .env("RUSTC_BOOTSTRAP", "1") + .env("LIB", &lib) + .run(); + }; + + run(Path::new("Cargo.toml"), &rustflags, true); + for bin in [ + "invalid-branch-target-abort", + "indirect-arity-mismatch-abort", + "indirect-type-mismatch-abort", + "cross-lang-cfi-types-crate-abort", + ] { + run_and_expect_cfi_abort(&target_dir, &tgt, bin); + } + for bin in ["cross-lang-cfi-types-crate-not-abort"] { + run_and_expect_cfi_not_abort(&target_dir, &tgt, bin); + } + + run( + Path::new("cross-lang-integer-normalization-abort/Cargo.toml"), + &rustflags_with_integer_normalization, + false, + ); + run_and_expect_cfi_abort(&target_dir, &tgt, "cross-lang-integer-normalization-abort"); + + run( + Path::new("cross-lang-integer-normalization-not-abort/Cargo.toml"), + &rustflags_with_integer_normalization, + false, + ); + run_and_expect_cfi_not_abort(&target_dir, &tgt, "cross-lang-integer-normalization-not-abort"); +} diff --git a/tests/ui/sanitizer/cfi/builds-with-no-builtins-crates.rs b/tests/ui/sanitizer/cfi/builds-with-no-builtins-crates.rs new file mode 100644 index 0000000000000..80062e239eb76 --- /dev/null +++ b/tests/ui/sanitizer/cfi/builds-with-no-builtins-crates.rs @@ -0,0 +1,22 @@ +// Verifies that `-Zsanitizer=cfi` builds with `#![no_builtins]` crates. +// See Issue #142284 +// +//@ needs-sanitizer-cfi +//@ compile-flags: -Clinker-plugin-lto -Copt-level=0 -Cunsafe-allow-abi-mismatch=sanitizer -Zsanitizer=cfi -Ctarget-feature=-crt-static +//@ compile-flags: --crate-type rlib +//@ build-pass + +#![no_builtins] +#![no_std] + +pub static FUNC: fn() = initializer; + +pub fn initializer() { + call(fma_with_fma); +} + +pub fn call(fn_ptr: fn()) { + fn_ptr(); +} + +pub fn fma_with_fma() {}