From e207006ad37d85b16929b16b843a81874eef7e4a Mon Sep 17 00:00:00 2001 From: Paul Kirth Date: Thu, 25 Sep 2025 12:47:36 -0700 Subject: [PATCH] Add default sanitizers to TargetOptions Some sanitizers are part of a system's ABI, like the shadow call stack on Aarch64 and RISC-V Fuchsia. Typically ABI options have other spellings, but LLVM has, for historical reasons, marked this as a sanitizer instead of an alternate ABI option. As a result, Fuchsia targets may not be compiled against the correct ABI unless this option is set. This hasn't caused correctness problems, since the backend reserves the SCS register, and thus preserves its value. But this is an issue for unwinding, as the SCS will not be an array of PCs describing the call complete call chain, and will have gaps from callers that don't use the correct ABI. In the long term, I'd like to see all the sanitizer configs that all frontends copy from clang moved into llvm's libFrontend, and exposed so that frontend consumers can use a small set of simple APIs to use sanitizers in a consistent way across the LLVM ecosystem, but that work is not yet ready today. --- compiler/rustc_codegen_llvm/src/abi.rs | 2 +- compiler/rustc_codegen_llvm/src/attributes.rs | 10 ++-------- compiler/rustc_codegen_ssa/src/back/link.rs | 8 ++------ compiler/rustc_codegen_ssa/src/back/write.rs | 2 +- compiler/rustc_metadata/src/creader.rs | 10 +++++----- compiler/rustc_metadata/src/native_libs.rs | 2 +- compiler/rustc_session/src/config/cfg.rs | 2 +- compiler/rustc_session/src/options.rs | 11 ++++++----- compiler/rustc_session/src/session.rs | 10 +++++++--- compiler/rustc_target/src/spec/json.rs | 7 +++++++ compiler/rustc_target/src/spec/mod.rs | 8 ++++++++ .../src/spec/targets/aarch64_unknown_fuchsia.rs | 1 + .../src/spec/targets/riscv64gc_unknown_fuchsia.rs | 1 + 13 files changed, 43 insertions(+), 31 deletions(-) diff --git a/compiler/rustc_codegen_llvm/src/abi.rs b/compiler/rustc_codegen_llvm/src/abi.rs index 3793a1470aada..6b296c3046ef3 100644 --- a/compiler/rustc_codegen_llvm/src/abi.rs +++ b/compiler/rustc_codegen_llvm/src/abi.rs @@ -96,7 +96,7 @@ fn get_attrs<'ll>(this: &ArgAttributes, cx: &CodegenCx<'ll, '_>) -> SmallVec<[&' } } } - } else if cx.tcx.sess.opts.unstable_opts.sanitizer.contains(SanitizerSet::MEMORY) { + } else if cx.tcx.sess.sanitizers().contains(SanitizerSet::MEMORY) { // If we're not optimising, *but* memory sanitizer is on, emit noundef, since it affects // memory sanitizer's behavior. diff --git a/compiler/rustc_codegen_llvm/src/attributes.rs b/compiler/rustc_codegen_llvm/src/attributes.rs index 209b8efa2c3b3..8092e9b9512d3 100644 --- a/compiler/rustc_codegen_llvm/src/attributes.rs +++ b/compiler/rustc_codegen_llvm/src/attributes.rs @@ -101,7 +101,7 @@ pub(crate) fn sanitize_attrs<'ll, 'tcx>( no_sanitize: SanitizerSet, ) -> SmallVec<[&'ll Attribute; 4]> { let mut attrs = SmallVec::new(); - let enabled = tcx.sess.opts.unstable_opts.sanitizer - no_sanitize; + let enabled = tcx.sess.sanitizers() - no_sanitize; if enabled.contains(SanitizerSet::ADDRESS) || enabled.contains(SanitizerSet::KERNELADDRESS) { attrs.push(llvm::AttributeKind::SanitizeAddress.create_attr(cx.llcx)); } @@ -240,13 +240,7 @@ fn probestack_attr<'ll, 'tcx>(cx: &SimpleCx<'ll>, tcx: TyCtxt<'tcx>) -> Option<& // Currently stack probes seem somewhat incompatible with the address // sanitizer and thread sanitizer. With asan we're already protected from // stack overflow anyway so we don't really need stack probes regardless. - if tcx - .sess - .opts - .unstable_opts - .sanitizer - .intersects(SanitizerSet::ADDRESS | SanitizerSet::THREAD) - { + if tcx.sess.sanitizers().intersects(SanitizerSet::ADDRESS | SanitizerSet::THREAD) { return None; } diff --git a/compiler/rustc_codegen_ssa/src/back/link.rs b/compiler/rustc_codegen_ssa/src/back/link.rs index ea538d3d46981..5219ee1abe889 100644 --- a/compiler/rustc_codegen_ssa/src/back/link.rs +++ b/compiler/rustc_codegen_ssa/src/back/link.rs @@ -1226,7 +1226,7 @@ fn add_sanitizer_libraries( return; } - let sanitizer = sess.opts.unstable_opts.sanitizer; + let sanitizer = sess.sanitizers(); if sanitizer.contains(SanitizerSet::ADDRESS) { link_sanitizer_runtime(sess, flavor, linker, "asan"); } @@ -2496,11 +2496,7 @@ fn add_order_independent_options( && crate_type == CrateType::Executable && !matches!(flavor, LinkerFlavor::Gnu(Cc::Yes, _)) { - let prefix = if sess.opts.unstable_opts.sanitizer.contains(SanitizerSet::ADDRESS) { - "asan/" - } else { - "" - }; + let prefix = if sess.sanitizers().contains(SanitizerSet::ADDRESS) { "asan/" } else { "" }; cmd.link_arg(format!("--dynamic-linker={prefix}ld.so.1")); } diff --git a/compiler/rustc_codegen_ssa/src/back/write.rs b/compiler/rustc_codegen_ssa/src/back/write.rs index 368a2e307bb27..c1e80e08a0eb4 100644 --- a/compiler/rustc_codegen_ssa/src/back/write.rs +++ b/compiler/rustc_codegen_ssa/src/back/write.rs @@ -176,7 +176,7 @@ impl ModuleConfig { debug_info_for_profiling: sess.opts.unstable_opts.debug_info_for_profiling, instrument_coverage: if_regular!(sess.instrument_coverage(), false), - sanitizer: if_regular!(sess.opts.unstable_opts.sanitizer, SanitizerSet::empty()), + sanitizer: if_regular!(sess.sanitizers(), SanitizerSet::empty()), sanitizer_dataflow_abilist: if_regular!( sess.opts.unstable_opts.sanitizer_dataflow_abilist.clone(), Vec::new() diff --git a/compiler/rustc_metadata/src/creader.rs b/compiler/rustc_metadata/src/creader.rs index 62de18072902e..4e2e1e21ec6dd 100644 --- a/compiler/rustc_metadata/src/creader.rs +++ b/compiler/rustc_metadata/src/creader.rs @@ -412,7 +412,7 @@ impl CStore { match (&left_name_val, &right_name_val) { (Some(l), Some(r)) => match l.1.opt.cmp(&r.1.opt) { cmp::Ordering::Equal => { - if !l.1.consistent(&tcx.sess.opts, Some(&r.1)) { + if !l.1.consistent(&tcx.sess, Some(&r.1)) { report_diff( &l.0.prefix, &l.0.name, @@ -424,26 +424,26 @@ impl CStore { right_name_val = None; } cmp::Ordering::Greater => { - if !r.1.consistent(&tcx.sess.opts, None) { + if !r.1.consistent(&tcx.sess, None) { report_diff(&r.0.prefix, &r.0.name, None, Some(&r.1.value_name)); } right_name_val = None; } cmp::Ordering::Less => { - if !l.1.consistent(&tcx.sess.opts, None) { + if !l.1.consistent(&tcx.sess, None) { report_diff(&l.0.prefix, &l.0.name, Some(&l.1.value_name), None); } left_name_val = None; } }, (Some(l), None) => { - if !l.1.consistent(&tcx.sess.opts, None) { + if !l.1.consistent(&tcx.sess, None) { report_diff(&l.0.prefix, &l.0.name, Some(&l.1.value_name), None); } left_name_val = None; } (None, Some(r)) => { - if !r.1.consistent(&tcx.sess.opts, None) { + if !r.1.consistent(&tcx.sess, None) { report_diff(&r.0.prefix, &r.0.name, None, Some(&r.1.value_name)); } right_name_val = None; diff --git a/compiler/rustc_metadata/src/native_libs.rs b/compiler/rustc_metadata/src/native_libs.rs index 15572063d45a6..9044cc2737fd6 100644 --- a/compiler/rustc_metadata/src/native_libs.rs +++ b/compiler/rustc_metadata/src/native_libs.rs @@ -71,7 +71,7 @@ pub fn walk_native_lib_search_dirs( || sess.target.os == "linux" || sess.target.os == "fuchsia" || sess.target.is_like_aix - || sess.target.is_like_darwin && !sess.opts.unstable_opts.sanitizer.is_empty() + || sess.target.is_like_darwin && !sess.sanitizers().is_empty() { f(&sess.target_tlib_path.dir, false)?; } diff --git a/compiler/rustc_session/src/config/cfg.rs b/compiler/rustc_session/src/config/cfg.rs index a72f6201dcea2..ddf0dba730cbf 100644 --- a/compiler/rustc_session/src/config/cfg.rs +++ b/compiler/rustc_session/src/config/cfg.rs @@ -224,7 +224,7 @@ pub(crate) fn default_configuration(sess: &Session) -> Cfg { ins_sym!(sym::relocation_model, sess.target.relocation_model.desc_symbol()); } - for mut s in sess.opts.unstable_opts.sanitizer { + for mut s in sess.sanitizers() { // KASAN is still ASAN under the hood, so it uses the same attribute. if s == SanitizerSet::KERNELADDRESS { s = SanitizerSet::ADDRESS; diff --git a/compiler/rustc_session/src/options.rs b/compiler/rustc_session/src/options.rs index b89aec7d22a91..9c2fa2576c8e5 100644 --- a/compiler/rustc_session/src/options.rs +++ b/compiler/rustc_session/src/options.rs @@ -22,7 +22,7 @@ use rustc_target::spec::{ use crate::config::*; use crate::search_paths::SearchPath; use crate::utils::NativeLib; -use crate::{EarlyDiagCtxt, lint}; +use crate::{EarlyDiagCtxt, Session, lint}; macro_rules! insert { ($opt_name:ident, $opt_expr:expr, $sub_hashes:expr) => { @@ -111,12 +111,12 @@ mod target_modifier_consistency_check { lparsed & tmod_sanitizers == rparsed & tmod_sanitizers } pub(super) fn sanitizer_cfi_normalize_integers( - opts: &Options, + sess: &Session, l: &TargetModifier, r: Option<&TargetModifier>, ) -> bool { // For kCFI, the helper flag -Zsanitizer-cfi-normalize-integers should also be a target modifier - if opts.unstable_opts.sanitizer.contains(SanitizerSet::KCFI) { + if sess.sanitizers().contains(SanitizerSet::KCFI) { if let Some(r) = r { return l.extend().tech_value == r.extend().tech_value; } else { @@ -133,7 +133,7 @@ impl TargetModifier { } // Custom consistency check for target modifiers (or default `l.tech_value == r.tech_value`) // When other is None, consistency with default value is checked - pub fn consistent(&self, opts: &Options, other: Option<&TargetModifier>) -> bool { + pub fn consistent(&self, sess: &Session, other: Option<&TargetModifier>) -> bool { assert!(other.is_none() || self.opt == other.unwrap().opt); match self.opt { OptionsTargetModifiers::UnstableOptions(unstable) => match unstable { @@ -142,7 +142,7 @@ impl TargetModifier { } UnstableOptionsTargetModifiers::sanitizer_cfi_normalize_integers => { return target_modifier_consistency_check::sanitizer_cfi_normalize_integers( - opts, self, other, + sess, self, other, ); } _ => {} @@ -2575,6 +2575,7 @@ written to standard error output)"), retpoline_external_thunk: bool = (false, parse_bool, [TRACKED TARGET_MODIFIER], "enables retpoline-external-thunk, retpoline-indirect-branches and retpoline-indirect-calls \ target features (default: no)"), + #[rustc_lint_opt_deny_field_access("use `Session::sanitizers()` instead of this field")] sanitizer: SanitizerSet = (SanitizerSet::empty(), parse_sanitizers, [TRACKED TARGET_MODIFIER], "use a sanitizer"), sanitizer_cfi_canonical_jump_tables: Option = (Some(true), parse_opt_bool, [TRACKED], diff --git a/compiler/rustc_session/src/session.rs b/compiler/rustc_session/src/session.rs index 522faf1db9ff1..e1e20959f902b 100644 --- a/compiler/rustc_session/src/session.rs +++ b/compiler/rustc_session/src/session.rs @@ -323,7 +323,7 @@ impl Session { } pub fn is_sanitizer_cfi_enabled(&self) -> bool { - self.opts.unstable_opts.sanitizer.contains(SanitizerSet::CFI) + self.sanitizers().contains(SanitizerSet::CFI) } pub fn is_sanitizer_cfi_canonical_jump_tables_disabled(&self) -> bool { @@ -347,7 +347,7 @@ impl Session { } pub fn is_sanitizer_kcfi_enabled(&self) -> bool { - self.opts.unstable_opts.sanitizer.contains(SanitizerSet::KCFI) + self.sanitizers().contains(SanitizerSet::KCFI) } pub fn is_split_lto_unit_enabled(&self) -> bool { @@ -527,7 +527,7 @@ impl Session { // AddressSanitizer and KernelAddressSanitizer uses lifetimes to detect use after scope bugs. // MemorySanitizer uses lifetimes to detect use of uninitialized stack variables. // HWAddressSanitizer will use lifetimes to detect use after scope bugs in the future. - || self.opts.unstable_opts.sanitizer.intersects(SanitizerSet::ADDRESS | SanitizerSet::KERNELADDRESS | SanitizerSet::MEMORY | SanitizerSet::HWADDRESS) + || self.sanitizers().intersects(SanitizerSet::ADDRESS | SanitizerSet::KERNELADDRESS | SanitizerSet::MEMORY | SanitizerSet::HWADDRESS) } pub fn diagnostic_width(&self) -> usize { @@ -922,6 +922,10 @@ impl Session { min } } + + pub fn sanitizers(&self) -> SanitizerSet { + return self.opts.unstable_opts.sanitizer | self.target.options.default_sanitizers; + } } // JUSTIFICATION: part of session construction diff --git a/compiler/rustc_target/src/spec/json.rs b/compiler/rustc_target/src/spec/json.rs index f236be92b3b60..1bcf3ce409d2d 100644 --- a/compiler/rustc_target/src/spec/json.rs +++ b/compiler/rustc_target/src/spec/json.rs @@ -214,6 +214,11 @@ impl Target { supported_sanitizers.into_iter().fold(SanitizerSet::empty(), |a, b| a | b); } + if let Some(default_sanitizers) = json.default_sanitizers { + base.default_sanitizers = + default_sanitizers.into_iter().fold(SanitizerSet::empty(), |a, b| a | b); + } + forward!(generate_arange_section); forward!(supports_stack_protector); forward!(small_data_threshold_support); @@ -392,6 +397,7 @@ impl ToJson for Target { target_option_val!(split_debuginfo); target_option_val!(supported_split_debuginfo); target_option_val!(supported_sanitizers); + target_option_val!(default_sanitizers); target_option_val!(c_enum_min_bits); target_option_val!(generate_arange_section); target_option_val!(supports_stack_protector); @@ -612,6 +618,7 @@ struct TargetSpecJson { split_debuginfo: Option, supported_split_debuginfo: Option>, supported_sanitizers: Option>, + default_sanitizers: Option>, generate_arange_section: Option, supports_stack_protector: Option, small_data_threshold_support: Option, diff --git a/compiler/rustc_target/src/spec/mod.rs b/compiler/rustc_target/src/spec/mod.rs index b49e7fc9cff66..c753089b1d3d3 100644 --- a/compiler/rustc_target/src/spec/mod.rs +++ b/compiler/rustc_target/src/spec/mod.rs @@ -2327,6 +2327,13 @@ pub struct TargetOptions { /// distributed with the target, the sanitizer should still appear in this list for the target. pub supported_sanitizers: SanitizerSet, + /// The sanitizers that are enabled by default on this target. + /// + /// Note that the support here is at a codegen level. If the machine code with sanitizer + /// enabled can generated on this target, but the necessary supporting libraries are not + /// distributed with the target, the sanitizer should still appear in this list for the target. + pub default_sanitizers: SanitizerSet, + /// Minimum number of bits in #[repr(C)] enum. Defaults to the size of c_int pub c_enum_min_bits: Option, @@ -2575,6 +2582,7 @@ impl Default for TargetOptions { // `Off` is supported by default, but targets can remove this manually, e.g. Windows. supported_split_debuginfo: Cow::Borrowed(&[SplitDebuginfo::Off]), supported_sanitizers: SanitizerSet::empty(), + default_sanitizers: SanitizerSet::empty(), c_enum_min_bits: None, generate_arange_section: true, supports_stack_protector: true, diff --git a/compiler/rustc_target/src/spec/targets/aarch64_unknown_fuchsia.rs b/compiler/rustc_target/src/spec/targets/aarch64_unknown_fuchsia.rs index 8366b6d9bd824..6dbe376d02f86 100644 --- a/compiler/rustc_target/src/spec/targets/aarch64_unknown_fuchsia.rs +++ b/compiler/rustc_target/src/spec/targets/aarch64_unknown_fuchsia.rs @@ -12,6 +12,7 @@ pub(crate) fn target() -> Target { | SanitizerSet::CFI | SanitizerSet::LEAK | SanitizerSet::SHADOWCALLSTACK; + base.default_sanitizers = SanitizerSet::SHADOWCALLSTACK; base.supports_xray = true; base.add_pre_link_args( diff --git a/compiler/rustc_target/src/spec/targets/riscv64gc_unknown_fuchsia.rs b/compiler/rustc_target/src/spec/targets/riscv64gc_unknown_fuchsia.rs index c4466e13d1439..28d2b4c54ef91 100644 --- a/compiler/rustc_target/src/spec/targets/riscv64gc_unknown_fuchsia.rs +++ b/compiler/rustc_target/src/spec/targets/riscv64gc_unknown_fuchsia.rs @@ -9,6 +9,7 @@ pub(crate) fn target() -> Target { base.max_atomic_width = Some(64); base.stack_probes = StackProbeType::Inline; base.supported_sanitizers = SanitizerSet::SHADOWCALLSTACK; + base.default_sanitizers = SanitizerSet::SHADOWCALLSTACK; base.supports_xray = true; Target {