1 // revisions: all strong basic none missing
2 // assembly-output: emit-asm
3 // ignore-macos slightly different policy on stack protection of arrays
4 // ignore-windows stack check code uses different function names
5 // ignore-nvptx64 stack protector is not supported
6 // [all] compile-flags: -Z stack-protector=all
7 // [strong] compile-flags: -Z stack-protector=strong
8 // [basic] compile-flags: -Z stack-protector=basic
9 // [none] compile-flags: -Z stack-protector=none
10 // compile-flags: -C opt-level=2 -Z merge-functions=disabled
12 #![crate_type = "lib"]
14 #![allow(incomplete_features)]
16 #![feature(unsized_locals, unsized_fn_params)]
19 // CHECK-LABEL: emptyfn:
22 // all: __stack_chk_fail
23 // strong-NOT: __stack_chk_fail
24 // basic-NOT: __stack_chk_fail
25 // none-NOT: __stack_chk_fail
26 // missing-NOT: __stack_chk_fail
29 // CHECK-LABEL: array_char
31 pub fn array_char(f: fn(*const char)) {
40 // Any type of local array variable leads to stack protection with the
41 // "strong" heuristic. The 'basic' heuristic only adds stack protection to
42 // functions with local array variables of a byte-sized type, however. Since
43 // 'char' is 4 bytes in Rust, this function is not protected by the 'basic'
46 // (This test *also* takes the address of the local stack variables. We
47 // cannot know that this isn't what triggers the `strong` heuristic.
48 // However, the test strategy of passing the address of a stack array to an
49 // external function is sufficient to trigger the `basic` heuristic (see
50 // test `array_u8_large()`). Since the `basic` heuristic only checks for the
51 // presence of stack-local array variables, we can be confident that this
52 // test also captures this part of the `strong` heuristic specification.)
54 // all: __stack_chk_fail
55 // strong: __stack_chk_fail
56 // basic-NOT: __stack_chk_fail
57 // none-NOT: __stack_chk_fail
58 // missing-NOT: __stack_chk_fail
61 // CHECK-LABEL: array_u8_1
63 pub fn array_u8_1(f: fn(*const u8)) {
67 // The 'strong' heuristic adds stack protection to functions with local
68 // array variables regardless of their size.
70 // all: __stack_chk_fail
71 // strong: __stack_chk_fail
72 // basic-NOT: __stack_chk_fail
73 // none-NOT: __stack_chk_fail
74 // missing-NOT: __stack_chk_fail
77 // CHECK-LABEL: array_u8_small:
79 pub fn array_u8_small(f: fn(*const u8)) {
85 // Small arrays do not lead to stack protection by the 'basic' heuristic.
87 // all: __stack_chk_fail
88 // strong: __stack_chk_fail
89 // basic-NOT: __stack_chk_fail
90 // none-NOT: __stack_chk_fail
91 // missing-NOT: __stack_chk_fail
94 // CHECK-LABEL: array_u8_large:
96 pub fn array_u8_large(f: fn(*const u8)) {
100 // Since `a` is a byte array with size greater than 8, the basic heuristic
101 // will also protect this function.
103 // all: __stack_chk_fail
104 // strong: __stack_chk_fail
105 // basic: __stack_chk_fail
106 // none-NOT: __stack_chk_fail
107 // missing-NOT: __stack_chk_fail
110 #[derive(Copy, Clone)]
111 pub struct ByteSizedNewtype(u8);
113 // CHECK-LABEL: array_bytesizednewtype_9:
115 pub fn array_bytesizednewtype_9(f: fn(*const ByteSizedNewtype)) {
116 let a = [ByteSizedNewtype(0); 9];
119 // Since `a` is a byte array in the LLVM output, the basic heuristic will
120 // also protect this function.
122 // all: __stack_chk_fail
123 // strong: __stack_chk_fail
124 // basic: __stack_chk_fail
125 // none-NOT: __stack_chk_fail
126 // missing-NOT: __stack_chk_fail
129 // CHECK-LABEL: local_var_addr_used_indirectly
131 pub fn local_var_addr_used_indirectly(f: fn(bool)) {
133 let a_addr = &a as *const _ as usize;
134 f(a_addr & 0x10 == 0);
136 // This function takes the address of a local variable taken. Although this
137 // address is never used as a way to refer to stack memory, the `strong`
138 // heuristic adds stack smash protection. This is also the case in C++:
140 // cat << EOF | clang++ -O2 -fstack-protector-strong -S -x c++ - -o - | grep stack_chk
141 // #include <cstdint>
142 // void f(void (*g)(bool)) {
144 // g((reinterpret_cast<uintptr_t>(&x) & 0x10U) == 0);
149 // all: __stack_chk_fail
150 // strong: __stack_chk_fail
151 // basic-NOT: __stack_chk_fail
152 // none-NOT: __stack_chk_fail
153 // missing-NOT: __stack_chk_fail
157 // CHECK-LABEL: local_string_addr_taken
159 pub fn local_string_addr_taken(f: fn(&String)) {
160 let x = String::new();
163 // Taking the address of the local variable `x` leads to stack smash
164 // protection with the `strong` heuristic, but not with the `basic`
165 // heuristic. It does not matter that the reference is not mut.
167 // An interesting note is that a similar function in C++ *would* be
168 // protected by the `basic` heuristic, because `std::string` has a char
169 // array internally as a small object optimization:
171 // cat <<EOF | clang++ -O2 -fstack-protector -S -x c++ - -o - | grep stack_chk
173 // void f(void (*g)(const std::string&)) {
181 // all: __stack_chk_fail
182 // strong: __stack_chk_fail
183 // basic-NOT: __stack_chk_fail
184 // none-NOT: __stack_chk_fail
185 // missing-NOT: __stack_chk_fail
188 pub trait SelfByRef {
192 impl SelfByRef for i32 {
198 // CHECK-LABEL: local_var_addr_taken_used_locally_only
200 pub fn local_var_addr_taken_used_locally_only(factory: fn() -> i32, sink: fn(i32)) {
205 // Even though the local variable conceptually has its address taken, as
206 // it's passed by reference to the trait function, the use of the reference
207 // is easily inlined. There is therefore no stack smash protection even with
208 // the `strong` heuristic.
210 // all: __stack_chk_fail
211 // strong-NOT: __stack_chk_fail
212 // basic-NOT: __stack_chk_fail
213 // none-NOT: __stack_chk_fail
214 // missing-NOT: __stack_chk_fail
217 pub struct Gigastruct {
225 // CHECK-LABEL: local_large_var_moved
227 pub fn local_large_var_moved(f: fn(Gigastruct)) {
228 let x = Gigastruct { does: 0, not: 1, have: 2, array: 3, members: 4 };
231 // Even though the local variable conceptually doesn't have its address
232 // taken, it's so large that the "move" is implemented with a reference to a
233 // stack-local variable in the ABI. Consequently, this function *is*
234 // protected by the `strong` heuristic. This is also the case for
235 // rvalue-references in C++, regardless of struct size:
237 // cat <<EOF | clang++ -O2 -fstack-protector-strong -S -x c++ - -o - | grep stack_chk
238 // #include <cstdint>
239 // #include <utility>
240 // void f(void (*g)(uint64_t&&)) {
247 // all: __stack_chk_fail
248 // strong: __stack_chk_fail
249 // basic-NOT: __stack_chk_fail
250 // none-NOT: __stack_chk_fail
251 // missing-NOT: __stack_chk_fail
254 // CHECK-LABEL: local_large_var_cloned
256 pub fn local_large_var_cloned(f: fn(Gigastruct)) {
257 f(Gigastruct { does: 0, not: 1, have: 2, array: 3, members: 4 });
259 // A new instance of `Gigastruct` is passed to `f()`, without any apparent
260 // connection to this stack frame. Still, since instances of `Gigastruct`
261 // are sufficiently large, it is allocated in the caller stack frame and
262 // passed as a pointer. As such, this function is *also* protected by the
263 // `strong` heuristic, just like `local_large_var_moved`. This is also the
264 // case for pass-by-value of sufficiently large structs in C++:
266 // cat <<EOF | clang++ -O2 -fstack-protector-strong -S -x c++ - -o - | grep stack_chk
267 // #include <cstdint>
268 // #include <utility>
269 // struct Gigastruct { uint64_t a, b, c, d, e; };
270 // void f(void (*g)(Gigastruct)) {
277 // all: __stack_chk_fail
278 // strong: __stack_chk_fail
279 // basic-NOT: __stack_chk_fail
280 // none-NOT: __stack_chk_fail
281 // missing-NOT: __stack_chk_fail
286 // A call to an external `alloca` function is *not* recognized as an
287 // `alloca(3)` operation. This function is a compiler built-in, as the
288 // man page explains. Clang translates it to an LLVM `alloca`
289 // instruction with a count argument, which is also what the LLVM stack
290 // protector heuristics looks for. The man page for `alloca(3)` details
291 // a way to avoid using the compiler built-in: pass a -std=c11
292 // argument, *and* don't include <alloca.h>. Though this leads to an
293 // external alloca() function being called, it doesn't lead to stack
294 // protection being included. It even fails with a linker error
295 // "undefined reference to `alloca'". Example:
297 // cat<<EOF | clang -fstack-protector-strong -x c -std=c11 - -o /dev/null
298 // #include <stdlib.h>
299 // void * alloca(size_t);
300 // void f(void (*g)(void*)) {
301 // void * p = alloca(10);
304 // int main() { return 0; }
307 // The following tests demonstrate that calls to an external `alloca`
308 // function in Rust also doesn't trigger stack protection.
310 fn alloca(size: usize) -> *mut ();
313 // CHECK-LABEL: alloca_small_compile_time_constant_arg
315 pub fn alloca_small_compile_time_constant_arg(f: fn(*mut ())) {
316 f(unsafe { alloca(8) });
318 // all: __stack_chk_fail
319 // strong-NOT: __stack_chk_fail
320 // basic-NOT: __stack_chk_fail
321 // none-NOT: __stack_chk_fail
322 // missing-NOT: __stack_chk_fail
325 // CHECK-LABEL: alloca_large_compile_time_constant_arg
327 pub fn alloca_large_compile_time_constant_arg(f: fn(*mut ())) {
328 f(unsafe { alloca(9) });
330 // all: __stack_chk_fail
331 // strong-NOT: __stack_chk_fail
332 // basic-NOT: __stack_chk_fail
333 // none-NOT: __stack_chk_fail
334 // missing-NOT: __stack_chk_fail
338 // CHECK-LABEL: alloca_dynamic_arg
340 pub fn alloca_dynamic_arg(f: fn(*mut ()), n: usize) {
341 f(unsafe { alloca(n) });
343 // all: __stack_chk_fail
344 // strong-NOT: __stack_chk_fail
345 // basic-NOT: __stack_chk_fail
346 // none-NOT: __stack_chk_fail
347 // missing-NOT: __stack_chk_fail
350 // The question then is: in what ways can Rust code generate array-`alloca`
351 // LLVM instructions? This appears to only be generated by
352 // rustc_codegen_ssa::traits::Builder::array_alloca() through
353 // rustc_codegen_ssa::mir::operand::OperandValue::store_unsized(). FWICT
354 // this is support for the "unsized locals" unstable feature:
355 // https://doc.rust-lang.org/unstable-book/language-features/unsized-locals.html.
358 // CHECK-LABEL: unsized_fn_param
360 pub fn unsized_fn_param(s: [u8], l: bool, f: fn([u8])) {
361 let n = if l { 1 } else { 2 };
362 f(*Box::<[u8]>::from(&s[0..n])); // slice-copy with Box::from
364 // Even though slices are conceptually passed by-value both into this
365 // function and into `f()`, this is implemented with pass-by-reference
366 // using a suitably constructed fat-pointer (as if the functions
367 // accepted &[u8]). This function therefore doesn't need dynamic array
368 // alloca, and is therefore not protected by the `strong` or `basic`
372 // all: __stack_chk_fail
373 // strong-NOT: __stack_chk_fail
374 // basic-NOT: __stack_chk_fail
375 // none-NOT: __stack_chk_fail
376 // missing-NOT: __stack_chk_fail
379 // CHECK-LABEL: unsized_local
381 pub fn unsized_local(s: &[u8], l: bool, f: fn(&mut [u8])) {
382 let n = if l { 1 } else { 2 };
383 let mut a: [u8] = *Box::<[u8]>::from(&s[0..n]); // slice-copy with Box::from
386 // This function allocates a slice as a local variable in its stack
387 // frame. Since the size is not a compile-time constant, an array
388 // alloca is required, and the function is protected by both the
389 // `strong` and `basic` heuristic.
391 // all: __stack_chk_fail
392 // strong: __stack_chk_fail
393 // basic: __stack_chk_fail
394 // none-NOT: __stack_chk_fail
395 // missing-NOT: __stack_chk_fail