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 <nikic@php.net>

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.
This commit is contained in:
Benjamin A. Bjørnseth 2021-04-06 21:37:49 +02:00
parent 883a241c08
commit bb9dee95ed
20 changed files with 1017 additions and 12 deletions

View file

@ -12,7 +12,7 @@
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(
@ -271,6 +282,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);

View file

@ -288,6 +288,31 @@ fn print(&self, req: PrintRequest, sess: &Session) {
}
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.
none
Do not generate stack canaries.
"#
);
}
req => llvm_util::print(req, sess),
}
}

View file

@ -166,6 +166,9 @@ pub enum Attribute {
InaccessibleMemOnly = 27,
SanitizeHWAddress = 28,
WillReturn = 29,
StackProtectReq = 30,
StackProtectStrong = 31,
StackProtect = 32,
}
/// LLVMIntPredicate

View file

@ -736,7 +736,12 @@ fn print_crate_info(
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

View file

@ -20,7 +20,9 @@
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;
@ -713,8 +715,8 @@ macro_rules! tracked {
// 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()));
@ -731,8 +733,8 @@ macro_rules! tracked {
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);
@ -764,7 +766,6 @@ macro_rules! tracked {
tracked!(relax_elf_relocations, Some(true));
tracked!(relro_level, Some(RelroLevel::Full));
tracked!(remap_cwd_prefix, Some(PathBuf::from("abc")));
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);
@ -772,15 +773,17 @@ macro_rules! tracked {
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);

View file

@ -79,6 +79,9 @@ enum LLVMRustAttribute {
InaccessibleMemOnly = 27,
SanitizeHWAddress = 28,
WillReturn = 29,
StackProtectReq = 30,
StackProtectStrong = 31,
StackProtect = 32,
};
typedef struct OpaqueRustString *RustStringRef;

View file

@ -213,6 +213,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");
}

View file

@ -538,6 +538,7 @@ pub enum PrintRequest {
TlsModels,
TargetSpec,
NativeStaticLibs,
StackProtectorStrategies,
}
#[derive(Copy, Clone)]
@ -1110,8 +1111,8 @@ pub fn rustc_short_optgroups() -> Vec<RustcOptGroup> {
"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"),
@ -1527,6 +1528,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
@ -2494,7 +2496,9 @@ pub fn needs_analysis(&self) -> bool {
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;
@ -2568,6 +2572,7 @@ fn hash(
Edition,
LinkerPluginLto,
SplitDebuginfo,
StackProtector,
SwitchWithOptPath,
SymbolManglingVersion,
SourceFileHashAlgorithm,

View file

@ -5,7 +5,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;
@ -385,6 +387,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 {
@ -917,6 +921,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! {
@ -1330,6 +1342,8 @@ mod parse {
"exclude spans when debug-printing compiler state (default: no)"),
src_hash_algorithm: Option<SourceFileHashAlgorithm> = (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],

View file

@ -27,7 +27,9 @@
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;
@ -732,6 +734,14 @@ pub fn split_debuginfo(&self) -> SplitDebuginfo {
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
}
@ -1411,6 +1421,15 @@ fn validate_commandline_args_with_session_available(sess: &Session) {
sess.err("`-Zsanitizer=cfi` requires `-Clto`");
}
}
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.

View file

@ -712,6 +712,59 @@ fn to_json(&self) -> Json {
}
}
/// 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<StackProtector, ()> {
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;)+
@ -1360,6 +1413,10 @@ pub struct TargetOptions {
/// Whether or not the DWARF `.debug_aranges` section should be generated.
pub generate_arange_section: bool,
/// 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 {
@ -1466,6 +1523,7 @@ fn default() -> TargetOptions {
default_adjusted_cabi: None,
c_enum_min_bits: 32,
generate_arange_section: true,
supports_stack_protector: true,
}
}
}
@ -2052,6 +2110,7 @@ macro_rules! key {
key!(default_adjusted_cabi, Option<Abi>)?;
key!(c_enum_min_bits, u64);
key!(generate_arange_section, bool);
key!(supports_stack_protector, bool);
if base.is_builtin {
// This can cause unfortunate ICEs later down the line.
@ -2292,6 +2351,7 @@ macro_rules! target_option_val {
target_option_val!(supported_sanitizers);
target_option_val!(c_enum_min_bits);
target_option_val!(generate_arange_section);
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());

View file

@ -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()
},
}

View file

@ -0,0 +1,396 @@
// revisions: all strong basic none missing
// assembly-output: emit-asm
// ignore-macos slightly different policy on stack protection of arrays
// ignore-windows stack check code uses different function names
// ignore-nvptx64 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 <cstdint>
// void f(void (*g)(bool)) {
// int32_t x;
// g((reinterpret_cast<uintptr_t>(&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 <<EOF | clang++ -O2 -fstack-protector -S -x c++ - -o - | grep stack_chk
// #include <string>
// 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 <<EOF | clang++ -O2 -fstack-protector-strong -S -x c++ - -o - | grep stack_chk
// #include <cstdint>
// #include <utility>
// 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 <<EOF | clang++ -O2 -fstack-protector-strong -S -x c++ - -o - | grep stack_chk
// #include <cstdint>
// #include <utility>
// 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 <alloca.h>. 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<<EOF | clang -fstack-protector-strong -x c -std=c11 - -o /dev/null
// #include <stdlib.h>
// 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
}

View file

@ -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
}

View file

@ -0,0 +1,34 @@
// revisions: all strong basic none
// ignore-nvptx64 stack protector not supported
// [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 {{.*}} }
}

View file

@ -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())
}

View file

@ -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

View file

@ -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

View file

@ -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(){}

View file

@ -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