From d9c11482a519facaf0e5e894db998e7adc804033 Mon Sep 17 00:00:00 2001 From: Alvin Sun Date: Mon, 6 Jul 2026 20:17:06 +0800 Subject: [PATCH] Add `-Zstack-protector-guard` target modifier Equivalent to Clang's `-mstack-protector-guard=*`. Allows configuring how the stack canary is accessed (global, TLS, or system register), which must be consistent across the crate graph. --- compiler/rustc_codegen_llvm/src/context.rs | 40 +++++++++++++++++ compiler/rustc_interface/src/tests.rs | 14 +++++- compiler/rustc_session/src/config.rs | 31 ++++++++++++- compiler/rustc_session/src/diagnostics.rs | 16 +++++++ compiler/rustc_session/src/options.rs | 43 ++++++++++++++++++ compiler/rustc_session/src/session.rs | 51 ++++++++++++++++++++++ 6 files changed, 191 insertions(+), 4 deletions(-) diff --git a/compiler/rustc_codegen_llvm/src/context.rs b/compiler/rustc_codegen_llvm/src/context.rs index 7ea30a5b4db6d..8ca915ac62ba5 100644 --- a/compiler/rustc_codegen_llvm/src/context.rs +++ b/compiler/rustc_codegen_llvm/src/context.rs @@ -557,6 +557,46 @@ pub(crate) unsafe fn create_module<'ll>( ); } + // Set stack-protector-guard module flags (equivalent to Clang's -mstack-protector-guard=*) + if let Some(ref guard) = sess.opts.unstable_opts.stack_protector_guard { + if let Some(ref mode) = guard.mode { + llvm::add_module_flag_str( + llmod, + llvm::ModuleFlagMergeBehavior::Error, + "stack-protector-guard", + mode.as_str(), + ); + } + if let Some(offset) = guard.offset { + llvm::add_module_flag_u32( + llmod, + llvm::ModuleFlagMergeBehavior::Error, + "stack-protector-guard-offset", + offset, + ); + } + if let Some(ref reg) = guard.reg { + if !reg.is_empty() { + llvm::add_module_flag_str( + llmod, + llvm::ModuleFlagMergeBehavior::Error, + "stack-protector-guard-reg", + reg, + ); + } + } + if let Some(ref sym) = guard.symbol { + if !sym.is_empty() { + llvm::add_module_flag_str( + llmod, + llvm::ModuleFlagMergeBehavior::Error, + "stack-protector-guard-symbol", + sym, + ); + } + } + } + // Add module flags specified via -Z llvm_module_flag for (key, value, merge_behavior) in &sess.opts.unstable_opts.llvm_module_flag { let merge_behavior = match merge_behavior.as_str() { diff --git a/compiler/rustc_interface/src/tests.rs b/compiler/rustc_interface/src/tests.rs index 71d1829f76584..b5ce84c443af1 100644 --- a/compiler/rustc_interface/src/tests.rs +++ b/compiler/rustc_interface/src/tests.rs @@ -16,8 +16,9 @@ use rustc_session::config::{ InstrumentCoverage, InstrumentMcount, InstrumentXRay, LinkSelfContained, LinkerPluginLto, LocationDetail, LtoCli, MirIncludeSpans, NextSolverConfig, Offload, Options, OutFileName, OutputType, OutputTypes, PAuthKey, PacRet, Passes, PatchableFunctionEntry, Polonius, - ProcMacroExecutionStrategy, Strip, SwitchWithOptPath, SymbolManglingVersion, WasiExecModel, - build_configuration, build_session_options, rustc_optgroups, + ProcMacroExecutionStrategy, StackProtectorGuard, StackProtectorGuardMode, Strip, + SwitchWithOptPath, SymbolManglingVersion, WasiExecModel, build_configuration, + build_session_options, rustc_optgroups, }; use rustc_session::lint::Level; use rustc_session::search_paths::SearchPath; @@ -893,6 +894,15 @@ fn test_unstable_options_tracking_hash() { tracked!(split_lto_unit, Some(true)); tracked!(src_hash_algorithm, Some(SourceFileHashAlgorithm::Sha1)); tracked!(stack_protector, StackProtector::All); + tracked!( + stack_protector_guard, + Some(StackProtectorGuard { + mode: Some(StackProtectorGuardMode::Sysreg), + offset: Some(0), + reg: Some("sp_el0".to_string()), + symbol: None, + }) + ); tracked!(staticlib_hide_internal_symbols, true); tracked!(staticlib_rename_internal_symbols, true); tracked!(teach, true); diff --git a/compiler/rustc_session/src/config.rs b/compiler/rustc_session/src/config.rs index 8ed01315ade53..463761dd9fd1b 100644 --- a/compiler/rustc_session/src/config.rs +++ b/compiler/rustc_session/src/config.rs @@ -1644,6 +1644,31 @@ impl PointerAuthOption { } } +#[derive(Clone, Copy, Hash, Debug, PartialEq)] +pub enum StackProtectorGuardMode { + Global, + Tls, + Sysreg, +} + +impl StackProtectorGuardMode { + pub fn as_str(&self) -> &'static str { + match self { + Self::Global => "global", + Self::Tls => "tls", + Self::Sysreg => "sysreg", + } + } +} + +#[derive(Clone, Hash, Debug, PartialEq, Default)] +pub struct StackProtectorGuard { + pub mode: Option, + pub offset: Option, + pub reg: Option, + pub symbol: Option, +} + pub fn build_configuration(sess: &Session, mut user_cfg: Cfg) -> Cfg { // First disallow some configuration given on the command line cfg::disallow_cfgs(sess, &user_cfg); @@ -3141,8 +3166,8 @@ pub(crate) mod dep_tracking { FunctionReturn, InliningThreshold, InstrumentCoverage, InstrumentMcount, InstrumentXRay, LinkerPluginLto, LocationDetail, LtoCli, MirStripDebugInfo, NextSolverConfig, Offload, OptLevel, OutFileName, OutputType, OutputTypes, PatchableFunctionEntry, PointerAuthOption, - Polonius, ResolveDocLinks, SourceFileHashAlgorithm, SplitDwarfKind, SwitchWithOptPath, - SymbolManglingVersion, WasiExecModel, + Polonius, ResolveDocLinks, SourceFileHashAlgorithm, SplitDwarfKind, StackProtectorGuard, + StackProtectorGuardMode, SwitchWithOptPath, SymbolManglingVersion, WasiExecModel, }; use crate::lint; use crate::utils::NativeLib; @@ -3240,6 +3265,8 @@ pub(crate) mod dep_tracking { LocationDetail, FmtDebug, BranchProtection, + StackProtectorGuard, + StackProtectorGuardMode, LanguageIdentifier, NextSolverConfig, PatchableFunctionEntry, diff --git a/compiler/rustc_session/src/diagnostics.rs b/compiler/rustc_session/src/diagnostics.rs index d1989609cefe2..7f203b32aa220 100644 --- a/compiler/rustc_session/src/diagnostics.rs +++ b/compiler/rustc_session/src/diagnostics.rs @@ -387,6 +387,12 @@ pub(crate) struct PointerAuthenticationTypeDiscriminationNotSupportedForTarget<' pub(crate) target_triple: &'a TargetTuple, } +#[derive(Diagnostic)] +#[diag("`-Z stack-protector-guard` is not supported for the `{$arch}` architecture")] +pub(crate) struct StackProtectorGuardUnsupportedArch { + pub(crate) arch: String, +} + #[derive(Diagnostic)] #[diag( "`-Z pointer-authentication` is not supported for target {$target_triple} and will be ignored" @@ -395,6 +401,16 @@ pub(crate) struct PointerAuthenticationNotSupportedForTarget<'a> { pub(crate) target_triple: &'a TargetTuple, } +#[derive(Diagnostic)] +#[diag( + "invalid value `{$guard}` for `-Z stack-protector-guard` on `{$arch}` architecture, expected one of: {$valid}" +)] +pub(crate) struct StackProtectorGuardInvalidValue { + pub(crate) arch: String, + pub(crate) guard: String, + pub(crate) valid: String, +} + #[derive(Diagnostic)] #[diag( "`-Z small-data-threshold` is not supported for target {$target_triple} and will be ignored" diff --git a/compiler/rustc_session/src/options.rs b/compiler/rustc_session/src/options.rs index 6f3a505c2c26f..4c501e591d665 100644 --- a/compiler/rustc_session/src/options.rs +++ b/compiler/rustc_session/src/options.rs @@ -853,6 +853,8 @@ mod desc { pub(crate) const parse_stack_protector: &str = "one of (`none` (default), `basic`, `strong`, or `all`)"; pub(crate) const parse_branch_protection: &str = "a `,` separated combination of `bti`, `gcs`, `pac-ret`, (optionally with `pc`, `b-key`, `leaf` if `pac-ret` is set)"; + pub(crate) const parse_stack_protector_guard: &str = + "one of (`global`, `tls`, `sysreg`), optionally with `offset=N`, `reg=R`, `symbol=S`"; pub(crate) const parse_proc_macro_execution_strategy: &str = "one of supported execution strategies (`same-thread`, or `cross-thread`)"; pub(crate) const parse_inlining_threshold: &str = @@ -1993,6 +1995,45 @@ pub mod parse { true } + pub(crate) fn parse_stack_protector_guard( + slot: &mut Option, + v: Option<&str>, + ) -> bool { + match v { + Some(s) => { + let slot = slot.get_or_insert_default(); + for opt in s.split(',') { + match opt { + "global" => slot.mode = Some(StackProtectorGuardMode::Global), + "tls" => slot.mode = Some(StackProtectorGuardMode::Tls), + "sysreg" => slot.mode = Some(StackProtectorGuardMode::Sysreg), + s if let Some(value) = s.strip_prefix("offset=") => { + match value.parse::() { + Ok(n) => slot.offset = Some(n), + Err(_) => return false, + } + } + s if let Some(value) = s.strip_prefix("reg=") => { + if value.is_empty() { + return false; + } + slot.reg = Some(value.to_string()); + } + s if let Some(value) = s.strip_prefix("symbol=") => { + if value.is_empty() { + return false; + } + slot.symbol = Some(value.to_string()); + } + _ => return false, + } + } + } + _ => return false, + } + true + } + pub(crate) fn parse_collapse_macro_debuginfo( slot: &mut CollapseMacroDebuginfo, v: Option<&str>, @@ -2806,6 +2847,8 @@ written to standard error output)"), #[rustc_lint_opt_deny_field_access("use `Session::stack_protector` instead of this field")] stack_protector: StackProtector = (StackProtector::None, parse_stack_protector, [TRACKED] { MITIGATION: StackProtector }, "control stack smash protection strategy (`rustc --print stack-protector-strategies` for details)"), + stack_protector_guard: Option = (None, parse_stack_protector_guard, [TRACKED] { TARGET_MODIFIER: StackProtectorGuard }, + "stack protector guard settings (`mode[,offset=N][,reg=R][,symbol=S]`; modes: `global`, `tls`, `sysreg`)"), staticlib_allow_rdylib_deps: bool = (false, parse_bool, [TRACKED], "allow staticlibs to have rust dylib dependencies"), staticlib_hide_internal_symbols: bool = (false, parse_bool, [TRACKED], diff --git a/compiler/rustc_session/src/session.rs b/compiler/rustc_session/src/session.rs index 733470a7e0471..56a6dee4fadd7 100644 --- a/compiler/rustc_session/src/session.rs +++ b/compiler/rustc_session/src/session.rs @@ -1588,6 +1588,57 @@ fn validate_commandline_args_with_session_available(sess: &Session) { } } + if let Some(ref guard) = sess.opts.unstable_opts.stack_protector_guard { + if let Some(ref mode) = guard.mode { + let valid: Option<(&[config::StackProtectorGuardMode], &str)> = match sess.target.arch { + Arch::X86 | Arch::X86_64 => Some(( + &[ + config::StackProtectorGuardMode::Tls, + config::StackProtectorGuardMode::Global, + ], + "tls, global", + )), + Arch::AArch64 => Some(( + &[ + config::StackProtectorGuardMode::Sysreg, + config::StackProtectorGuardMode::Global, + ], + "sysreg, global", + )), + Arch::Arm => Some(( + &[ + config::StackProtectorGuardMode::Tls, + config::StackProtectorGuardMode::Global, + ], + "tls, global", + )), + Arch::RiscV32 | Arch::RiscV64 => Some(( + &[ + config::StackProtectorGuardMode::Sysreg, + config::StackProtectorGuardMode::Global, + ], + "sysreg, global", + )), + _ => None, + }; + match valid { + Some((modes, _)) if modes.contains(mode) => {} + Some((_, valid_str)) => { + sess.dcx().emit_err(diagnostics::StackProtectorGuardInvalidValue { + arch: sess.target.arch.to_string(), + guard: mode.as_str().to_string(), + valid: valid_str.to_string(), + }); + } + None => { + sess.dcx().emit_err(diagnostics::StackProtectorGuardUnsupportedArch { + arch: sess.target.arch.to_string(), + }); + } + } + } + } + if sess.opts.unstable_opts.small_data_threshold.is_some() { if sess.target.small_data_threshold_support() == SmallDataThresholdSupport::None { sess.dcx().emit_warn(diagnostics::SmallDataThresholdNotSupportedForTarget {