Skip to content
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
28 changes: 19 additions & 9 deletions compiler/rustc_codegen_ssa/src/back/write.rs
Original file line number Diff line number Diff line change
Expand Up @@ -136,22 +136,32 @@ 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
// the assumption is that the final link-step will read the bitcode
// 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).
Comment thread
bjorn3 marked this conversation as resolved.
EmitObj::Bitcode
} else if need_bitcode_in_object(tcx) || sess.target.requires_lto {
EmitObj::ObjectCode(BitcodeSection::Full)
Expand Down
45 changes: 45 additions & 0 deletions tests/run-make-cargo/sanitizer-cfi-build-std-clang/Cargo.lock
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4

[[package]]
name = "cfi-types"
version = "0.0.8"

[[package]]
name = "cross-lang-cfi-types-crate-abort"
version = "0.1.0"
dependencies = [
"cfi-types",
]

[[package]]
name = "cross-lang-cfi-types-crate-not-abort"
version = "0.1.0"
dependencies = [
"cfi-types",
]

[[package]]
name = "indirect-arity-mismatch-abort"
version = "0.1.0"

[[package]]
name = "indirect-pointee-type-mismatch-abort"
version = "0.1.0"

[[package]]
name = "indirect-return-type-mismatch-abort"
version = "0.1.0"

[[package]]
name = "indirect-type-mismatch-abort"
version = "0.1.0"

[[package]]
name = "indirect-type-qualifier-mismatch-abort"
version = "0.1.0"

[[package]]
name = "invalid-branch-target-abort"
version = "0.1.0"
13 changes: 13 additions & 0 deletions tests/run-make-cargo/sanitizer-cfi-build-std-clang/Cargo.toml
Comment thread
rcvalle marked this conversation as resolved.
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# Workspace mirroring the examples in <https://github.com/rcvalle/rust-cfi-examples>.
[workspace]
resolver = "2"
members = [
"invalid-branch-target-abort",
"indirect-arity-mismatch-abort",
"indirect-pointee-type-mismatch-abort",
"indirect-return-type-mismatch-abort",
"indirect-type-qualifier-mismatch-abort",
"indirect-type-mismatch-abort",
"cross-lang-cfi-types-crate-abort",
"cross-lang-cfi-types-crate-not-abort",
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
[package]
name = "cross-lang-cfi-types-crate-abort"
version = "0.1.0"
edition = "2021"

[dependencies]
cfi-types = { path = "../vendor/cfi-types" }
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
include!("../shared_build_rs.rs");

fn main() {
build_foo_static_lib(&[]);
}
Comment thread
rcvalle marked this conversation as resolved.
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
int
do_twice(int (*fn)(int), int arg)
{
return fn(arg) + fn(arg);
}
Original file line number Diff line number Diff line change
@@ -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);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
[package]
name = "cross-lang-cfi-types-crate-not-abort"
version = "0.1.0"
edition = "2021"

[dependencies]
cfi-types = { path = "../vendor/cfi-types" }
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
include!("../shared_build_rs.rs");

fn main() {
build_foo_static_lib(&[]);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
#include <stdio.h>
#include <stdlib.h>

// 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);
}
Original file line number Diff line number Diff line change
@@ -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));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4

[[package]]
name = "cross-lang-integer-normalization-abort"
version = "0.1.0"
Comment thread
rcvalle marked this conversation as resolved.
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
[package]
name = "cross-lang-integer-normalization-abort"
version = "0.1.0"
edition = "2021"

# 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"
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
include!("../shared_build_rs.rs");

fn main() {
build_foo_static_lib(&["-fsanitize-cfi-icall-experimental-normalize-integers"]);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
int
do_twice(int (*fn)(int), int arg)
{
return fn(arg) + fn(arg);
}
Original file line number Diff line number Diff line change
@@ -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;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Should these perhaps use std::ffi::c_int to ensure the bit width matches C on all targets?

Copy link
Copy Markdown
Member Author

@rcvalle rcvalle May 6, 2026

Choose a reason for hiding this comment

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

This test is mainly to emphasize how the C integer types are encoded by their respective sizes when using integer normalization so I think it makes more sense the Rust side to have a sized integer types. Note all these tests are gated to only-x86_64-unknown-linux-gnu because of the naked_asm in the invalid-branch-target-abort test and to save CI resources anyway.

}

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);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4

[[package]]
name = "cross-lang-integer-normalization-not-abort"
version = "0.1.0"
Comment thread
rcvalle marked this conversation as resolved.
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
[package]
name = "cross-lang-integer-normalization-not-abort"
version = "0.1.0"
edition = "2021"

# 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"
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
include!("../shared_build_rs.rs");

fn main() {
build_foo_static_lib(&["-fsanitize-cfi-icall-experimental-normalize-integers"]);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
#include <stdio.h>
#include <stdlib.h>

// 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);
}
Loading
Loading