Skip to content

Commit ae8e5e2

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 84197 and was not deemed large enough to require additional process. The tracking issue for this feature is 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 <#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 84197 - mrcnski documented it at 111722 - wesleywiser added tests for Windows at 116037 - davidtwco worked on the feature at 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 issue 1550
1 parent 5930afc commit ae8e5e2

43 files changed

Lines changed: 299 additions & 227 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

bootstrap.example.toml

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -760,10 +760,9 @@
760760
#rust.frame-pointers = false
761761

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

769768
# Prints each test name as it is executed, to help debug issues in the test harness itself.

compiler/rustc_codegen_llvm/src/attributes.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -306,7 +306,6 @@ fn stackprotector_attr<'ll>(cx: &SimpleCx<'ll>, sess: &Session) -> Option<&'ll A
306306
StackProtector::None => return None,
307307
StackProtector::All => AttributeKind::StackProtectReq,
308308
StackProtector::Strong => AttributeKind::StackProtectStrong,
309-
StackProtector::Basic => AttributeKind::StackProtect,
310309
};
311310

312311
Some(sspattr.create_attr(cx.llcx))

compiler/rustc_codegen_llvm/src/lib.rs

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -281,18 +281,19 @@ impl CodegenBackend for LlvmCodegenBackend {
281281
Generate stack canaries in all functions.
282282
283283
strong
284-
Generate stack canaries in a function if it either:
285-
- has a local variable of `[T; N]` type, regardless of `T` and `N`
286-
- takes the address of a local variable.
287-
288-
(Note that a local variable being borrowed is not equivalent to its
289-
address being taken: e.g. some borrows may be removed by optimization,
290-
while by-value argument passing may be implemented with reference to a
291-
local stack variable in the ABI.)
292-
293-
basic
294-
Generate stack canaries in functions with local variables of `[T; N]`
295-
type, where `T` is byte-sized and `N` >= 8.
284+
Generate stack canaries for all functions, unless the compiler
285+
can prove these functions can't be the source of a stack
286+
buffer overflow (even in the presence of undefined behavior).
287+
288+
This provides similar security guarantees to Clang's
289+
`-fstack-protector-strong`.
290+
291+
The exact rules are unstable and subject to change, but
292+
currently, it generates stack protectors for functions that,
293+
*post-optimization*, contain LLVM allocas (which
294+
include all stack allocations - including fixed-size
295+
allocations - that are used in a way that is not completely
296+
determined by static control flow).
296297
297298
none
298299
Do not generate stack canaries.

compiler/rustc_interface/src/tests.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -662,6 +662,7 @@ fn test_codegen_options_tracking_hash() {
662662
tracked!(relocation_model, Some(RelocModel::Pic));
663663
tracked!(relro_level, Some(RelroLevel::Full));
664664
tracked!(split_debuginfo, Some(SplitDebuginfo::Packed));
665+
tracked!(stack_protector, Some(StackProtector::All));
665666
tracked!(symbol_mangling_version, Some(SymbolManglingVersion::V0));
666667
tracked!(target_cpu, Some(String::from("abc")));
667668
tracked!(target_feature, String::from("all the features, all of them"));
@@ -892,7 +893,7 @@ fn test_unstable_options_tracking_hash() {
892893
tracked!(small_data_threshold, Some(16));
893894
tracked!(split_lto_unit, Some(true));
894895
tracked!(src_hash_algorithm, Some(SourceFileHashAlgorithm::Sha1));
895-
tracked!(stack_protector, StackProtector::All);
896+
tracked!(stack_protector, Some(StackProtector::All));
896897
tracked!(staticlib_hide_internal_symbols, true);
897898
tracked!(teach, true);
898899
tracked!(thinlto, Some(true));

compiler/rustc_session/src/errors.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -364,7 +364,7 @@ pub(crate) struct EmbedSourceRequiresDebugInfo;
364364

365365
#[derive(Diagnostic)]
366366
#[diag(
367-
"`-Z stack-protector={$stack_protector}` is not supported for target {$target_triple} and will be ignored"
367+
"`-C stack-protector={$stack_protector}` is not supported for target {$target_triple} and will be ignored"
368368
)]
369369
pub(crate) struct StackProtectorNotSupportedForTarget<'a> {
370370
pub(crate) stack_protector: StackProtector,

compiler/rustc_session/src/options.rs

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -828,8 +828,7 @@ mod desc {
828828
pub(crate) const parse_polonius: &str = "either no value or `legacy` (the default), or `next`";
829829
pub(crate) const parse_annotate_moves: &str =
830830
"either a boolean (`yes`, `no`, `on`, `off`, etc.), or a size limit in bytes";
831-
pub(crate) const parse_stack_protector: &str =
832-
"one of (`none` (default), `basic`, `strong`, or `all`)";
831+
pub(crate) const parse_stack_protector: &str = "one of (`none` (default), `strong`, or `all`)";
833832
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)";
834833
pub(crate) const parse_proc_macro_execution_strategy: &str =
835834
"one of supported execution strategies (`same-thread`, or `cross-thread`)";
@@ -1879,9 +1878,12 @@ pub mod parse {
18791878
true
18801879
}
18811880

1882-
pub(crate) fn parse_stack_protector(slot: &mut StackProtector, v: Option<&str>) -> bool {
1881+
pub(crate) fn parse_stack_protector(
1882+
slot: &mut Option<StackProtector>,
1883+
v: Option<&str>,
1884+
) -> bool {
18831885
match v.and_then(|s| StackProtector::from_str(s).ok()) {
1884-
Some(ssp) => *slot = ssp,
1886+
Some(ssp) => *slot = Some(ssp),
18851887
_ => return false,
18861888
}
18871889
true
@@ -2185,6 +2187,9 @@ options! {
21852187
#[rustc_lint_opt_deny_field_access("use `Session::split_debuginfo` instead of this field")]
21862188
split_debuginfo: Option<SplitDebuginfo> = (None, parse_split_debuginfo, [TRACKED],
21872189
"how to handle split-debuginfo, a platform-specific option"),
2190+
#[rustc_lint_opt_deny_field_access("use `Session::stack_protector` instead of this field")]
2191+
stack_protector: Option<StackProtector> = (None, parse_stack_protector, [TRACKED] { MITIGATION: StackProtector },
2192+
"control stack smashing protection strategy (`rustc --print stack-protector-strategies` for details)"),
21882193
strip: Strip = (Strip::None, parse_strip, [UNTRACKED],
21892194
"tell the linker which information to strip (`none` (default), `debuginfo` or `symbols`)"),
21902195
symbol_mangling_version: Option<SymbolManglingVersion> = (None,
@@ -2692,8 +2697,8 @@ written to standard error output)"),
26922697
src_hash_algorithm: Option<SourceFileHashAlgorithm> = (None, parse_src_file_hash, [TRACKED],
26932698
"hash algorithm of source files in debug info (`md5`, `sha1`, or `sha256`)"),
26942699
#[rustc_lint_opt_deny_field_access("use `Session::stack_protector` instead of this field")]
2695-
stack_protector: StackProtector = (StackProtector::None, parse_stack_protector, [TRACKED] { MITIGATION: StackProtector },
2696-
"control stack smash protection strategy (`rustc --print stack-protector-strategies` for details)"),
2700+
stack_protector: Option<StackProtector> = (None, parse_stack_protector, [TRACKED] { MITIGATION: StackProtector },
2701+
"control stack smashing protection strategy (`rustc --print stack-protector-strategies` for details)"),
26972702
staticlib_allow_rdylib_deps: bool = (false, parse_bool, [TRACKED],
26982703
"allow staticlibs to have rust dylib dependencies"),
26992704
staticlib_hide_internal_symbols: bool = (false, parse_bool, [TRACKED],

compiler/rustc_session/src/options/mitigation_coverage.rs

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ impl DeniedPartialMitigationLevel {
2020
pub fn level_str(&self) -> &'static str {
2121
match self {
2222
DeniedPartialMitigationLevel::StackProtector(StackProtector::All) => "=all",
23-
DeniedPartialMitigationLevel::StackProtector(StackProtector::Basic) => "=basic",
2423
DeniedPartialMitigationLevel::StackProtector(StackProtector::Strong) => "=strong",
2524
// currently `=disabled` should not appear
2625
DeniedPartialMitigationLevel::Enabled(false) => "=disabled",
@@ -36,9 +35,6 @@ impl std::fmt::Display for DeniedPartialMitigationLevel {
3635
DeniedPartialMitigationLevel::StackProtector(StackProtector::All) => {
3736
write!(f, "all")
3837
}
39-
DeniedPartialMitigationLevel::StackProtector(StackProtector::Basic) => {
40-
write!(f, "basic")
41-
}
4238
DeniedPartialMitigationLevel::StackProtector(StackProtector::Strong) => {
4339
write!(f, "strong")
4440
}
@@ -203,6 +199,7 @@ denied_partial_mitigations! {
203199
enum DeniedPartialMitigationKind {
204200
// The mitigation name should match the option name in rustc_session::options,
205201
// to allow for resetting the mitigation
202+
206203
(StackProtector, "stack-protector", EditionFuture, self.stack_protector()),
207204
(ControlFlowGuard, "control-flow-guard", EditionFuture, self.opts.cg.control_flow_guard == CFGuard::Checks)
208205
}

compiler/rustc_session/src/session.rs

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -772,11 +772,12 @@ impl Session {
772772
}
773773

774774
pub fn stack_protector(&self) -> StackProtector {
775-
if self.target.options.supports_stack_protector {
776-
self.opts.unstable_opts.stack_protector
777-
} else {
778-
StackProtector::None
779-
}
775+
// -C stack-protector overwrites -Z stack-protector, default to StackProtector::None
776+
self.opts
777+
.cg
778+
.stack_protector
779+
.or(self.opts.unstable_opts.stack_protector)
780+
.unwrap_or(StackProtector::None)
780781
}
781782

782783
pub fn must_emit_unwind_tables(&self) -> bool {
@@ -1287,10 +1288,10 @@ fn validate_commandline_args_with_session_available(sess: &Session) {
12871288
}
12881289
}
12891290

1290-
if sess.opts.unstable_opts.stack_protector != StackProtector::None {
1291+
if sess.stack_protector() != StackProtector::None {
12911292
if !sess.target.options.supports_stack_protector {
1292-
sess.dcx().emit_warn(errors::StackProtectorNotSupportedForTarget {
1293-
stack_protector: sess.opts.unstable_opts.stack_protector,
1293+
sess.dcx().emit_err(errors::StackProtectorNotSupportedForTarget {
1294+
stack_protector: sess.stack_protector(),
12941295
target_triple: &sess.opts.target_triple,
12951296
});
12961297
}

compiler/rustc_target/src/spec/mod.rs

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1336,12 +1336,6 @@ crate::target_spec_enum! {
13361336
/// Disable stack canary generation.
13371337
None = "none",
13381338

1339-
/// On LLVM, mark all generated LLVM functions with the `ssp` attribute (see
1340-
/// llvm/docs/LangRef.rst). This triggers stack canary generation in
1341-
/// functions which contain an array of a byte-sized type with more than
1342-
/// eight elements.
1343-
Basic = "basic",
1344-
13451339
/// On LLVM, mark all generated LLVM functions with the `sspstrong`
13461340
/// attribute (see llvm/docs/LangRef.rst). This triggers stack canary
13471341
/// generation in functions which either contain an array, or which take

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

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

969969
if let Some(stack_protector) = &self.config.rust_stack_protector {
970-
rustflags.arg(&format!("-Zstack-protector={stack_protector}"));
970+
rustflags.arg(&format!("-Cstack-protector={stack_protector}"));
971971
}
972972

973973
let debuginfo_level = match mode {

0 commit comments

Comments
 (0)