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 @@ -713,10 +713,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_llvm/src/attributes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -269,7 +269,6 @@ fn stackprotector_attr<'ll>(cx: &CodegenCx<'ll, '_>) -> Option<&'ll Attribute> {
StackProtector::None => return None,
StackProtector::All => AttributeKind::StackProtectReq,
StackProtector::Strong => AttributeKind::StackProtectStrong,
StackProtector::Basic => AttributeKind::StackProtect,
};

Some(sspattr.create_attr(cx.llcx))
Expand Down
25 changes: 13 additions & 12 deletions compiler/rustc_codegen_llvm/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -282,18 +282,19 @@ impl CodegenBackend for LlvmCodegenBackend {
Generate stack canaries in all functions.

strong
Generate stack canaries in a function if it either:
- has a local variable of `[T; N]` type, regardless of `T` and `N`
- takes the address of a local variable.

(Note that a local variable being borrowed is not equivalent to its
address being taken: e.g. some borrows may be removed by optimization,
while by-value argument passing may be implemented with reference to a
local stack variable in the ABI.)

basic
Generate stack canaries in functions with local variables of `[T; N]`
type, where `T` is byte-sized and `N` >= 8.
Generate stack canaries for 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
include all stack allocations - including fixed-size
allocations - that are used in a way that is not completely
determined by static control flow).

none
Do not generate stack canaries.
Copy link
Member

Choose a reason for hiding this comment

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

I know it's not widely used by Clang/Gcc, but should we add why the basic mode/strategy is not applicable to Rust and thus not available in case people look for it for matching build configurations?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think that outside of rustc it's always called -fstack-protector (rather than "basic stack protection", which is not a name I've ever heard outside of a Rust context), but I agree that it makes sense to have documentation for why it does not exist. I think only in the markdown file, rather than in this file (tho I have no problem putting it in both places).

Copy link
Member

Choose a reason for hiding this comment

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

Yes, agreed. I actually meant mainly in the markdown docs, but left the comment in the wrong place. Something like: -fstack-protector or -fstack-protector=basic is not available/applicable to Rust because... (or equivalent to it). What do you think?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sure, will do that soon-ish.

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 @@ -641,6 +641,7 @@ fn test_codegen_options_tracking_hash() {
tracked!(relro_level, Some(RelroLevel::Full));
tracked!(soft_float, true);
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 @@ -871,7 +872,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!(teach, true);
tracked!(thinlto, Some(true));
tracked!(tiny_const_eval_limit, true);
Expand Down
2 changes: 1 addition & 1 deletion compiler/rustc_session/messages.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ session_target_requires_unwind_tables = target requires unwind tables, they cann

session_target_small_data_threshold_not_supported = `-Z small-data-threshold` is not supported for target {$target_triple} and will be ignored

session_target_stack_protector_not_supported = `-Z stack-protector={$stack_protector}` is not supported for target {$target_triple} and will be ignored
session_target_stack_protector_not_supported = `-C stack-protector={$stack_protector}` is not supported for target {$target_triple}

session_unexpected_builtin_cfg = unexpected `--cfg {$cfg}` flag
.controlled_by = config `{$cfg_name}` is only supposed to be controlled by `{$controlled_by}`
Expand Down
14 changes: 10 additions & 4 deletions compiler/rustc_session/src/options.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1870,9 +1870,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 @@ -2161,6 +2164,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],
"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 @@ -2635,8 +2641,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],
"control stack smash protection strategy (`rustc --print stack-protector-strategies` for details)"),
stack_protector: Option<StackProtector> = (None, parse_stack_protector, [TRACKED],
"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_prefer_dynamic: bool = (false, parse_bool, [TRACKED],
Expand Down
17 changes: 9 additions & 8 deletions compiler/rustc_session/src/session.rs
Original file line number Diff line number Diff line change
Expand Up @@ -734,11 +734,12 @@ 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
self.opts
.cg
.stack_protector
.or(self.opts.unstable_opts.stack_protector)
.unwrap_or(StackProtector::None)
}

pub fn must_emit_unwind_tables(&self) -> bool {
Expand Down Expand Up @@ -1276,10 +1277,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(errors::StackProtectorNotSupportedForTarget {
stack_protector: sess.opts.unstable_opts.stack_protector,
sess.dcx().emit_err(errors::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 @@ -1347,12 +1347,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: 1 addition & 1 deletion src/bootstrap/src/core/builder/cargo.rs
Original file line number Diff line number Diff line change
Expand Up @@ -910,7 +910,7 @@ impl Builder<'_> {
cargo.env(profile_var("STRIP"), self.config.rust_strip.to_string());

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

if !matches!(cmd_kind, Kind::Build | Kind::Check | Kind::Clippy | Kind::Fix) && want_rustdoc
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 @@ -672,6 +672,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
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 many 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` | ??? |
| 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,12 +1,11 @@
//@ revisions: all strong basic none missing
//@ revisions: all strong none missing
//@ assembly-output: emit-asm
//@ 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
//@ [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

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

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

// all: __security_check_cookie
// strong: __security_check_cookie
// basic-NOT: __security_check_cookie
// none-NOT: __security_check_cookie
// missing-NOT: __security_check_cookie
}
Expand All @@ -66,10 +62,10 @@ pub fn array_u8_small(f: fn(*const u8)) {
f(&b as *const _);

// Small arrays do not lead to stack protection by the 'basic' heuristic.
// (basic is not currently supported, leaving the test anyway).

// all: __security_check_cookie
// strong: __security_check_cookie
// basic-NOT: __security_check_cookie
// none-NOT: __security_check_cookie
// missing-NOT: __security_check_cookie
}
Expand All @@ -82,10 +78,10 @@ pub fn array_u8_large(f: fn(*const u8)) {

// Since `a` is a byte array with size greater than 8, the basic heuristic
// will also protect this function.
// (basic is not currently supported, leaving the test anyway).

// all: __security_check_cookie
// strong: __security_check_cookie
// basic: __security_check_cookie
// none-NOT: __security_check_cookie
// missing-NOT: __security_check_cookie
}
Expand All @@ -101,10 +97,10 @@ pub fn array_bytesizednewtype_9(f: fn(*const ByteSizedNewtype)) {

// Since `a` is a byte array in the LLVM output, the basic heuristic will
// also protect this function.
// (basic is not currently supported, leaving the test anyway).

// all: __security_check_cookie
// strong: __security_check_cookie
// basic: __security_check_cookie
// none-NOT: __security_check_cookie
// missing-NOT: __security_check_cookie
}
Expand All @@ -118,7 +114,7 @@ pub fn local_var_addr_used_indirectly(f: fn(bool)) {

// This function takes the address of a local variable taken. Although this
// address is never used as a way to refer to stack memory, the `strong`
// heuristic adds stack smash protection. This is also the case in C++:
// heuristic adds stack smashing protection. This is also the case in C++:
// ```
// cat << EOF | clang++ -O2 -fstack-protector-strong -S -x c++ - -o - | grep stack_chk
// #include <cstdint>
Expand All @@ -128,10 +124,10 @@ pub fn local_var_addr_used_indirectly(f: fn(bool)) {
// }
// EOF
// ```
// (basic is not currently supported, leaving the test anyway).

// all: __security_check_cookie
// strong: __security_check_cookie
// basic-NOT: __security_check_cookie
// none-NOT: __security_check_cookie
// missing-NOT: __security_check_cookie
}
Expand Down Expand Up @@ -159,10 +155,10 @@ pub fn local_string_addr_taken(f: fn(&String)) {
// EOF
// ```
//
// (basic is not currently supported, leaving the test anyway).

// all: __security_check_cookie
// strong-NOT: __security_check_cookie
// basic-NOT: __security_check_cookie
// none-NOT: __security_check_cookie
// missing-NOT: __security_check_cookie
}
Expand All @@ -186,12 +182,11 @@ pub fn local_var_addr_taken_used_locally_only(factory: fn() -> i32, sink: fn(i32

// Even though the local variable conceptually has its address taken, as
// it's passed by reference to the trait function, the use of the reference
// is easily inlined. There is therefore no stack smash protection even with
// is easily inlined. There is therefore no stack smashing protection even with
// the `strong` heuristic.

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

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

// all: __security_check_cookie
// strong: __security_check_cookie
// basic: __security_check_cookie
// none-NOT: __security_check_cookie
// missing-NOT: __security_check_cookie
}
Expand Down Expand Up @@ -297,7 +290,6 @@ pub fn alloca_small_compile_time_constant_arg(f: fn(*mut ())) {

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

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

// all: __security_check_cookie
// strong-NOT: __security_check_cookie
// basic-NOT: __security_check_cookie
// none-NOT: __security_check_cookie
// missing-NOT: __security_check_cookie
}
Expand Down Expand Up @@ -353,7 +343,6 @@ pub fn unsized_fn_param(s: [u8], l: bool, f: fn([u8])) {
// all-NOT: __security_check_cookie
// strong-NOT: __security_check_cookie

// basic-NOT: __security_check_cookie
// none-NOT: __security_check_cookie
// missing-NOT: __security_check_cookie
}
Loading
Loading