Skip to content

Commit 844a5b5

Browse files
author
Ariel Ben-Yehuda
committed
Stabilize -Zstack-protector as -Cstack-protector
I propose stabilizing `-Cstack-protector` as `-Zstack-protector`. This PR adds a new `-Cstack-protector` flag, leaving the unstable `-Z` flag as is to ease the transition period. The `-Z` flag will be removed in the future. No RFC/MCP, this flag was added in rust-lang#84197 and was not deemed large enough to require additional process. The tracking issue for this feature is rust-lang#114903. The `-Cstack-protector=strong` mode uses the same underlying heuristics as Clang's `-fstack-protector-strong`. These heuristics weren't designed for Rust, and may be over-conservative in some cases - for example, if Rust stores a field's data in an alloca using an LLVM array type, LLVM regard the alloca as meaning that the function has a C array, and enable stack overflow canaries even if the function accesses the alloca in a safe way. Some people thought we should wait on stabilization until there are better heuristics, but I didn't hear about any concrete case where this unduly harms performance, and I think that when a need comes, we can improve the heuristics in LLVM after stabilization. The heuristics do seem to not be under-conservative, so this should not be a security risk. The `-Cstack-protector=basic` mode (`-fstack-protector`) uses heuristics that are specifically designed to catch old-C-style string manipulation. This is not a good fit to Rust, which does not perform much unsafe C-style string manipulation. As far as I can tell, nobody has been asking for it, and few people are using it even in today's C - modern distros (e.g. [Debian]) tend to use `-fstack-protector-strong`. Therefore, `-Cstack-protector=basic` has been **removed**. If anyone is interested in it, they are welcome to add it back as an unstable option. [Debian]: https://wiki.debian.org/Hardening#DEB_BUILD_HARDENING_STACKPROTECTOR_.28gcc.2Fg.2B-.2B-_-fstack-protector-strong.29 Most implementation was done in <rust-lang#84197>. The command-line attribute enables the relevant LLVM attribute on all functions in <https://github.com/rust-lang/rust/blob/68baa87ba6f03f8b6af2a368690161f1601e4040/compiler/rustc_codegen_llvm/src/attributes.rs#L267-L276>. Each target can indicate that it does not support stack canaries - currently, the GPU platforms `nvptx64-nvidia-cuda` and `amdgcn-amd-amdhsa`. On these platforms, use of `-Cstack-protector` causes an error. The feature has tests that make sure that the LLVM heuristic gives reasonable results for several functions, by checking for `__security_check_cookie` (on Windows) or `__stack_chk_fail` (on Linux). See <https://github.com/rust-lang/rust/tree/68baa87ba6f03f8b6af2a368690161f1601e4040/tests/assembly-llvm/stack-protector> No call-for-testing has been conducted, but the feature seems to be in use. No reported bugs seem to exist. - @bbjornse was the original implementor at rust-lang#84197 - @mrcnski documented it at rust-lang#111722 - @wesleywiser added tests for Windows at rust-lang#116037 - @davidtwco worked on the feature at rust-lang#121742 - @nikic provided support from the LLVM side (on Zulip on <https://rust-lang.zulipchat.com/#narrow/channel/233931-t-compiler.2Fmajor-changes/topic/Proposal.20for.20Adapt.20Stack.20Protector.20for.20Ru.E2.80.A6.20compiler-team.23841> and elsewhere), thanks @nikic! No FIXMEs related to this feature. This feature cannot cause undefined behavior. No changes to reference/spec, docs added to the codegen docs as part of the stabilization PR. No. None. No support needed for rustdoc, clippy, rust-analyzer, rustfmt or rustup. Cargo could expose this as an option in build profiles but I would expect the decision as to what version should be used would be made for the entire crate graph at build time rather than by individual package authors. `-C stack-protector` is propagated to C compilers using cc-rs via rust-lang/cc-rs#1550
1 parent beeb8e3 commit 844a5b5

24 files changed

+164
-92
lines changed

bootstrap.example.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -713,7 +713,7 @@
713713
#rust.frame-pointers = false
714714

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

compiler/rustc_codegen_llvm/src/lib.rs

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -282,19 +282,32 @@ impl CodegenBackend for LlvmCodegenBackend {
282282
Generate stack canaries in all functions.
283283
284284
strong
285-
Generate stack canaries in a function if it either:
286-
- has a local variable of `[T; N]` type, regardless of `T` and `N`
287-
- takes the address of a local variable.
285+
Generate stack canaries for all functions, unless the compiler
286+
can prove these functions can't be the source of a stack
287+
buffer overflow (even in the presence of undefined behavior).
288288
289-
(Note that a local variable being borrowed is not equivalent to its
290-
address being taken: e.g. some borrows may be removed by optimization,
291-
while by-value argument passing may be implemented with reference to a
292-
local stack variable in the ABI.)
289+
This provides the same security guarantees as Clang's
290+
`-fstack-protector=strong`.
291+
292+
The exact rules are unstable and subject to change, but
293+
currently, it generates stack protectors for functions that,
294+
*post-optimization*, contain either arrays (of any size
295+
or type) or address-taken locals.
293296
294297
basic
295-
Generate stack canaries in functions with local variables of `[T; N]`
298+
Generate stack canaries in functions that are heuristically
299+
suspected to contain buffer overflows.
300+
301+
The heuristic is subject to change, but currently it
302+
includes functions with local variables of `[T; N]`
296303
type, where `T` is byte-sized and `N` >= 8.
297304
305+
This heuristic originated from C, where it detects
306+
functions that allocate a `char buf[N];` buffer on the
307+
stack, and are therefore likely to have a stack buffer overflow
308+
in the case of a length-calculation error. It is *not* a good
309+
heuristic for Rust code.
310+
298311
none
299312
Do not generate stack canaries.
300313
"#

compiler/rustc_interface/src/tests.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -641,6 +641,7 @@ fn test_codegen_options_tracking_hash() {
641641
tracked!(relro_level, Some(RelroLevel::Full));
642642
tracked!(soft_float, true);
643643
tracked!(split_debuginfo, Some(SplitDebuginfo::Packed));
644+
tracked!(stack_protector, StackProtector::All);
644645
tracked!(symbol_mangling_version, Some(SymbolManglingVersion::V0));
645646
tracked!(target_cpu, Some(String::from("abc")));
646647
tracked!(target_feature, String::from("all the features, all of them"));
@@ -871,7 +872,6 @@ fn test_unstable_options_tracking_hash() {
871872
tracked!(small_data_threshold, Some(16));
872873
tracked!(split_lto_unit, Some(true));
873874
tracked!(src_hash_algorithm, Some(SourceFileHashAlgorithm::Sha1));
874-
tracked!(stack_protector, StackProtector::All);
875875
tracked!(teach, true);
876876
tracked!(thinlto, Some(true));
877877
tracked!(tiny_const_eval_limit, true);

compiler/rustc_session/messages.ftl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@ session_target_requires_unwind_tables = target requires unwind tables, they cann
129129
130130
session_target_small_data_threshold_not_supported = `-Z small-data-threshold` is not supported for target {$target_triple} and will be ignored
131131
132-
session_target_stack_protector_not_supported = `-Z stack-protector={$stack_protector}` is not supported for target {$target_triple} and will be ignored
132+
session_target_stack_protector_not_supported = `-C stack-protector={$stack_protector}` is not supported for target {$target_triple}
133133
134134
session_unexpected_builtin_cfg = unexpected `--cfg {$cfg}` flag
135135
.controlled_by = config `{$cfg_name}` is only supposed to be controlled by `{$controlled_by}`

compiler/rustc_session/src/options.rs

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1870,9 +1870,12 @@ pub mod parse {
18701870
true
18711871
}
18721872

1873-
pub(crate) fn parse_stack_protector(slot: &mut StackProtector, v: Option<&str>) -> bool {
1873+
pub(crate) fn parse_stack_protector(
1874+
slot: &mut Option<StackProtector>,
1875+
v: Option<&str>,
1876+
) -> bool {
18741877
match v.and_then(|s| StackProtector::from_str(s).ok()) {
1875-
Some(ssp) => *slot = ssp,
1878+
Some(ssp) => *slot = Some(ssp),
18761879
_ => return false,
18771880
}
18781881
true
@@ -2161,6 +2164,9 @@ options! {
21612164
#[rustc_lint_opt_deny_field_access("use `Session::split_debuginfo` instead of this field")]
21622165
split_debuginfo: Option<SplitDebuginfo> = (None, parse_split_debuginfo, [TRACKED],
21632166
"how to handle split-debuginfo, a platform-specific option"),
2167+
#[rustc_lint_opt_deny_field_access("use `Session::stack_protector` instead of this field")]
2168+
stack_protector: Option<StackProtector> = (None, parse_stack_protector, [TRACKED],
2169+
"control stack smash protection strategy (`rustc --print stack-protector-strategies` for details)"),
21642170
strip: Strip = (Strip::None, parse_strip, [UNTRACKED],
21652171
"tell the linker which information to strip (`none` (default), `debuginfo` or `symbols`)"),
21662172
symbol_mangling_version: Option<SymbolManglingVersion> = (None,
@@ -2635,7 +2641,7 @@ written to standard error output)"),
26352641
src_hash_algorithm: Option<SourceFileHashAlgorithm> = (None, parse_src_file_hash, [TRACKED],
26362642
"hash algorithm of source files in debug info (`md5`, `sha1`, or `sha256`)"),
26372643
#[rustc_lint_opt_deny_field_access("use `Session::stack_protector` instead of this field")]
2638-
stack_protector: StackProtector = (StackProtector::None, parse_stack_protector, [TRACKED],
2644+
stack_protector: Option<StackProtector> = (None, parse_stack_protector, [TRACKED],
26392645
"control stack smash protection strategy (`rustc --print stack-protector-strategies` for details)"),
26402646
staticlib_allow_rdylib_deps: bool = (false, parse_bool, [TRACKED],
26412647
"allow staticlibs to have rust dylib dependencies"),

compiler/rustc_session/src/session.rs

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -792,11 +792,12 @@ impl Session {
792792
}
793793

794794
pub fn stack_protector(&self) -> StackProtector {
795-
if self.target.options.supports_stack_protector {
796-
self.opts.unstable_opts.stack_protector
797-
} else {
798-
StackProtector::None
799-
}
795+
// -C stack-protector overwrites -Z stack-protector, default to StackProtector::None
796+
self.opts
797+
.cg
798+
.stack_protector
799+
.or(self.opts.unstable_opts.stack_protector)
800+
.unwrap_or(StackProtector::None)
800801
}
801802

802803
pub fn must_emit_unwind_tables(&self) -> bool {
@@ -1334,10 +1335,10 @@ fn validate_commandline_args_with_session_available(sess: &Session) {
13341335
}
13351336
}
13361337

1337-
if sess.opts.unstable_opts.stack_protector != StackProtector::None {
1338+
if sess.stack_protector() != StackProtector::None {
13381339
if !sess.target.options.supports_stack_protector {
1339-
sess.dcx().emit_warn(errors::StackProtectorNotSupportedForTarget {
1340-
stack_protector: sess.opts.unstable_opts.stack_protector,
1340+
sess.dcx().emit_err(errors::StackProtectorNotSupportedForTarget {
1341+
stack_protector: sess.stack_protector(),
13411342
target_triple: &sess.opts.target_triple,
13421343
});
13431344
}

src/bootstrap/src/core/builder/cargo.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -910,7 +910,7 @@ impl Builder<'_> {
910910
cargo.env(profile_var("STRIP"), self.config.rust_strip.to_string());
911911

912912
if let Some(stack_protector) = &self.config.rust_stack_protector {
913-
rustflags.arg(&format!("-Zstack-protector={stack_protector}"));
913+
rustflags.arg(&format!("-Cstack-protector={stack_protector}"));
914914
}
915915

916916
if !matches!(cmd_kind, Kind::Build | Kind::Check | Kind::Clippy | Kind::Fix) && want_rustdoc

src/doc/rustc/src/codegen-options/index.md

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -672,6 +672,34 @@ Note that all three options are supported on Linux and Apple platforms,
672672
Attempting to use an unsupported option requires using the nightly channel
673673
with the `-Z unstable-options` flag.
674674

675+
## stack-protector
676+
677+
The option `-C stack-protector` (currently also supported in the
678+
old style `-Z stack-protector`) controls the generation of
679+
stack-protector canaries.
680+
681+
This flag controls stack smashing protection strategy.
682+
683+
Supported values for this option are:
684+
- `none` (default): Disable stack canary generation
685+
- `basic`: Generate stack canaries in functions that are suspected
686+
to have a high chance of containing stack buffer overflows (deprecated).
687+
- `strong`: Generate stack canaries in all functions, unless the compiler
688+
can prove these functions can't be the source of a stack
689+
buffer overflow (even in the presence of undefined behavior).
690+
691+
This provides the same security guarantees as Clang's
692+
`-fstack-protector=strong`.
693+
694+
The exact rules are unstable and subject to change, but
695+
currently, it generates stack protectors for functions that,
696+
*post-optimization*, contain either arrays (of any size
697+
or type) or address-taken locals.
698+
- `all`: Generate stack canaries in all functions
699+
700+
Stack protectors are not supported on many GPU targets, use of stack
701+
protectors on these targets is an error.
702+
675703
## strip
676704

677705
The option `-C strip=val` controls stripping of debuginfo and similar auxiliary

src/doc/rustc/src/exploit-mitigations.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ equivalent.
6262
| Stack clashing protection | Yes | Yes | 1.20.0 (2017-08-31) |
6363
| Read-only relocations and immediate binding | Yes | Yes | 1.21.0 (2017-10-12) |
6464
| Heap corruption protection | Yes | Yes | 1.32.0 (2019-01-17) (via operating system default or specified allocator) |
65-
| Stack smashing protection | Yes | No, `-Z stack-protector` | Nightly |
65+
| Stack smashing protection | Yes | No, `-C stack-protector` | ??? |
6666
| Forward-edge control flow protection | Yes | No, `-Z sanitizer=cfi` | Nightly |
6767
| Backward-edge control flow protection (e.g., shadow and safe stack) | Yes | No, `-Z sanitizer=shadow-call-stack,safestack` | Nightly |
6868

tests/assembly-llvm/stack-protector/stack-protector-heuristics-effect-windows-32bit.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,10 @@
33
//@ only-windows
44
//@ only-msvc
55
//@ ignore-64bit 64-bit table based SEH has slightly different behaviors than classic SEH
6-
//@ [all] compile-flags: -Z stack-protector=all
7-
//@ [strong] compile-flags: -Z stack-protector=strong
8-
//@ [basic] compile-flags: -Z stack-protector=basic
9-
//@ [none] compile-flags: -Z stack-protector=none
6+
//@ [all] compile-flags: -C stack-protector=all
7+
//@ [strong] compile-flags: -C stack-protector=strong
8+
//@ [basic] compile-flags: -C stack-protector=basic
9+
//@ [none] compile-flags: -C stack-protector=none
1010
//@ compile-flags: -C opt-level=2 -Z merge-functions=disabled
1111

1212
#![crate_type = "lib"]

0 commit comments

Comments
 (0)