Skip to content

Commit a8f6e61

Browse files
committed
Auto merge of #89652 - rcvalle:rust-cfi, r=nagisa
Add LLVM CFI support to the Rust compiler This PR adds LLVM Control Flow Integrity (CFI) support to the Rust compiler. It initially provides forward-edge control flow protection for Rust-compiled code only by aggregating function pointers in groups identified by their number of arguments. Forward-edge control flow protection for C or C++ and Rust -compiled code "mixed binaries" (i.e., for when C or C++ and Rust -compiled code share the same virtual address space) will be provided in later work as part of this project by defining and using compatible type identifiers (see Type metadata in the design document in the tracking issue #89653). LLVM CFI can be enabled with -Zsanitizer=cfi and requires LTO (i.e., -Clto). Thank you, `@eddyb` and `@pcc,` for all the help!
2 parents 47aeac6 + c5708ca commit a8f6e61

35 files changed

+473
-39
lines changed

compiler/rustc_codegen_gcc/src/builder.rs

+10
Original file line numberDiff line numberDiff line change
@@ -915,6 +915,16 @@ impl<'a, 'gcc, 'tcx> BuilderMethods<'a, 'tcx> for Builder<'a, 'gcc, 'tcx> {
915915
// TODO(antoyo)
916916
}
917917

918+
fn type_metadata(&mut self, _function: RValue<'gcc>, _typeid: String) {
919+
// Unsupported.
920+
}
921+
922+
fn typeid_metadata(&mut self, _typeid: String) -> RValue<'gcc> {
923+
// Unsupported.
924+
self.context.new_rvalue_from_int(self.int_type, 0)
925+
}
926+
927+
918928
fn store(&mut self, val: RValue<'gcc>, ptr: RValue<'gcc>, align: Align) -> RValue<'gcc> {
919929
self.store_with_flags(val, ptr, align, MemFlags::empty())
920930
}

compiler/rustc_codegen_gcc/src/intrinsic/mod.rs

+5
Original file line numberDiff line numberDiff line change
@@ -367,6 +367,11 @@ impl<'a, 'gcc, 'tcx> IntrinsicCallMethods<'tcx> for Builder<'a, 'gcc, 'tcx> {
367367
// TODO(antoyo)
368368
}
369369

370+
fn type_test(&mut self, _pointer: Self::Value, _typeid: Self::Value) -> Self::Value {
371+
// Unsupported.
372+
self.context.new_rvalue_from_int(self.int_type, 0)
373+
}
374+
370375
fn va_start(&mut self, _va_list: RValue<'gcc>) -> RValue<'gcc> {
371376
unimplemented!();
372377
}

compiler/rustc_codegen_llvm/src/builder.rs

+26
Original file line numberDiff line numberDiff line change
@@ -604,6 +604,32 @@ impl BuilderMethods<'a, 'tcx> for Builder<'a, 'll, 'tcx> {
604604
}
605605
}
606606

607+
fn type_metadata(&mut self, function: &'ll Value, typeid: String) {
608+
let typeid_metadata = self.typeid_metadata(typeid);
609+
let v = [self.const_usize(0), typeid_metadata];
610+
unsafe {
611+
llvm::LLVMGlobalSetMetadata(
612+
function,
613+
llvm::MD_type as c_uint,
614+
llvm::LLVMValueAsMetadata(llvm::LLVMMDNodeInContext(
615+
self.cx.llcx,
616+
v.as_ptr(),
617+
v.len() as c_uint,
618+
)),
619+
)
620+
}
621+
}
622+
623+
fn typeid_metadata(&mut self, typeid: String) -> Self::Value {
624+
unsafe {
625+
llvm::LLVMMDStringInContext(
626+
self.cx.llcx,
627+
typeid.as_ptr() as *const c_char,
628+
typeid.as_bytes().len() as c_uint,
629+
)
630+
}
631+
}
632+
607633
fn store(&mut self, val: &'ll Value, ptr: &'ll Value, align: Align) -> &'ll Value {
608634
self.store_with_flags(val, ptr, align, MemFlags::empty())
609635
}

compiler/rustc_codegen_llvm/src/context.rs

+11
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,15 @@ pub unsafe fn create_module(
221221
llvm::LLVMRustAddModuleFlag(llmod, avoid_plt, 1);
222222
}
223223

224+
if sess.is_sanitizer_cfi_enabled() {
225+
// FIXME(rcvalle): Add support for non canonical jump tables.
226+
let canonical_jump_tables = "CFI Canonical Jump Tables\0".as_ptr().cast();
227+
// FIXME(rcvalle): Add it with Override behavior flag--LLVMRustAddModuleFlag adds it with
228+
// Warning behavior flag. Add support for specifying the behavior flag to
229+
// LLVMRustAddModuleFlag.
230+
llvm::LLVMRustAddModuleFlag(llmod, canonical_jump_tables, 1);
231+
}
232+
224233
// Control Flow Guard is currently only supported by the MSVC linker on Windows.
225234
if sess.target.is_like_msvc {
226235
match sess.opts.cg.control_flow_guard {
@@ -779,6 +788,8 @@ impl CodegenCx<'b, 'tcx> {
779788
ifn!("llvm.instrprof.increment", fn(i8p, t_i64, t_i32, t_i32) -> void);
780789
}
781790

791+
ifn!("llvm.type.test", fn(i8p, self.type_metadata()) -> i1);
792+
782793
if self.sess().opts.debuginfo != DebugInfo::None {
783794
ifn!("llvm.dbg.declare", fn(self.type_metadata(), self.type_metadata()) -> void);
784795
ifn!("llvm.dbg.value", fn(self.type_metadata(), t_i64, self.type_metadata()) -> void);

compiler/rustc_codegen_llvm/src/intrinsic.rs

+8
Original file line numberDiff line numberDiff line change
@@ -401,6 +401,14 @@ impl IntrinsicCallMethods<'tcx> for Builder<'a, 'll, 'tcx> {
401401
}
402402
}
403403

404+
fn type_test(&mut self, pointer: Self::Value, typeid: Self::Value) -> Self::Value {
405+
// Test the called operand using llvm.type.test intrinsic. The LowerTypeTests link-time
406+
// optimization pass replaces calls to this intrinsic with code to test type membership.
407+
let i8p_ty = self.type_i8p();
408+
let bitcast = self.bitcast(pointer, i8p_ty);
409+
self.call_intrinsic("llvm.type.test", &[bitcast, typeid])
410+
}
411+
404412
fn va_start(&mut self, va_list: &'ll Value) -> &'ll Value {
405413
self.call_intrinsic("llvm.va_start", &[va_list])
406414
}

compiler/rustc_codegen_llvm/src/llvm/ffi.rs

+3
Original file line numberDiff line numberDiff line change
@@ -416,6 +416,7 @@ pub enum MetadataType {
416416
MD_nontemporal = 9,
417417
MD_mem_parallel_loop_access = 10,
418418
MD_nonnull = 11,
419+
MD_type = 19,
419420
}
420421

421422
/// LLVMRustAsmDialect
@@ -1002,6 +1003,8 @@ extern "C" {
10021003
pub fn LLVMSetValueName2(Val: &Value, Name: *const c_char, NameLen: size_t);
10031004
pub fn LLVMReplaceAllUsesWith(OldVal: &'a Value, NewVal: &'a Value);
10041005
pub fn LLVMSetMetadata(Val: &'a Value, KindID: c_uint, Node: &'a Value);
1006+
pub fn LLVMGlobalSetMetadata(Val: &'a Value, KindID: c_uint, Metadata: &'a Metadata);
1007+
pub fn LLVMValueAsMetadata(Node: &'a Value) -> &Metadata;
10051008

10061009
// Operations on constants of any type
10071010
pub fn LLVMConstNull(Ty: &Type) -> &Value;

compiler/rustc_codegen_ssa/src/mir/block.rs

+35-3
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ use rustc_middle::ty::print::{with_no_trimmed_paths, with_no_visible_paths};
1919
use rustc_middle::ty::{self, Instance, Ty, TypeFoldable};
2020
use rustc_span::source_map::Span;
2121
use rustc_span::{sym, Symbol};
22+
use rustc_symbol_mangling::typeid_for_fnabi;
2223
use rustc_target::abi::call::{ArgAbi, FnAbi, PassMode};
2324
use rustc_target::abi::{self, HasDataLayout, WrappingRange};
2425
use rustc_target::spec::abi::Abi;
@@ -818,12 +819,43 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
818819
self.codegen_argument(&mut bx, location, &mut llargs, last_arg);
819820
}
820821

821-
let fn_ptr = match (llfn, instance) {
822-
(Some(llfn), _) => llfn,
823-
(None, Some(instance)) => bx.get_fn_addr(instance),
822+
let (is_indirect_call, fn_ptr) = match (llfn, instance) {
823+
(Some(llfn), _) => (true, llfn),
824+
(None, Some(instance)) => (false, bx.get_fn_addr(instance)),
824825
_ => span_bug!(span, "no llfn for call"),
825826
};
826827

828+
// For backends that support CFI using type membership (i.e., testing whether a given
829+
// pointer is associated with a type identifier).
830+
if bx.tcx().sess.is_sanitizer_cfi_enabled() && is_indirect_call {
831+
// Emit type metadata and checks.
832+
// FIXME(rcvalle): Add support for generalized identifiers.
833+
// FIXME(rcvalle): Create distinct unnamed MDNodes for internal identifiers.
834+
let typeid = typeid_for_fnabi(bx.tcx(), fn_abi);
835+
let typeid_metadata = bx.typeid_metadata(typeid.clone());
836+
837+
// Test whether the function pointer is associated with the type identifier.
838+
let cond = bx.type_test(fn_ptr, typeid_metadata);
839+
let mut bx_pass = bx.build_sibling_block("type_test.pass");
840+
let mut bx_fail = bx.build_sibling_block("type_test.fail");
841+
bx.cond_br(cond, bx_pass.llbb(), bx_fail.llbb());
842+
843+
helper.do_call(
844+
self,
845+
&mut bx_pass,
846+
fn_abi,
847+
fn_ptr,
848+
&llargs,
849+
destination.as_ref().map(|&(_, target)| (ret_dest, target)),
850+
cleanup,
851+
);
852+
853+
bx_fail.abort();
854+
bx_fail.unreachable();
855+
856+
return;
857+
}
858+
827859
helper.do_call(
828860
self,
829861
&mut bx,

compiler/rustc_codegen_ssa/src/mir/mod.rs

+8
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ use rustc_middle::mir;
44
use rustc_middle::mir::interpret::ErrorHandled;
55
use rustc_middle::ty::layout::{FnAbiOf, HasTyCtxt, TyAndLayout};
66
use rustc_middle::ty::{self, Instance, Ty, TypeFoldable};
7+
use rustc_symbol_mangling::typeid_for_fnabi;
78
use rustc_target::abi::call::{FnAbi, PassMode};
89

910
use std::iter;
@@ -244,6 +245,13 @@ pub fn codegen_mir<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>>(
244245
for (bb, _) in traversal::reverse_postorder(&mir) {
245246
fx.codegen_block(bb);
246247
}
248+
249+
// For backends that support CFI using type membership (i.e., testing whether a given pointer
250+
// is associated with a type identifier).
251+
if cx.tcx().sess.is_sanitizer_cfi_enabled() {
252+
let typeid = typeid_for_fnabi(cx.tcx(), fn_abi);
253+
bx.type_metadata(llfn, typeid.clone());
254+
}
247255
}
248256

249257
/// Produces, for each argument, a `Value` pointing at the

compiler/rustc_codegen_ssa/src/traits/builder.rs

+2
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,8 @@ pub trait BuilderMethods<'a, 'tcx>:
158158

159159
fn range_metadata(&mut self, load: Self::Value, range: WrappingRange);
160160
fn nonnull_metadata(&mut self, load: Self::Value);
161+
fn type_metadata(&mut self, function: Self::Function, typeid: String);
162+
fn typeid_metadata(&mut self, typeid: String) -> Self::Value;
161163

162164
fn store(&mut self, val: Self::Value, ptr: Self::Value, align: Align) -> Self::Value;
163165
fn store_with_flags(

compiler/rustc_codegen_ssa/src/traits/intrinsic.rs

+2
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ pub trait IntrinsicCallMethods<'tcx>: BackendTypes {
2424
///
2525
/// Currently has any effect only when LLVM versions prior to 12.0 are used as the backend.
2626
fn sideeffect(&mut self);
27+
/// Trait method used to test whether a given pointer is associated with a type identifier.
28+
fn type_test(&mut self, pointer: Self::Value, typeid: Self::Value) -> Self::Value;
2729
/// Trait method used to inject `va_start` on the "spoofed" `VaListImpl` in
2830
/// Rust defined C-variadic functions.
2931
fn va_start(&mut self, val: Self::Value) -> Self::Value;

compiler/rustc_session/src/options.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -351,8 +351,7 @@ mod desc {
351351
pub const parse_panic_strategy: &str = "either `unwind` or `abort`";
352352
pub const parse_opt_panic_strategy: &str = parse_panic_strategy;
353353
pub const parse_relro_level: &str = "one of: `full`, `partial`, or `off`";
354-
pub const parse_sanitizers: &str =
355-
"comma separated list of sanitizers: `address`, `hwaddress`, `leak`, `memory` or `thread`";
354+
pub const parse_sanitizers: &str = "comma separated list of sanitizers: `address`, `cfi`, `hwaddress`, `leak`, `memory` or `thread`";
356355
pub const parse_sanitizer_memory_track_origins: &str = "0, 1, or 2";
357356
pub const parse_cfguard: &str =
358357
"either a boolean (`yes`, `no`, `on`, `off`, etc), `checks`, or `nochecks`";
@@ -605,6 +604,7 @@ mod parse {
605604
for s in v.split(',') {
606605
*slot |= match s {
607606
"address" => SanitizerSet::ADDRESS,
607+
"cfi" => SanitizerSet::CFI,
608608
"leak" => SanitizerSet::LEAK,
609609
"memory" => SanitizerSet::MEMORY,
610610
"thread" => SanitizerSet::THREAD,

compiler/rustc_session/src/session.rs

+13
Original file line numberDiff line numberDiff line change
@@ -672,6 +672,9 @@ impl Session {
672672
pub fn is_nightly_build(&self) -> bool {
673673
self.opts.unstable_features.is_nightly_build()
674674
}
675+
pub fn is_sanitizer_cfi_enabled(&self) -> bool {
676+
self.opts.debugging_opts.sanitizer.contains(SanitizerSet::CFI)
677+
}
675678
pub fn overflow_checks(&self) -> bool {
676679
self.opts
677680
.cg
@@ -1398,6 +1401,16 @@ fn validate_commandline_args_with_session_available(sess: &Session) {
13981401
disable it using `-C target-feature=-crt-static`",
13991402
);
14001403
}
1404+
1405+
// LLVM CFI requires LTO.
1406+
if sess.is_sanitizer_cfi_enabled() {
1407+
if sess.opts.cg.lto == config::LtoCli::Unspecified
1408+
|| sess.opts.cg.lto == config::LtoCli::No
1409+
|| sess.opts.cg.lto == config::LtoCli::Thin
1410+
{
1411+
sess.err("`-Zsanitizer=cfi` requires `-Clto`");
1412+
}
1413+
}
14011414
}
14021415

14031416
/// Holds data on the current incremental compilation session, if there is one.

compiler/rustc_span/src/symbol.rs

+1
Original file line numberDiff line numberDiff line change
@@ -407,6 +407,7 @@ symbols! {
407407
cfg_target_thread_local,
408408
cfg_target_vendor,
409409
cfg_version,
410+
cfi,
410411
char,
411412
client,
412413
clippy,

compiler/rustc_symbol_mangling/src/lib.rs

+7-1
Original file line numberDiff line numberDiff line change
@@ -104,8 +104,9 @@ use rustc_middle::middle::codegen_fn_attrs::CodegenFnAttrFlags;
104104
use rustc_middle::mir::mono::{InstantiationMode, MonoItem};
105105
use rustc_middle::ty::query::Providers;
106106
use rustc_middle::ty::subst::SubstsRef;
107-
use rustc_middle::ty::{self, Instance, TyCtxt};
107+
use rustc_middle::ty::{self, Instance, Ty, TyCtxt};
108108
use rustc_session::config::SymbolManglingVersion;
109+
use rustc_target::abi::call::FnAbi;
109110

110111
use tracing::debug;
111112

@@ -151,6 +152,11 @@ fn symbol_name_provider(tcx: TyCtxt<'tcx>, instance: Instance<'tcx>) -> ty::Symb
151152
ty::SymbolName::new(tcx, &symbol_name)
152153
}
153154

155+
/// This function computes the typeid for the given function ABI.
156+
pub fn typeid_for_fnabi(tcx: TyCtxt<'tcx>, fn_abi: &FnAbi<'tcx, Ty<'tcx>>) -> String {
157+
v0::mangle_typeid_for_fnabi(tcx, fn_abi)
158+
}
159+
154160
/// Computes the symbol name for the given instance. This function will call
155161
/// `compute_instantiating_crate` if it needs to factor the instantiating crate
156162
/// into the symbol name.

compiler/rustc_symbol_mangling/src/v0.rs

+36
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ use rustc_middle::ty::layout::IntegerExt;
99
use rustc_middle::ty::print::{Print, Printer};
1010
use rustc_middle::ty::subst::{GenericArg, GenericArgKind, Subst};
1111
use rustc_middle::ty::{self, FloatTy, Instance, IntTy, Ty, TyCtxt, TypeFoldable, UintTy};
12+
use rustc_target::abi::call::FnAbi;
1213
use rustc_target::abi::Integer;
1314
use rustc_target::spec::abi::Abi;
1415

@@ -55,6 +56,41 @@ pub(super) fn mangle(
5556
std::mem::take(&mut cx.out)
5657
}
5758

59+
pub(super) fn mangle_typeid_for_fnabi(
60+
_tcx: TyCtxt<'tcx>,
61+
fn_abi: &FnAbi<'tcx, Ty<'tcx>>,
62+
) -> String {
63+
// LLVM uses type metadata to allow IR modules to aggregate pointers by their types.[1] This
64+
// type metadata is used by LLVM Control Flow Integrity to test whether a given pointer is
65+
// associated with a type identifier (i.e., test type membership).
66+
//
67+
// Clang uses the Itanium C++ ABI's[2] virtual tables and RTTI typeinfo structure name[3] as
68+
// type metadata identifiers for function pointers. The typeinfo name encoding is a
69+
// two-character code (i.e., “TS”) prefixed to the type encoding for the function.
70+
//
71+
// For cross-language LLVM CFI support, a compatible encoding must be used by either
72+
//
73+
// a. Using a superset of types that encompasses types used by Clang (i.e., Itanium C++ ABI's
74+
// type encodings[4]), or at least types used at the FFI boundary.
75+
// b. Reducing the types to the least common denominator between types used by Clang (or at
76+
// least types used at the FFI boundary) and Rust compilers (if even possible).
77+
// c. Creating a new ABI for cross-language CFI and using it for Clang and Rust compilers (and
78+
// possibly other compilers).
79+
//
80+
// Option (b) may weaken the protection for Rust-compiled only code, so it should be provided
81+
// as an alternative to a Rust-specific encoding for when mixing Rust and C and C++ -compiled
82+
// code. Option (c) would require changes to Clang to use the new ABI.
83+
//
84+
// [1] https://llvm.org/docs/TypeMetadata.html
85+
// [2] https://itanium-cxx-abi.github.io/cxx-abi/abi.html
86+
// [3] https://itanium-cxx-abi.github.io/cxx-abi/abi.html#mangling-special-vtables
87+
// [4] https://itanium-cxx-abi.github.io/cxx-abi/abi.html#mangling-type
88+
//
89+
// FIXME(rcvalle): See comment above.
90+
let arg_count = fn_abi.args.len() + fn_abi.ret.is_indirect() as usize;
91+
format!("typeid{}", arg_count)
92+
}
93+
5894
struct BinderLevel {
5995
/// The range of distances from the root of what's
6096
/// being printed, to the lifetimes in a binder.

compiler/rustc_target/src/spec/aarch64_apple_darwin.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ pub fn target() -> Target {
66
base.max_atomic_width = Some(128);
77

88
// FIXME: The leak sanitizer currently fails the tests, see #88132.
9-
base.supported_sanitizers = SanitizerSet::ADDRESS | SanitizerSet::THREAD;
9+
base.supported_sanitizers = SanitizerSet::ADDRESS | SanitizerSet::CFI | SanitizerSet::THREAD;
1010

1111
base.pre_link_args.insert(LinkerFlavor::Gcc, vec!["-arch".to_string(), "arm64".to_string()]);
1212
base.link_env_remove.extend(super::apple_base::macos_link_env_remove());

compiler/rustc_target/src/spec/aarch64_fuchsia.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ pub fn target() -> Target {
88
arch: "aarch64".to_string(),
99
options: TargetOptions {
1010
max_atomic_width: Some(128),
11-
supported_sanitizers: SanitizerSet::ADDRESS,
11+
supported_sanitizers: SanitizerSet::ADDRESS | SanitizerSet::CFI,
1212
..super::fuchsia_base::opts()
1313
},
1414
}

compiler/rustc_target/src/spec/aarch64_linux_android.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ pub fn target() -> Target {
1414
// As documented in https://developer.android.com/ndk/guides/cpu-features.html
1515
// the neon (ASIMD) and FP must exist on all android aarch64 targets.
1616
features: "+neon,+fp-armv8".to_string(),
17-
supported_sanitizers: SanitizerSet::HWADDRESS,
17+
supported_sanitizers: SanitizerSet::CFI | SanitizerSet::HWADDRESS,
1818
..super::android_base::opts()
1919
},
2020
}

compiler/rustc_target/src/spec/aarch64_unknown_freebsd.rs

+1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ pub fn target() -> Target {
99
options: TargetOptions {
1010
max_atomic_width: Some(128),
1111
supported_sanitizers: SanitizerSet::ADDRESS
12+
| SanitizerSet::CFI
1213
| SanitizerSet::MEMORY
1314
| SanitizerSet::THREAD,
1415
..super::freebsd_base::opts()

compiler/rustc_target/src/spec/aarch64_unknown_linux_gnu.rs

+1
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ pub fn target() -> Target {
1010
mcount: "\u{1}_mcount".to_string(),
1111
max_atomic_width: Some(128),
1212
supported_sanitizers: SanitizerSet::ADDRESS
13+
| SanitizerSet::CFI
1314
| SanitizerSet::LEAK
1415
| SanitizerSet::MEMORY
1516
| SanitizerSet::THREAD

0 commit comments

Comments
 (0)