Add cross-language LLVM CFI support to the Rust compiler

This commit adds cross-language LLVM Control Flow Integrity (CFI)
support to the Rust compiler by adding the
`-Zsanitizer-cfi-normalize-integers` option to be used with Clang
`-fsanitize-cfi-icall-normalize-integers` for normalizing integer types
(see https://reviews.llvm.org/D139395).

It provides 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). For more
information about LLVM CFI and cross-language LLVM CFI support for the
Rust compiler, see design document in the tracking issue #89653.

Cross-language LLVM CFI can be enabled with -Zsanitizer=cfi and
-Zsanitizer-cfi-normalize-integers, and requires proper (i.e.,
non-rustc) LTO (i.e., -Clinker-plugin-lto).
This commit is contained in:
Ramon de C Valle 2022-12-12 22:42:44 -08:00
parent f40e4da5d9
commit 0d183f8e74
4 changed files with 14 additions and 24 deletions

View File

@ -501,7 +501,7 @@ fn codegen_inline_asm(&mut self, template: &[InlineAsmTemplatePiece], rust_opera
if options.contains(InlineAsmOptions::NORETURN) { if options.contains(InlineAsmOptions::NORETURN) {
let builtin_unreachable = self.context.get_builtin_function("__builtin_unreachable"); let builtin_unreachable = self.context.get_builtin_function("__builtin_unreachable");
let builtin_unreachable: RValue<'gcc> = unsafe { std::mem::transmute(builtin_unreachable) }; let builtin_unreachable: RValue<'gcc> = unsafe { std::mem::transmute(builtin_unreachable) };
self.call(self.type_void(), None, builtin_unreachable, &[], None); self.call(self.type_void(), None, None, builtin_unreachable, &[], None);
} }
// Write results to outputs. // Write results to outputs.

View File

@ -35,6 +35,7 @@
}; };
use rustc_data_structures::fx::FxHashSet; use rustc_data_structures::fx::FxHashSet;
use rustc_middle::bug; use rustc_middle::bug;
use rustc_middle::middle::codegen_fn_attrs::CodegenFnAttrs;
use rustc_middle::ty::{ParamEnv, Ty, TyCtxt}; use rustc_middle::ty::{ParamEnv, Ty, TyCtxt};
use rustc_middle::ty::layout::{FnAbiError, FnAbiOfHelpers, FnAbiRequest, HasParamEnv, HasTyCtxt, LayoutError, LayoutOfHelpers, TyAndLayout}; use rustc_middle::ty::layout::{FnAbiError, FnAbiOfHelpers, FnAbiRequest, HasParamEnv, HasTyCtxt, LayoutError, LayoutOfHelpers, TyAndLayout};
use rustc_span::Span; use rustc_span::Span;
@ -455,12 +456,12 @@ fn switch(&mut self, value: RValue<'gcc>, default_block: Block<'gcc>, cases: imp
} }
#[cfg(feature="master")] #[cfg(feature="master")]
fn invoke(&mut self, typ: Type<'gcc>, _fn_abi: Option<&FnAbi<'tcx, Ty<'tcx>>>, func: RValue<'gcc>, args: &[RValue<'gcc>], then: Block<'gcc>, catch: Block<'gcc>, _funclet: Option<&Funclet>) -> RValue<'gcc> { fn invoke(&mut self, typ: Type<'gcc>, fn_attrs: Option<&CodegenFnAttrs>, _fn_abi: Option<&FnAbi<'tcx, Ty<'tcx>>>, func: RValue<'gcc>, args: &[RValue<'gcc>], then: Block<'gcc>, catch: Block<'gcc>, _funclet: Option<&Funclet>) -> RValue<'gcc> {
let try_block = self.current_func().new_block("try"); let try_block = self.current_func().new_block("try");
let current_block = self.block.clone(); let current_block = self.block.clone();
self.block = try_block; self.block = try_block;
let call = self.call(typ, None, func, args, None); // TODO(antoyo): use funclet here? let call = self.call(typ, fn_attrs, None, func, args, None); // TODO(antoyo): use funclet here?
self.block = current_block; self.block = current_block;
let return_value = self.current_func() let return_value = self.current_func()
@ -483,8 +484,8 @@ fn invoke(&mut self, typ: Type<'gcc>, _fn_abi: Option<&FnAbi<'tcx, Ty<'tcx>>>, f
} }
#[cfg(not(feature="master"))] #[cfg(not(feature="master"))]
fn invoke(&mut self, typ: Type<'gcc>, fn_abi: Option<&FnAbi<'tcx, Ty<'tcx>>>, func: RValue<'gcc>, args: &[RValue<'gcc>], then: Block<'gcc>, catch: Block<'gcc>, _funclet: Option<&Funclet>) -> RValue<'gcc> { fn invoke(&mut self, typ: Type<'gcc>, fn_attrs: &CodegenFnAttrs, fn_abi: Option<&FnAbi<'tcx, Ty<'tcx>>>, func: RValue<'gcc>, args: &[RValue<'gcc>], then: Block<'gcc>, catch: Block<'gcc>, _funclet: Option<&Funclet>) -> RValue<'gcc> {
let call_site = self.call(typ, None, func, args, None); let call_site = self.call(typ, fn_attrs, None, func, args, None);
let condition = self.context.new_rvalue_from_int(self.bool_type, 1); let condition = self.context.new_rvalue_from_int(self.bool_type, 1);
self.llbb().end_with_conditional(None, condition, then, catch); self.llbb().end_with_conditional(None, condition, then, catch);
if let Some(_fn_abi) = fn_abi { if let Some(_fn_abi) = fn_abi {
@ -1351,6 +1352,7 @@ fn lifetime_end(&mut self, _ptr: RValue<'gcc>, _size: Size) {
fn call( fn call(
&mut self, &mut self,
_typ: Type<'gcc>, _typ: Type<'gcc>,
_fn_attrs: Option<&CodegenFnAttrs>,
fn_abi: Option<&FnAbi<'tcx, Ty<'tcx>>>, fn_abi: Option<&FnAbi<'tcx, Ty<'tcx>>>,
func: RValue<'gcc>, func: RValue<'gcc>,
args: &[RValue<'gcc>], args: &[RValue<'gcc>],

View File

@ -113,7 +113,7 @@ fn codegen_intrinsic_call(&mut self, instance: Instance<'tcx>, fn_abi: &FnAbi<'t
_ if simple.is_some() => { _ if simple.is_some() => {
// FIXME(antoyo): remove this cast when the API supports function. // FIXME(antoyo): remove this cast when the API supports function.
let func = unsafe { std::mem::transmute(simple.expect("simple")) }; let func = unsafe { std::mem::transmute(simple.expect("simple")) };
self.call(self.type_void(), None, func, &args.iter().map(|arg| arg.immediate()).collect::<Vec<_>>(), None) self.call(self.type_void(), None, None, func, &args.iter().map(|arg| arg.immediate()).collect::<Vec<_>>(), None)
}, },
sym::likely => { sym::likely => {
self.expect(args[0].immediate(), true) self.expect(args[0].immediate(), true)
@ -326,7 +326,7 @@ fn codegen_intrinsic_call(&mut self, instance: Instance<'tcx>, fn_abi: &FnAbi<'t
let masked = self.and(addr, mask); let masked = self.and(addr, mask);
self.bitcast(masked, void_ptr_type) self.bitcast(masked, void_ptr_type)
}, },
_ if name_str.starts_with("simd_") => { _ if name_str.starts_with("simd_") => {
match generic_simd_intrinsic(self, name, callee_ty, args, ret_ty, llret_ty, span) { match generic_simd_intrinsic(self, name, callee_ty, args, ret_ty, llret_ty, span) {
Ok(llval) => llval, Ok(llval) => llval,
@ -354,7 +354,7 @@ fn codegen_intrinsic_call(&mut self, instance: Instance<'tcx>, fn_abi: &FnAbi<'t
fn abort(&mut self) { fn abort(&mut self) {
let func = self.context.get_builtin_function("abort"); let func = self.context.get_builtin_function("abort");
let func: RValue<'gcc> = unsafe { std::mem::transmute(func) }; let func: RValue<'gcc> = unsafe { std::mem::transmute(func) };
self.call(self.type_void(), None, func, &[], None); self.call(self.type_void(), None, None, func, &[], None);
} }
fn assume(&mut self, value: Self::Value) { fn assume(&mut self, value: Self::Value) {
@ -1135,7 +1135,7 @@ fn saturating_sub(&mut self, lhs: RValue<'gcc>, rhs: RValue<'gcc>, signed: bool,
fn try_intrinsic<'a, 'b, 'gcc, 'tcx>(bx: &'b mut Builder<'a, 'gcc, 'tcx>, try_func: RValue<'gcc>, data: RValue<'gcc>, _catch_func: RValue<'gcc>, dest: RValue<'gcc>) { fn try_intrinsic<'a, 'b, 'gcc, 'tcx>(bx: &'b mut Builder<'a, 'gcc, 'tcx>, try_func: RValue<'gcc>, data: RValue<'gcc>, _catch_func: RValue<'gcc>, dest: RValue<'gcc>) {
if bx.sess().panic_strategy() == PanicStrategy::Abort { if bx.sess().panic_strategy() == PanicStrategy::Abort {
bx.call(bx.type_void(), None, try_func, &[data], None); bx.call(bx.type_void(), None, None, try_func, &[data], None);
// Return 0 unconditionally from the intrinsic call; // Return 0 unconditionally from the intrinsic call;
// we can never unwind. // we can never unwind.
let ret_align = bx.tcx.data_layout.i32_align.abi; let ret_align = bx.tcx.data_layout.i32_align.abi;
@ -1204,21 +1204,21 @@ fn codegen_gnu_try<'gcc>(bx: &mut Builder<'_, 'gcc, '_>, try_func: RValue<'gcc>,
let zero = bx.cx.context.new_rvalue_zero(bx.int_type); let zero = bx.cx.context.new_rvalue_zero(bx.int_type);
let ptr = bx.cx.context.new_call(None, eh_pointer_builtin, &[zero]); let ptr = bx.cx.context.new_call(None, eh_pointer_builtin, &[zero]);
let catch_ty = bx.type_func(&[bx.type_i8p(), bx.type_i8p()], bx.type_void()); let catch_ty = bx.type_func(&[bx.type_i8p(), bx.type_i8p()], bx.type_void());
bx.call(catch_ty, None, catch_func, &[data, ptr], None); bx.call(catch_ty, None, None, catch_func, &[data, ptr], None);
bx.ret(bx.const_i32(1)); bx.ret(bx.const_i32(1));
// NOTE: the blocks must be filled before adding the try/catch, otherwise gcc will not // NOTE: the blocks must be filled before adding the try/catch, otherwise gcc will not
// generate a try/catch. // generate a try/catch.
// FIXME(antoyo): add a check in the libgccjit API to prevent this. // FIXME(antoyo): add a check in the libgccjit API to prevent this.
bx.switch_to_block(current_block); bx.switch_to_block(current_block);
bx.invoke(try_func_ty, None, try_func, &[data], then, catch, None); bx.invoke(try_func_ty, None, None, try_func, &[data], then, catch, None);
}); });
let func = unsafe { std::mem::transmute(func) }; let func = unsafe { std::mem::transmute(func) };
// Note that no invoke is used here because by definition this function // Note that no invoke is used here because by definition this function
// can't panic (that's what it's catching). // can't panic (that's what it's catching).
let ret = bx.call(llty, None, func, &[try_func, data, catch_func], None); let ret = bx.call(llty, None, None, func, &[try_func, data, catch_func], None);
let i32_align = bx.tcx().data_layout.i32_align.abi; let i32_align = bx.tcx().data_layout.i32_align.abi;
bx.store(ret, dest, i32_align); bx.store(ret, dest, i32_align);
} }

View File

@ -280,16 +280,4 @@ pub fn struct_fields<'gcc, 'tcx>(cx: &CodegenCx<'gcc, 'tcx>, layout: TyAndLayout
} }
impl<'gcc, 'tcx> TypeMembershipMethods<'tcx> for CodegenCx<'gcc, 'tcx> { impl<'gcc, 'tcx> TypeMembershipMethods<'tcx> for CodegenCx<'gcc, 'tcx> {
fn set_type_metadata(&self, _function: RValue<'gcc>, _typeid: String) {
// Unsupported.
}
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.
}
} }