From 65698ae9f30f5ad72224edd1884fb4ddd1279366 Mon Sep 17 00:00:00 2001 From: Ramon de C Valle Date: Mon, 21 Nov 2022 21:29:00 -0800 Subject: [PATCH] Add LLVM KCFI support to the Rust compiler This commit adds LLVM Kernel Control Flow Integrity (KCFI) support to the Rust compiler. It initially provides forward-edge control flow protection for operating systems kernels for Rust-compiled code only by aggregating function pointers in groups identified by their return and parameter types. (See llvm/llvm-project@cff5bef.) 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 identifying C char and integer type uses at the time types are encoded (see Type metadata in the design document in the tracking issue #89653). LLVM KCFI can be enabled with -Zsanitizer=kcfi. Co-authored-by: bjorn3 <17426603+bjorn3@users.noreply.github.com> --- Cargo.lock | 12 +++++ compiler/rustc_codegen_gcc/src/type_.rs | 4 ++ compiler/rustc_codegen_llvm/src/allocator.rs | 14 ++++-- compiler/rustc_codegen_llvm/src/builder.rs | 50 ++++++++++++++++--- compiler/rustc_codegen_llvm/src/context.rs | 5 ++ compiler/rustc_codegen_llvm/src/declare.rs | 7 ++- compiler/rustc_codegen_llvm/src/llvm/ffi.rs | 8 ++- compiler/rustc_codegen_llvm/src/type_.rs | 15 ++++++ .../rustc_codegen_ssa/src/traits/type_.rs | 1 + compiler/rustc_feature/src/builtin_attrs.rs | 2 +- compiler/rustc_hir_analysis/src/collect.rs | 4 +- .../rustc_llvm/llvm-wrapper/RustWrapper.cpp | 16 +++--- compiler/rustc_session/src/options.rs | 3 +- compiler/rustc_session/src/session.rs | 12 +++++ compiler/rustc_span/src/symbol.rs | 1 + compiler/rustc_symbol_mangling/Cargo.toml | 1 + compiler/rustc_symbol_mangling/src/typeid.rs | 24 +++++++++ .../src/spec/aarch64_unknown_none.rs | 5 +- compiler/rustc_target/src/spec/mod.rs | 6 ++- .../src/spec/x86_64_unknown_none.rs | 3 +- .../codegen/sanitizer-kcfi-add-kcfi-flag.rs | 11 ++++ ...mit-kcfi-operand-bundle-itanium-cxx-abi.rs | 47 +++++++++++++++++ .../ui/invalid/invalid-no-sanitize.stderr | 2 +- src/tools/compiletest/src/header.rs | 2 + src/tools/compiletest/src/util.rs | 2 + src/tools/tidy/src/deps.rs | 2 + 26 files changed, 231 insertions(+), 28 deletions(-) create mode 100644 src/test/codegen/sanitizer-kcfi-add-kcfi-flag.rs create mode 100644 src/test/codegen/sanitizer-kcfi-emit-kcfi-operand-bundle-itanium-cxx-abi.rs diff --git a/Cargo.lock b/Cargo.lock index fb6140e74fe..51108df4406 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4269,6 +4269,7 @@ dependencies = [ "rustc_span", "rustc_target", "tracing", + "twox-hash", ] [[package]] @@ -5197,6 +5198,17 @@ dependencies = [ "tracing-subscriber", ] +[[package]] +name = "twox-hash" +version = "1.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97fee6b57c6a41524a810daee9286c02d7752c4253064d0b05472833a438f675" +dependencies = [ + "cfg-if 1.0.0", + "rand 0.8.5", + "static_assertions", +] + [[package]] name = "type-map" version = "0.4.0" diff --git a/compiler/rustc_codegen_gcc/src/type_.rs b/compiler/rustc_codegen_gcc/src/type_.rs index bdf7318ce48..89a415cdb36 100644 --- a/compiler/rustc_codegen_gcc/src/type_.rs +++ b/compiler/rustc_codegen_gcc/src/type_.rs @@ -300,4 +300,8 @@ fn typeid_metadata(&self, _typeid: String) -> RValue<'gcc> { // Unsupported. self.context.new_rvalue_from_int(self.int_type, 0) } + + fn set_kcfi_type_metadata(&self, _function: RValue<'gcc>, _kcfi_typeid: u32) { + // Unsupported. + } } diff --git a/compiler/rustc_codegen_llvm/src/allocator.rs b/compiler/rustc_codegen_llvm/src/allocator.rs index fed56cdd438..668d9292705 100644 --- a/compiler/rustc_codegen_llvm/src/allocator.rs +++ b/compiler/rustc_codegen_llvm/src/allocator.rs @@ -88,7 +88,8 @@ pub(crate) unsafe fn codegen( callee, args.as_ptr(), args.len() as c_uint, - None, + [].as_ptr(), + 0 as c_uint, ); llvm::LLVMSetTailCall(ret, True); if output.is_some() { @@ -132,8 +133,15 @@ pub(crate) unsafe fn codegen( .enumerate() .map(|(i, _)| llvm::LLVMGetParam(llfn, i as c_uint)) .collect::>(); - let ret = - llvm::LLVMRustBuildCall(llbuilder, ty, callee, args.as_ptr(), args.len() as c_uint, None); + let ret = llvm::LLVMRustBuildCall( + llbuilder, + ty, + callee, + args.as_ptr(), + args.len() as c_uint, + [].as_ptr(), + 0 as c_uint, + ); llvm::LLVMSetTailCall(ret, True); llvm::LLVMBuildRetVoid(llbuilder); llvm::LLVMDisposeBuilder(llbuilder); diff --git a/compiler/rustc_codegen_llvm/src/builder.rs b/compiler/rustc_codegen_llvm/src/builder.rs index 77dd15ef4d8..83bffb20e0c 100644 --- a/compiler/rustc_codegen_llvm/src/builder.rs +++ b/compiler/rustc_codegen_llvm/src/builder.rs @@ -20,6 +20,7 @@ }; use rustc_middle::ty::{self, Ty, TyCtxt}; use rustc_span::Span; +use rustc_symbol_mangling::typeid::kcfi_typeid_for_fnabi; use rustc_target::abi::{self, call::FnAbi, Align, Size, WrappingRange}; use rustc_target::spec::{HasTargetSpec, Target}; use std::borrow::Cow; @@ -225,9 +226,25 @@ fn invoke( debug!("invoke {:?} with args ({:?})", llfn, args); let args = self.check_call("invoke", llty, llfn, args); - let bundle = funclet.map(|funclet| funclet.bundle()); - let bundle = bundle.as_ref().map(|b| &*b.raw); + let funclet_bundle = funclet.map(|funclet| funclet.bundle()); + let funclet_bundle = funclet_bundle.as_ref().map(|b| &*b.raw); + let mut bundles = vec![funclet_bundle]; + // Set KCFI operand bundle + let is_indirect_call = unsafe { llvm::LLVMIsAFunction(llfn).is_none() }; + let kcfi_bundle = + if self.tcx.sess.is_sanitizer_kcfi_enabled() && fn_abi.is_some() && is_indirect_call { + let kcfi_typeid = kcfi_typeid_for_fnabi(self.tcx, fn_abi.unwrap()); + Some(llvm::OperandBundleDef::new("kcfi", &[self.const_u32(kcfi_typeid)])) + } else { + None + }; + if kcfi_bundle.is_some() { + let kcfi_bundle = kcfi_bundle.as_ref().map(|b| &*b.raw); + bundles.push(kcfi_bundle); + } + + bundles.retain(|bundle| bundle.is_some()); let invoke = unsafe { llvm::LLVMRustBuildInvoke( self.llbuilder, @@ -237,7 +254,8 @@ fn invoke( args.len() as c_uint, then, catch, - bundle, + bundles.as_ptr(), + bundles.len() as c_uint, UNNAMED, ) }; @@ -1143,7 +1161,8 @@ fn instrprof_increment( llfn, args.as_ptr() as *const &llvm::Value, args.len() as c_uint, - None, + [].as_ptr(), + 0 as c_uint, ); } } @@ -1159,9 +1178,25 @@ fn call( debug!("call {:?} with args ({:?})", llfn, args); let args = self.check_call("call", llty, llfn, args); - let bundle = funclet.map(|funclet| funclet.bundle()); - let bundle = bundle.as_ref().map(|b| &*b.raw); + let funclet_bundle = funclet.map(|funclet| funclet.bundle()); + let funclet_bundle = funclet_bundle.as_ref().map(|b| &*b.raw); + let mut bundles = vec![funclet_bundle]; + // Set KCFI operand bundle + let is_indirect_call = unsafe { llvm::LLVMIsAFunction(llfn).is_none() }; + let kcfi_bundle = + if self.tcx.sess.is_sanitizer_kcfi_enabled() && fn_abi.is_some() && is_indirect_call { + let kcfi_typeid = kcfi_typeid_for_fnabi(self.tcx, fn_abi.unwrap()); + Some(llvm::OperandBundleDef::new("kcfi", &[self.const_u32(kcfi_typeid)])) + } else { + None + }; + if kcfi_bundle.is_some() { + let kcfi_bundle = kcfi_bundle.as_ref().map(|b| &*b.raw); + bundles.push(kcfi_bundle); + } + + bundles.retain(|bundle| bundle.is_some()); let call = unsafe { llvm::LLVMRustBuildCall( self.llbuilder, @@ -1169,7 +1204,8 @@ fn call( llfn, args.as_ptr() as *const &llvm::Value, args.len() as c_uint, - bundle, + bundles.as_ptr(), + bundles.len() as c_uint, ) }; if let Some(fn_abi) = fn_abi { diff --git a/compiler/rustc_codegen_llvm/src/context.rs b/compiler/rustc_codegen_llvm/src/context.rs index 4dcc7cd5447..aa1735f38ac 100644 --- a/compiler/rustc_codegen_llvm/src/context.rs +++ b/compiler/rustc_codegen_llvm/src/context.rs @@ -250,6 +250,11 @@ pub unsafe fn create_module<'ll>( ); } + if sess.is_sanitizer_kcfi_enabled() { + let kcfi = "kcfi\0".as_ptr().cast(); + llvm::LLVMRustAddModuleFlag(llmod, llvm::LLVMModFlagBehavior::Override, kcfi, 1); + } + // Control Flow Guard is currently only supported by the MSVC linker on Windows. if sess.target.is_like_msvc { match sess.opts.cg.control_flow_guard { diff --git a/compiler/rustc_codegen_llvm/src/declare.rs b/compiler/rustc_codegen_llvm/src/declare.rs index dc21a02cec4..6a575095f7e 100644 --- a/compiler/rustc_codegen_llvm/src/declare.rs +++ b/compiler/rustc_codegen_llvm/src/declare.rs @@ -20,7 +20,7 @@ use crate::value::Value; use rustc_codegen_ssa::traits::TypeMembershipMethods; use rustc_middle::ty::Ty; -use rustc_symbol_mangling::typeid::typeid_for_fnabi; +use rustc_symbol_mangling::typeid::{kcfi_typeid_for_fnabi, typeid_for_fnabi}; use smallvec::SmallVec; /// Declare a function. @@ -136,6 +136,11 @@ pub fn declare_fn(&self, name: &str, fn_abi: &FnAbi<'tcx, Ty<'tcx>>) -> &'ll Val self.set_type_metadata(llfn, typeid); } + if self.tcx.sess.is_sanitizer_kcfi_enabled() { + let kcfi_typeid = kcfi_typeid_for_fnabi(self.tcx, fn_abi); + self.set_kcfi_type_metadata(llfn, kcfi_typeid); + } + llfn } diff --git a/compiler/rustc_codegen_llvm/src/llvm/ffi.rs b/compiler/rustc_codegen_llvm/src/llvm/ffi.rs index f4519849730..9bcc39cc760 100644 --- a/compiler/rustc_codegen_llvm/src/llvm/ffi.rs +++ b/compiler/rustc_codegen_llvm/src/llvm/ffi.rs @@ -427,6 +427,7 @@ pub enum MetadataType { MD_type = 19, MD_vcall_visibility = 28, MD_noundef = 29, + MD_kcfi_type = 36, } /// LLVMRustAsmDialect @@ -1060,6 +1061,7 @@ pub fn LLVMStructTypeInContext<'a>( pub fn LLVMGlobalSetMetadata<'a>(Val: &'a Value, KindID: c_uint, Metadata: &'a Metadata); pub fn LLVMRustGlobalAddMetadata<'a>(Val: &'a Value, KindID: c_uint, Metadata: &'a Metadata); pub fn LLVMValueAsMetadata(Node: &Value) -> &Metadata; + pub fn LLVMIsAFunction(Val: &Value) -> Option<&Value>; // Operations on constants of any type pub fn LLVMConstNull(Ty: &Type) -> &Value; @@ -1270,7 +1272,8 @@ pub fn LLVMRustBuildInvoke<'a>( NumArgs: c_uint, Then: &'a BasicBlock, Catch: &'a BasicBlock, - Bundle: Option<&OperandBundleDef<'a>>, + OpBundles: *const Option<&OperandBundleDef<'a>>, + NumOpBundles: c_uint, Name: *const c_char, ) -> &'a Value; pub fn LLVMBuildLandingPad<'a>( @@ -1640,7 +1643,8 @@ pub fn LLVMRustBuildCall<'a>( Fn: &'a Value, Args: *const &'a Value, NumArgs: c_uint, - Bundle: Option<&OperandBundleDef<'a>>, + OpBundles: *const Option<&OperandBundleDef<'a>>, + NumOpBundles: c_uint, ) -> &'a Value; pub fn LLVMRustBuildMemCpy<'a>( B: &Builder<'a>, diff --git a/compiler/rustc_codegen_llvm/src/type_.rs b/compiler/rustc_codegen_llvm/src/type_.rs index 5eec7dc6130..a5fab3479e7 100644 --- a/compiler/rustc_codegen_llvm/src/type_.rs +++ b/compiler/rustc_codegen_llvm/src/type_.rs @@ -316,4 +316,19 @@ fn typeid_metadata(&self, typeid: String) -> &'ll Value { ) } } + + fn set_kcfi_type_metadata(&self, function: &'ll Value, kcfi_typeid: u32) { + let kcfi_type_metadata = self.const_u32(kcfi_typeid); + unsafe { + llvm::LLVMGlobalSetMetadata( + function, + llvm::MD_kcfi_type as c_uint, + llvm::LLVMMDNodeInContext2( + self.llcx, + &llvm::LLVMValueAsMetadata(kcfi_type_metadata), + 1, + ), + ) + } + } } diff --git a/compiler/rustc_codegen_ssa/src/traits/type_.rs b/compiler/rustc_codegen_ssa/src/traits/type_.rs index 86481d5d758..109161ccc83 100644 --- a/compiler/rustc_codegen_ssa/src/traits/type_.rs +++ b/compiler/rustc_codegen_ssa/src/traits/type_.rs @@ -122,6 +122,7 @@ fn scalar_pair_element_backend_type( pub trait TypeMembershipMethods<'tcx>: Backend<'tcx> { fn set_type_metadata(&self, function: Self::Function, typeid: String); fn typeid_metadata(&self, typeid: String) -> Self::Value; + fn set_kcfi_type_metadata(&self, function: Self::Function, typeid: u32); } pub trait ArgAbiMethods<'tcx>: HasCodegen<'tcx> { diff --git a/compiler/rustc_feature/src/builtin_attrs.rs b/compiler/rustc_feature/src/builtin_attrs.rs index 4b6e068db43..6d8e78a0f18 100644 --- a/compiler/rustc_feature/src/builtin_attrs.rs +++ b/compiler/rustc_feature/src/builtin_attrs.rs @@ -394,7 +394,7 @@ pub struct BuiltinAttribute { ungated!(instruction_set, Normal, template!(List: "set"), ErrorPreceding), gated!( no_sanitize, Normal, - template!(List: "address, memory, thread"), DuplicatesOk, + template!(List: "address, kcfi, memory, thread"), DuplicatesOk, experimental!(no_sanitize) ), gated!(no_coverage, Normal, template!(Word), WarnFollowing, experimental!(no_coverage)), diff --git a/compiler/rustc_hir_analysis/src/collect.rs b/compiler/rustc_hir_analysis/src/collect.rs index a738ee4a148..7dce80de561 100644 --- a/compiler/rustc_hir_analysis/src/collect.rs +++ b/compiler/rustc_hir_analysis/src/collect.rs @@ -1859,6 +1859,8 @@ fn codegen_fn_attrs(tcx: TyCtxt<'_>, did: DefId) -> CodegenFnAttrs { codegen_fn_attrs.no_sanitize |= SanitizerSet::ADDRESS; } else if item.has_name(sym::cfi) { codegen_fn_attrs.no_sanitize |= SanitizerSet::CFI; + } else if item.has_name(sym::kcfi) { + codegen_fn_attrs.no_sanitize |= SanitizerSet::KCFI; } else if item.has_name(sym::memory) { codegen_fn_attrs.no_sanitize |= SanitizerSet::MEMORY; } else if item.has_name(sym::memtag) { @@ -1872,7 +1874,7 @@ fn codegen_fn_attrs(tcx: TyCtxt<'_>, did: DefId) -> CodegenFnAttrs { } else { tcx.sess .struct_span_err(item.span(), "invalid argument for `no_sanitize`") - .note("expected one of: `address`, `cfi`, `hwaddress`, `memory`, `memtag`, `shadow-call-stack`, or `thread`") + .note("expected one of: `address`, `cfi`, `hwaddress`, `kcfi`, `memory`, `memtag`, `shadow-call-stack`, or `thread`") .emit(); } } diff --git a/compiler/rustc_llvm/llvm-wrapper/RustWrapper.cpp b/compiler/rustc_llvm/llvm-wrapper/RustWrapper.cpp index 216c35d6da0..49d028009fa 100644 --- a/compiler/rustc_llvm/llvm-wrapper/RustWrapper.cpp +++ b/compiler/rustc_llvm/llvm-wrapper/RustWrapper.cpp @@ -1460,13 +1460,13 @@ extern "C" void LLVMRustFreeOperandBundleDef(OperandBundleDef *Bundle) { extern "C" LLVMValueRef LLVMRustBuildCall(LLVMBuilderRef B, LLVMTypeRef Ty, LLVMValueRef Fn, LLVMValueRef *Args, unsigned NumArgs, - OperandBundleDef *Bundle) { + OperandBundleDef **OpBundles, + unsigned NumOpBundles) { Value *Callee = unwrap(Fn); FunctionType *FTy = unwrap(Ty); - unsigned Len = Bundle ? 1 : 0; - ArrayRef Bundles = makeArrayRef(Bundle, Len); return wrap(unwrap(B)->CreateCall( - FTy, Callee, makeArrayRef(unwrap(Args), NumArgs), Bundles)); + FTy, Callee, makeArrayRef(unwrap(Args), NumArgs), + makeArrayRef(*OpBundles, NumOpBundles))); } extern "C" LLVMValueRef LLVMRustGetInstrProfIncrementIntrinsic(LLVMModuleRef M) { @@ -1506,14 +1506,14 @@ extern "C" LLVMValueRef LLVMRustBuildInvoke(LLVMBuilderRef B, LLVMTypeRef Ty, LLVMValueRef Fn, LLVMValueRef *Args, unsigned NumArgs, LLVMBasicBlockRef Then, LLVMBasicBlockRef Catch, - OperandBundleDef *Bundle, const char *Name) { + OperandBundleDef **OpBundles, unsigned NumOpBundles, + const char *Name) { Value *Callee = unwrap(Fn); FunctionType *FTy = unwrap(Ty); - unsigned Len = Bundle ? 1 : 0; - ArrayRef Bundles = makeArrayRef(Bundle, Len); return wrap(unwrap(B)->CreateInvoke(FTy, Callee, unwrap(Then), unwrap(Catch), makeArrayRef(unwrap(Args), NumArgs), - Bundles, Name)); + makeArrayRef(*OpBundles, NumOpBundles), + Name)); } extern "C" void LLVMRustPositionBuilderAtStart(LLVMBuilderRef B, diff --git a/compiler/rustc_session/src/options.rs b/compiler/rustc_session/src/options.rs index f9ee202466f..088e9be28d3 100644 --- a/compiler/rustc_session/src/options.rs +++ b/compiler/rustc_session/src/options.rs @@ -368,7 +368,7 @@ mod desc { pub const parse_opt_panic_strategy: &str = parse_panic_strategy; pub const parse_oom_strategy: &str = "either `panic` or `abort`"; pub const parse_relro_level: &str = "one of: `full`, `partial`, or `off`"; - pub const parse_sanitizers: &str = "comma separated list of sanitizers: `address`, `cfi`, `hwaddress`, `leak`, `memory`, `memtag`, `shadow-call-stack`, or `thread`"; + pub const parse_sanitizers: &str = "comma separated list of sanitizers: `address`, `cfi`, `hwaddress`, `kcfi`, `leak`, `memory`, `memtag`, `shadow-call-stack`, or `thread`"; pub const parse_sanitizer_memory_track_origins: &str = "0, 1, or 2"; pub const parse_cfguard: &str = "either a boolean (`yes`, `no`, `on`, `off`, etc), `checks`, or `nochecks`"; @@ -675,6 +675,7 @@ pub(crate) fn parse_sanitizers(slot: &mut SanitizerSet, v: Option<&str>) -> bool *slot |= match s { "address" => SanitizerSet::ADDRESS, "cfi" => SanitizerSet::CFI, + "kcfi" => SanitizerSet::KCFI, "leak" => SanitizerSet::LEAK, "memory" => SanitizerSet::MEMORY, "memtag" => SanitizerSet::MEMTAG, diff --git a/compiler/rustc_session/src/session.rs b/compiler/rustc_session/src/session.rs index d602acec53e..f13f42cdf75 100644 --- a/compiler/rustc_session/src/session.rs +++ b/compiler/rustc_session/src/session.rs @@ -683,6 +683,10 @@ pub fn is_sanitizer_cfi_enabled(&self) -> bool { self.opts.unstable_opts.sanitizer.contains(SanitizerSet::CFI) } + pub fn is_sanitizer_kcfi_enabled(&self) -> bool { + self.opts.unstable_opts.sanitizer.contains(SanitizerSet::KCFI) + } + /// Check whether this compile session and crate type use static crt. pub fn crt_static(&self, crate_type: Option) -> bool { if !self.target.crt_static_respected { @@ -1530,6 +1534,14 @@ fn validate_commandline_args_with_session_available(sess: &Session) { } } + // LLVM CFI and KCFI are mutually exclusive + if sess.is_sanitizer_cfi_enabled() && sess.is_sanitizer_kcfi_enabled() { + sess.emit_err(CannotMixAndMatchSanitizers { + first: "cfi".to_string(), + second: "kcfi".to_string(), + }); + } + if sess.opts.unstable_opts.stack_protector != StackProtector::None { if !sess.target.options.supports_stack_protector { sess.emit_warning(StackProtectorNotSupportedForTarget { diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs index 29312a21b4d..55eae88caf4 100644 --- a/compiler/rustc_span/src/symbol.rs +++ b/compiler/rustc_span/src/symbol.rs @@ -828,6 +828,7 @@ item_like_imports, iter, iter_repeat, + kcfi, keyword, kind, kreg, diff --git a/compiler/rustc_symbol_mangling/Cargo.toml b/compiler/rustc_symbol_mangling/Cargo.toml index 2a29ad6a9e5..4e447eab02e 100644 --- a/compiler/rustc_symbol_mangling/Cargo.toml +++ b/compiler/rustc_symbol_mangling/Cargo.toml @@ -10,6 +10,7 @@ bitflags = "1.2.1" tracing = "0.1" punycode = "0.4.0" rustc-demangle = "0.1.21" +twox-hash = "1.6.3" rustc_span = { path = "../rustc_span" } rustc_middle = { path = "../rustc_middle" } diff --git a/compiler/rustc_symbol_mangling/src/typeid.rs b/compiler/rustc_symbol_mangling/src/typeid.rs index 9228bea43f9..53983bed718 100644 --- a/compiler/rustc_symbol_mangling/src/typeid.rs +++ b/compiler/rustc_symbol_mangling/src/typeid.rs @@ -3,6 +3,8 @@ use rustc_middle::ty::{FnSig, Ty, TyCtxt}; use rustc_target::abi::call::FnAbi; +use std::hash::Hasher; +use twox_hash::XxHash64; mod typeid_itanium_cxx_abi; use typeid_itanium_cxx_abi::TypeIdOptions; @@ -16,3 +18,25 @@ pub fn typeid_for_fnabi<'tcx>(tcx: TyCtxt<'tcx>, fn_abi: &FnAbi<'tcx, Ty<'tcx>>) pub fn typeid_for_fnsig<'tcx>(tcx: TyCtxt<'tcx>, fn_sig: &FnSig<'tcx>) -> String { typeid_itanium_cxx_abi::typeid_for_fnsig(tcx, fn_sig, TypeIdOptions::NO_OPTIONS) } + +/// Returns an LLVM KCFI type metadata identifier for the specified FnAbi. +pub fn kcfi_typeid_for_fnabi<'tcx>(tcx: TyCtxt<'tcx>, fn_abi: &FnAbi<'tcx, Ty<'tcx>>) -> u32 { + // An LLVM KCFI type metadata identifier is a 32-bit constant produced by taking the lower half + // of the xxHash64 of the type metadata identifier. (See llvm/llvm-project@cff5bef.) + let mut hash: XxHash64 = Default::default(); + hash.write( + typeid_itanium_cxx_abi::typeid_for_fnabi(tcx, fn_abi, TypeIdOptions::NO_OPTIONS).as_bytes(), + ); + hash.finish() as u32 +} + +/// Returns an LLVM KCFI type metadata identifier for the specified FnSig. +pub fn kcfi_typeid_for_fnsig<'tcx>(tcx: TyCtxt<'tcx>, fn_sig: &FnSig<'tcx>) -> u32 { + // An LLVM KCFI type metadata identifier is a 32-bit constant produced by taking the lower half + // of the xxHash64 of the type metadata identifier. (See llvm/llvm-project@cff5bef.) + let mut hash: XxHash64 = Default::default(); + hash.write( + typeid_itanium_cxx_abi::typeid_for_fnsig(tcx, fn_sig, TypeIdOptions::NO_OPTIONS).as_bytes(), + ); + hash.finish() as u32 +} diff --git a/compiler/rustc_target/src/spec/aarch64_unknown_none.rs b/compiler/rustc_target/src/spec/aarch64_unknown_none.rs index 4ae6d4120c9..aca52e1478e 100644 --- a/compiler/rustc_target/src/spec/aarch64_unknown_none.rs +++ b/compiler/rustc_target/src/spec/aarch64_unknown_none.rs @@ -6,13 +6,16 @@ // // For example, `-C target-cpu=cortex-a53`. -use super::{Cc, LinkerFlavor, Lld, PanicStrategy, RelocModel, Target, TargetOptions}; +use super::{ + Cc, LinkerFlavor, Lld, PanicStrategy, RelocModel, SanitizerSet, Target, TargetOptions, +}; pub fn target() -> Target { let opts = TargetOptions { linker_flavor: LinkerFlavor::Gnu(Cc::No, Lld::Yes), linker: Some("rust-lld".into()), features: "+strict-align,+neon,+fp-armv8".into(), + supported_sanitizers: SanitizerSet::KCFI, relocation_model: RelocModel::Static, disable_redzone: true, max_atomic_width: Some(128), diff --git a/compiler/rustc_target/src/spec/mod.rs b/compiler/rustc_target/src/spec/mod.rs index 6d936d2cb9f..65ab7b953a1 100644 --- a/compiler/rustc_target/src/spec/mod.rs +++ b/compiler/rustc_target/src/spec/mod.rs @@ -803,7 +803,7 @@ fn to_json(&self) -> Json { bitflags::bitflags! { #[derive(Default, Encodable, Decodable)] - pub struct SanitizerSet: u8 { + pub struct SanitizerSet: u16 { const ADDRESS = 1 << 0; const LEAK = 1 << 1; const MEMORY = 1 << 2; @@ -812,6 +812,7 @@ pub struct SanitizerSet: u8 { const CFI = 1 << 5; const MEMTAG = 1 << 6; const SHADOWCALLSTACK = 1 << 7; + const KCFI = 1 << 8; } } @@ -823,6 +824,7 @@ pub fn as_str(self) -> Option<&'static str> { Some(match self { SanitizerSet::ADDRESS => "address", SanitizerSet::CFI => "cfi", + SanitizerSet::KCFI => "kcfi", SanitizerSet::LEAK => "leak", SanitizerSet::MEMORY => "memory", SanitizerSet::MEMTAG => "memtag", @@ -858,6 +860,7 @@ fn into_iter(self) -> Self::IntoIter { [ SanitizerSet::ADDRESS, SanitizerSet::CFI, + SanitizerSet::KCFI, SanitizerSet::LEAK, SanitizerSet::MEMORY, SanitizerSet::MEMTAG, @@ -2292,6 +2295,7 @@ macro_rules! key { base.$key_name |= match s.as_str() { Some("address") => SanitizerSet::ADDRESS, Some("cfi") => SanitizerSet::CFI, + Some("kcfi") => SanitizerSet::KCFI, Some("leak") => SanitizerSet::LEAK, Some("memory") => SanitizerSet::MEMORY, Some("memtag") => SanitizerSet::MEMTAG, diff --git a/compiler/rustc_target/src/spec/x86_64_unknown_none.rs b/compiler/rustc_target/src/spec/x86_64_unknown_none.rs index e4d33c2b8c6..32060c35c11 100644 --- a/compiler/rustc_target/src/spec/x86_64_unknown_none.rs +++ b/compiler/rustc_target/src/spec/x86_64_unknown_none.rs @@ -5,7 +5,7 @@ // features. use super::{Cc, CodeModel, LinkerFlavor, Lld, PanicStrategy}; -use super::{RelroLevel, StackProbeType, Target, TargetOptions}; +use super::{RelroLevel, SanitizerSet, StackProbeType, Target, TargetOptions}; pub fn target() -> Target { let opts = TargetOptions { @@ -20,6 +20,7 @@ pub fn target() -> Target { features: "-mmx,-sse,-sse2,-sse3,-ssse3,-sse4.1,-sse4.2,-3dnow,-3dnowa,-avx,-avx2,+soft-float" .into(), + supported_sanitizers: SanitizerSet::KCFI, disable_redzone: true, panic_strategy: PanicStrategy::Abort, code_model: Some(CodeModel::Kernel), diff --git a/src/test/codegen/sanitizer-kcfi-add-kcfi-flag.rs b/src/test/codegen/sanitizer-kcfi-add-kcfi-flag.rs new file mode 100644 index 00000000000..c2eb852aec3 --- /dev/null +++ b/src/test/codegen/sanitizer-kcfi-add-kcfi-flag.rs @@ -0,0 +1,11 @@ +// Verifies that "kcfi" module flag is added. +// +// needs-sanitizer-kcfi +// compile-flags: -Ctarget-feature=-crt-static -Zsanitizer=kcfi + +#![crate_type="lib"] + +pub fn foo() { +} + +// CHECK: !{{[0-9]+}} = !{i32 4, !"kcfi", i32 1} diff --git a/src/test/codegen/sanitizer-kcfi-emit-kcfi-operand-bundle-itanium-cxx-abi.rs b/src/test/codegen/sanitizer-kcfi-emit-kcfi-operand-bundle-itanium-cxx-abi.rs new file mode 100644 index 00000000000..0afd9727517 --- /dev/null +++ b/src/test/codegen/sanitizer-kcfi-emit-kcfi-operand-bundle-itanium-cxx-abi.rs @@ -0,0 +1,47 @@ +// Verifies that KCFI type metadata for functions are emitted. +// +// revisions: aarch64 x86_64 +// [aarch64] compile-flags: --target aarch64-unknown-none +// [aarch64] needs-llvm-components: aarch64 +// [x86_64] compile-flags: --target x86_64-unknown-none +// [x86_64] needs-llvm-components: +// compile-flags: -Cno-prepopulate-passes -Zsanitizer=kcfi + +#![crate_type="lib"] +#![feature(no_core, lang_items)] +#![no_core] + +#[lang="sized"] +trait Sized { } +#[lang="copy"] +trait Copy { } + +impl Copy for i32 {} + +pub fn foo(f: fn(i32) -> i32, arg: i32) -> i32 { + // CHECK-LABEL: define{{.*}}foo + // FIXME(rcvalle): Change to !kcfi_type when Rust is updated to LLVM 16 + // CHECK-SAME: {{.*}}! ![[TYPE1:[0-9]+]] + // CHECK: call i32 %f(i32 %arg){{.*}}[ "kcfi"(i32 -1666898348) ] + f(arg) +} + +pub fn bar(f: fn(i32, i32) -> i32, arg1: i32, arg2: i32) -> i32 { + // CHECK-LABEL: define{{.*}}bar + // FIXME(rcvalle): Change to !kcfi_type when Rust is updated to LLVM 16 + // CHECK-SAME: {{.*}}! ![[TYPE2:[0-9]+]] + // CHECK: call i32 %f(i32 %arg1, i32 %arg2){{.*}}[ "kcfi"(i32 -1789026986) ] + f(arg1, arg2) +} + +pub fn baz(f: fn(i32, i32, i32) -> i32, arg1: i32, arg2: i32, arg3: i32) -> i32 { + // CHECK-LABEL: define{{.*}}baz + // FIXME(rcvalle): Change to !kcfi_type when Rust is updated to LLVM 16 + // CHECK-SAME: {{.*}}! ![[TYPE3:[0-9]+]] + // CHECK: call i32 %f(i32 %arg1, i32 %arg2, i32 %arg3){{.*}}[ "kcfi"(i32 1248878270) ] + f(arg1, arg2, arg3) +} + +// CHECK: ![[TYPE1]] = !{i32 653723426} +// CHECK: ![[TYPE2]] = !{i32 412174924} +// CHECK: ![[TYPE3]] = !{i32 -636668840} diff --git a/src/test/ui/invalid/invalid-no-sanitize.stderr b/src/test/ui/invalid/invalid-no-sanitize.stderr index d328cafa00b..4600034952b 100644 --- a/src/test/ui/invalid/invalid-no-sanitize.stderr +++ b/src/test/ui/invalid/invalid-no-sanitize.stderr @@ -4,7 +4,7 @@ error: invalid argument for `no_sanitize` LL | #[no_sanitize(brontosaurus)] | ^^^^^^^^^^^^ | - = note: expected one of: `address`, `cfi`, `hwaddress`, `memory`, `memtag`, `shadow-call-stack`, or `thread` + = note: expected one of: `address`, `cfi`, `hwaddress`, `kcfi`, `memory`, `memtag`, `shadow-call-stack`, or `thread` error: aborting due to previous error diff --git a/src/tools/compiletest/src/header.rs b/src/tools/compiletest/src/header.rs index 0d9a629e179..eae9e80a397 100644 --- a/src/tools/compiletest/src/header.rs +++ b/src/tools/compiletest/src/header.rs @@ -906,6 +906,7 @@ pub fn make_test_description( let has_asm_support = config.has_asm_support(); let has_asan = util::ASAN_SUPPORTED_TARGETS.contains(&&*config.target); let has_cfi = util::CFI_SUPPORTED_TARGETS.contains(&&*config.target); + let has_kcfi = util::KCFI_SUPPORTED_TARGETS.contains(&&*config.target); let has_lsan = util::LSAN_SUPPORTED_TARGETS.contains(&&*config.target); let has_msan = util::MSAN_SUPPORTED_TARGETS.contains(&&*config.target); let has_tsan = util::TSAN_SUPPORTED_TARGETS.contains(&&*config.target); @@ -957,6 +958,7 @@ pub fn make_test_description( && config.parse_name_directive(ln, "needs-sanitizer-support"); ignore |= !has_asan && config.parse_name_directive(ln, "needs-sanitizer-address"); ignore |= !has_cfi && config.parse_name_directive(ln, "needs-sanitizer-cfi"); + ignore |= !has_kcfi && config.parse_name_directive(ln, "needs-sanitizer-kcfi"); ignore |= !has_lsan && config.parse_name_directive(ln, "needs-sanitizer-leak"); ignore |= !has_msan && config.parse_name_directive(ln, "needs-sanitizer-memory"); ignore |= !has_tsan && config.parse_name_directive(ln, "needs-sanitizer-thread"); diff --git a/src/tools/compiletest/src/util.rs b/src/tools/compiletest/src/util.rs index ec36f1e4fb7..ccba313ee35 100644 --- a/src/tools/compiletest/src/util.rs +++ b/src/tools/compiletest/src/util.rs @@ -42,6 +42,8 @@ "x86_64-unknown-netbsd", ]; +pub const KCFI_SUPPORTED_TARGETS: &[&str] = &["aarch64-linux-none", "x86_64-linux-none"]; + pub const LSAN_SUPPORTED_TARGETS: &[&str] = &[ // FIXME: currently broken, see #88132 // "aarch64-apple-darwin", diff --git a/src/tools/tidy/src/deps.rs b/src/tools/tidy/src/deps.rs index 8155ec9dd27..36dec1d415f 100644 --- a/src/tools/tidy/src/deps.rs +++ b/src/tools/tidy/src/deps.rs @@ -214,6 +214,7 @@ "snap", "stable_deref_trait", "stacker", + "static_assertions", "syn", "synstructure", "tempfile", @@ -234,6 +235,7 @@ "tracing-log", "tracing-subscriber", "tracing-tree", + "twox-hash", "type-map", "typenum", "unic-char-property",