Skip to content

Commit bb9dee9

Browse files
committed
add rustc 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 patch adds a rustc option `-Z stack-protector={none,basic,strong,all}` which controls the use of these attributes. This gives rustc the same stack smash protection support as clang offers through options `-fno-stack-protector`, `-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 #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 patch 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 #26612). This could possibly also be included as a stack-protection heuristic. An alternative is to add it as a sanitizer (#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. Reviewed-by: Nikita Popov <[email protected]> Extra commits during review: - [address-review] make the stack-protector option unstable - [address-review] reduce detail level of stack-protector option help text - [address-review] correct grammar in comment - [address-review] use compiler flag to avoid merging functions in test - [address-review] specify min LLVM version in fortanix stack-protector test Only for Fortanix test, since this target specifically requests the `--x86-experimental-lvi-inline-asm-hardening` flag. - [address-review] specify required LLVM components in stack-protector tests - move stack protector option enum closer to other similar option enums - rustc_interface/tests: sort debug option list in tracking hash test - add an explicit `none` stack-protector option Revert "set LLVM requirements for all stack protector support test revisions" This reverts commit a49b74f92a4e7d701d6f6cf63d207a8aff2e0f68.
1 parent 883a241 commit bb9dee9

20 files changed

+1017
-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(
@@ -271,6 +282,7 @@ pub fn from_fn_attrs(cx: &CodegenCx<'ll, 'tcx>, llfn: &'ll Value, instance: ty::
271282
set_frame_pointer_type(cx, llfn);
272283
set_instrument_function(cx, llfn);
273284
set_probestack(cx, llfn);
285+
set_stackprotector(cx, llfn);
274286

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

compiler/rustc_codegen_llvm/src/lib.rs

+25
Original file line numberDiff line numberDiff line change
@@ -288,6 +288,31 @@ impl CodegenBackend for LlvmCodegenBackend {
288288
}
289289
println!();
290290
}
291+
PrintRequest::StackProtectorStrategies => {
292+
println!(
293+
r#"Available stack protector strategies:
294+
all
295+
Generate stack canaries in all functions.
296+
297+
strong
298+
Generate stack canaries in a function if it either:
299+
- has a local variable of `[T; N]` type, regardless of `T` and `N`
300+
- takes the address of a local variable.
301+
302+
(Note that a local variable being borrowed is not equivalent to its
303+
address being taken: e.g. some borrows may be removed by optimization,
304+
while by-value argument passing may be implemented with reference to a
305+
local stack variable in the ABI.)
306+
307+
basic
308+
Generate stack canaries in functions with:
309+
- local variables of `[T; N]` type, where `T` is byte-sized and `N` > 8.
310+
311+
none
312+
Do not generate stack canaries.
313+
"#
314+
);
315+
}
291316
req => llvm_util::print(req, sess),
292317
}
293318
}

compiler/rustc_codegen_llvm/src/llvm/ffi.rs

+3
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,9 @@ pub enum Attribute {
166166
InaccessibleMemOnly = 27,
167167
SanitizeHWAddress = 28,
168168
WillReturn = 29,
169+
StackProtectReq = 30,
170+
StackProtectStrong = 31,
171+
StackProtect = 32,
169172
}
170173

171174
/// 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
@@ -20,7 +20,9 @@ use rustc_span::edition::{Edition, DEFAULT_EDITION};
2020
use rustc_span::symbol::sym;
2121
use rustc_span::SourceFileHashAlgorithm;
2222
use rustc_target::spec::{CodeModel, LinkerFlavor, MergeFunctions, PanicStrategy};
23-
use rustc_target::spec::{RelocModel, RelroLevel, SanitizerSet, SplitDebuginfo, TlsModel};
23+
use rustc_target::spec::{
24+
RelocModel, RelroLevel, SanitizerSet, SplitDebuginfo, StackProtector, TlsModel,
25+
};
2426

2527
use std::collections::{BTreeMap, BTreeSet};
2628
use std::iter::FromIterator;
@@ -713,8 +715,8 @@ fn test_debugging_options_tracking_hash() {
713715
// This list is in alphabetical order.
714716
tracked!(allow_features, Some(vec![String::from("lang_items")]));
715717
tracked!(always_encode_mir, true);
716-
tracked!(assume_incomplete_release, true);
717718
tracked!(asm_comments, true);
719+
tracked!(assume_incomplete_release, true);
718720
tracked!(binary_dep_depinfo, true);
719721
tracked!(chalk, true);
720722
tracked!(codegen_backend, Some("abc".to_string()));
@@ -731,8 +733,8 @@ fn test_debugging_options_tracking_hash() {
731733
tracked!(human_readable_cgu_names, true);
732734
tracked!(inline_in_all_cgus, Some(true));
733735
tracked!(inline_mir, Some(true));
734-
tracked!(inline_mir_threshold, Some(123));
735736
tracked!(inline_mir_hint_threshold, Some(123));
737+
tracked!(inline_mir_threshold, Some(123));
736738
tracked!(instrument_coverage, Some(InstrumentCoverage::All));
737739
tracked!(instrument_mcount, true);
738740
tracked!(link_only, true);
@@ -764,23 +766,24 @@ fn test_debugging_options_tracking_hash() {
764766
tracked!(relax_elf_relocations, Some(true));
765767
tracked!(relro_level, Some(RelroLevel::Full));
766768
tracked!(remap_cwd_prefix, Some(PathBuf::from("abc")));
767-
tracked!(simulate_remapped_rust_src_base, Some(PathBuf::from("/rustc/abc")));
768769
tracked!(report_delayed_bugs, true);
769770
tracked!(sanitizer, SanitizerSet::ADDRESS);
770771
tracked!(sanitizer_memory_track_origins, 2);
771772
tracked!(sanitizer_recover, SanitizerSet::ADDRESS);
772773
tracked!(saturating_float_casts, Some(true));
773774
tracked!(share_generics, Some(true));
774775
tracked!(show_span, Some(String::from("abc")));
776+
tracked!(simulate_remapped_rust_src_base, Some(PathBuf::from("/rustc/abc")));
775777
tracked!(src_hash_algorithm, Some(SourceFileHashAlgorithm::Sha1));
778+
tracked!(stack_protector, StackProtector::All);
776779
tracked!(symbol_mangling_version, Some(SymbolManglingVersion::V0));
777780
tracked!(teach, true);
778781
tracked!(thinlto, Some(true));
779782
tracked!(thir_unsafeck, true);
780-
tracked!(tune_cpu, Some(String::from("abc")));
781783
tracked!(tls_model, Some(TlsModel::GeneralDynamic));
782784
tracked!(trap_unreachable, Some(false));
783785
tracked!(treat_err_as_bug, NonZeroUsize::new(1));
786+
tracked!(tune_cpu, Some(String::from("abc")));
784787
tracked!(unleash_the_miri_inside_of_you, true);
785788
tracked!(use_ctors_section, Some(true));
786789
tracked!(verify_llvm_ir, true);

compiler/rustc_llvm/llvm-wrapper/LLVMWrapper.h

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

8487
typedef struct OpaqueRustString *RustStringRef;

compiler/rustc_llvm/llvm-wrapper/RustWrapper.cpp

+6
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,12 @@ static Attribute::AttrKind fromRust(LLVMRustAttribute Kind) {
213213
return Attribute::SanitizeHWAddress;
214214
case WillReturn:
215215
return Attribute::WillReturn;
216+
case StackProtectReq:
217+
return Attribute::StackProtectReq;
218+
case StackProtectStrong:
219+
return Attribute::StackProtectStrong;
220+
case StackProtect:
221+
return Attribute::StackProtect;
216222
}
217223
report_fatal_error("bad AttributeKind");
218224
}

compiler/rustc_session/src/config.rs

+8-3
Original file line numberDiff line numberDiff line change
@@ -538,6 +538,7 @@ pub enum PrintRequest {
538538
TlsModels,
539539
TargetSpec,
540540
NativeStaticLibs,
541+
StackProtectorStrategies,
541542
}
542543

543544
#[derive(Copy, Clone)]
@@ -1110,8 +1111,8 @@ pub fn rustc_short_optgroups() -> Vec<RustcOptGroup> {
11101111
"print",
11111112
"Compiler information to print on stdout",
11121113
"[crate-name|file-names|sysroot|target-libdir|cfg|target-list|\
1113-
target-cpus|target-features|relocation-models|\
1114-
code-models|tls-models|target-spec-json|native-static-libs]",
1114+
target-cpus|target-features|relocation-models|code-models|\
1115+
tls-models|target-spec-json|native-static-libs|stack-protector-strategies]",
11151116
),
11161117
opt::flagmulti_s("g", "", "Equivalent to -C debuginfo=2"),
11171118
opt::flagmulti_s("O", "", "Equivalent to -C opt-level=2"),
@@ -1527,6 +1528,7 @@ fn collect_print_requests(
15271528
"code-models" => PrintRequest::CodeModels,
15281529
"tls-models" => PrintRequest::TlsModels,
15291530
"native-static-libs" => PrintRequest::NativeStaticLibs,
1531+
"stack-protector-strategies" => PrintRequest::StackProtectorStrategies,
15301532
"target-spec-json" => {
15311533
if dopts.unstable_options {
15321534
PrintRequest::TargetSpec
@@ -2494,7 +2496,9 @@ crate mod dep_tracking {
24942496
use rustc_span::edition::Edition;
24952497
use rustc_span::RealFileName;
24962498
use rustc_target::spec::{CodeModel, MergeFunctions, PanicStrategy, RelocModel};
2497-
use rustc_target::spec::{RelroLevel, SanitizerSet, SplitDebuginfo, TargetTriple, TlsModel};
2499+
use rustc_target::spec::{
2500+
RelroLevel, SanitizerSet, SplitDebuginfo, StackProtector, TargetTriple, TlsModel,
2501+
};
24982502
use std::collections::hash_map::DefaultHasher;
24992503
use std::collections::BTreeMap;
25002504
use std::hash::Hash;
@@ -2568,6 +2572,7 @@ crate mod dep_tracking {
25682572
Edition,
25692573
LinkerPluginLto,
25702574
SplitDebuginfo,
2575+
StackProtector,
25712576
SwitchWithOptPath,
25722577
SymbolManglingVersion,
25732578
SourceFileHashAlgorithm,

compiler/rustc_session/src/options.rs

+15-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@ use crate::lint;
55
use crate::search_paths::SearchPath;
66
use crate::utils::NativeLib;
77
use rustc_target::spec::{CodeModel, LinkerFlavor, MergeFunctions, PanicStrategy, SanitizerSet};
8-
use rustc_target::spec::{RelocModel, RelroLevel, SplitDebuginfo, TargetTriple, TlsModel};
8+
use rustc_target::spec::{
9+
RelocModel, RelroLevel, SplitDebuginfo, StackProtector, TargetTriple, TlsModel,
10+
};
911

1012
use rustc_feature::UnstableFeatures;
1113
use rustc_span::edition::Edition;
@@ -385,6 +387,8 @@ mod desc {
385387
pub const parse_split_debuginfo: &str =
386388
"one of supported split-debuginfo modes (`off`, `packed`, or `unpacked`)";
387389
pub const parse_gcc_ld: &str = "one of: no value, `lld`";
390+
pub const parse_stack_protector: &str =
391+
"one of (`none` (default), `basic`, `strong`, or `all`)";
388392
}
389393

390394
mod parse {
@@ -917,6 +921,14 @@ mod parse {
917921
}
918922
true
919923
}
924+
925+
crate fn parse_stack_protector(slot: &mut StackProtector, v: Option<&str>) -> bool {
926+
match v.and_then(|s| StackProtector::from_str(s).ok()) {
927+
Some(ssp) => *slot = ssp,
928+
_ => return false,
929+
}
930+
true
931+
}
920932
}
921933

922934
options! {
@@ -1330,6 +1342,8 @@ options! {
13301342
"exclude spans when debug-printing compiler state (default: no)"),
13311343
src_hash_algorithm: Option<SourceFileHashAlgorithm> = (None, parse_src_file_hash, [TRACKED],
13321344
"hash algorithm of source files in debug info (`md5`, `sha1`, or `sha256`)"),
1345+
stack_protector: StackProtector = (StackProtector::None, parse_stack_protector, [TRACKED],
1346+
"control stack smash protection strategy (`rustc --print stack-protector-strategies` for details)"),
13331347
strip: Strip = (Strip::None, parse_strip, [UNTRACKED],
13341348
"tell the linker which information to strip (`none` (default), `debuginfo` or `symbols`)"),
13351349
split_dwarf_inlining: bool = (true, parse_bool, [UNTRACKED],

compiler/rustc_session/src/session.rs

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

3234
use std::cell::{self, RefCell};
3335
use std::env;
@@ -732,6 +734,14 @@ impl Session {
732734
self.opts.cg.split_debuginfo.unwrap_or(self.target.split_debuginfo)
733735
}
734736

737+
pub fn stack_protector(&self) -> StackProtector {
738+
if self.target.options.supports_stack_protector {
739+
self.opts.debugging_opts.stack_protector
740+
} else {
741+
StackProtector::None
742+
}
743+
}
744+
735745
pub fn target_can_use_split_dwarf(&self) -> bool {
736746
!self.target.is_like_windows && !self.target.is_like_osx
737747
}
@@ -1411,6 +1421,15 @@ fn validate_commandline_args_with_session_available(sess: &Session) {
14111421
sess.err("`-Zsanitizer=cfi` requires `-Clto`");
14121422
}
14131423
}
1424+
1425+
if sess.opts.debugging_opts.stack_protector != StackProtector::None {
1426+
if !sess.target.options.supports_stack_protector {
1427+
sess.warn(&format!(
1428+
"`-Z stack-protector={}` is not supported for target {} and will be ignored",
1429+
sess.opts.debugging_opts.stack_protector, sess.opts.target_triple
1430+
))
1431+
}
1432+
}
14141433
}
14151434

14161435
/// 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
@@ -712,6 +712,59 @@ impl ToJson for FramePointer {
712712
}
713713
}
714714

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

13611414
/// Whether or not the DWARF `.debug_aranges` section should be generated.
13621415
pub generate_arange_section: bool,
1416+
1417+
/// Whether the target supports stack canary checks. `true` by default,
1418+
/// since this is most common among tier 1 and tier 2 targets.
1419+
pub supports_stack_protector: bool,
13631420
}
13641421

13651422
impl Default for TargetOptions {
@@ -1466,6 +1523,7 @@ impl Default for TargetOptions {
14661523
default_adjusted_cabi: None,
14671524
c_enum_min_bits: 32,
14681525
generate_arange_section: true,
1526+
supports_stack_protector: true,
14691527
}
14701528
}
14711529
}
@@ -2052,6 +2110,7 @@ impl Target {
20522110
key!(default_adjusted_cabi, Option<Abi>)?;
20532111
key!(c_enum_min_bits, u64);
20542112
key!(generate_arange_section, bool);
2113+
key!(supports_stack_protector, bool);
20552114

20562115
if base.is_builtin {
20572116
// This can cause unfortunate ICEs later down the line.
@@ -2292,6 +2351,7 @@ impl ToJson for Target {
22922351
target_option_val!(supported_sanitizers);
22932352
target_option_val!(c_enum_min_bits);
22942353
target_option_val!(generate_arange_section);
2354+
target_option_val!(supports_stack_protector);
22952355

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

0 commit comments

Comments
 (0)