Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 2 additions & 3 deletions bootstrap.example.toml
Original file line number Diff line number Diff line change
Expand Up @@ -780,10 +780,9 @@
#rust.frame-pointers = false

# Indicates whether stack protectors should be used
# via the unstable option `-Zstack-protector`.
# via `-Cstack-protector`.
#
# Valid options are : `none`(default),`basic`,`strong`, or `all`.
# `strong` and `basic` options may be buggy and are not recommended, see rust-lang/rust#114903.
# Valid options are : `none`(default), `strong`, or `all`.
#rust.stack-protector = "none"

# Prints each test name as it is executed, to help debug issues in the test harness itself.
Expand Down
1 change: 0 additions & 1 deletion compiler/rustc_codegen_gcc/src/gcc_util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -316,7 +316,6 @@ pub fn new_context<'gcc>(sess: &Session) -> Context<'gcc> {
match sess.stack_protector() {
StackProtector::All => context.add_command_line_option("-fstack-protector-all"),
StackProtector::Strong => context.add_command_line_option("-fstack-protector-strong"),
StackProtector::Basic => context.add_command_line_option("-fstack-protector"),
StackProtector::None => (),
}

Expand Down
1 change: 0 additions & 1 deletion compiler/rustc_codegen_llvm/src/attributes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -390,7 +390,6 @@ fn stackprotector_attr<'ll>(cx: &SimpleCx<'ll>, sess: &Session) -> Option<&'ll A
StackProtector::None => return None,
StackProtector::All => AttributeKind::StackProtectReq,
StackProtector::Strong => AttributeKind::StackProtectStrong,
StackProtector::Basic => AttributeKind::StackProtect,
};

Some(sspattr.create_attr(cx.llcx))
Expand Down
3 changes: 2 additions & 1 deletion compiler/rustc_interface/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -663,6 +663,7 @@ fn test_codegen_options_tracking_hash() {
tracked!(relocation_model, Some(RelocModel::Pic));
tracked!(relro_level, Some(RelroLevel::Full));
tracked!(split_debuginfo, Some(SplitDebuginfo::Packed));
tracked!(stack_protector, Some(StackProtector::All));
tracked!(symbol_mangling_version, Some(SymbolManglingVersion::V0));
tracked!(target_cpu, Some(String::from("abc")));
tracked!(target_feature, String::from("all the features, all of them"));
Expand Down Expand Up @@ -899,7 +900,7 @@ fn test_unstable_options_tracking_hash() {
tracked!(small_data_threshold, Some(16));
tracked!(split_lto_unit, Some(true));
tracked!(src_hash_algorithm, Some(SourceFileHashAlgorithm::Sha1));
tracked!(stack_protector, StackProtector::All);
tracked!(stack_protector, Some(StackProtector::All));
tracked!(staticlib_hide_internal_symbols, true);
tracked!(staticlib_rename_internal_symbols, true);
tracked!(teach, true);
Expand Down
9 changes: 6 additions & 3 deletions compiler/rustc_metadata/src/creader.rs
Original file line number Diff line number Diff line change
Expand Up @@ -506,9 +506,12 @@ impl CStore {
mitigation_level: my_mitigation.level.level_str().to_string(),
extern_crate: data.name(),
};
if my_mitigation.kind == DeniedPartialMitigationKind::StackProtector {
// make stack-protector only a forward-compat warning since it was
// pretty widely used
if my_mitigation.kind == DeniedPartialMitigationKind::StackProtector
&& !tcx.sess.using_stable_stack_protector()
{
// make stack-protector with -Z stack-protector only a forward-compat warning since it was
// pretty widely used. When using -C stack-protector, it should still be a hard error since
// -C stack-protector has no pre-existing users
tcx.sess.psess.buffer_crate_lint(PARTIAL_STACK_PROTECTOR, diagnostic);
} else {
tcx.dcx().emit_err(diagnostic);
Expand Down
4 changes: 1 addition & 3 deletions compiler/rustc_session/src/diagnostics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -386,9 +386,7 @@ pub(crate) struct EmbedSourceInsufficientDwarfVersion {
pub(crate) struct EmbedSourceRequiresDebugInfo;

#[derive(Diagnostic)]
#[diag(
"`-Z stack-protector={$stack_protector}` is not supported for target {$target_triple} and will be ignored"
)]
#[diag("`-C stack-protector={$stack_protector}` is not supported for target {$target_triple}")]
pub(crate) struct StackProtectorNotSupportedForTarget<'a> {
pub(crate) stack_protector: StackProtector,
pub(crate) target_triple: &'a TargetTuple,
Expand Down
17 changes: 11 additions & 6 deletions compiler/rustc_session/src/options.rs
Original file line number Diff line number Diff line change
Expand Up @@ -917,8 +917,7 @@ mod desc {
"either no value or one of `legacy` (the default), `off`, or `next`";
pub(crate) const parse_annotate_moves: &str =
"either a boolean (`yes`, `no`, `on`, `off`, etc.), or a size limit in bytes";
pub(crate) const parse_stack_protector: &str =
"one of (`none` (default), `basic`, `strong`, or `all`)";
pub(crate) const parse_stack_protector: &str = "one of (`none` (default), `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_proc_macro_execution_strategy: &str =
"one of supported execution strategies (`same-thread`, or `cross-thread`)";
Expand Down Expand Up @@ -2023,9 +2022,12 @@ pub mod parse {
true
}

pub(crate) fn parse_stack_protector(slot: &mut StackProtector, v: Option<&str>) -> bool {
pub(crate) fn parse_stack_protector(
slot: &mut Option<StackProtector>,
v: Option<&str>,
) -> bool {
match v.and_then(|s| StackProtector::from_str(s).ok()) {
Some(ssp) => *slot = ssp,
Some(ssp) => *slot = Some(ssp),
_ => return false,
}
true
Expand Down Expand Up @@ -2347,6 +2349,9 @@ options! {
#[rustc_lint_opt_deny_field_access("use `Session::split_debuginfo` instead of this field")]
split_debuginfo: Option<SplitDebuginfo> = (None, parse_split_debuginfo, [TRACKED],
"how to handle split-debuginfo, a platform-specific option"),
#[rustc_lint_opt_deny_field_access("use `Session::stack_protector` instead of this field")]
stack_protector: Option<StackProtector> = (None, parse_stack_protector, [TRACKED] { MITIGATION: StackProtector },
"control stack smashing protection strategy (`rustc --print stack-protector-strategies` for details)"),
strip: Strip = (Strip::None, parse_strip, [UNTRACKED],
"tell the linker which information to strip (`none` (default), `debuginfo` or `symbols`)"),
symbol_mangling_version: Option<SymbolManglingVersion> = (None,
Expand Down Expand Up @@ -2911,8 +2916,8 @@ written to standard error output)"),
src_hash_algorithm: Option<SourceFileHashAlgorithm> = (None, parse_src_file_hash, [TRACKED],
"hash algorithm of source files in debug info (`md5`, `sha1`, or `sha256`)"),
#[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: Option<StackProtector> = (None, parse_stack_protector, [TRACKED] { MITIGATION: StackProtector },
"control stack smashing protection strategy (`rustc --print stack-protector-strategies` for details)"),
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],
Expand Down
5 changes: 0 additions & 5 deletions compiler/rustc_session/src/options/mitigation_coverage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ impl DeniedPartialMitigationLevel {
pub fn level_str(&self) -> &'static str {
match self {
DeniedPartialMitigationLevel::StackProtector(StackProtector::All) => "=all",
DeniedPartialMitigationLevel::StackProtector(StackProtector::Basic) => "=basic",
DeniedPartialMitigationLevel::StackProtector(StackProtector::Strong) => "=strong",
// currently `=disabled` should not appear
DeniedPartialMitigationLevel::Enabled(false) => "=disabled",
Expand All @@ -36,9 +35,6 @@ impl std::fmt::Display for DeniedPartialMitigationLevel {
DeniedPartialMitigationLevel::StackProtector(StackProtector::All) => {
write!(f, "all")
}
DeniedPartialMitigationLevel::StackProtector(StackProtector::Basic) => {
write!(f, "basic")
}
DeniedPartialMitigationLevel::StackProtector(StackProtector::Strong) => {
write!(f, "strong")
}
Expand Down Expand Up @@ -204,7 +200,6 @@ denied_partial_mitigations! {
// The mitigation name should match the option name in rustc_session::options,
// to allow for resetting the mitigation

// stack-protector is an unstable option, so it can be denied-partial
(StackProtector, "stack-protector", Edition2015, self.stack_protector()),
(ControlFlowGuard, "control-flow-guard", EditionFuture, self.opts.cg.control_flow_guard == CFGuard::Checks)
}
Expand Down
21 changes: 13 additions & 8 deletions compiler/rustc_session/src/session.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1110,11 +1110,16 @@ impl Session {
}

pub fn stack_protector(&self) -> StackProtector {
if self.target.options.supports_stack_protector {
self.opts.unstable_opts.stack_protector
} else {
StackProtector::None
}
// -C stack-protector overwrites -Z stack-protector, default to StackProtector::None

@rcvalle rcvalle Oct 20, 2026 •

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just to confirm this is the intended behavior:

If -C stack-protector=none -Z stack-protector=all is used, sess.stack_protector() evaluates to StackProtector::None. And if -C stack-protector=strong -Z stack-protector=all is passed, sess.stack_protector() evaluates to StackProtector::Strong. Is this the agreed upon and expected behavior?

View changes since the review

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This matches dwarf_version behavior, so I think is the right thing to do

self.opts
.cg
.stack_protector
.or(self.opts.unstable_opts.stack_protector)
.unwrap_or(StackProtector::None)
}

pub fn using_stable_stack_protector(&self) -> bool {
self.opts.cg.stack_protector.is_some()
}

/// Returns the `-Zbranch-protection` info. Note that it is adjusted to the current target, e.g.
Expand Down Expand Up @@ -1728,10 +1733,10 @@ fn validate_commandline_args_with_session_available(sess: &Session) {
}
}

if sess.opts.unstable_opts.stack_protector != StackProtector::None {
if sess.stack_protector() != StackProtector::None {
if !sess.target.options.supports_stack_protector {
sess.dcx().emit_warn(diagnostics::StackProtectorNotSupportedForTarget {
stack_protector: sess.opts.unstable_opts.stack_protector,
sess.dcx().emit_err(diagnostics::StackProtectorNotSupportedForTarget {
stack_protector: sess.stack_protector(),
target_triple: &sess.opts.target_triple,
});
}
Expand Down
6 changes: 0 additions & 6 deletions compiler/rustc_target/src/spec/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1204,12 +1204,6 @@ crate::target_spec_enum! {
/// Disable stack canary generation.
None = "none",

/// On LLVM, mark all generated LLVM functions with the `ssp` attribute (see
/// llvm/docs/LangRef.rst). This triggers stack canary generation in
/// functions which contain an array of a byte-sized type with more than
/// eight elements.
Basic = "basic",

/// On LLVM, mark all generated LLVM functions with the `sspstrong`
/// attribute (see llvm/docs/LangRef.rst). This triggers stack canary
/// generation in functions which either contain an array, or which take
Expand Down
2 changes: 2 additions & 0 deletions src/bootstrap/src/core/builder/cargo.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1083,6 +1083,8 @@ impl Builder<'_> {
cargo.env(profile_var("STRIP"), self.config.rust_strip.to_string());

if let Some(stack_protector) = &self.config.rust_stack_protector {
// Use -Zstack-protector here instead of -Cstack-protector
// to avoid bootstrap problems using the 1.100 stage0 compiler.
rustflags.arg(&format!("-Zstack-protector={stack_protector}"));
}

Expand Down
37 changes: 37 additions & 0 deletions src/doc/rustc/src/codegen-options/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -680,6 +680,43 @@ Note that all three options are supported on Linux and Apple platforms,
Attempting to use an unsupported option requires using the nightly channel
with the `-Z unstable-options` flag.

## stack-protector

The option `-C stack-protector` (currently also supported in the
old style `-Z stack-protector`) controls the generation of
stack-protector canaries.

This flag controls stack smashing protection strategy.

Supported values for this option are:
- `none` (default): Disable stack canary generation
- `strong`: Generate stack canaries in all functions, unless the compiler
can prove these functions can't be the source of a stack
buffer overflow (even in the presence of undefined behavior).

This provides similar security guarantees to Clang's
`-fstack-protector-strong`.

The exact rules are unstable and subject to change, but
currently, it generates stack protectors for functions that,
*post-optimization*, contain LLVM allocas (which
Comment thread
arielb1 marked this conversation as resolved.
Resolved
include all stack allocations - including fixed-size
allocations - that are used in a way that is not completely
determined by static control flow).
- `all`: Generate stack canaries in all functions

rustc does not have a mode equivalent to Clang's (or GCC's)
plain `-fstack-protector`. `-fstack-protector` is an older heuristic
designed for C, that only protects functions that allocate a
`char buf[N];` buffer on the stack, making it prone to buffer overflows
from length miscalculations. This heuristic is poorly suited for Rust
code. Even in C codebases, `-fstack-protector-strong` is nowadays
preferred because plain `-fstack-protector` misses many stack
buffer overflows.

Stack protectors are not supported on some targets such as GPU targets, use
of stack protectors on these targets is an error.

## strip

The option `-C strip=val` controls stripping of debuginfo and similar auxiliary
Expand Down
2 changes: 1 addition & 1 deletion src/doc/rustc/src/exploit-mitigations.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ equivalent.
| Stack clashing protection | Yes | Yes | 1.20.0 (2017-08-31) |
| Read-only relocations and immediate binding | Yes | Yes | 1.21.0 (2017-10-12) |
| Heap corruption protection | Yes | Yes | 1.32.0 (2019-01-17) (via operating system default or specified allocator) |
| Stack smashing protection | Yes | No, `-Z stack-protector` | Nightly |
| Stack smashing protection | Yes | No, `-C stack-protector` | 1.101.0 (2026-12-24) |
| Forward-edge control flow protection | Yes | No, `-Z sanitizer=cfi` | Nightly |
| Backward-edge control flow protection (e.g., shadow and safe stack) | Yes | No, `-Z sanitizer=shadow-call-stack,safestack` | Nightly |

Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
//@ revisions: all strong basic none missing
//@ revisions: all strong none missing
//@ assembly-output: emit-asm
//@ ignore-apple slightly different policy on stack protection of arrays
//@ ignore-msvc stack check code uses different function names
//@ ignore-nvptx64 stack protector is not supported
//@ ignore-wasm32-unknown-unknown
//@ [all] compile-flags: -Z stack-protector=all
//@ [strong] compile-flags: -Z stack-protector=strong
//@ [basic] compile-flags: -Z stack-protector=basic
//@ [none] compile-flags: -Z stack-protector=none
//@ [all] compile-flags: -C stack-protector=all
//@ [strong] compile-flags: -C stack-protector=strong
//@ [none] compile-flags: -C stack-protector=none
//@ compile-flags: -C opt-level=2 -Z merge-functions=disabled -Z allow-partial-mitigations=stack-protector

#![crate_type = "lib"]
Expand All @@ -19,7 +18,6 @@
pub fn emptyfn() {
// all: __stack_chk_fail
// strong-NOT: __stack_chk_fail
// basic-NOT: __stack_chk_fail
// none-NOT: __stack_chk_fail
// missing-NOT: __stack_chk_fail
}
Expand All @@ -37,7 +35,6 @@ pub fn array_char(f: fn(*const char)) {

// all: __stack_chk_fail
// strong: __stack_chk_fail
// basic: __stack_chk_fail
// none-NOT: __stack_chk_fail
// missing-NOT: __stack_chk_fail
}
Expand All @@ -53,7 +50,6 @@ pub fn array_u8_1(f: fn(*const u8)) {

// all: __stack_chk_fail
// strong: __stack_chk_fail
// basic-NOT: __stack_chk_fail
// none-NOT: __stack_chk_fail
// missing-NOT: __stack_chk_fail
}
Expand All @@ -70,7 +66,6 @@ pub fn array_u8_small(f: fn(*const u8)) {

// all: __stack_chk_fail
// strong: __stack_chk_fail
// basic-NOT: __stack_chk_fail
// none-NOT: __stack_chk_fail
// missing-NOT: __stack_chk_fail
}
Expand All @@ -86,7 +81,6 @@ pub fn array_u8_large(f: fn(*const u8)) {

// all: __stack_chk_fail
// strong: __stack_chk_fail
// basic: __stack_chk_fail
// none-NOT: __stack_chk_fail
// missing-NOT: __stack_chk_fail
}
Expand All @@ -105,7 +99,6 @@ pub fn array_bytesizednewtype_9(f: fn(*const ByteSizedNewtype)) {

// all: __stack_chk_fail
// strong: __stack_chk_fail
// basic: __stack_chk_fail
// none-NOT: __stack_chk_fail
// missing-NOT: __stack_chk_fail
}
Expand All @@ -132,7 +125,6 @@ pub fn local_var_addr_used_indirectly(f: fn(bool)) {

// all: __stack_chk_fail
// strong: __stack_chk_fail
// basic-NOT: __stack_chk_fail
// none-NOT: __stack_chk_fail
// missing-NOT: __stack_chk_fail
}
Expand All @@ -148,7 +140,6 @@ pub fn local_string_addr_taken(f: fn(&String)) {

// all: __stack_chk_fail
// strong: __stack_chk_fail
// basic: __stack_chk_fail
// none-NOT: __stack_chk_fail
// missing-NOT: __stack_chk_fail
}
Expand Down Expand Up @@ -177,7 +168,6 @@ pub fn local_var_addr_taken_used_locally_only(factory: fn() -> i32, sink: fn(i32

// all: __stack_chk_fail
// strong-NOT: __stack_chk_fail
// basic-NOT: __stack_chk_fail
// none-NOT: __stack_chk_fail
// missing-NOT: __stack_chk_fail
}
Expand Down Expand Up @@ -214,7 +204,6 @@ pub fn local_large_var_moved(f: fn(Gigastruct)) {

// all: __stack_chk_fail
// strong: __stack_chk_fail
// basic: __stack_chk_fail
// none-NOT: __stack_chk_fail
// missing-NOT: __stack_chk_fail
}
Expand Down Expand Up @@ -251,7 +240,6 @@ pub fn local_large_var_cloned(f: fn(Gigastruct)) {

// all: __stack_chk_fail
// strong: __stack_chk_fail
// basic: __stack_chk_fail
// none-NOT: __stack_chk_fail
// missing-NOT: __stack_chk_fail
}
Expand All @@ -275,7 +263,6 @@ pub fn alloca_small_compile_time_constant_arg(f: fn(*mut ())) {

// all: __stack_chk_fail
// strong: __stack_chk_fail
// basic: __stack_chk_fail
// none-NOT: __stack_chk_fail
// missing-NOT: __stack_chk_fail
}
Expand All @@ -287,7 +274,6 @@ pub fn alloca_large_compile_time_constant_arg(f: fn(*mut ())) {

// all: __stack_chk_fail
// strong: __stack_chk_fail
// basic: __stack_chk_fail
// none-NOT: __stack_chk_fail
// missing-NOT: __stack_chk_fail
}
Expand All @@ -299,7 +285,6 @@ pub fn alloca_dynamic_arg(f: fn(*mut ()), n: usize) {

// all: __stack_chk_fail
// strong: __stack_chk_fail
// basic: __stack_chk_fail
// none-NOT: __stack_chk_fail
// missing-NOT: __stack_chk_fail
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@
//@ only-windows
//@ only-msvc
//@ ignore-64bit 64-bit table based SEH has slightly different behaviors than classic SEH
//@ [all] compile-flags: -Z stack-protector=all
//@ [strong] compile-flags: -Z stack-protector=strong
//@ [none] compile-flags: -Z stack-protector=none
//@ [all] compile-flags: -C stack-protector=all
//@ [strong] compile-flags: -C stack-protector=strong
//@ [none] compile-flags: -C stack-protector=none
//@ compile-flags: -Z allow-partial-mitigations=stack-protector -C opt-level=2 -Z merge-functions=disabled

#![crate_type = "lib"]
Expand Down
Loading
Loading