diff --git a/compiler/rustc_codegen_llvm/src/attributes.rs b/compiler/rustc_codegen_llvm/src/attributes.rs index 56b93f8346680..c180706c581a6 100644 --- a/compiler/rustc_codegen_llvm/src/attributes.rs +++ b/compiler/rustc_codegen_llvm/src/attributes.rs @@ -12,7 +12,7 @@ use rustc_middle::ty::{self, TyCtxt}; use rustc_session::config::OptLevel; use rustc_session::Session; use rustc_target::spec::abi::Abi; -use rustc_target::spec::{FramePointer, SanitizerSet, StackProbeType}; +use rustc_target::spec::{FramePointer, SanitizerSet, StackProbeType, StackProtector}; use crate::attributes; use crate::llvm::AttributePlace::Function; @@ -161,6 +161,17 @@ fn set_probestack(cx: &CodegenCx<'ll, '_>, llfn: &'ll Value) { } } +fn set_stackprotector(cx: &CodegenCx<'ll, '_>, llfn: &'ll Value) { + let sspattr = match cx.sess().stack_protector() { + StackProtector::None => return, + StackProtector::All => Attribute::StackProtectReq, + StackProtector::Strong => Attribute::StackProtectStrong, + StackProtector::Basic => Attribute::StackProtect, + }; + + sspattr.apply_llfn(Function, llfn) +} + pub fn apply_target_cpu_attr(cx: &CodegenCx<'ll, '_>, llfn: &'ll Value) { let target_cpu = SmallCStr::new(llvm_util::target_cpu(cx.tcx.sess)); llvm::AddFunctionAttrStringValue( @@ -267,6 +278,7 @@ pub fn from_fn_attrs(cx: &CodegenCx<'ll, 'tcx>, llfn: &'ll Value, instance: ty:: set_frame_pointer_type(cx, llfn); set_instrument_function(cx, llfn); set_probestack(cx, llfn); + set_stackprotector(cx, llfn); if codegen_fn_attrs.flags.contains(CodegenFnAttrFlags::COLD) { Attribute::Cold.apply_llfn(Function, llfn); diff --git a/compiler/rustc_codegen_llvm/src/lib.rs b/compiler/rustc_codegen_llvm/src/lib.rs index 1da14344b1d26..5756e47590272 100644 --- a/compiler/rustc_codegen_llvm/src/lib.rs +++ b/compiler/rustc_codegen_llvm/src/lib.rs @@ -232,6 +232,29 @@ impl CodegenBackend for LlvmCodegenBackend { } println!(); } + PrintRequest::StackProtectorStrategies => { + println!( + r#"Available stack protector strategies: + all + 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. + +"# + ); + } req => llvm_util::print(req, sess), } } diff --git a/compiler/rustc_codegen_llvm/src/llvm/ffi.rs b/compiler/rustc_codegen_llvm/src/llvm/ffi.rs index 3f2ed02d90df3..3accd9785650d 100644 --- a/compiler/rustc_codegen_llvm/src/llvm/ffi.rs +++ b/compiler/rustc_codegen_llvm/src/llvm/ffi.rs @@ -159,6 +159,9 @@ pub enum Attribute { InaccessibleMemOnly = 27, SanitizeHWAddress = 28, WillReturn = 29, + StackProtectReq = 30, + StackProtectStrong = 31, + StackProtect = 32, } /// LLVMIntPredicate diff --git a/compiler/rustc_driver/src/lib.rs b/compiler/rustc_driver/src/lib.rs index 53053327d0dab..9edcf1dbb747d 100644 --- a/compiler/rustc_driver/src/lib.rs +++ b/compiler/rustc_driver/src/lib.rs @@ -736,7 +736,12 @@ impl RustcDefaultCalls { println!("{}", cfg); } } - RelocationModels | CodeModels | TlsModels | TargetCPUs | TargetFeatures => { + RelocationModels + | CodeModels + | TlsModels + | TargetCPUs + | StackProtectorStrategies + | TargetFeatures => { codegen_backend.print(*req, sess); } // Any output here interferes with Cargo's parsing of other printed output diff --git a/compiler/rustc_interface/src/tests.rs b/compiler/rustc_interface/src/tests.rs index afab919bc3c2c..4729c9e004261 100644 --- a/compiler/rustc_interface/src/tests.rs +++ b/compiler/rustc_interface/src/tests.rs @@ -18,7 +18,9 @@ use rustc_span::edition::{Edition, DEFAULT_EDITION}; use rustc_span::symbol::sym; use rustc_span::SourceFileHashAlgorithm; use rustc_target::spec::{CodeModel, LinkerFlavor, MergeFunctions, PanicStrategy}; -use rustc_target::spec::{RelocModel, RelroLevel, SanitizerSet, SplitDebuginfo, TlsModel}; +use rustc_target::spec::{ + RelocModel, RelroLevel, SanitizerSet, SplitDebuginfo, StackProtector, TlsModel, +}; use std::collections::{BTreeMap, BTreeSet}; use std::iter::FromIterator; @@ -709,8 +711,8 @@ fn test_debugging_options_tracking_hash() { // This list is in alphabetical order. tracked!(allow_features, Some(vec![String::from("lang_items")])); tracked!(always_encode_mir, true); - tracked!(assume_incomplete_release, true); tracked!(asm_comments, true); + tracked!(assume_incomplete_release, true); tracked!(binary_dep_depinfo, true); tracked!(chalk, true); tracked!(codegen_backend, Some("abc".to_string())); @@ -726,8 +728,8 @@ fn test_debugging_options_tracking_hash() { tracked!(human_readable_cgu_names, true); tracked!(inline_in_all_cgus, Some(true)); tracked!(inline_mir, Some(true)); - tracked!(inline_mir_threshold, Some(123)); tracked!(inline_mir_hint_threshold, Some(123)); + tracked!(inline_mir_threshold, Some(123)); tracked!(instrument_coverage, Some(InstrumentCoverage::All)); tracked!(instrument_mcount, true); tracked!(link_only, true); @@ -753,7 +755,6 @@ fn test_debugging_options_tracking_hash() { tracked!(profiler_runtime, "abc".to_string()); tracked!(relax_elf_relocations, Some(true)); tracked!(relro_level, Some(RelroLevel::Full)); - tracked!(simulate_remapped_rust_src_base, Some(PathBuf::from("/rustc/abc"))); tracked!(report_delayed_bugs, true); tracked!(sanitizer, SanitizerSet::ADDRESS); tracked!(sanitizer_memory_track_origins, 2); @@ -761,15 +762,17 @@ fn test_debugging_options_tracking_hash() { tracked!(saturating_float_casts, Some(true)); tracked!(share_generics, Some(true)); tracked!(show_span, Some(String::from("abc"))); + tracked!(simulate_remapped_rust_src_base, Some(PathBuf::from("/rustc/abc"))); tracked!(src_hash_algorithm, Some(SourceFileHashAlgorithm::Sha1)); + tracked!(stack_protector, StackProtector::All); tracked!(symbol_mangling_version, Some(SymbolManglingVersion::V0)); tracked!(teach, true); tracked!(thinlto, Some(true)); tracked!(thir_unsafeck, true); - tracked!(tune_cpu, Some(String::from("abc"))); tracked!(tls_model, Some(TlsModel::GeneralDynamic)); tracked!(trap_unreachable, Some(false)); tracked!(treat_err_as_bug, NonZeroUsize::new(1)); + tracked!(tune_cpu, Some(String::from("abc"))); tracked!(unleash_the_miri_inside_of_you, true); tracked!(use_ctors_section, Some(true)); tracked!(verify_llvm_ir, true); diff --git a/compiler/rustc_llvm/llvm-wrapper/LLVMWrapper.h b/compiler/rustc_llvm/llvm-wrapper/LLVMWrapper.h index 0b1b68d83b7b9..51c835245997e 100644 --- a/compiler/rustc_llvm/llvm-wrapper/LLVMWrapper.h +++ b/compiler/rustc_llvm/llvm-wrapper/LLVMWrapper.h @@ -80,6 +80,9 @@ enum LLVMRustAttribute { InaccessibleMemOnly = 27, SanitizeHWAddress = 28, WillReturn = 29, + StackProtectReq = 30, + StackProtectStrong = 31, + StackProtect = 32, }; typedef struct OpaqueRustString *RustStringRef; diff --git a/compiler/rustc_llvm/llvm-wrapper/RustWrapper.cpp b/compiler/rustc_llvm/llvm-wrapper/RustWrapper.cpp index 9850f395a0f65..b86a9ac8265e2 100644 --- a/compiler/rustc_llvm/llvm-wrapper/RustWrapper.cpp +++ b/compiler/rustc_llvm/llvm-wrapper/RustWrapper.cpp @@ -199,6 +199,12 @@ static Attribute::AttrKind fromRust(LLVMRustAttribute Kind) { return Attribute::SanitizeHWAddress; case WillReturn: return Attribute::WillReturn; + case StackProtectReq: + return Attribute::StackProtectReq; + case StackProtectStrong: + return Attribute::StackProtectStrong; + case StackProtect: + return Attribute::StackProtect; } report_fatal_error("bad AttributeKind"); } diff --git a/compiler/rustc_session/src/config.rs b/compiler/rustc_session/src/config.rs index fdedb7e6a4afe..062c424478c6b 100644 --- a/compiler/rustc_session/src/config.rs +++ b/compiler/rustc_session/src/config.rs @@ -512,6 +512,7 @@ pub enum PrintRequest { TlsModels, TargetSpec, NativeStaticLibs, + StackProtectorStrategies, } #[derive(Copy, Clone)] @@ -1067,8 +1068,8 @@ pub fn rustc_short_optgroups() -> Vec { "print", "Compiler information to print on stdout", "[crate-name|file-names|sysroot|target-libdir|cfg|target-list|\ - target-cpus|target-features|relocation-models|\ - code-models|tls-models|target-spec-json|native-static-libs]", + target-cpus|target-features|relocation-models|code-models|\ + tls-models|target-spec-json|native-static-libs|stack-protector-strategies]", ), opt::flagmulti_s("g", "", "Equivalent to -C debuginfo=2"), opt::flagmulti_s("O", "", "Equivalent to -C opt-level=2"), @@ -1484,6 +1485,7 @@ fn collect_print_requests( "code-models" => PrintRequest::CodeModels, "tls-models" => PrintRequest::TlsModels, "native-static-libs" => PrintRequest::NativeStaticLibs, + "stack-protector-strategies" => PrintRequest::StackProtectorStrategies, "target-spec-json" => { if dopts.unstable_options { PrintRequest::TargetSpec @@ -2414,7 +2416,9 @@ crate mod dep_tracking { use rustc_span::edition::Edition; use rustc_span::RealFileName; use rustc_target::spec::{CodeModel, MergeFunctions, PanicStrategy, RelocModel}; - use rustc_target::spec::{RelroLevel, SanitizerSet, SplitDebuginfo, TargetTriple, TlsModel}; + use rustc_target::spec::{ + RelroLevel, SanitizerSet, SplitDebuginfo, StackProtector, TargetTriple, TlsModel, + }; use std::collections::hash_map::DefaultHasher; use std::collections::BTreeMap; use std::hash::Hash; @@ -2488,6 +2492,7 @@ crate mod dep_tracking { Edition, LinkerPluginLto, SplitDebuginfo, + StackProtector, SwitchWithOptPath, SymbolManglingVersion, SourceFileHashAlgorithm, diff --git a/compiler/rustc_session/src/options.rs b/compiler/rustc_session/src/options.rs index 447be84b5a70a..d57b438c6e4b0 100644 --- a/compiler/rustc_session/src/options.rs +++ b/compiler/rustc_session/src/options.rs @@ -6,7 +6,9 @@ use crate::search_paths::SearchPath; use crate::utils::NativeLib; use rustc_target::spec::{CodeModel, LinkerFlavor, MergeFunctions, PanicStrategy, SanitizerSet}; -use rustc_target::spec::{RelocModel, RelroLevel, SplitDebuginfo, TargetTriple, TlsModel}; +use rustc_target::spec::{ + RelocModel, RelroLevel, SplitDebuginfo, StackProtector, TargetTriple, TlsModel, +}; use rustc_feature::UnstableFeatures; use rustc_span::edition::Edition; @@ -381,6 +383,8 @@ mod desc { pub const parse_split_debuginfo: &str = "one of supported split-debuginfo modes (`off`, `packed`, or `unpacked`)"; pub const parse_gcc_ld: &str = "one of: no value, `lld`"; + pub const parse_stack_protector: &str = + "one of (`none` (default), `basic`, `strong`, or `all`)"; } mod parse { @@ -884,6 +888,14 @@ mod parse { } true } + + crate fn parse_stack_protector(slot: &mut StackProtector, v: Option<&str>) -> bool { + match v.and_then(|s| StackProtector::from_str(s).ok()) { + Some(ssp) => *slot = ssp, + _ => return false, + } + true + } } options! { @@ -1275,6 +1287,8 @@ options! { "exclude spans when debug-printing compiler state (default: no)"), src_hash_algorithm: Option = (None, parse_src_file_hash, [TRACKED], "hash algorithm of source files in debug info (`md5`, `sha1`, or `sha256`)"), + stack_protector: StackProtector = (StackProtector::None, parse_stack_protector, [TRACKED], + "control stack smash 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`)"), split_dwarf_inlining: bool = (true, parse_bool, [UNTRACKED], diff --git a/compiler/rustc_session/src/session.rs b/compiler/rustc_session/src/session.rs index 4471e1e0ae8b4..7eef8aba13c64 100644 --- a/compiler/rustc_session/src/session.rs +++ b/compiler/rustc_session/src/session.rs @@ -28,7 +28,9 @@ use rustc_span::source_map::{FileLoader, MultiSpan, RealFileLoader, SourceMap, S use rustc_span::{sym, SourceFileHashAlgorithm, Symbol}; use rustc_target::asm::InlineAsmArch; use rustc_target::spec::{CodeModel, PanicStrategy, RelocModel, RelroLevel}; -use rustc_target::spec::{SanitizerSet, SplitDebuginfo, Target, TargetTriple, TlsModel}; +use rustc_target::spec::{ + SanitizerSet, SplitDebuginfo, StackProtector, Target, TargetTriple, TlsModel, +}; use std::cell::{self, RefCell}; use std::env; @@ -747,6 +749,14 @@ impl Session { self.opts.cg.split_debuginfo.unwrap_or(self.target.split_debuginfo) } + pub fn stack_protector(&self) -> StackProtector { + if self.target.options.supports_stack_protector { + self.opts.debugging_opts.stack_protector + } else { + StackProtector::None + } + } + pub fn target_can_use_split_dwarf(&self) -> bool { !self.target.is_like_windows && !self.target.is_like_osx } @@ -1391,6 +1401,15 @@ fn validate_commandline_args_with_session_available(sess: &Session) { disable it using `-C target-feature=-crt-static`", ); } + + if sess.opts.debugging_opts.stack_protector != StackProtector::None { + if !sess.target.options.supports_stack_protector { + sess.warn(&format!( + "`-Z stack-protector={}` is not supported for target {} and will be ignored", + sess.opts.debugging_opts.stack_protector, sess.opts.target_triple + )) + } + } } /// Holds data on the current incremental compilation session, if there is one. diff --git a/compiler/rustc_target/src/spec/mod.rs b/compiler/rustc_target/src/spec/mod.rs index 273221360b8b5..0194521223ebe 100644 --- a/compiler/rustc_target/src/spec/mod.rs +++ b/compiler/rustc_target/src/spec/mod.rs @@ -705,6 +705,59 @@ impl ToJson for FramePointer { } } +/// Controls use of stack canaries. +#[derive(Clone, Copy, Debug, PartialEq, Hash, Eq)] +pub enum StackProtector { + /// Disable stack canary generation. + 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, + + /// 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 + /// the address of a local variable. + Strong, + + /// Generate stack canaries in all functions. + All, +} + +impl StackProtector { + fn as_str(&self) -> &'static str { + match self { + StackProtector::None => "none", + StackProtector::Basic => "basic", + StackProtector::Strong => "strong", + StackProtector::All => "all", + } + } +} + +impl FromStr for StackProtector { + type Err = (); + + fn from_str(s: &str) -> Result { + Ok(match s { + "none" => StackProtector::None, + "basic" => StackProtector::Basic, + "strong" => StackProtector::Strong, + "all" => StackProtector::All, + _ => return Err(()), + }) + } +} + +impl fmt::Display for StackProtector { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str(self.as_str()) + } +} + macro_rules! supported_targets { ( $(($( $triple:literal, )+ $module:ident ),)+ ) => { $(mod $module;)+ @@ -1339,6 +1392,10 @@ pub struct TargetOptions { /// Minimum number of bits in #[repr(C)] enum. Defaults to 32. pub c_enum_min_bits: u64, + + /// Whether the target supports stack canary checks. `true` by default, + /// since this is most common among tier 1 and tier 2 targets. + pub supports_stack_protector: bool, } impl Default for TargetOptions { @@ -1444,6 +1501,7 @@ impl Default for TargetOptions { supported_sanitizers: SanitizerSet::empty(), default_adjusted_cabi: None, c_enum_min_bits: 32, + supports_stack_protector: true, } } } @@ -2029,6 +2087,7 @@ impl Target { key!(supported_sanitizers, SanitizerSet)?; key!(default_adjusted_cabi, Option)?; key!(c_enum_min_bits, u64); + key!(supports_stack_protector, bool); if base.is_builtin { // This can cause unfortunate ICEs later down the line. @@ -2268,6 +2327,7 @@ impl ToJson for Target { target_option_val!(split_debuginfo); target_option_val!(supported_sanitizers); target_option_val!(c_enum_min_bits); + target_option_val!(supports_stack_protector); if let Some(abi) = self.default_adjusted_cabi { d.insert("default-adjusted-cabi".to_string(), Abi::name(abi).to_json()); diff --git a/compiler/rustc_target/src/spec/nvptx64_nvidia_cuda.rs b/compiler/rustc_target/src/spec/nvptx64_nvidia_cuda.rs index 083262cf3514c..ba32a312910d8 100644 --- a/compiler/rustc_target/src/spec/nvptx64_nvidia_cuda.rs +++ b/compiler/rustc_target/src/spec/nvptx64_nvidia_cuda.rs @@ -44,6 +44,10 @@ pub fn target() -> Target { // produce kernel functions that call other kernel functions. // This behavior is not supported by PTX ISA. merge_functions: MergeFunctions::Disabled, + + // The LLVM backend does not support stack canaries for this target + supports_stack_protector: false, + ..Default::default() }, } diff --git a/src/test/assembly/stack-protector/stack-protector-heuristics-effect.rs b/src/test/assembly/stack-protector/stack-protector-heuristics-effect.rs new file mode 100644 index 0000000000000..c688006abc329 --- /dev/null +++ b/src/test/assembly/stack-protector/stack-protector-heuristics-effect.rs @@ -0,0 +1,395 @@ +// revisions: all strong basic none missing +// assembly-output: emit-asm +// ignore-windows stack check code uses different function names +// ignore-nvptx stack protector is not supported +// [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 +// compile-flags: -C opt-level=2 -Z merge-functions=disabled + +#![crate_type = "lib"] + +#![allow(incomplete_features)] + +#![feature(unsized_locals, unsized_fn_params)] + + +// CHECK-LABEL: emptyfn: +#[no_mangle] +pub fn emptyfn() { + // all: __stack_chk_fail + // strong-NOT: __stack_chk_fail + // basic-NOT: __stack_chk_fail + // none-NOT: __stack_chk_fail + // missing-NOT: __stack_chk_fail +} + +// CHECK-LABEL: array_char +#[no_mangle] +pub fn array_char(f: fn(*const char)) { + let a = ['c'; 1]; + let b = ['d'; 3]; + let c = ['e'; 15]; + + f(&a as *const _); + f(&b as *const _); + f(&c as *const _); + + // Any type of local array variable leads to stack protection with the + // "strong" heuristic. The 'basic' heuristic only adds stack protection to + // functions with local array variables of a byte-sized type, however. Since + // 'char' is 4 bytes in Rust, this function is not protected by the 'basic' + // heuristic + // + // (This test *also* takes the address of the local stack variables. We + // cannot know that this isn't what triggers the `strong` heuristic. + // However, the test strategy of passing the address of a stack array to an + // external function is sufficient to trigger the `basic` heuristic (see + // test `array_u8_large()`). Since the `basic` heuristic only checks for the + // presence of stack-local array variables, we can be confident that this + // test also captures this part of the `strong` heuristic specification.) + + // all: __stack_chk_fail + // strong: __stack_chk_fail + // basic-NOT: __stack_chk_fail + // none-NOT: __stack_chk_fail + // missing-NOT: __stack_chk_fail +} + +// CHECK-LABEL: array_u8_1 +#[no_mangle] +pub fn array_u8_1(f: fn(*const u8)) { + let a = [0u8; 1]; + f(&a as *const _); + + // The 'strong' heuristic adds stack protection to functions with local + // array variables regardless of their size. + + // all: __stack_chk_fail + // strong: __stack_chk_fail + // basic-NOT: __stack_chk_fail + // none-NOT: __stack_chk_fail + // missing-NOT: __stack_chk_fail +} + +// CHECK-LABEL: array_u8_small: +#[no_mangle] +pub fn array_u8_small(f: fn(*const u8)) { + let a = [0u8; 2]; + let b = [0u8; 8]; + f(&a as *const _); + f(&b as *const _); + + // Small arrays do not lead to stack protection by the 'basic' heuristic. + + // all: __stack_chk_fail + // strong: __stack_chk_fail + // basic-NOT: __stack_chk_fail + // none-NOT: __stack_chk_fail + // missing-NOT: __stack_chk_fail +} + +// CHECK-LABEL: array_u8_large: +#[no_mangle] +pub fn array_u8_large(f: fn(*const u8)) { + let a = [0u8; 9]; + f(&a as *const _); + + // Since `a` is a byte array with size greater than 8, the basic heuristic + // will also protect this function. + + // all: __stack_chk_fail + // strong: __stack_chk_fail + // basic: __stack_chk_fail + // none-NOT: __stack_chk_fail + // missing-NOT: __stack_chk_fail +} + +#[derive(Copy, Clone)] +pub struct ByteSizedNewtype(u8); + +// CHECK-LABEL: array_bytesizednewtype_9: +#[no_mangle] +pub fn array_bytesizednewtype_9(f: fn(*const ByteSizedNewtype)) { + let a = [ByteSizedNewtype(0); 9]; + f(&a as *const _); + + // Since `a` is a byte array in the LLVM output, the basic heuristic will + // also protect this function. + + // all: __stack_chk_fail + // strong: __stack_chk_fail + // basic: __stack_chk_fail + // none-NOT: __stack_chk_fail + // missing-NOT: __stack_chk_fail +} + +// CHECK-LABEL: local_var_addr_used_indirectly +#[no_mangle] +pub fn local_var_addr_used_indirectly(f: fn(bool)) { + let a = 5; + let a_addr = &a as *const _ as usize; + f(a_addr & 0x10 == 0); + + // 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++: + // ``` + // cat << EOF | clang++ -O2 -fstack-protector-strong -S -x c++ - -o - | grep stack_chk + // #include + // void f(void (*g)(bool)) { + // int32_t x; + // g((reinterpret_cast(&x) & 0x10U) == 0); + // } + // EOF + // ``` + + // all: __stack_chk_fail + // strong: __stack_chk_fail + // basic-NOT: __stack_chk_fail + // none-NOT: __stack_chk_fail + // missing-NOT: __stack_chk_fail +} + + +// CHECK-LABEL: local_string_addr_taken +#[no_mangle] +pub fn local_string_addr_taken(f: fn(&String)) { + let x = String::new(); + f(&x); + + // Taking the address of the local variable `x` leads to stack smash + // protection with the `strong` heuristic, but not with the `basic` + // heuristic. It does not matter that the reference is not mut. + // + // An interesting note is that a similar function in C++ *would* be + // protected by the `basic` heuristic, because `std::string` has a char + // array internally as a small object optimization: + // ``` + // cat < + // void f(void (*g)(const std::string&)) { + // std::string x; + // g(x); + // } + // EOF + // ``` + // + + // all: __stack_chk_fail + // strong: __stack_chk_fail + // basic-NOT: __stack_chk_fail + // none-NOT: __stack_chk_fail + // missing-NOT: __stack_chk_fail +} + +pub trait SelfByRef { + fn f(&self) -> i32; +} + +impl SelfByRef for i32 { + fn f(&self) -> i32 { + return self + 1; + } +} + +// CHECK-LABEL: local_var_addr_taken_used_locally_only +#[no_mangle] +pub fn local_var_addr_taken_used_locally_only(factory: fn() -> i32, sink: fn(i32)) { + let x = factory(); + let g = x.f(); + sink(g); + + // 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 + // the `strong` heuristic. + + // all: __stack_chk_fail + // strong-NOT: __stack_chk_fail + // basic-NOT: __stack_chk_fail + // none-NOT: __stack_chk_fail + // missing-NOT: __stack_chk_fail +} + +pub struct Gigastruct { + does: u64, + not: u64, + have: u64, + array: u64, + members: u64 +} + +// CHECK-LABEL: local_large_var_moved +#[no_mangle] +pub fn local_large_var_moved(f: fn(Gigastruct)) { + let x = Gigastruct { does: 0, not: 1, have: 2, array: 3, members: 4 }; + f(x); + + // Even though the local variable conceptually doesn't have its address + // taken, it's so large that the "move" is implemented with a reference to a + // stack-local variable in the ABI. Consequently, this function *is* + // protected by the `strong` heuristic. This is also the case for + // rvalue-references in C++, regardless of struct size: + // ``` + // cat < + // #include + // void f(void (*g)(uint64_t&&)) { + // uint64_t x; + // g(std::move(x)); + // } + // EOF + // ``` + + // all: __stack_chk_fail + // strong: __stack_chk_fail + // basic-NOT: __stack_chk_fail + // none-NOT: __stack_chk_fail + // missing-NOT: __stack_chk_fail +} + +// CHECK-LABEL: local_large_var_cloned +#[no_mangle] +pub fn local_large_var_cloned(f: fn(Gigastruct)) { + f(Gigastruct { does: 0, not: 1, have: 2, array: 3, members: 4 }); + + // A new instance of `Gigastruct` is passed to `f()`, without any apparent + // connection to this stack frame. Still, since instances of `Gigastruct` + // are sufficiently large, it is allocated in the caller stack frame and + // passed as a pointer. As such, this function is *also* protected by the + // `strong` heuristic, just like `local_large_var_moved`. This is also the + // case for pass-by-value of sufficiently large structs in C++: + // ``` + // cat < + // #include + // struct Gigastruct { uint64_t a, b, c, d, e; }; + // void f(void (*g)(Gigastruct)) { + // g(Gigastruct{}); + // } + // EOF + // ``` + + + // all: __stack_chk_fail + // strong: __stack_chk_fail + // basic-NOT: __stack_chk_fail + // none-NOT: __stack_chk_fail + // missing-NOT: __stack_chk_fail +} + + +extern "C" { + // A call to an external `alloca` function is *not* recognized as an + // `alloca(3)` operation. This function is a compiler built-in, as the + // man page explains. Clang translates it to an LLVM `alloca` + // instruction with a count argument, which is also what the LLVM stack + // protector heuristics looks for. The man page for `alloca(3)` details + // a way to avoid using the compiler built-in: pass a -std=c11 + // argument, *and* don't include . Though this leads to an + // external alloca() function being called, it doesn't lead to stack + // protection being included. It even fails with a linker error + // "undefined reference to `alloca'". Example: + // ``` + // cat< + // void * alloca(size_t); + // void f(void (*g)(void*)) { + // void * p = alloca(10); + // g(p); + // } + // int main() { return 0; } + // EOF + // ``` + // The following tests demonstrate that calls to an external `alloca` + // function in Rust also doesn't trigger stack protection. + + fn alloca(size: usize) -> *mut (); +} + +// CHECK-LABEL: alloca_small_compile_time_constant_arg +#[no_mangle] +pub fn alloca_small_compile_time_constant_arg(f: fn(*mut ())) { + f(unsafe { alloca(8) }); + + // all: __stack_chk_fail + // strong-NOT: __stack_chk_fail + // basic-NOT: __stack_chk_fail + // none-NOT: __stack_chk_fail + // missing-NOT: __stack_chk_fail +} + +// CHECK-LABEL: alloca_large_compile_time_constant_arg +#[no_mangle] +pub fn alloca_large_compile_time_constant_arg(f: fn(*mut ())) { + f(unsafe { alloca(9) }); + + // all: __stack_chk_fail + // strong-NOT: __stack_chk_fail + // basic-NOT: __stack_chk_fail + // none-NOT: __stack_chk_fail + // missing-NOT: __stack_chk_fail +} + + +// CHECK-LABEL: alloca_dynamic_arg +#[no_mangle] +pub fn alloca_dynamic_arg(f: fn(*mut ()), n: usize) { + f(unsafe { alloca(n) }); + + // all: __stack_chk_fail + // strong-NOT: __stack_chk_fail + // basic-NOT: __stack_chk_fail + // none-NOT: __stack_chk_fail + // missing-NOT: __stack_chk_fail +} + +// The question then is: in what ways can Rust code generate array-`alloca` +// LLVM instructions? This appears to only be generated by +// rustc_codegen_ssa::traits::Builder::array_alloca() through +// rustc_codegen_ssa::mir::operand::OperandValue::store_unsized(). FWICT +// this is support for the "unsized locals" unstable feature: +// https://doc.rust-lang.org/unstable-book/language-features/unsized-locals.html. + + +// CHECK-LABEL: unsized_fn_param +#[no_mangle] +pub fn unsized_fn_param(s: [u8], l: bool, f: fn([u8])) { + let n = if l { 1 } else { 2 }; + f(*Box::<[u8]>::from(&s[0..n])); // slice-copy with Box::from + + // Even though slices are conceptually passed by-value both into this + // function and into `f()`, this is implemented with pass-by-reference + // using a suitably constructed fat-pointer (as if the functions + // accepted &[u8]). This function therefore doesn't need dynamic array + // alloca, and is therefore not protected by the `strong` or `basic` + // heuristics. + + + // all: __stack_chk_fail + // strong-NOT: __stack_chk_fail + // basic-NOT: __stack_chk_fail + // none-NOT: __stack_chk_fail + // missing-NOT: __stack_chk_fail +} + +// CHECK-LABEL: unsized_local +#[no_mangle] +pub fn unsized_local(s: &[u8], l: bool, f: fn(&mut [u8])) { + let n = if l { 1 } else { 2 }; + let mut a: [u8] = *Box::<[u8]>::from(&s[0..n]); // slice-copy with Box::from + f(&mut a); + + // This function allocates a slice as a local variable in its stack + // frame. Since the size is not a compile-time constant, an array + // alloca is required, and the function is protected by both the + // `strong` and `basic` heuristic. + + // all: __stack_chk_fail + // strong: __stack_chk_fail + // basic: __stack_chk_fail + // none-NOT: __stack_chk_fail + // missing-NOT: __stack_chk_fail +} diff --git a/src/test/assembly/stack-protector/stack-protector-target-support.rs b/src/test/assembly/stack-protector/stack-protector-target-support.rs new file mode 100644 index 0000000000000..5ba46d082e157 --- /dev/null +++ b/src/test/assembly/stack-protector/stack-protector-target-support.rs @@ -0,0 +1,286 @@ +// Test that stack smash protection code is emitted for all tier1 and tier2 +// targets, with the exception of nvptx64-nvidia-cuda +// +// revisions: r1 r2 r3 r4 r5 r6 r7 r8 r9 r10 r11 r12 r13 r14 r15 r16 r17 r18 r19 r20 r21 r22 r23 +// revisions: r24 r25 r26 r27 r28 r29 r30 r31 r32 r33 r34 r35 r36 r37 r38 r39 r40 r41 r42 r43 r44 +// revisions: r45 r46 r47 r48 r49 r50 r51 r52 r53 r54 r55 r56 r57 r58 r59 r60 r61 r62 r63 r64 r65 +// revisions: r66 r67 r68 r69 r70 r71 r72 r73 r74 r75 r76 r77 r78 r79 r80 r81 r82 r83 r84 +// assembly-output: emit-asm +// [r1] compile-flags: --target aarch64-unknown-linux-gnu +// [r1] needs-llvm-components: aarch64 +// [r2] compile-flags: --target i686-pc-windows-gnu +// [r2] needs-llvm-components: x86 +// [r3] compile-flags: --target i686-pc-windows-msvc +// [r3] needs-llvm-components: x86 +// [r4] compile-flags: --target i686-unknown-linux-gnu +// [r4] needs-llvm-components: x86 +// [r5] compile-flags: --target x86_64-apple-darwin +// [r5] needs-llvm-components: x86 +// [r6] compile-flags: --target x86_64-pc-windows-gnu +// [r6] needs-llvm-components: x86 +// [r7] compile-flags: --target x86_64-pc-windows-msvc +// [r7] needs-llvm-components: x86 +// [r8] compile-flags: --target x86_64-unknown-linux-gnu +// [r8] needs-llvm-components: x86 +// [r9] compile-flags: --target aarch64-apple-darwin +// [r9] needs-llvm-components: aarch64 +// [r10] compile-flags: --target aarch64-apple-ios +// [r10] needs-llvm-components: aarch64 +// [r11] compile-flags: --target aarch64-fuchsia +// [r11] needs-llvm-components: aarch64 +// [r12] compile-flags: --target aarch64-linux-android +// [r12] needs-llvm-components: aarch64 +// [r13] compile-flags: --target aarch64-pc-windows-msvc +// [r13] needs-llvm-components: aarch64 +// [r14] compile-flags: --target aarch64-unknown-linux-musl +// [r14] needs-llvm-components: aarch64 +// [r15] compile-flags: --target aarch64-unknown-none +// [r15] needs-llvm-components: aarch64 +// [r16] compile-flags: --target aarch64-unknown-none-softfloat +// [r16] needs-llvm-components: aarch64 +// [r17] compile-flags: --target arm-linux-androideabi +// [r17] needs-llvm-components: arm +// [r18] compile-flags: --target arm-unknown-linux-gnueabi +// [r18] needs-llvm-components: arm +// [r19] compile-flags: --target arm-unknown-linux-gnueabihf +// [r19] needs-llvm-components: arm +// [r20] compile-flags: --target arm-unknown-linux-musleabi +// [r20] needs-llvm-components: arm +// [r21] compile-flags: --target arm-unknown-linux-musleabihf +// [r21] needs-llvm-components: arm +// [r22] compile-flags: --target armebv7r-none-eabi +// [r22] needs-llvm-components: arm +// [r23] compile-flags: --target armebv7r-none-eabihf +// [r23] needs-llvm-components: arm +// [r24] compile-flags: --target armv5te-unknown-linux-gnueabi +// [r24] needs-llvm-components: arm +// [r25] compile-flags: --target armv5te-unknown-linux-musleabi +// [r25] needs-llvm-components: arm +// [r26] compile-flags: --target armv7-linux-androideabi +// [r26] needs-llvm-components: arm +// [r27] compile-flags: --target armv7a-none-eabi +// [r27] needs-llvm-components: arm +// [r28] compile-flags: --target armv7r-none-eabi +// [r28] needs-llvm-components: arm +// [r29] compile-flags: --target armv7r-none-eabihf +// [r29] needs-llvm-components: arm +// [r30] compile-flags: --target armv7-unknown-linux-gnueabi +// [r30] needs-llvm-components: arm +// [r31] compile-flags: --target armv7-unknown-linux-gnueabihf +// [r31] needs-llvm-components: arm +// [r32] compile-flags: --target armv7-unknown-linux-musleabi +// [r32] needs-llvm-components: arm +// [r33] compile-flags: --target armv7-unknown-linux-musleabihf +// [r33] needs-llvm-components: arm +// [r34] compile-flags: --target asmjs-unknown-emscripten +// [r34] needs-llvm-components: webassembly +// [r35] compile-flags: --target i586-pc-windows-msvc +// [r35] needs-llvm-components: x86 +// [r36] compile-flags: --target i586-unknown-linux-gnu +// [r36] needs-llvm-components: x86 +// [r37] compile-flags: --target i586-unknown-linux-musl +// [r37] needs-llvm-components: x86 +// [r38] compile-flags: --target i686-linux-android +// [r38] needs-llvm-components: x86 +// [r39] compile-flags: --target i686-unknown-freebsd +// [r39] needs-llvm-components: x86 +// [r40] compile-flags: --target i686-unknown-linux-musl +// [r40] needs-llvm-components: x86 +// [r41] compile-flags: --target mips-unknown-linux-gnu +// [r41] needs-llvm-components: mips +// [r42] compile-flags: --target mips-unknown-linux-musl +// [r42] needs-llvm-components: mips +// [r43] compile-flags: --target mips64-unknown-linux-gnuabi64 +// [r43] needs-llvm-components: mips +// [r44] compile-flags: --target mips64-unknown-linux-muslabi64 +// [r44] needs-llvm-components: mips +// [r45] compile-flags: --target mips64el-unknown-linux-gnuabi64 +// [r45] needs-llvm-components: mips +// [r46] compile-flags: --target mips64el-unknown-linux-muslabi64 +// [r46] needs-llvm-components: mips +// [r47] compile-flags: --target mipsel-unknown-linux-gnu +// [r47] needs-llvm-components: mips +// [r48] compile-flags: --target mipsel-unknown-linux-musl +// [r48] needs-llvm-components: mips +// [r49] compile-flags: --target nvptx64-nvidia-cuda +// [r49] needs-llvm-components: nvptx +// [r50] compile-flags: --target powerpc-unknown-linux-gnu +// [r50] needs-llvm-components: powerpc +// [r51] compile-flags: --target powerpc64-unknown-linux-gnu +// [r51] needs-llvm-components: powerpc +// [r52] compile-flags: --target powerpc64le-unknown-linux-gnu +// [r52] needs-llvm-components: powerpc +// [r53] compile-flags: --target riscv32i-unknown-none-elf +// [r53] needs-llvm-components: riscv +// [r54] compile-flags: --target riscv32imac-unknown-none-elf +// [r54] needs-llvm-components: riscv +// [r55] compile-flags:--target riscv32imc-unknown-none-elf +// [r55] needs-llvm-components: riscv +// [r56] compile-flags:--target riscv64gc-unknown-linux-gnu +// [r56] needs-llvm-components: riscv +// [r57] compile-flags:--target riscv64gc-unknown-none-elf +// [r57] needs-llvm-components: riscv +// [r58] compile-flags:--target riscv64imac-unknown-none-elf +// [r58] needs-llvm-components: riscv +// [r59] compile-flags:--target s390x-unknown-linux-gnu +// [r59] needs-llvm-components: systemz +// [r60] compile-flags:--target sparc64-unknown-linux-gnu +// [r60] needs-llvm-components: sparc +// [r61] compile-flags:--target sparcv9-sun-solaris +// [r61] needs-llvm-components: sparc +// [r62] compile-flags:--target thumbv6m-none-eabi +// [r62] needs-llvm-components: arm +// [r63] compile-flags:--target thumbv7em-none-eabi +// [r63] needs-llvm-components: arm +// [r64] compile-flags:--target thumbv7em-none-eabihf +// [r64] needs-llvm-components: arm +// [r65] compile-flags:--target thumbv7m-none-eabi +// [r65] needs-llvm-components: arm +// [r66] compile-flags:--target thumbv7neon-linux-androideabi +// [r66] needs-llvm-components: arm +// [r67] compile-flags:--target thumbv7neon-unknown-linux-gnueabihf +// [r67] needs-llvm-components: arm +// [r68] compile-flags:--target thumbv8m.base-none-eabi +// [r68] needs-llvm-components: arm +// [r69] compile-flags:--target thumbv8m.main-none-eabi +// [r69] needs-llvm-components: arm +// [r70] compile-flags:--target thumbv8m.main-none-eabihf +// [r70] needs-llvm-components: arm +// [r71] compile-flags:--target wasm32-unknown-emscripten +// [r71] needs-llvm-components: webassembly +// [r72] compile-flags:--target wasm32-unknown-unknown +// [r72] needs-llvm-components: webassembly +// [r73] compile-flags:--target wasm32-wasi +// [r73] needs-llvm-components: webassembly +// [r74] compile-flags:--target x86_64-apple-ios +// [r74] needs-llvm-components: x86 +// [r75] compile-flags:--target x86_64-fortanix-unknown-sgx +// [r75] needs-llvm-components: x86 +// [r75] min-llvm-version: 11.0.0 +// [r76] compile-flags:--target x86_64-fuchsia +// [r76] needs-llvm-components: x86 +// [r77] compile-flags:--target x86_64-linux-android +// [r77] needs-llvm-components: x86 +// [r78] compile-flags:--target x86_64-sun-solaris +// [r78] needs-llvm-components: x86 +// [r79] compile-flags:--target x86_64-unknown-freebsd +// [r79] needs-llvm-components: x86 +// [r80] compile-flags:--target x86_64-unknown-illumos +// [r80] needs-llvm-components: x86 +// [r81] compile-flags:--target x86_64-unknown-linux-gnux32 +// [r81] needs-llvm-components: x86 +// [r82] compile-flags:--target x86_64-unknown-linux-musl +// [r82] needs-llvm-components: x86 +// [r83] compile-flags:--target x86_64-unknown-netbsd +// [r83] needs-llvm-components: x86 +// [r84] compile-flags: --target x86_64-unknown-redox +// [r84] needs-llvm-components: x86 +// compile-flags: -Z stack-protector=all +// compile-flags: -C opt-level=2 + +#![crate_type = "lib"] + +#![feature(no_core, lang_items)] +#![crate_type = "lib"] +#![no_core] + +#[lang = "sized"] +trait Sized {} +#[lang = "copy"] +trait Copy {} + +#[no_mangle] +pub fn foo() { + // CHECK: foo{{:|()}} + + // MSVC does the stack checking within a stack-check function: + // r3: calll @__security_check_cookie + // r7: callq __security_check_cookie + // r13: bl __security_check_cookie + // r35: calll @__security_check_cookie + + // cuda doesn't support stack-smash protection + // r49-NOT: __security_check_cookie + // r49-NOT: __stack_chk_fail + + // Other targets do stack checking within the function, and call a failure function on error + // r1: __stack_chk_fail + // r2: __stack_chk_fail + // r4: __stack_chk_fail + // r5: __stack_chk_fail + // r6: __stack_chk_fail + // r8: __stack_chk_fail + // r9: __stack_chk_fail + // r10: __stack_chk_fail + // r11: __stack_chk_fail + // r12: __stack_chk_fail + // r14: __stack_chk_fail + // r15: __stack_chk_fail + // r16: __stack_chk_fail + // r17: __stack_chk_fail + // r18: __stack_chk_fail + // r19: __stack_chk_fail + // r20: __stack_chk_fail + // r21: __stack_chk_fail + // r22: __stack_chk_fail + // r23: __stack_chk_fail + // r24: __stack_chk_fail + // r25: __stack_chk_fail + // r26: __stack_chk_fail + // r27: __stack_chk_fail + // r28: __stack_chk_fail + // r29: __stack_chk_fail + // r30: __stack_chk_fail + // r31: __stack_chk_fail + // r32: __stack_chk_fail + // r33: __stack_chk_fail + // r34: __stack_chk_fail + // r36: __stack_chk_fail + // r37: __stack_chk_fail + // r38: __stack_chk_fail + // r39: __stack_chk_fail + // r40: __stack_chk_fail + // r41: __stack_chk_fail + // r42: __stack_chk_fail + // r43: __stack_chk_fail + // r44: __stack_chk_fail + // r45: __stack_chk_fail + // r46: __stack_chk_fail + // r47: __stack_chk_fail + // r48: __stack_chk_fail + // r50: __stack_chk_fail + // r51: __stack_chk_fail + // r52: __stack_chk_fail + // r53: __stack_chk_fail + // r54: __stack_chk_fail + // r55: __stack_chk_fail + // r56: __stack_chk_fail + // r57: __stack_chk_fail + // r58: __stack_chk_fail + // r59: __stack_chk_fail + // r60: __stack_chk_fail + // r61: __stack_chk_fail + // r62: __stack_chk_fail + // r63: __stack_chk_fail + // r64: __stack_chk_fail + // r65: __stack_chk_fail + // r66: __stack_chk_fail + // r67: __stack_chk_fail + // r68: __stack_chk_fail + // r69: __stack_chk_fail + // r70: __stack_chk_fail + // r71: __stack_chk_fail + // r72: __stack_chk_fail + // r73: __stack_chk_fail + // r74: __stack_chk_fail + // r75: __stack_chk_fail + // r76: __stack_chk_fail + // r77: __stack_chk_fail + // r78: __stack_chk_fail + // r79: __stack_chk_fail + // r80: __stack_chk_fail + // r81: __stack_chk_fail + // r82: __stack_chk_fail + // r83: __stack_chk_fail + // r84: __stack_chk_fail +} diff --git a/src/test/codegen/stack-protector.rs b/src/test/codegen/stack-protector.rs new file mode 100644 index 0000000000000..2198593eb57ae --- /dev/null +++ b/src/test/codegen/stack-protector.rs @@ -0,0 +1,33 @@ +// revisions: all strong basic none +// [all] compile-flags: -Z stack-protector=all +// [strong] compile-flags: -Z stack-protector=strong +// [basic] compile-flags: -Z stack-protector=basic + +#![crate_type = "lib"] + +#[no_mangle] +pub fn foo() { + // CHECK: @foo() unnamed_addr #0 + + // all-NOT: attributes #0 = { {{.*}} sspstrong {{.*}} } + // all-NOT: attributes #0 = { {{.*}} ssp {{.*}} } + // all: attributes #0 = { {{.*}} sspreq {{.*}} } + // all-NOT: attributes #0 = { {{.*}} sspstrong {{.*}} } + // all-NOT: attributes #0 = { {{.*}} ssp {{.*}} } + + // strong-NOT: attributes #0 = { {{.*}} sspreq {{.*}} } + // strong-NOT: attributes #0 = { {{.*}} ssp {{.*}} } + // strong: attributes #0 = { {{.*}} sspstrong {{.*}} } + // strong-NOT: attributes #0 = { {{.*}} sspreq {{.*}} } + // strong-NOT: attributes #0 = { {{.*}} ssp {{.*}} } + + // basic-NOT: attributes #0 = { {{.*}} sspreq {{.*}} } + // basic-NOT: attributes #0 = { {{.*}} sspstrong {{.*}} } + // basic: attributes #0 = { {{.*}} ssp {{.*}} } + // basic-NOT: attributes #0 = { {{.*}} sspreq {{.*}} } + // basic-NOT: attributes #0 = { {{.*}} sspstrong {{.*}} } + + // none-NOT: attributes #0 = { {{.*}} sspreq {{.*}} } + // none-NOT: attributes #0 = { {{.*}} sspstrong {{.*}} } + // none-NOT: attributes #0 = { {{.*}} ssp {{.*}} } +} diff --git a/src/test/ui/abi/stack-protector.rs b/src/test/ui/abi/stack-protector.rs new file mode 100644 index 0000000000000..24bd2e2194313 --- /dev/null +++ b/src/test/ui/abi/stack-protector.rs @@ -0,0 +1,99 @@ +// run-pass +// only-x86_64-unknown-linux-gnu +// revisions: ssp no-ssp +// [ssp] compile-flags: -Z stack-protector=all +// compile-flags: -C opt-level=2 +// compile-flags: -g + +use std::env; +use std::process::{Command, ExitStatus}; + +fn main() { + if env::args().len() == 1 { + // The test is initially run without arguments. Start the process again, + // this time *with* an argument; in this configuration, the test program + // will deliberately smash the stack. + let cur_argv0 = env::current_exe().unwrap(); + let mut child = Command::new(&cur_argv0); + child.arg("stacksmash"); + + if cfg!(ssp) { + assert_stack_smash_prevented(&mut child); + } else { + assert_stack_smashed(&mut child); + } + } else { + vulnerable_function(); + // If we return here the test is broken: it should either have called + // malicious_code() which terminates the process, or be caught by the + // stack check which also terminates the process. + panic!("TEST BUG: stack smash unsuccessful"); + } +} + +// Avoid inlining to make sure the return address is pushed to stack. +#[inline(never)] +fn vulnerable_function() { + let mut x = 5usize; + let stackaddr = &mut x as *mut usize; + let bad_code_ptr = malicious_code as usize; + // Overwrite the on-stack return address with the address of `malicious_code()`, + // thereby jumping to that function when returning from `vulnerable_function()`. + unsafe { fill(stackaddr, bad_code_ptr, 20); } +} + +// Use an uninlined function with its own stack frame to make sure that we don't +// clobber e.g. the counter or address local variable. +#[inline(never)] +unsafe fn fill(addr: *mut usize, val: usize, count: usize) { + let mut addr = addr; + for _ in 0..count { + *addr = val; + addr = addr.add(1); + } +} + +// We jump to malicious_code() having wreaked havoc with the previous stack +// frame and not setting up a new one. This function is therefore constrained, +// e.g. both println!() and std::process::exit() segfaults if called. We +// therefore keep the amount of work to a minimum by calling POSIX functions +// directly. +// The function is un-inlined just to make it possible to set a breakpoint here. +#[inline(never)] +fn malicious_code() { + let msg = [112u8, 119u8, 110u8, 101u8, 100u8, 33u8, 0u8]; // "pwned!\0" ascii + unsafe { + write(1, &msg as *const u8, msg.len()); + _exit(0); + } +} +extern "C" { + fn write(fd: i32, buf: *const u8, count: usize) -> isize; + fn _exit(status: i32) -> !; +} + + +fn assert_stack_smash_prevented(cmd: &mut Command) { + let (status, stdout, stderr) = run(cmd); + assert!(!status.success()); + assert!(stdout.is_empty()); + assert!(stderr.contains("stack smashing detected")); +} + +fn assert_stack_smashed(cmd: &mut Command) { + let (status, stdout, stderr) = run(cmd); + assert!(status.success()); + assert!(stdout.contains("pwned!")); + assert!(stderr.is_empty()); +} + + +fn run(cmd: &mut Command) -> (ExitStatus, String, String) { + let output = cmd.output().unwrap(); + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + println!("status: {}", output.status); + println!("stdout: {}", stdout); + println!("stderr: {}", stderr); + (output.status, stdout.to_string(), stderr.to_string()) +} diff --git a/src/test/ui/stack-protector/warn-stack-protector-unsupported.all.stderr b/src/test/ui/stack-protector/warn-stack-protector-unsupported.all.stderr new file mode 100644 index 0000000000000..54887715523c1 --- /dev/null +++ b/src/test/ui/stack-protector/warn-stack-protector-unsupported.all.stderr @@ -0,0 +1,4 @@ +warning: `-Z stack-protector=all` is not supported for target nvptx64-nvidia-cuda and will be ignored + +warning: 1 warning emitted + diff --git a/src/test/ui/stack-protector/warn-stack-protector-unsupported.basic.stderr b/src/test/ui/stack-protector/warn-stack-protector-unsupported.basic.stderr new file mode 100644 index 0000000000000..f7a1ee39fb9af --- /dev/null +++ b/src/test/ui/stack-protector/warn-stack-protector-unsupported.basic.stderr @@ -0,0 +1,4 @@ +warning: `-Z stack-protector=basic` is not supported for target nvptx64-nvidia-cuda and will be ignored + +warning: 1 warning emitted + diff --git a/src/test/ui/stack-protector/warn-stack-protector-unsupported.rs b/src/test/ui/stack-protector/warn-stack-protector-unsupported.rs new file mode 100644 index 0000000000000..6df5d3cd5ae1b --- /dev/null +++ b/src/test/ui/stack-protector/warn-stack-protector-unsupported.rs @@ -0,0 +1,19 @@ +// build-pass +// revisions: all strong basic +// compile-flags: --target nvptx64-nvidia-cuda +// needs-llvm-components: nvptx +// [all] compile-flags: -Z stack-protector=all +// [strong] compile-flags: -Z stack-protector=strong +// [basic] compile-flags: -Z stack-protector=basic + +#![crate_type = "lib"] +#![feature(no_core, lang_items)] +#![no_std] +#![no_core] + +#[lang = "sized"] +trait Sized {} +#[lang = "copy"] +trait Copy {} + +pub fn main(){} diff --git a/src/test/ui/stack-protector/warn-stack-protector-unsupported.strong.stderr b/src/test/ui/stack-protector/warn-stack-protector-unsupported.strong.stderr new file mode 100644 index 0000000000000..ccc2f9f2cc5b8 --- /dev/null +++ b/src/test/ui/stack-protector/warn-stack-protector-unsupported.strong.stderr @@ -0,0 +1,4 @@ +warning: `-Z stack-protector=strong` is not supported for target nvptx64-nvidia-cuda and will be ignored + +warning: 1 warning emitted +