diff options
Diffstat (limited to '')
8 files changed, 872 insertions, 3 deletions
diff --git a/tests/assembly/asm/arm-types.rs b/tests/assembly/asm/arm-types.rs index b22a26ce3..9520f9327 100644 --- a/tests/assembly/asm/arm-types.rs +++ b/tests/assembly/asm/arm-types.rs @@ -1,6 +1,7 @@ // assembly-output: emit-asm // compile-flags: --target armv7-unknown-linux-gnueabihf // compile-flags: -C target-feature=+neon +// compile-flags: -C opt-level=0 // needs-llvm-components: arm #![feature(no_core, lang_items, rustc_attrs, repr_simd)] diff --git a/tests/assembly/closure-inherit-target-feature.rs b/tests/assembly/closure-inherit-target-feature.rs index 65728a155..7acda76e2 100644 --- a/tests/assembly/closure-inherit-target-feature.rs +++ b/tests/assembly/closure-inherit-target-feature.rs @@ -1,4 +1,5 @@ // only-x86_64 +// ignore-sgx Tests incompatible with LVI mitigations // assembly-output: emit-asm // make sure the feature is not enabled at compile-time // compile-flags: -C opt-level=3 -C target-feature=-sse4.1 -C llvm-args=-x86-asm-syntax=intel @@ -22,6 +23,7 @@ pub unsafe fn sse41_blend_nofeature(x: __m128, y: __m128) -> __m128 { f(x, y) } +#[no_mangle] #[target_feature(enable = "sse4.1")] pub fn sse41_blend_noinline(x: __m128, y: __m128) -> __m128 { let f = { diff --git a/tests/assembly/dwarf5.rs b/tests/assembly/dwarf5.rs index f41e6bd55..253baafb8 100644 --- a/tests/assembly/dwarf5.rs +++ b/tests/assembly/dwarf5.rs @@ -1,6 +1,6 @@ // Makes sure that `-Z dwarf-version=5` causes `rustc` to emit DWARF version 5. // assembly-output: emit-asm -// compile-flags: -g --target x86_64-unknown-linux-gnu -Z dwarf-version=5 +// compile-flags: -g --target x86_64-unknown-linux-gnu -Z dwarf-version=5 -Copt-level=0 // needs-llvm-components: x86 #![feature(no_core, lang_items)] diff --git a/tests/assembly/libs/issue-115339-zip-arrays.rs b/tests/assembly/libs/issue-115339-zip-arrays.rs new file mode 100644 index 000000000..26b7b9770 --- /dev/null +++ b/tests/assembly/libs/issue-115339-zip-arrays.rs @@ -0,0 +1,25 @@ +// assembly-output: emit-asm +// # zen3 previously exhibited odd vectorization +// compile-flags: --crate-type=lib -Ctarget-cpu=znver3 -O +// only-x86_64 +// ignore-sgx + +use std::iter; + +// previously this produced a long chain of +// 56: vpextrb $6, %xmm0, %ecx +// 57: orb %cl, 22(%rsi) +// 58: vpextrb $7, %xmm0, %ecx +// 59: orb %cl, 23(%rsi) +// [...] + +// CHECK-LABEL: zip_arrays: +#[no_mangle] +pub fn zip_arrays(mut a: [u8; 32], b: [u8; 32]) -> [u8; 32] { + // CHECK-NOT: vpextrb + // CHECK-NOT: orb %cl + // CHECK: vorps + iter::zip(&mut a, b).for_each(|(a, b)| *a |= b); + // CHECK: retq + a +} diff --git a/tests/assembly/stack-protector/stack-protector-heuristics-effect-windows-32bit.rs b/tests/assembly/stack-protector/stack-protector-heuristics-effect-windows-32bit.rs new file mode 100644 index 000000000..fca2c85d5 --- /dev/null +++ b/tests/assembly/stack-protector/stack-protector-heuristics-effect-windows-32bit.rs @@ -0,0 +1,406 @@ +// revisions: all strong basic none missing +// assembly-output: emit-asm +// only-windows +// only-msvc +// ignore-64bit 64-bit table based SEH has slightly different behaviors than classic SEH +// [all] compile-flags: -Z stack-protector=all +// [strong] compile-flags: -Z stack-protector=strong +// [basic] compile-flags: -Z stack-protector=basic +// [none] compile-flags: -Z stack-protector=none +// 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: __security_check_cookie + // strong-NOT: __security_check_cookie + // basic-NOT: __security_check_cookie + // none-NOT: __security_check_cookie + // missing-NOT: __security_check_cookie +} + +// 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: __security_check_cookie + // strong: __security_check_cookie + // basic-NOT: __security_check_cookie + // none-NOT: __security_check_cookie + // missing-NOT: __security_check_cookie +} + +// 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: __security_check_cookie + // strong: __security_check_cookie + // basic-NOT: __security_check_cookie + // none-NOT: __security_check_cookie + // missing-NOT: __security_check_cookie +} + +// CHECK-LABEL: array_u8_small: +#[no_mangle] +pub fn array_u8_small(f: fn(*const u8)) { + let a = [0u8; 2]; + let b = [0u8; 7]; + f(&a as *const _); + f(&b as *const _); + + // Small arrays do not lead to stack protection by the 'basic' heuristic. + + // all: __security_check_cookie + // strong: __security_check_cookie + // basic-NOT: __security_check_cookie + // none-NOT: __security_check_cookie + // missing-NOT: __security_check_cookie +} + +// 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: __security_check_cookie + // strong: __security_check_cookie + // basic: __security_check_cookie + // none-NOT: __security_check_cookie + // missing-NOT: __security_check_cookie +} + +#[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: __security_check_cookie + // strong: __security_check_cookie + // basic: __security_check_cookie + // none-NOT: __security_check_cookie + // missing-NOT: __security_check_cookie +} + +// 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: __security_check_cookie + // strong: __security_check_cookie + // basic-NOT: __security_check_cookie + // none-NOT: __security_check_cookie + // missing-NOT: __security_check_cookie +} + + +// 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: __security_check_cookie + // strong-NOT: __security_check_cookie + // basic-NOT: __security_check_cookie + // none-NOT: __security_check_cookie + // missing-NOT: __security_check_cookie +} + +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: __security_check_cookie + // strong-NOT: __security_check_cookie + // basic-NOT: __security_check_cookie + // none-NOT: __security_check_cookie + // missing-NOT: __security_check_cookie +} + +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: __security_check_cookie + // strong: __security_check_cookie + // basic-NOT: __security_check_cookie + // none-NOT: __security_check_cookie + // missing-NOT: __security_check_cookie +} + +// 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: __security_check_cookie + // strong: __security_check_cookie + // basic-NOT: __security_check_cookie + // none-NOT: __security_check_cookie + // missing-NOT: __security_check_cookie +} + + +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: __security_check_cookie + // strong-NOT: __security_check_cookie + // basic-NOT: __security_check_cookie + // none-NOT: __security_check_cookie + // missing-NOT: __security_check_cookie +} + +// 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: __security_check_cookie + // strong-NOT: __security_check_cookie + // basic-NOT: __security_check_cookie + // none-NOT: __security_check_cookie + // missing-NOT: __security_check_cookie +} + + +// CHECK-LABEL: alloca_dynamic_arg +#[no_mangle] +pub fn alloca_dynamic_arg(f: fn(*mut ()), n: usize) { + f(unsafe { alloca(n) }); + + // all: __security_check_cookie + // strong-NOT: __security_check_cookie + // basic-NOT: __security_check_cookie + // none-NOT: __security_check_cookie + // missing-NOT: __security_check_cookie +} + +// 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. + + + // We should have a __security_check_cookie call in `all` and `strong` modes but + // LLVM does not support generating stack protectors in functions with funclet + // based EH personalities. + // https://github.com/llvm/llvm-project/blob/37fd3c96b917096d8a550038f6e61cdf0fc4174f/llvm/lib/CodeGen/StackProtector.cpp#L103C1-L109C4 + // all-NOT: __security_check_cookie + // strong-NOT: __security_check_cookie + + // basic-NOT: __security_check_cookie + // none-NOT: __security_check_cookie + // missing-NOT: __security_check_cookie +} + +// 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. + + // We should have a __security_check_cookie call in `all`, `strong` and `basic` modes but + // LLVM does not support generating stack protectors in functions with funclet + // based EH personalities. + // https://github.com/llvm/llvm-project/blob/37fd3c96b917096d8a550038f6e61cdf0fc4174f/llvm/lib/CodeGen/StackProtector.cpp#L103C1-L109C4 + // all-NOT: __security_check_cookie + // strong-NOT: __security_check_cookie + // basic-NOT: __security_check_cookie + + // none-NOT: __security_check_cookie + // missing-NOT: __security_check_cookie +} diff --git a/tests/assembly/stack-protector/stack-protector-heuristics-effect-windows-64bit.rs b/tests/assembly/stack-protector/stack-protector-heuristics-effect-windows-64bit.rs new file mode 100644 index 000000000..d9abf554a --- /dev/null +++ b/tests/assembly/stack-protector/stack-protector-heuristics-effect-windows-64bit.rs @@ -0,0 +1,414 @@ +// revisions: all strong basic none missing +// assembly-output: emit-asm +// only-windows +// only-msvc +// ignore-32bit 64-bit table based SEH has slightly different behaviors than classic SEH +// [all] compile-flags: -Z stack-protector=all +// [strong] compile-flags: -Z stack-protector=strong +// [basic] compile-flags: -Z stack-protector=basic +// [none] compile-flags: -Z stack-protector=none +// 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: __security_check_cookie + // strong-NOT: __security_check_cookie + // basic-NOT: __security_check_cookie + // none-NOT: __security_check_cookie + // missing-NOT: __security_check_cookie +} + +// 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: __security_check_cookie + // strong: __security_check_cookie + // basic-NOT: __security_check_cookie + // none-NOT: __security_check_cookie + // missing-NOT: __security_check_cookie +} + +// 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: __security_check_cookie + // strong: __security_check_cookie + // basic-NOT: __security_check_cookie + // none-NOT: __security_check_cookie + // missing-NOT: __security_check_cookie +} + +// CHECK-LABEL: array_u8_small: +#[no_mangle] +pub fn array_u8_small(f: fn(*const u8)) { + let a = [0u8; 2]; + let b = [0u8; 7]; + f(&a as *const _); + f(&b as *const _); + + // Small arrays do not lead to stack protection by the 'basic' heuristic. + + // all: __security_check_cookie + // strong: __security_check_cookie + // basic-NOT: __security_check_cookie + // none-NOT: __security_check_cookie + // missing-NOT: __security_check_cookie +} + +// 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: __security_check_cookie + // strong: __security_check_cookie + // basic: __security_check_cookie + // none-NOT: __security_check_cookie + // missing-NOT: __security_check_cookie +} + +#[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: __security_check_cookie + // strong: __security_check_cookie + // basic: __security_check_cookie + // none-NOT: __security_check_cookie + // missing-NOT: __security_check_cookie +} + +// 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: __security_check_cookie + // strong: __security_check_cookie + // basic-NOT: __security_check_cookie + // none-NOT: __security_check_cookie + // missing-NOT: __security_check_cookie +} + + +// CHECK-LABEL: local_string_addr_taken +#[no_mangle] +pub fn local_string_addr_taken(f: fn(&String)) { + // CHECK-DAG: .seh_endprologue + 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 + // ``` + // + + // We should have a __security_check_cookie call in `all` and `strong` modes but + // LLVM does not support generating stack protectors in functions with funclet + // based EH personalities. + // https://github.com/llvm/llvm-project/blob/37fd3c96b917096d8a550038f6e61cdf0fc4174f/llvm/lib/CodeGen/StackProtector.cpp#L103C1-L109C4 + // all-NOT: __security_check_cookie + // strong-NOT: __security_check_cookie + + // basic-NOT: __security_check_cookie + // none-NOT: __security_check_cookie + // missing-NOT: __security_check_cookie + + // CHECK-DAG: .seh_endproc +} + +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: __security_check_cookie + // strong-NOT: __security_check_cookie + // basic-NOT: __security_check_cookie + // none-NOT: __security_check_cookie + // missing-NOT: __security_check_cookie +} + +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: __security_check_cookie + // strong: __security_check_cookie + // basic-NOT: __security_check_cookie + // none-NOT: __security_check_cookie + // missing-NOT: __security_check_cookie +} + +// 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: __security_check_cookie + // strong: __security_check_cookie + // basic-NOT: __security_check_cookie + // none-NOT: __security_check_cookie + // missing-NOT: __security_check_cookie +} + + +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: __security_check_cookie + // strong-NOT: __security_check_cookie + // basic-NOT: __security_check_cookie + // none-NOT: __security_check_cookie + // missing-NOT: __security_check_cookie +} + +// 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: __security_check_cookie + // strong-NOT: __security_check_cookie + // basic-NOT: __security_check_cookie + // none-NOT: __security_check_cookie + // missing-NOT: __security_check_cookie +} + + +// CHECK-LABEL: alloca_dynamic_arg +#[no_mangle] +pub fn alloca_dynamic_arg(f: fn(*mut ()), n: usize) { + f(unsafe { alloca(n) }); + + // all: __security_check_cookie + // strong-NOT: __security_check_cookie + // basic-NOT: __security_check_cookie + // none-NOT: __security_check_cookie + // missing-NOT: __security_check_cookie +} + +// 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. + + + // We should have a __security_check_cookie call in `all` and `strong` modes but + // LLVM does not support generating stack protectors in functions with funclet + // based EH personalities. + // https://github.com/llvm/llvm-project/blob/37fd3c96b917096d8a550038f6e61cdf0fc4174f/llvm/lib/CodeGen/StackProtector.cpp#L103C1-L109C4 + // all-NOT: __security_check_cookie + // strong-NOT: __security_check_cookie + + // basic-NOT: __security_check_cookie + // none-NOT: __security_check_cookie + // missing-NOT: __security_check_cookie +} + +// 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. + + // We should have a __security_check_cookie call in `all`, `strong` and `basic` modes but + // LLVM does not support generating stack protectors in functions with funclet + // based EH personalities. + // https://github.com/llvm/llvm-project/blob/37fd3c96b917096d8a550038f6e61cdf0fc4174f/llvm/lib/CodeGen/StackProtector.cpp#L103C1-L109C4 + // all-NOT: __security_check_cookie + // strong-NOT: __security_check_cookie + // basic-NOT: __security_check_cookie + + // none-NOT: __security_check_cookie + // missing-NOT: __security_check_cookie +} diff --git a/tests/assembly/stack-protector/stack-protector-heuristics-effect.rs b/tests/assembly/stack-protector/stack-protector-heuristics-effect.rs index a7c9e4845..ca566b6e4 100644 --- a/tests/assembly/stack-protector/stack-protector-heuristics-effect.rs +++ b/tests/assembly/stack-protector/stack-protector-heuristics-effect.rs @@ -1,7 +1,7 @@ // 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-msvc stack check code uses different function names // ignore-nvptx64 stack protector is not supported // ignore-wasm32-bare // [all] compile-flags: -Z stack-protector=all @@ -9,6 +9,7 @@ // [basic] compile-flags: -Z stack-protector=basic // [none] compile-flags: -Z stack-protector=none // compile-flags: -C opt-level=2 -Z merge-functions=disabled +// min-llvm-version: 17.0.2 #![crate_type = "lib"] @@ -371,7 +372,7 @@ pub fn unsized_fn_param(s: [u8], l: bool, f: fn([u8])) { // all: __stack_chk_fail - // strong: __stack_chk_fail + // strong-NOT: __stack_chk_fail // basic-NOT: __stack_chk_fail // none-NOT: __stack_chk_fail // missing-NOT: __stack_chk_fail diff --git a/tests/assembly/x86_64-array-pair-load-store-merge.rs b/tests/assembly/x86_64-array-pair-load-store-merge.rs new file mode 100644 index 000000000..55e317e91 --- /dev/null +++ b/tests/assembly/x86_64-array-pair-load-store-merge.rs @@ -0,0 +1,20 @@ +// assembly-output: emit-asm +// compile-flags: --crate-type=lib -O -C llvm-args=-x86-asm-syntax=intel +// only-x86_64 +// ignore-sgx +// ignore-macos (manipulates rsp too) + +// Depending on various codegen choices, this might end up copying +// a `<2 x i8>`, an `i16`, or two `i8`s. +// Regardless of those choices, make sure the instructions use (2-byte) words. + +// CHECK-LABEL: array_copy_2_elements: +#[no_mangle] +pub fn array_copy_2_elements(a: &[u8; 2], p: &mut [u8; 2]) { + // CHECK-NOT: byte + // CHECK-NOT: mov + // CHECK: mov{{.+}}, word ptr + // CHECK-NEXT: mov word ptr + // CHECK-NEXT: ret + *p = *a; +} |