Skip to content

Commit b78cdfc

Browse files
authored
Rollup merge of rust-lang#84197 - bbjornse:stack-protector, r=nikic
add codegen option for using LLVM stack smash protection LLVM has built-in heuristics for adding stack canaries to functions. These heuristics can be selected with LLVM function attributes. This PR adds a codegen option `-C stack-protector={basic,strong,all}` which controls the use of these attributes. This gives rustc the same stack smash protection support as clang offers through options `-fstack-protector`, `-fstack-protector-strong`, and `-fstack-protector-all`. The protection this can offer is demonstrated in test/ui/abi/stack-protector.rs. This fills a gap in the current list of rustc exploit mitigations (https://doc.rust-lang.org/rustc/exploit-mitigations.html), originally discussed in rust-lang#15179. Stack smash protection adds runtime overhead and is therefore still off by default, but now users have the option to trade performance for security as they see fit. An example use case is adding Rust code in an existing C/C++ code base compiled with stack smash protection. Without the ability to add stack smash protection to the Rust code, the code base artifacts could be exploitable in ways not possible if the code base remained pure C/C++. Stack smash protection support is present in LLVM for almost all the current tier 1/tier 2 targets: see test/assembly/stack-protector/stack-protector-target-support.rs. The one exception is nvptx64-nvidia-cuda. This PR follows clang's example, and adds a warning message printed if stack smash protection is used with this target (see test/ui/stack-protector/warn-stack-protector-unsupported.rs). Support for tier 3 targets has not been checked. Since the heuristics are applied at the LLVM level, the heuristics are expected to add stack smash protection to a fraction of functions comparable to C/C++. Some experiments demonstrating how Rust code is affected by the different heuristics can be found in test/assembly/stack-protector/stack-protector-heuristics-effect.rs. There is potential for better heuristics using Rust-specific safety information. For example it might be reasonable to skip stack smash protection in functions which transitively only use safe Rust code, or which uses only a subset of functions the user declares safe (such as anything under `std.*`). Such alternative heuristics could be added at a later point. LLVM also offers a "safestack" sanitizer as an alternative way to guard against stack smashing (see rust-lang#26612). This could possibly also be included as a stack-protection heuristic. An alternative is to add it as a sanitizer (rust-lang#39699). This is what clang does: safestack is exposed with option `-fsanitize=safe-stack`. The options are only supported by the LLVM backend, but as with other codegen options it is visible in the main codegen option help menu. The heuristic names "basic", "strong", and "all" are hopefully sufficiently generic to be usable in other backends as well.
2 parents c7dbe7a + eaa6461 commit b78cdfc

20 files changed

+1013
-12
lines changed

compiler/rustc_codegen_llvm/src/attributes.rs

+13-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ use rustc_middle::ty::{self, TyCtxt};
1212
use rustc_session::config::OptLevel;
1313
use rustc_session::Session;
1414
use rustc_target::spec::abi::Abi;
15-
use rustc_target::spec::{FramePointer, SanitizerSet, StackProbeType};
15+
use rustc_target::spec::{FramePointer, SanitizerSet, StackProbeType, StackProtector};
1616

1717
use crate::attributes;
1818
use crate::llvm::AttributePlace::Function;
@@ -161,6 +161,17 @@ fn set_probestack(cx: &CodegenCx<'ll, '_>, llfn: &'ll Value) {
161161
}
162162
}
163163

164+
fn set_stackprotector(cx: &CodegenCx<'ll, '_>, llfn: &'ll Value) {
165+
let sspattr = match cx.sess().stack_protector() {
166+
StackProtector::None => return,
167+
StackProtector::All => Attribute::StackProtectReq,
168+
StackProtector::Strong => Attribute::StackProtectStrong,
169+
StackProtector::Basic => Attribute::StackProtect,
170+
};
171+
172+
sspattr.apply_llfn(Function, llfn)
173+
}
174+
164175
pub fn apply_target_cpu_attr(cx: &CodegenCx<'ll, '_>, llfn: &'ll Value) {
165176
let target_cpu = SmallCStr::new(llvm_util::target_cpu(cx.tcx.sess));
166177
llvm::AddFunctionAttrStringValue(
@@ -267,6 +278,7 @@ pub fn from_fn_attrs(cx: &CodegenCx<'ll, 'tcx>, llfn: &'ll Value, instance: ty::
267278
set_frame_pointer_type(cx, llfn);
268279
set_instrument_function(cx, llfn);
269280
set_probestack(cx, llfn);
281+
set_stackprotector(cx, llfn);
270282

271283
if codegen_fn_attrs.flags.contains(CodegenFnAttrFlags::COLD) {
272284
Attribute::Cold.apply_llfn(Function, llfn);

compiler/rustc_codegen_llvm/src/lib.rs

+23
Original file line numberDiff line numberDiff line change
@@ -232,6 +232,29 @@ impl CodegenBackend for LlvmCodegenBackend {
232232
}
233233
println!();
234234
}
235+
PrintRequest::StackProtectorStrategies => {
236+
println!(
237+
r#"Available stack protector strategies:
238+
all
239+
Generate stack canaries in all functions.
240+
241+
strong
242+
Generate stack canaries in a function if it either:
243+
- has a local variable of `[T; N]` type, regardless of `T` and `N`
244+
- takes the address of a local variable.
245+
246+
(Note that a local variable being borrowed is not equivalent to its
247+
address being taken: e.g. some borrows may be removed by optimization,
248+
while by-value argument passing may be implemented with reference to a
249+
local stack variable in the ABI.)
250+
251+
basic
252+
Generate stack canaries in functions with:
253+
- local variables of `[T; N]` type, where `T` is byte-sized and `N` > 8.
254+
255+
"#
256+
);
257+
}
235258
req => llvm_util::print(req, sess),
236259
}
237260
}

compiler/rustc_codegen_llvm/src/llvm/ffi.rs

+3
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,9 @@ pub enum Attribute {
159159
InaccessibleMemOnly = 27,
160160
SanitizeHWAddress = 28,
161161
WillReturn = 29,
162+
StackProtectReq = 30,
163+
StackProtectStrong = 31,
164+
StackProtect = 32,
162165
}
163166

164167
/// LLVMIntPredicate

compiler/rustc_driver/src/lib.rs

+6-1
Original file line numberDiff line numberDiff line change
@@ -736,7 +736,12 @@ impl RustcDefaultCalls {
736736
println!("{}", cfg);
737737
}
738738
}
739-
RelocationModels | CodeModels | TlsModels | TargetCPUs | TargetFeatures => {
739+
RelocationModels
740+
| CodeModels
741+
| TlsModels
742+
| TargetCPUs
743+
| StackProtectorStrategies
744+
| TargetFeatures => {
740745
codegen_backend.print(*req, sess);
741746
}
742747
// Any output here interferes with Cargo's parsing of other printed output

compiler/rustc_interface/src/tests.rs

+8-5
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,9 @@ use rustc_span::edition::{Edition, DEFAULT_EDITION};
1818
use rustc_span::symbol::sym;
1919
use rustc_span::SourceFileHashAlgorithm;
2020
use rustc_target::spec::{CodeModel, LinkerFlavor, MergeFunctions, PanicStrategy};
21-
use rustc_target::spec::{RelocModel, RelroLevel, SanitizerSet, SplitDebuginfo, TlsModel};
21+
use rustc_target::spec::{
22+
RelocModel, RelroLevel, SanitizerSet, SplitDebuginfo, StackProtector, TlsModel,
23+
};
2224

2325
use std::collections::{BTreeMap, BTreeSet};
2426
use std::iter::FromIterator;
@@ -709,8 +711,8 @@ fn test_debugging_options_tracking_hash() {
709711
// This list is in alphabetical order.
710712
tracked!(allow_features, Some(vec![String::from("lang_items")]));
711713
tracked!(always_encode_mir, true);
712-
tracked!(assume_incomplete_release, true);
713714
tracked!(asm_comments, true);
715+
tracked!(assume_incomplete_release, true);
714716
tracked!(binary_dep_depinfo, true);
715717
tracked!(chalk, true);
716718
tracked!(codegen_backend, Some("abc".to_string()));
@@ -726,8 +728,8 @@ fn test_debugging_options_tracking_hash() {
726728
tracked!(human_readable_cgu_names, true);
727729
tracked!(inline_in_all_cgus, Some(true));
728730
tracked!(inline_mir, Some(true));
729-
tracked!(inline_mir_threshold, Some(123));
730731
tracked!(inline_mir_hint_threshold, Some(123));
732+
tracked!(inline_mir_threshold, Some(123));
731733
tracked!(instrument_coverage, Some(InstrumentCoverage::All));
732734
tracked!(instrument_mcount, true);
733735
tracked!(link_only, true);
@@ -753,23 +755,24 @@ fn test_debugging_options_tracking_hash() {
753755
tracked!(profiler_runtime, "abc".to_string());
754756
tracked!(relax_elf_relocations, Some(true));
755757
tracked!(relro_level, Some(RelroLevel::Full));
756-
tracked!(simulate_remapped_rust_src_base, Some(PathBuf::from("/rustc/abc")));
757758
tracked!(report_delayed_bugs, true);
758759
tracked!(sanitizer, SanitizerSet::ADDRESS);
759760
tracked!(sanitizer_memory_track_origins, 2);
760761
tracked!(sanitizer_recover, SanitizerSet::ADDRESS);
761762
tracked!(saturating_float_casts, Some(true));
762763
tracked!(share_generics, Some(true));
763764
tracked!(show_span, Some(String::from("abc")));
765+
tracked!(simulate_remapped_rust_src_base, Some(PathBuf::from("/rustc/abc")));
764766
tracked!(src_hash_algorithm, Some(SourceFileHashAlgorithm::Sha1));
767+
tracked!(stack_protector, StackProtector::All);
765768
tracked!(symbol_mangling_version, Some(SymbolManglingVersion::V0));
766769
tracked!(teach, true);
767770
tracked!(thinlto, Some(true));
768771
tracked!(thir_unsafeck, true);
769-
tracked!(tune_cpu, Some(String::from("abc")));
770772
tracked!(tls_model, Some(TlsModel::GeneralDynamic));
771773
tracked!(trap_unreachable, Some(false));
772774
tracked!(treat_err_as_bug, NonZeroUsize::new(1));
775+
tracked!(tune_cpu, Some(String::from("abc")));
773776
tracked!(unleash_the_miri_inside_of_you, true);
774777
tracked!(use_ctors_section, Some(true));
775778
tracked!(verify_llvm_ir, true);

compiler/rustc_llvm/llvm-wrapper/LLVMWrapper.h

+3
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,9 @@ enum LLVMRustAttribute {
8080
InaccessibleMemOnly = 27,
8181
SanitizeHWAddress = 28,
8282
WillReturn = 29,
83+
StackProtectReq = 30,
84+
StackProtectStrong = 31,
85+
StackProtect = 32,
8386
};
8487

8588
typedef struct OpaqueRustString *RustStringRef;

compiler/rustc_llvm/llvm-wrapper/RustWrapper.cpp

+6
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,12 @@ static Attribute::AttrKind fromRust(LLVMRustAttribute Kind) {
199199
return Attribute::SanitizeHWAddress;
200200
case WillReturn:
201201
return Attribute::WillReturn;
202+
case StackProtectReq:
203+
return Attribute::StackProtectReq;
204+
case StackProtectStrong:
205+
return Attribute::StackProtectStrong;
206+
case StackProtect:
207+
return Attribute::StackProtect;
202208
}
203209
report_fatal_error("bad AttributeKind");
204210
}

compiler/rustc_session/src/config.rs

+8-3
Original file line numberDiff line numberDiff line change
@@ -512,6 +512,7 @@ pub enum PrintRequest {
512512
TlsModels,
513513
TargetSpec,
514514
NativeStaticLibs,
515+
StackProtectorStrategies,
515516
}
516517

517518
#[derive(Copy, Clone)]
@@ -1067,8 +1068,8 @@ pub fn rustc_short_optgroups() -> Vec<RustcOptGroup> {
10671068
"print",
10681069
"Compiler information to print on stdout",
10691070
"[crate-name|file-names|sysroot|target-libdir|cfg|target-list|\
1070-
target-cpus|target-features|relocation-models|\
1071-
code-models|tls-models|target-spec-json|native-static-libs]",
1071+
target-cpus|target-features|relocation-models|code-models|\
1072+
tls-models|target-spec-json|native-static-libs|stack-protector-strategies]",
10721073
),
10731074
opt::flagmulti_s("g", "", "Equivalent to -C debuginfo=2"),
10741075
opt::flagmulti_s("O", "", "Equivalent to -C opt-level=2"),
@@ -1484,6 +1485,7 @@ fn collect_print_requests(
14841485
"code-models" => PrintRequest::CodeModels,
14851486
"tls-models" => PrintRequest::TlsModels,
14861487
"native-static-libs" => PrintRequest::NativeStaticLibs,
1488+
"stack-protector-strategies" => PrintRequest::StackProtectorStrategies,
14871489
"target-spec-json" => {
14881490
if dopts.unstable_options {
14891491
PrintRequest::TargetSpec
@@ -2414,7 +2416,9 @@ crate mod dep_tracking {
24142416
use rustc_span::edition::Edition;
24152417
use rustc_span::RealFileName;
24162418
use rustc_target::spec::{CodeModel, MergeFunctions, PanicStrategy, RelocModel};
2417-
use rustc_target::spec::{RelroLevel, SanitizerSet, SplitDebuginfo, TargetTriple, TlsModel};
2419+
use rustc_target::spec::{
2420+
RelroLevel, SanitizerSet, SplitDebuginfo, StackProtector, TargetTriple, TlsModel,
2421+
};
24182422
use std::collections::hash_map::DefaultHasher;
24192423
use std::collections::BTreeMap;
24202424
use std::hash::Hash;
@@ -2488,6 +2492,7 @@ crate mod dep_tracking {
24882492
Edition,
24892493
LinkerPluginLto,
24902494
SplitDebuginfo,
2495+
StackProtector,
24912496
SwitchWithOptPath,
24922497
SymbolManglingVersion,
24932498
SourceFileHashAlgorithm,

compiler/rustc_session/src/options.rs

+15-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@ use crate::search_paths::SearchPath;
66
use crate::utils::NativeLib;
77

88
use rustc_target::spec::{CodeModel, LinkerFlavor, MergeFunctions, PanicStrategy, SanitizerSet};
9-
use rustc_target::spec::{RelocModel, RelroLevel, SplitDebuginfo, TargetTriple, TlsModel};
9+
use rustc_target::spec::{
10+
RelocModel, RelroLevel, SplitDebuginfo, StackProtector, TargetTriple, TlsModel,
11+
};
1012

1113
use rustc_feature::UnstableFeatures;
1214
use rustc_span::edition::Edition;
@@ -381,6 +383,8 @@ mod desc {
381383
pub const parse_split_debuginfo: &str =
382384
"one of supported split-debuginfo modes (`off`, `packed`, or `unpacked`)";
383385
pub const parse_gcc_ld: &str = "one of: no value, `lld`";
386+
pub const parse_stack_protector: &str =
387+
"one of (`none` (default), `basic`, `strong`, or `all`)";
384388
}
385389

386390
mod parse {
@@ -884,6 +888,14 @@ mod parse {
884888
}
885889
true
886890
}
891+
892+
crate fn parse_stack_protector(slot: &mut StackProtector, v: Option<&str>) -> bool {
893+
match v.and_then(|s| StackProtector::from_str(s).ok()) {
894+
Some(ssp) => *slot = ssp,
895+
_ => return false,
896+
}
897+
true
898+
}
887899
}
888900

889901
options! {
@@ -1275,6 +1287,8 @@ options! {
12751287
"exclude spans when debug-printing compiler state (default: no)"),
12761288
src_hash_algorithm: Option<SourceFileHashAlgorithm> = (None, parse_src_file_hash, [TRACKED],
12771289
"hash algorithm of source files in debug info (`md5`, `sha1`, or `sha256`)"),
1290+
stack_protector: StackProtector = (StackProtector::None, parse_stack_protector, [TRACKED],
1291+
"control stack smash protection strategy (`rustc --print stack-protector-strategies` for details)"),
12781292
strip: Strip = (Strip::None, parse_strip, [UNTRACKED],
12791293
"tell the linker which information to strip (`none` (default), `debuginfo` or `symbols`)"),
12801294
split_dwarf_inlining: bool = (true, parse_bool, [UNTRACKED],

compiler/rustc_session/src/session.rs

+20-1
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,9 @@ use rustc_span::source_map::{FileLoader, MultiSpan, RealFileLoader, SourceMap, S
2828
use rustc_span::{sym, SourceFileHashAlgorithm, Symbol};
2929
use rustc_target::asm::InlineAsmArch;
3030
use rustc_target::spec::{CodeModel, PanicStrategy, RelocModel, RelroLevel};
31-
use rustc_target::spec::{SanitizerSet, SplitDebuginfo, Target, TargetTriple, TlsModel};
31+
use rustc_target::spec::{
32+
SanitizerSet, SplitDebuginfo, StackProtector, Target, TargetTriple, TlsModel,
33+
};
3234

3335
use std::cell::{self, RefCell};
3436
use std::env;
@@ -747,6 +749,14 @@ impl Session {
747749
self.opts.cg.split_debuginfo.unwrap_or(self.target.split_debuginfo)
748750
}
749751

752+
pub fn stack_protector(&self) -> StackProtector {
753+
if self.target.options.supports_stack_protector {
754+
self.opts.debugging_opts.stack_protector
755+
} else {
756+
StackProtector::None
757+
}
758+
}
759+
750760
pub fn target_can_use_split_dwarf(&self) -> bool {
751761
!self.target.is_like_windows && !self.target.is_like_osx
752762
}
@@ -1391,6 +1401,15 @@ fn validate_commandline_args_with_session_available(sess: &Session) {
13911401
disable it using `-C target-feature=-crt-static`",
13921402
);
13931403
}
1404+
1405+
if sess.opts.debugging_opts.stack_protector != StackProtector::None {
1406+
if !sess.target.options.supports_stack_protector {
1407+
sess.warn(&format!(
1408+
"`-Z stack-protector={}` is not supported for target {} and will be ignored",
1409+
sess.opts.debugging_opts.stack_protector, sess.opts.target_triple
1410+
))
1411+
}
1412+
}
13941413
}
13951414

13961415
/// Holds data on the current incremental compilation session, if there is one.

compiler/rustc_target/src/spec/mod.rs

+60
Original file line numberDiff line numberDiff line change
@@ -705,6 +705,59 @@ impl ToJson for FramePointer {
705705
}
706706
}
707707

708+
/// Controls use of stack canaries.
709+
#[derive(Clone, Copy, Debug, PartialEq, Hash, Eq)]
710+
pub enum StackProtector {
711+
/// Disable stack canary generation.
712+
None,
713+
714+
/// On LLVM, mark all generated LLVM functions with the `ssp` attribute (see
715+
/// llvm/docs/LangRef.rst). This triggers stack canary generation in
716+
/// functions which contain an array of a byte-sized type with more than
717+
/// eight elements.
718+
Basic,
719+
720+
/// On LLVM, mark all generated LLVM functions with the `sspstrong`
721+
/// attribute (see llvm/docs/LangRef.rst). This triggers stack canary
722+
/// generation in functions which either contain an array, or which take
723+
/// the address of a local variable.
724+
Strong,
725+
726+
/// Generate stack canaries in all functions.
727+
All,
728+
}
729+
730+
impl StackProtector {
731+
fn as_str(&self) -> &'static str {
732+
match self {
733+
StackProtector::None => "none",
734+
StackProtector::Basic => "basic",
735+
StackProtector::Strong => "strong",
736+
StackProtector::All => "all",
737+
}
738+
}
739+
}
740+
741+
impl FromStr for StackProtector {
742+
type Err = ();
743+
744+
fn from_str(s: &str) -> Result<StackProtector, ()> {
745+
Ok(match s {
746+
"none" => StackProtector::None,
747+
"basic" => StackProtector::Basic,
748+
"strong" => StackProtector::Strong,
749+
"all" => StackProtector::All,
750+
_ => return Err(()),
751+
})
752+
}
753+
}
754+
755+
impl fmt::Display for StackProtector {
756+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
757+
f.write_str(self.as_str())
758+
}
759+
}
760+
708761
macro_rules! supported_targets {
709762
( $(($( $triple:literal, )+ $module:ident ),)+ ) => {
710763
$(mod $module;)+
@@ -1339,6 +1392,10 @@ pub struct TargetOptions {
13391392

13401393
/// Minimum number of bits in #[repr(C)] enum. Defaults to 32.
13411394
pub c_enum_min_bits: u64,
1395+
1396+
/// Whether the target supports stack canary checks. `true` by default,
1397+
/// since this is most common among tier 1 and tier 2 targets.
1398+
pub supports_stack_protector: bool,
13421399
}
13431400

13441401
impl Default for TargetOptions {
@@ -1444,6 +1501,7 @@ impl Default for TargetOptions {
14441501
supported_sanitizers: SanitizerSet::empty(),
14451502
default_adjusted_cabi: None,
14461503
c_enum_min_bits: 32,
1504+
supports_stack_protector: true,
14471505
}
14481506
}
14491507
}
@@ -2029,6 +2087,7 @@ impl Target {
20292087
key!(supported_sanitizers, SanitizerSet)?;
20302088
key!(default_adjusted_cabi, Option<Abi>)?;
20312089
key!(c_enum_min_bits, u64);
2090+
key!(supports_stack_protector, bool);
20322091

20332092
if base.is_builtin {
20342093
// This can cause unfortunate ICEs later down the line.
@@ -2268,6 +2327,7 @@ impl ToJson for Target {
22682327
target_option_val!(split_debuginfo);
22692328
target_option_val!(supported_sanitizers);
22702329
target_option_val!(c_enum_min_bits);
2330+
target_option_val!(supports_stack_protector);
22712331

22722332
if let Some(abi) = self.default_adjusted_cabi {
22732333
d.insert("default-adjusted-cabi".to_string(), Abi::name(abi).to_json());

0 commit comments

Comments
 (0)