diff --git a/compiler/rustc_codegen_gcc/src/builder.rs b/compiler/rustc_codegen_gcc/src/builder.rs index ac908418ee4bf..fff2aa6df7c72 100644 --- a/compiler/rustc_codegen_gcc/src/builder.rs +++ b/compiler/rustc_codegen_gcc/src/builder.rs @@ -915,6 +915,16 @@ impl<'a, 'gcc, 'tcx> BuilderMethods<'a, 'tcx> for Builder<'a, 'gcc, 'tcx> { // TODO(antoyo) } + fn type_metadata(&mut self, _function: RValue<'gcc>, _typeid: String) { + // Unsupported. + } + + fn typeid_metadata(&mut self, _typeid: String) -> RValue<'gcc> { + // Unsupported. + self.context.new_rvalue_from_int(self.int_type, 0) + } + + fn store(&mut self, val: RValue<'gcc>, ptr: RValue<'gcc>, align: Align) -> RValue<'gcc> { self.store_with_flags(val, ptr, align, MemFlags::empty()) } diff --git a/compiler/rustc_codegen_gcc/src/intrinsic/mod.rs b/compiler/rustc_codegen_gcc/src/intrinsic/mod.rs index 375d422cb25c4..64bd586662d38 100644 --- a/compiler/rustc_codegen_gcc/src/intrinsic/mod.rs +++ b/compiler/rustc_codegen_gcc/src/intrinsic/mod.rs @@ -367,6 +367,11 @@ impl<'a, 'gcc, 'tcx> IntrinsicCallMethods<'tcx> for Builder<'a, 'gcc, 'tcx> { // TODO(antoyo) } + fn type_test(&mut self, _pointer: Self::Value, _typeid: Self::Value) -> Self::Value { + // Unsupported. + self.context.new_rvalue_from_int(self.int_type, 0) + } + fn va_start(&mut self, _va_list: RValue<'gcc>) -> RValue<'gcc> { unimplemented!(); } diff --git a/compiler/rustc_codegen_llvm/src/builder.rs b/compiler/rustc_codegen_llvm/src/builder.rs index 9f7b8616d7817..a94b7985987fa 100644 --- a/compiler/rustc_codegen_llvm/src/builder.rs +++ b/compiler/rustc_codegen_llvm/src/builder.rs @@ -604,6 +604,32 @@ impl BuilderMethods<'a, 'tcx> for Builder<'a, 'll, 'tcx> { } } + fn type_metadata(&mut self, function: &'ll Value, typeid: String) { + let typeid_metadata = self.typeid_metadata(typeid); + let v = [self.const_usize(0), typeid_metadata]; + unsafe { + llvm::LLVMGlobalSetMetadata( + function, + llvm::MD_type as c_uint, + llvm::LLVMValueAsMetadata(llvm::LLVMMDNodeInContext( + self.cx.llcx, + v.as_ptr(), + v.len() as c_uint, + )), + ) + } + } + + fn typeid_metadata(&mut self, typeid: String) -> Self::Value { + unsafe { + llvm::LLVMMDStringInContext( + self.cx.llcx, + typeid.as_ptr() as *const c_char, + typeid.as_bytes().len() as c_uint, + ) + } + } + fn store(&mut self, val: &'ll Value, ptr: &'ll Value, align: Align) -> &'ll Value { self.store_with_flags(val, ptr, align, MemFlags::empty()) } diff --git a/compiler/rustc_codegen_llvm/src/context.rs b/compiler/rustc_codegen_llvm/src/context.rs index 257a0ac89d86f..cda766039c167 100644 --- a/compiler/rustc_codegen_llvm/src/context.rs +++ b/compiler/rustc_codegen_llvm/src/context.rs @@ -221,6 +221,15 @@ pub unsafe fn create_module( llvm::LLVMRustAddModuleFlag(llmod, avoid_plt, 1); } + if sess.is_sanitizer_cfi_enabled() { + // FIXME(rcvalle): Add support for non canonical jump tables. + let canonical_jump_tables = "CFI Canonical Jump Tables\0".as_ptr().cast(); + // FIXME(rcvalle): Add it with Override behavior flag--LLVMRustAddModuleFlag adds it with + // Warning behavior flag. Add support for specifying the behavior flag to + // LLVMRustAddModuleFlag. + llvm::LLVMRustAddModuleFlag(llmod, canonical_jump_tables, 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 { @@ -779,6 +788,8 @@ impl CodegenCx<'b, 'tcx> { ifn!("llvm.instrprof.increment", fn(i8p, t_i64, t_i32, t_i32) -> void); } + ifn!("llvm.type.test", fn(i8p, self.type_metadata()) -> i1); + if self.sess().opts.debuginfo != DebugInfo::None { ifn!("llvm.dbg.declare", fn(self.type_metadata(), self.type_metadata()) -> void); ifn!("llvm.dbg.value", fn(self.type_metadata(), t_i64, self.type_metadata()) -> void); diff --git a/compiler/rustc_codegen_llvm/src/intrinsic.rs b/compiler/rustc_codegen_llvm/src/intrinsic.rs index c43141c769519..e63fb22829a3f 100644 --- a/compiler/rustc_codegen_llvm/src/intrinsic.rs +++ b/compiler/rustc_codegen_llvm/src/intrinsic.rs @@ -401,6 +401,14 @@ impl IntrinsicCallMethods<'tcx> for Builder<'a, 'll, 'tcx> { } } + fn type_test(&mut self, pointer: Self::Value, typeid: Self::Value) -> Self::Value { + // Test the called operand using llvm.type.test intrinsic. The LowerTypeTests link-time + // optimization pass replaces calls to this intrinsic with code to test type membership. + let i8p_ty = self.type_i8p(); + let bitcast = self.bitcast(pointer, i8p_ty); + self.call_intrinsic("llvm.type.test", &[bitcast, typeid]) + } + fn va_start(&mut self, va_list: &'ll Value) -> &'ll Value { self.call_intrinsic("llvm.va_start", &[va_list]) } diff --git a/compiler/rustc_codegen_llvm/src/llvm/ffi.rs b/compiler/rustc_codegen_llvm/src/llvm/ffi.rs index 63eca00de2a4f..b258eb36aa89b 100644 --- a/compiler/rustc_codegen_llvm/src/llvm/ffi.rs +++ b/compiler/rustc_codegen_llvm/src/llvm/ffi.rs @@ -389,6 +389,7 @@ pub enum MetadataType { MD_nontemporal = 9, MD_mem_parallel_loop_access = 10, MD_nonnull = 11, + MD_type = 19, } /// LLVMRustAsmDialect @@ -975,6 +976,8 @@ extern "C" { pub fn LLVMSetValueName2(Val: &Value, Name: *const c_char, NameLen: size_t); pub fn LLVMReplaceAllUsesWith(OldVal: &'a Value, NewVal: &'a Value); pub fn LLVMSetMetadata(Val: &'a Value, KindID: c_uint, Node: &'a Value); + pub fn LLVMGlobalSetMetadata(Val: &'a Value, KindID: c_uint, Metadata: &'a Metadata); + pub fn LLVMValueAsMetadata(Node: &'a Value) -> &Metadata; // Operations on constants of any type pub fn LLVMConstNull(Ty: &Type) -> &Value; diff --git a/compiler/rustc_codegen_ssa/src/mir/block.rs b/compiler/rustc_codegen_ssa/src/mir/block.rs index b0a5631549df8..8f739a264eacb 100644 --- a/compiler/rustc_codegen_ssa/src/mir/block.rs +++ b/compiler/rustc_codegen_ssa/src/mir/block.rs @@ -19,6 +19,7 @@ use rustc_middle::ty::print::{with_no_trimmed_paths, with_no_visible_paths}; use rustc_middle::ty::{self, Instance, Ty, TypeFoldable}; use rustc_span::source_map::Span; use rustc_span::{sym, Symbol}; +use rustc_symbol_mangling::typeid_for_fnabi; use rustc_target::abi::call::{ArgAbi, FnAbi, PassMode}; use rustc_target::abi::{self, HasDataLayout, WrappingRange}; use rustc_target::spec::abi::Abi; @@ -818,12 +819,43 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { self.codegen_argument(&mut bx, location, &mut llargs, last_arg); } - let fn_ptr = match (llfn, instance) { - (Some(llfn), _) => llfn, - (None, Some(instance)) => bx.get_fn_addr(instance), + let (is_indirect_call, fn_ptr) = match (llfn, instance) { + (Some(llfn), _) => (true, llfn), + (None, Some(instance)) => (false, bx.get_fn_addr(instance)), _ => span_bug!(span, "no llfn for call"), }; + // For backends that support CFI using type membership (i.e., testing whether a given + // pointer is associated with a type identifier). + if bx.tcx().sess.is_sanitizer_cfi_enabled() && is_indirect_call { + // Emit type metadata and checks. + // FIXME(rcvalle): Add support for generalized identifiers. + // FIXME(rcvalle): Create distinct unnamed MDNodes for internal identifiers. + let typeid = typeid_for_fnabi(bx.tcx(), fn_abi); + let typeid_metadata = bx.typeid_metadata(typeid.clone()); + + // Test whether the function pointer is associated with the type identifier. + let cond = bx.type_test(fn_ptr, typeid_metadata); + let mut bx_pass = bx.build_sibling_block("type_test.pass"); + let mut bx_fail = bx.build_sibling_block("type_test.fail"); + bx.cond_br(cond, bx_pass.llbb(), bx_fail.llbb()); + + helper.do_call( + self, + &mut bx_pass, + fn_abi, + fn_ptr, + &llargs, + destination.as_ref().map(|&(_, target)| (ret_dest, target)), + cleanup, + ); + + bx_fail.abort(); + bx_fail.unreachable(); + + return; + } + helper.do_call( self, &mut bx, diff --git a/compiler/rustc_codegen_ssa/src/mir/mod.rs b/compiler/rustc_codegen_ssa/src/mir/mod.rs index 476ddbd93980c..1cd400eecfbd2 100644 --- a/compiler/rustc_codegen_ssa/src/mir/mod.rs +++ b/compiler/rustc_codegen_ssa/src/mir/mod.rs @@ -4,6 +4,7 @@ use rustc_middle::mir; use rustc_middle::mir::interpret::ErrorHandled; use rustc_middle::ty::layout::{FnAbiOf, HasTyCtxt, TyAndLayout}; use rustc_middle::ty::{self, Instance, Ty, TypeFoldable}; +use rustc_symbol_mangling::typeid_for_fnabi; use rustc_target::abi::call::{FnAbi, PassMode}; use std::iter; @@ -244,6 +245,13 @@ pub fn codegen_mir<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>>( for (bb, _) in traversal::reverse_postorder(&mir) { fx.codegen_block(bb); } + + // For backends that support CFI using type membership (i.e., testing whether a given pointer + // is associated with a type identifier). + if cx.tcx().sess.is_sanitizer_cfi_enabled() { + let typeid = typeid_for_fnabi(cx.tcx(), fn_abi); + bx.type_metadata(llfn, typeid.clone()); + } } /// Produces, for each argument, a `Value` pointing at the diff --git a/compiler/rustc_codegen_ssa/src/traits/builder.rs b/compiler/rustc_codegen_ssa/src/traits/builder.rs index e7da96f0adafd..158e658301eed 100644 --- a/compiler/rustc_codegen_ssa/src/traits/builder.rs +++ b/compiler/rustc_codegen_ssa/src/traits/builder.rs @@ -158,6 +158,8 @@ pub trait BuilderMethods<'a, 'tcx>: fn range_metadata(&mut self, load: Self::Value, range: WrappingRange); fn nonnull_metadata(&mut self, load: Self::Value); + fn type_metadata(&mut self, function: Self::Function, typeid: String); + fn typeid_metadata(&mut self, typeid: String) -> Self::Value; fn store(&mut self, val: Self::Value, ptr: Self::Value, align: Align) -> Self::Value; fn store_with_flags( diff --git a/compiler/rustc_codegen_ssa/src/traits/intrinsic.rs b/compiler/rustc_codegen_ssa/src/traits/intrinsic.rs index 777436ad2ae8f..78bf22ef9f2e2 100644 --- a/compiler/rustc_codegen_ssa/src/traits/intrinsic.rs +++ b/compiler/rustc_codegen_ssa/src/traits/intrinsic.rs @@ -24,6 +24,8 @@ pub trait IntrinsicCallMethods<'tcx>: BackendTypes { /// /// Currently has any effect only when LLVM versions prior to 12.0 are used as the backend. fn sideeffect(&mut self); + /// Trait method used to test whether a given pointer is associated with a type identifier. + fn type_test(&mut self, pointer: Self::Value, typeid: Self::Value) -> Self::Value; /// Trait method used to inject `va_start` on the "spoofed" `VaListImpl` in /// Rust defined C-variadic functions. fn va_start(&mut self, val: Self::Value) -> Self::Value; diff --git a/compiler/rustc_session/src/options.rs b/compiler/rustc_session/src/options.rs index b3d36b396c51c..6f8f8b8f27ecf 100644 --- a/compiler/rustc_session/src/options.rs +++ b/compiler/rustc_session/src/options.rs @@ -351,8 +351,7 @@ mod desc { pub const parse_panic_strategy: &str = "either `unwind` or `abort`"; pub const parse_opt_panic_strategy: &str = parse_panic_strategy; pub const parse_relro_level: &str = "one of: `full`, `partial`, or `off`"; - pub const parse_sanitizers: &str = - "comma separated list of sanitizers: `address`, `hwaddress`, `leak`, `memory` or `thread`"; + pub const parse_sanitizers: &str = "comma separated list of sanitizers: `address`, `cfi`, `hwaddress`, `leak`, `memory` 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`"; @@ -584,6 +583,7 @@ mod parse { for s in v.split(',') { *slot |= match s { "address" => SanitizerSet::ADDRESS, + "cfi" => SanitizerSet::CFI, "leak" => SanitizerSet::LEAK, "memory" => SanitizerSet::MEMORY, "thread" => SanitizerSet::THREAD, diff --git a/compiler/rustc_session/src/session.rs b/compiler/rustc_session/src/session.rs index b6ba6cc1dd659..0f6a3ddccbaf0 100644 --- a/compiler/rustc_session/src/session.rs +++ b/compiler/rustc_session/src/session.rs @@ -672,6 +672,9 @@ impl Session { pub fn is_nightly_build(&self) -> bool { self.opts.unstable_features.is_nightly_build() } + pub fn is_sanitizer_cfi_enabled(&self) -> bool { + self.opts.debugging_opts.sanitizer.contains(SanitizerSet::CFI) + } pub fn overflow_checks(&self) -> bool { self.opts .cg @@ -1398,6 +1401,16 @@ fn validate_commandline_args_with_session_available(sess: &Session) { disable it using `-C target-feature=-crt-static`", ); } + + // LLVM CFI requires LTO. + if sess.is_sanitizer_cfi_enabled() { + if sess.opts.cg.lto == config::LtoCli::Unspecified + || sess.opts.cg.lto == config::LtoCli::No + || sess.opts.cg.lto == config::LtoCli::Thin + { + sess.err("`-Zsanitizer=cfi` requires `-Clto`"); + } + } } /// Holds data on the current incremental compilation session, if there is one. diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs index 9551120ca5522..1d746dd6f2c82 100644 --- a/compiler/rustc_span/src/symbol.rs +++ b/compiler/rustc_span/src/symbol.rs @@ -408,6 +408,7 @@ symbols! { cfg_target_thread_local, cfg_target_vendor, cfg_version, + cfi, char, client, clippy, diff --git a/compiler/rustc_symbol_mangling/src/lib.rs b/compiler/rustc_symbol_mangling/src/lib.rs index 220c9f7e2ec2b..bb7b452955609 100644 --- a/compiler/rustc_symbol_mangling/src/lib.rs +++ b/compiler/rustc_symbol_mangling/src/lib.rs @@ -103,8 +103,9 @@ use rustc_middle::middle::codegen_fn_attrs::CodegenFnAttrFlags; use rustc_middle::mir::mono::{InstantiationMode, MonoItem}; use rustc_middle::ty::query::Providers; use rustc_middle::ty::subst::SubstsRef; -use rustc_middle::ty::{self, Instance, TyCtxt}; +use rustc_middle::ty::{self, Instance, Ty, TyCtxt}; use rustc_session::config::SymbolManglingVersion; +use rustc_target::abi::call::FnAbi; use tracing::debug; @@ -150,6 +151,11 @@ fn symbol_name_provider(tcx: TyCtxt<'tcx>, instance: Instance<'tcx>) -> ty::Symb ty::SymbolName::new(tcx, &symbol_name) } +/// This function computes the typeid for the given function ABI. +pub fn typeid_for_fnabi(tcx: TyCtxt<'tcx>, fn_abi: &FnAbi<'tcx, Ty<'tcx>>) -> String { + v0::mangle_typeid_for_fnabi(tcx, fn_abi) +} + /// Computes the symbol name for the given instance. This function will call /// `compute_instantiating_crate` if it needs to factor the instantiating crate /// into the symbol name. diff --git a/compiler/rustc_symbol_mangling/src/v0.rs b/compiler/rustc_symbol_mangling/src/v0.rs index 521730dfeb01c..0363ddb0e6eee 100644 --- a/compiler/rustc_symbol_mangling/src/v0.rs +++ b/compiler/rustc_symbol_mangling/src/v0.rs @@ -9,6 +9,7 @@ use rustc_middle::ty::layout::IntegerExt; use rustc_middle::ty::print::{Print, Printer}; use rustc_middle::ty::subst::{GenericArg, GenericArgKind, Subst}; use rustc_middle::ty::{self, FloatTy, Instance, IntTy, Ty, TyCtxt, TypeFoldable, UintTy}; +use rustc_target::abi::call::FnAbi; use rustc_target::abi::Integer; use rustc_target::spec::abi::Abi; @@ -55,6 +56,41 @@ pub(super) fn mangle( std::mem::take(&mut cx.out) } +pub(super) fn mangle_typeid_for_fnabi( + _tcx: TyCtxt<'tcx>, + fn_abi: &FnAbi<'tcx, Ty<'tcx>>, +) -> String { + // LLVM uses type metadata to allow IR modules to aggregate pointers by their types.[1] This + // type metadata is used by LLVM Control Flow Integrity to test whether a given pointer is + // associated with a type identifier (i.e., test type membership). + // + // Clang uses the Itanium C++ ABI's[2] virtual tables and RTTI typeinfo structure name[3] as + // type metadata identifiers for function pointers. The typeinfo name encoding is a + // two-character code (i.e., “TS”) prefixed to the type encoding for the function. + // + // For cross-language LLVM CFI support, a compatible encoding must be used by either + // + // a. Using a superset of types that encompasses types used by Clang (i.e., Itanium C++ ABI's + // type encodings[4]), or at least types used at the FFI boundary. + // b. Reducing the types to the least common denominator between types used by Clang (or at + // least types used at the FFI boundary) and Rust compilers (if even possible). + // c. Creating a new ABI for cross-language CFI and using it for Clang and Rust compilers (and + // possibly other compilers). + // + // Option (b) may weaken the protection for Rust-compiled only code, so it should be provided + // as an alternative to a Rust-specific encoding for when mixing Rust and C and C++ -compiled + // code. Option (c) would require changes to Clang to use the new ABI. + // + // [1] https://llvm.org/docs/TypeMetadata.html + // [2] https://itanium-cxx-abi.github.io/cxx-abi/abi.html + // [3] https://itanium-cxx-abi.github.io/cxx-abi/abi.html#mangling-special-vtables + // [4] https://itanium-cxx-abi.github.io/cxx-abi/abi.html#mangling-type + // + // FIXME(rcvalle): See comment above. + let arg_count = fn_abi.args.len() + fn_abi.ret.is_indirect() as usize; + format!("typeid{}", arg_count) +} + struct BinderLevel { /// The range of distances from the root of what's /// being printed, to the lifetimes in a binder. diff --git a/compiler/rustc_target/src/spec/aarch64_apple_darwin.rs b/compiler/rustc_target/src/spec/aarch64_apple_darwin.rs index dc91f12309649..2c71fb8afeede 100644 --- a/compiler/rustc_target/src/spec/aarch64_apple_darwin.rs +++ b/compiler/rustc_target/src/spec/aarch64_apple_darwin.rs @@ -6,7 +6,7 @@ pub fn target() -> Target { base.max_atomic_width = Some(128); // FIXME: The leak sanitizer currently fails the tests, see #88132. - base.supported_sanitizers = SanitizerSet::ADDRESS | SanitizerSet::THREAD; + base.supported_sanitizers = SanitizerSet::ADDRESS | SanitizerSet::CFI | SanitizerSet::THREAD; base.pre_link_args.insert(LinkerFlavor::Gcc, vec!["-arch".to_string(), "arm64".to_string()]); base.link_env_remove.extend(super::apple_base::macos_link_env_remove()); diff --git a/compiler/rustc_target/src/spec/aarch64_fuchsia.rs b/compiler/rustc_target/src/spec/aarch64_fuchsia.rs index 56d71df6bda24..05e0c65dd5c38 100644 --- a/compiler/rustc_target/src/spec/aarch64_fuchsia.rs +++ b/compiler/rustc_target/src/spec/aarch64_fuchsia.rs @@ -8,7 +8,7 @@ pub fn target() -> Target { arch: "aarch64".to_string(), options: TargetOptions { max_atomic_width: Some(128), - supported_sanitizers: SanitizerSet::ADDRESS, + supported_sanitizers: SanitizerSet::ADDRESS | SanitizerSet::CFI, ..super::fuchsia_base::opts() }, } diff --git a/compiler/rustc_target/src/spec/aarch64_linux_android.rs b/compiler/rustc_target/src/spec/aarch64_linux_android.rs index 409cab72ec219..1e9abbbe1e787 100644 --- a/compiler/rustc_target/src/spec/aarch64_linux_android.rs +++ b/compiler/rustc_target/src/spec/aarch64_linux_android.rs @@ -14,7 +14,7 @@ pub fn target() -> Target { // As documented in https://developer.android.com/ndk/guides/cpu-features.html // the neon (ASIMD) and FP must exist on all android aarch64 targets. features: "+neon,+fp-armv8".to_string(), - supported_sanitizers: SanitizerSet::HWADDRESS, + supported_sanitizers: SanitizerSet::CFI | SanitizerSet::HWADDRESS, ..super::android_base::opts() }, } diff --git a/compiler/rustc_target/src/spec/aarch64_unknown_freebsd.rs b/compiler/rustc_target/src/spec/aarch64_unknown_freebsd.rs index 0caecd2987bd5..03ee7ba4875c9 100644 --- a/compiler/rustc_target/src/spec/aarch64_unknown_freebsd.rs +++ b/compiler/rustc_target/src/spec/aarch64_unknown_freebsd.rs @@ -9,6 +9,7 @@ pub fn target() -> Target { options: TargetOptions { max_atomic_width: Some(128), supported_sanitizers: SanitizerSet::ADDRESS + | SanitizerSet::CFI | SanitizerSet::MEMORY | SanitizerSet::THREAD, ..super::freebsd_base::opts() diff --git a/compiler/rustc_target/src/spec/aarch64_unknown_linux_gnu.rs b/compiler/rustc_target/src/spec/aarch64_unknown_linux_gnu.rs index 3e92ecbae054c..c8d46adbfd92b 100644 --- a/compiler/rustc_target/src/spec/aarch64_unknown_linux_gnu.rs +++ b/compiler/rustc_target/src/spec/aarch64_unknown_linux_gnu.rs @@ -10,6 +10,7 @@ pub fn target() -> Target { mcount: "\u{1}_mcount".to_string(), max_atomic_width: Some(128), supported_sanitizers: SanitizerSet::ADDRESS + | SanitizerSet::CFI | SanitizerSet::LEAK | SanitizerSet::MEMORY | SanitizerSet::THREAD diff --git a/compiler/rustc_target/src/spec/mod.rs b/compiler/rustc_target/src/spec/mod.rs index ff5dfa3f74625..2663172e3daba 100644 --- a/compiler/rustc_target/src/spec/mod.rs +++ b/compiler/rustc_target/src/spec/mod.rs @@ -602,6 +602,7 @@ bitflags::bitflags! { const MEMORY = 1 << 2; const THREAD = 1 << 3; const HWADDRESS = 1 << 4; + const CFI = 1 << 5; } } @@ -612,6 +613,7 @@ impl SanitizerSet { fn as_str(self) -> Option<&'static str> { Some(match self { SanitizerSet::ADDRESS => "address", + SanitizerSet::CFI => "cfi", SanitizerSet::LEAK => "leak", SanitizerSet::MEMORY => "memory", SanitizerSet::THREAD => "thread", @@ -644,6 +646,7 @@ impl IntoIterator for SanitizerSet { fn into_iter(self) -> Self::IntoIter { [ SanitizerSet::ADDRESS, + SanitizerSet::CFI, SanitizerSet::LEAK, SanitizerSet::MEMORY, SanitizerSet::THREAD, @@ -1805,6 +1808,7 @@ impl Target { for s in a { base.$key_name |= match s.as_string() { Some("address") => SanitizerSet::ADDRESS, + Some("cfi") => SanitizerSet::CFI, Some("leak") => SanitizerSet::LEAK, Some("memory") => SanitizerSet::MEMORY, Some("thread") => SanitizerSet::THREAD, diff --git a/compiler/rustc_target/src/spec/x86_64_apple_darwin.rs b/compiler/rustc_target/src/spec/x86_64_apple_darwin.rs index 60fd42970c7d6..22fdaabfcb89b 100644 --- a/compiler/rustc_target/src/spec/x86_64_apple_darwin.rs +++ b/compiler/rustc_target/src/spec/x86_64_apple_darwin.rs @@ -13,7 +13,8 @@ pub fn target() -> Target { base.link_env_remove.extend(super::apple_base::macos_link_env_remove()); // don't use probe-stack=inline-asm until rust#83139 and rust#84667 are resolved base.stack_probes = StackProbeType::Call; - base.supported_sanitizers = SanitizerSet::ADDRESS | SanitizerSet::LEAK | SanitizerSet::THREAD; + base.supported_sanitizers = + SanitizerSet::ADDRESS | SanitizerSet::CFI | SanitizerSet::LEAK | SanitizerSet::THREAD; // Clang automatically chooses a more specific target based on // MACOSX_DEPLOYMENT_TARGET. To enable cross-language LTO to work diff --git a/compiler/rustc_target/src/spec/x86_64_fuchsia.rs b/compiler/rustc_target/src/spec/x86_64_fuchsia.rs index aa65ebe1f9dbd..c253c0c30b3d3 100644 --- a/compiler/rustc_target/src/spec/x86_64_fuchsia.rs +++ b/compiler/rustc_target/src/spec/x86_64_fuchsia.rs @@ -6,7 +6,7 @@ pub fn target() -> Target { base.max_atomic_width = Some(64); // don't use probe-stack=inline-asm until rust#83139 and rust#84667 are resolved base.stack_probes = StackProbeType::Call; - base.supported_sanitizers = SanitizerSet::ADDRESS; + base.supported_sanitizers = SanitizerSet::ADDRESS | SanitizerSet::CFI; Target { llvm_target: "x86_64-fuchsia".to_string(), diff --git a/compiler/rustc_target/src/spec/x86_64_pc_solaris.rs b/compiler/rustc_target/src/spec/x86_64_pc_solaris.rs index 34b6d2901c820..6aa0728668277 100644 --- a/compiler/rustc_target/src/spec/x86_64_pc_solaris.rs +++ b/compiler/rustc_target/src/spec/x86_64_pc_solaris.rs @@ -8,7 +8,7 @@ pub fn target() -> Target { base.max_atomic_width = Some(64); // don't use probe-stack=inline-asm until rust#83139 and rust#84667 are resolved base.stack_probes = StackProbeType::Call; - base.supported_sanitizers = SanitizerSet::ADDRESS; + base.supported_sanitizers = SanitizerSet::ADDRESS | SanitizerSet::CFI; Target { llvm_target: "x86_64-pc-solaris".to_string(), diff --git a/compiler/rustc_target/src/spec/x86_64_unknown_freebsd.rs b/compiler/rustc_target/src/spec/x86_64_unknown_freebsd.rs index b5fc15f5e04bf..24cc7ae788b45 100644 --- a/compiler/rustc_target/src/spec/x86_64_unknown_freebsd.rs +++ b/compiler/rustc_target/src/spec/x86_64_unknown_freebsd.rs @@ -7,7 +7,8 @@ pub fn target() -> Target { base.pre_link_args.entry(LinkerFlavor::Gcc).or_default().push("-m64".to_string()); // don't use probe-stack=inline-asm until rust#83139 and rust#84667 are resolved base.stack_probes = StackProbeType::Call; - base.supported_sanitizers = SanitizerSet::ADDRESS | SanitizerSet::MEMORY | SanitizerSet::THREAD; + base.supported_sanitizers = + SanitizerSet::ADDRESS | SanitizerSet::CFI | SanitizerSet::MEMORY | SanitizerSet::THREAD; Target { llvm_target: "x86_64-unknown-freebsd".to_string(), diff --git a/compiler/rustc_target/src/spec/x86_64_unknown_illumos.rs b/compiler/rustc_target/src/spec/x86_64_unknown_illumos.rs index ec196a7f82329..79ccf63acfada 100644 --- a/compiler/rustc_target/src/spec/x86_64_unknown_illumos.rs +++ b/compiler/rustc_target/src/spec/x86_64_unknown_illumos.rs @@ -5,7 +5,7 @@ pub fn target() -> Target { base.pre_link_args.insert(LinkerFlavor::Gcc, vec!["-m64".to_string(), "-std=c99".to_string()]); base.cpu = "x86-64".to_string(); base.max_atomic_width = Some(64); - base.supported_sanitizers = SanitizerSet::ADDRESS; + base.supported_sanitizers = SanitizerSet::ADDRESS | SanitizerSet::CFI; Target { // LLVM does not currently have a separate illumos target, diff --git a/compiler/rustc_target/src/spec/x86_64_unknown_linux_gnu.rs b/compiler/rustc_target/src/spec/x86_64_unknown_linux_gnu.rs index 085079e06e570..c2484f2d8f66d 100644 --- a/compiler/rustc_target/src/spec/x86_64_unknown_linux_gnu.rs +++ b/compiler/rustc_target/src/spec/x86_64_unknown_linux_gnu.rs @@ -7,8 +7,11 @@ pub fn target() -> Target { base.pre_link_args.entry(LinkerFlavor::Gcc).or_default().push("-m64".to_string()); // don't use probe-stack=inline-asm until rust#83139 and rust#84667 are resolved base.stack_probes = StackProbeType::Call; - base.supported_sanitizers = - SanitizerSet::ADDRESS | SanitizerSet::LEAK | SanitizerSet::MEMORY | SanitizerSet::THREAD; + base.supported_sanitizers = SanitizerSet::ADDRESS + | SanitizerSet::CFI + | SanitizerSet::LEAK + | SanitizerSet::MEMORY + | SanitizerSet::THREAD; Target { llvm_target: "x86_64-unknown-linux-gnu".to_string(), diff --git a/compiler/rustc_target/src/spec/x86_64_unknown_linux_musl.rs b/compiler/rustc_target/src/spec/x86_64_unknown_linux_musl.rs index 5ad243aa4075e..a5e79803335b7 100644 --- a/compiler/rustc_target/src/spec/x86_64_unknown_linux_musl.rs +++ b/compiler/rustc_target/src/spec/x86_64_unknown_linux_musl.rs @@ -8,8 +8,11 @@ pub fn target() -> Target { // don't use probe-stack=inline-asm until rust#83139 and rust#84667 are resolved base.stack_probes = StackProbeType::Call; base.static_position_independent_executables = true; - base.supported_sanitizers = - SanitizerSet::ADDRESS | SanitizerSet::LEAK | SanitizerSet::MEMORY | SanitizerSet::THREAD; + base.supported_sanitizers = SanitizerSet::ADDRESS + | SanitizerSet::CFI + | SanitizerSet::LEAK + | SanitizerSet::MEMORY + | SanitizerSet::THREAD; Target { llvm_target: "x86_64-unknown-linux-musl".to_string(), diff --git a/compiler/rustc_target/src/spec/x86_64_unknown_netbsd.rs b/compiler/rustc_target/src/spec/x86_64_unknown_netbsd.rs index 9ba86280d5197..bdb2be4f863e2 100644 --- a/compiler/rustc_target/src/spec/x86_64_unknown_netbsd.rs +++ b/compiler/rustc_target/src/spec/x86_64_unknown_netbsd.rs @@ -7,8 +7,11 @@ pub fn target() -> Target { base.pre_link_args.entry(LinkerFlavor::Gcc).or_default().push("-m64".to_string()); // don't use probe-stack=inline-asm until rust#83139 and rust#84667 are resolved base.stack_probes = StackProbeType::Call; - base.supported_sanitizers = - SanitizerSet::ADDRESS | SanitizerSet::LEAK | SanitizerSet::MEMORY | SanitizerSet::THREAD; + base.supported_sanitizers = SanitizerSet::ADDRESS + | SanitizerSet::CFI + | SanitizerSet::LEAK + | SanitizerSet::MEMORY + | SanitizerSet::THREAD; Target { llvm_target: "x86_64-unknown-netbsd".to_string(), diff --git a/compiler/rustc_typeck/src/collect.rs b/compiler/rustc_typeck/src/collect.rs index df7f2aea9c3ac..18e8ed394e814 100644 --- a/compiler/rustc_typeck/src/collect.rs +++ b/compiler/rustc_typeck/src/collect.rs @@ -2879,6 +2879,8 @@ fn codegen_fn_attrs(tcx: TyCtxt<'_>, id: DefId) -> CodegenFnAttrs { for item in list.iter() { if item.has_name(sym::address) { 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::memory) { codegen_fn_attrs.no_sanitize |= SanitizerSet::MEMORY; } else if item.has_name(sym::thread) { diff --git a/src/doc/rustc/src/exploit-mitigations.md b/src/doc/rustc/src/exploit-mitigations.md index 70df5170b21c1..fa38dd54d60c8 100644 --- a/src/doc/rustc/src/exploit-mitigations.md +++ b/src/doc/rustc/src/exploit-mitigations.md @@ -123,9 +123,9 @@ equivalent. Forward-edge control flow protection - No + Yes - + Nightly @@ -465,24 +465,27 @@ implementations such as [LLVM ControlFlowIntegrity commercially available [grsecurity/PaX Reuse Attack Protector (RAP)](https://grsecurity.net/rap_faq). -The Rust compiler does not support forward-edge control flow protection on -Linux6. There is work currently ongoing to add support -for the [sanitizers](https://github.com/google/sanitizers)[40], which may or -may not include support for LLVM CFI. +The Rust compiler supports forward-edge control flow protection on nightly +builds[40]-[41] 6. ```text -$ readelf -s target/release/hello-rust | grep __cfi_init +$ readelf -s -W target/debug/rust-cfi | grep "\.cfi" + 12: 0000000000005170 46 FUNC LOCAL DEFAULT 14 _RNvCsjaOHoaNjor6_8rust_cfi7add_one.cfi + 15: 00000000000051a0 16 FUNC LOCAL DEFAULT 14 _RNvCsjaOHoaNjor6_8rust_cfi7add_two.cfi + 17: 0000000000005270 396 FUNC LOCAL DEFAULT 14 _RNvCsjaOHoaNjor6_8rust_cfi4main.cfi +... ``` -Fig. 15. Checking if LLVM CFI is enabled for a given binary. +Fig. 15. Checking if LLVM CFI is enabled for a given binary[41]. -The presence of the `__cfi_init` symbol (and references to `__cfi_check`) -indicates that LLVM CFI (i.e., forward-edge control flow protection) is -enabled for a given binary. Conversely, the absence of the `__cfi_init` -symbol (and references to `__cfi_check`) indicates that LLVM CFI is not -enabled for a given binary (see Fig. 15). +The presence of symbols suffixed with ".cfi" or the `__cfi_init` symbol (and +references to `__cfi_check`) indicates that LLVM CFI (i.e., forward-edge control +flow protection) is enabled for a given binary. Conversely, the absence of +symbols suffixed with ".cfi" or the `__cfi_init` symbol (and references to +`__cfi_check`) indicates that LLVM CFI is not enabled for a given binary (see +Fig. 15). -6\. It supports Control Flow Guard (CFG) on Windows (see +6\. It also supports Control Flow Guard (CFG) on Windows (see ). @@ -689,5 +692,8 @@ defaults (unrelated to `READ_IMPLIES_EXEC`). 39. A. Crichton. “Remove the alloc\_jemalloc crate #55238.” GitHub. . -40. J. Aparicio. 2017. “Tracking issue for sanitizer support #39699.” - . +40. R. de C Valle. “Tracking Issue for LLVM Control Flow Integrity (CFI) Support + for Rust #89653.” GitHub. . + +41. “ControlFlowIntegrity.” The Rust Unstable Book. + . diff --git a/src/doc/unstable-book/src/compiler-flags/sanitizer.md b/src/doc/unstable-book/src/compiler-flags/sanitizer.md index 29a267053b47d..b3dbc9a995679 100644 --- a/src/doc/unstable-book/src/compiler-flags/sanitizer.md +++ b/src/doc/unstable-book/src/compiler-flags/sanitizer.md @@ -1,19 +1,24 @@ # `sanitizer` -The tracking issue for this feature is: [#39699](https://github.com/rust-lang/rust/issues/39699). +The tracking issues for this feature are: + +* [#39699](https://github.com/rust-lang/rust/issues/39699). +* [#89653](https://github.com/rust-lang/rust/issues/89653). ------------------------ This feature allows for use of one of following sanitizers: * [AddressSanitizer][clang-asan] a fast memory error detector. +* [ControlFlowIntegrity][clang-cfi] LLVM Control Flow Integrity (CFI) provides + forward-edge control flow protection. * [HWAddressSanitizer][clang-hwasan] a memory error detector similar to AddressSanitizer, but based on partial hardware assistance. * [LeakSanitizer][clang-lsan] a run-time memory leak detector. * [MemorySanitizer][clang-msan] a detector of uninitialized reads. * [ThreadSanitizer][clang-tsan] a fast data race detector. -To enable a sanitizer compile with `-Zsanitizer=address`, +To enable a sanitizer compile with `-Zsanitizer=address`,`-Zsanitizer=cfi`, `-Zsanitizer=hwaddress`, `-Zsanitizer=leak`, `-Zsanitizer=memory` or `-Zsanitizer=thread`. @@ -177,6 +182,176 @@ Shadow byte legend (one shadow byte represents 8 application bytes): ==39249==ABORTING ``` +# ControlFlowIntegrity + +The LLVM Control Flow Integrity (CFI) support in the Rust compiler 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 by defining and using +compatible type identifiers (see Type metadata in the design document in the +tracking issue [#89653](https://github.com/rust-lang/rust/issues/89653)). + +LLVM CFI can be enabled with -Zsanitizer=cfi and requires LTO (i.e., -Clto). + +## Example + +```text +#![feature(asm, naked_functions)] + +use std::mem; + +fn add_one(x: i32) -> i32 { + x + 1 +} + +#[naked] +pub extern "C" fn add_two(x: i32) { + // x + 2 preceeded by a landing pad/nop block + unsafe { + asm!( + " + nop + nop + nop + nop + nop + nop + nop + nop + nop + lea rax, [rdi+2] + ret + ", + options(noreturn) + ); + } +} + +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 { + // Offsets 0-8 make it land in the landing pad/nop block, and offsets 1-8 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); +} +``` +Fig. 1. Modified example from the [Advanced Functions and +Closures][rust-book-ch19-05] chapter of the [The Rust Programming +Language][rust-book] book. + +[//]: # (FIXME: Replace with output from cargo using nightly when #89652 is merged) + +```shell +$ rustc rust_cfi.rs -o rust_cfi +$ ./rust_cfi +The answer is: 12 +With CFI enabled, you should not see the next answer +The next answer is: 14 +$ +``` +Fig. 2. Build and execution of the modified example with LLVM CFI disabled. + +[//]: # (FIXME: Replace with output from cargo using nightly when #89652 is merged) + +```shell +$ rustc -Clto -Zsanitizer=cfi rust_cfi.rs -o rust_cfi +$ ./rust_cfi +The answer is: 12 +With CFI enabled, you should not see the next answer +Illegal instruction +$ +``` +Fig. 3. Build and execution of the modified example with LLVM CFI enabled. + +When LLVM CFI is enabled, if there are any attempts to change/hijack control +flow using an indirect branch/call to an invalid destination, the execution is +terminated (see Fig. 3). + +```rust +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); +} +``` +Fig. 4. Another modified example from the [Advanced Functions and +Closures][rust-book-ch19-05] chapter of the [The Rust Programming +Language][rust-book] book. + +[//]: # (FIXME: Replace with output from cargo using nightly when #89652 is merged) + +```shell +$ rustc rust_cfi.rs -o rust_cfi +$ ./rust_cfi +The answer is: 12 +With CFI enabled, you should not see the next answer +The next answer is: 14 +$ +``` +Fig. 5. Build and execution of the modified example with LLVM CFI disabled. + +[//]: # (FIXME: Replace with output from cargo using nightly when #89652 is merged) + +```shell +$ rustc -Clto -Zsanitizer=cfi rust_cfi.rs -o rust_cfi +$ ./rust_cfi +The answer is: 12 +With CFI enabled, you should not see the next answer +Illegal instruction +$ +``` +Fig. 6. Build and execution of the modified example with LLVM CFI enabled. + +When LLVM CFI is enabled, if there are any attempts to change/hijack control +flow using an indirect branch/call to a function with different number of +arguments than intended/passed in the call/branch site, the execution is also +terminated (see Fig. 6). + +Forward-edge control flow protection not only by aggregating function pointers +in groups identified by their number of arguments, but also their argument +types, will also be provided in later work by defining and using compatible type +identifiers (see Type metadata in the design document in the tracking +issue [#89653](https://github.com/rust-lang/rust/issues/89653)). + +[rust-book-ch19-05]: https://doc.rust-lang.org/book/ch19-05-advanced-functions-and-closures.html +[rust-book]: https://doc.rust-lang.org/book/title-page.html + # HWAddressSanitizer HWAddressSanitizer is a newer variant of AddressSanitizer that consumes much @@ -404,12 +579,14 @@ Sanitizers produce symbolized stacktraces when llvm-symbolizer binary is in `PAT * [Sanitizers project page](https://github.com/google/sanitizers/wiki/) * [AddressSanitizer in Clang][clang-asan] +* [ControlFlowIntegrity in Clang][clang-cfi] * [HWAddressSanitizer in Clang][clang-hwasan] * [LeakSanitizer in Clang][clang-lsan] * [MemorySanitizer in Clang][clang-msan] * [ThreadSanitizer in Clang][clang-tsan] [clang-asan]: https://clang.llvm.org/docs/AddressSanitizer.html +[clang-cfi]: https://clang.llvm.org/docs/ControlFlowIntegrity.html [clang-hwasan]: https://clang.llvm.org/docs/HardwareAssistedAddressSanitizerDesign.html [clang-lsan]: https://clang.llvm.org/docs/LeakSanitizer.html [clang-msan]: https://clang.llvm.org/docs/MemorySanitizer.html diff --git a/src/test/codegen/sanitizer_cfi_add_canonical_jump_tables_flag.rs b/src/test/codegen/sanitizer_cfi_add_canonical_jump_tables_flag.rs new file mode 100644 index 0000000000000..68f81808861a8 --- /dev/null +++ b/src/test/codegen/sanitizer_cfi_add_canonical_jump_tables_flag.rs @@ -0,0 +1,14 @@ +// Verifies that "CFI Canonical Jump Tables" module flag is added. +// +// ignore-windows +// needs-sanitizer-cfi +// only-aarch64 +// only-x86_64 +// compile-flags: -Clto -Zsanitizer=cfi + +#![crate_type="lib"] + +pub fn foo() { +} + +// CHECK: !{{[0-9]+}} = !{i32 2, !"CFI Canonical Jump Tables", i32 1} diff --git a/src/test/codegen/sanitizer_cfi_emit_type_checks.rs b/src/test/codegen/sanitizer_cfi_emit_type_checks.rs new file mode 100644 index 0000000000000..9ed0422ceff15 --- /dev/null +++ b/src/test/codegen/sanitizer_cfi_emit_type_checks.rs @@ -0,0 +1,24 @@ +// Verifies that pointer type membership tests for indirect calls are emitted. +// +// ignore-windows +// needs-sanitizer-cfi +// only-aarch64 +// only-x86_64 +// compile-flags: -Clto -Cno-prepopulate-passes -Zsanitizer=cfi + +#![crate_type="lib"] + +pub fn foo(f: fn(i32) -> i32, arg: i32) -> i32 { + // CHECK-LABEL: define{{.*}}foo{{.*}}!type !{{[0-9]+}} + // CHECK: start: + // CHECK-NEXT: %0 = bitcast i32 (i32)* %f to i8* + // CHECK-NEXT: %1 = call i1 @llvm.type.test(i8* %0, metadata !"{{[[:print:]]+}}") + // CHECK-NEXT: br i1 %1, label %type_test.pass, label %type_test.fail + // CHECK: type_test.pass: + // CHECK-NEXT: %2 = call i32 %f(i32 %arg) + // CHECK-NEXT: br label %bb1 + // CHECK: type_test.fail: + // CHECK-NEXT: call void @llvm.trap() + // CHECK-NEXT: unreachable + f(arg) +} diff --git a/src/test/codegen/sanitizer_cfi_emit_type_metadata.rs b/src/test/codegen/sanitizer_cfi_emit_type_metadata.rs new file mode 100644 index 0000000000000..96fced47e786d --- /dev/null +++ b/src/test/codegen/sanitizer_cfi_emit_type_metadata.rs @@ -0,0 +1,31 @@ +// Verifies that type metadata for functions are emitted. +// +// ignore-windows +// needs-sanitizer-cfi +// only-aarch64 +// only-x86_64 +// compile-flags: -Clto -Cno-prepopulate-passes -Zsanitizer=cfi + +#![crate_type="lib"] + +pub fn foo(f: fn(i32) -> i32, arg: i32) -> i32 { + // CHECK-LABEL: define{{.*}}foo{{.*}}!type !{{[0-9]+}} + // CHECK: %1 = call i1 @llvm.type.test(i8* %0, metadata !"typeid1") + f(arg) +} + +pub fn bar(f: fn(i32, i32) -> i32, arg1: i32, arg2: i32) -> i32 { + // CHECK-LABEL: define{{.*}}bar{{.*}}!type !{{[0-9]+}} + // CHECK: %1 = call i1 @llvm.type.test(i8* %0, metadata !"typeid2") + f(arg1, arg2) +} + +pub fn baz(f: fn(i32, i32, i32) -> i32, arg1: i32, arg2: i32, arg3: i32) -> i32 { + // CHECK-LABEL: define{{.*}}baz{{.*}}!type !{{[0-9]+}} + // CHECK: %1 = call i1 @llvm.type.test(i8* %0, metadata !"typeid3") + f(arg1, arg2, arg3) +} + +// CHECK: !{{[0-9]+}} = !{i64 0, !"typeid2"} +// CHECK: !{{[0-9]+}} = !{i64 0, !"typeid3"} +// CHECK: !{{[0-9]+}} = !{i64 0, !"typeid4"}