]> git.lizzy.rs Git - rust.git/blob - tests/assembly/stack-protector/stack-protector-heuristics-effect.rs
Rollup merge of #106797 - FawazTirmizi:dev/issues/104284, r=bjorn3
[rust.git] / tests / assembly / stack-protector / stack-protector-heuristics-effect.rs
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
11
12 #![crate_type = "lib"]
13
14 #![allow(incomplete_features)]
15
16 #![feature(unsized_locals, unsized_fn_params)]
17
18
19 // CHECK-LABEL: emptyfn:
20 #[no_mangle]
21 pub fn 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
27 }
28
29 // CHECK-LABEL: array_char
30 #[no_mangle]
31 pub fn array_char(f: fn(*const char)) {
32     let a = ['c'; 1];
33     let b = ['d'; 3];
34     let c = ['e'; 15];
35
36     f(&a as *const _);
37     f(&b as *const _);
38     f(&c as *const _);
39
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'
44     // heuristic
45     //
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.)
53
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
59 }
60
61 // CHECK-LABEL: array_u8_1
62 #[no_mangle]
63 pub fn array_u8_1(f: fn(*const u8)) {
64     let a = [0u8; 1];
65     f(&a as *const _);
66
67     // The 'strong' heuristic adds stack protection to functions with local
68     // array variables regardless of their size.
69
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
75 }
76
77 // CHECK-LABEL: array_u8_small:
78 #[no_mangle]
79 pub fn array_u8_small(f: fn(*const u8)) {
80     let a = [0u8; 2];
81     let b = [0u8; 7];
82     f(&a as *const _);
83     f(&b as *const _);
84
85     // Small arrays do not lead to stack protection by the 'basic' heuristic.
86
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
92 }
93
94 // CHECK-LABEL: array_u8_large:
95 #[no_mangle]
96 pub fn array_u8_large(f: fn(*const u8)) {
97     let a = [0u8; 9];
98     f(&a as *const _);
99
100     // Since `a` is a byte array with size greater than 8, the basic heuristic
101     // will also protect this function.
102
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
108 }
109
110 #[derive(Copy, Clone)]
111 pub struct ByteSizedNewtype(u8);
112
113 // CHECK-LABEL: array_bytesizednewtype_9:
114 #[no_mangle]
115 pub fn array_bytesizednewtype_9(f: fn(*const ByteSizedNewtype)) {
116     let a = [ByteSizedNewtype(0); 9];
117     f(&a as *const _);
118
119     // Since `a` is a byte array in the LLVM output, the basic heuristic will
120     // also protect this function.
121
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
127 }
128
129 // CHECK-LABEL: local_var_addr_used_indirectly
130 #[no_mangle]
131 pub fn local_var_addr_used_indirectly(f: fn(bool)) {
132     let a = 5;
133     let a_addr = &a as *const _ as usize;
134     f(a_addr & 0x10 == 0);
135
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++:
139     // ```
140     // cat << EOF | clang++ -O2 -fstack-protector-strong -S -x c++ - -o - | grep stack_chk
141     // #include <cstdint>
142     // void f(void (*g)(bool)) {
143     //     int32_t x;
144     //     g((reinterpret_cast<uintptr_t>(&x) & 0x10U) == 0);
145     // }
146     // EOF
147     // ```
148
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
154 }
155
156
157 // CHECK-LABEL: local_string_addr_taken
158 #[no_mangle]
159 pub fn local_string_addr_taken(f: fn(&String)) {
160     let x = String::new();
161     f(&x);
162
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.
166     //
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:
170     // ```
171     // cat <<EOF | clang++ -O2 -fstack-protector -S -x c++ - -o - | grep stack_chk
172     // #include <string>
173     // void f(void (*g)(const std::string&)) {
174     //     std::string x;
175     //     g(x);
176     // }
177     // EOF
178     // ```
179     //
180
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
186 }
187
188 pub trait SelfByRef {
189     fn f(&self) -> i32;
190 }
191
192 impl SelfByRef for i32 {
193     fn f(&self) -> i32 {
194         return self + 1;
195     }
196 }
197
198 // CHECK-LABEL: local_var_addr_taken_used_locally_only
199 #[no_mangle]
200 pub fn local_var_addr_taken_used_locally_only(factory: fn() -> i32, sink: fn(i32)) {
201     let x = factory();
202     let g = x.f();
203     sink(g);
204
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.
209
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
215 }
216
217 pub struct Gigastruct {
218     does: u64,
219     not: u64,
220     have: u64,
221     array: u64,
222     members: u64
223 }
224
225 // CHECK-LABEL: local_large_var_moved
226 #[no_mangle]
227 pub fn local_large_var_moved(f: fn(Gigastruct)) {
228     let x = Gigastruct { does: 0, not: 1, have: 2, array: 3, members: 4 };
229     f(x);
230
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:
236     // ```
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&&)) {
241     //     uint64_t x;
242     //     g(std::move(x));
243     // }
244     // EOF
245     // ```
246
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
252 }
253
254 // CHECK-LABEL: local_large_var_cloned
255 #[no_mangle]
256 pub fn local_large_var_cloned(f: fn(Gigastruct)) {
257     f(Gigastruct { does: 0, not: 1, have: 2, array: 3, members: 4 });
258
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++:
265     // ```
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)) {
271     //     g(Gigastruct{});
272     // }
273     // EOF
274     // ```
275
276
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
282 }
283
284
285 extern "C" {
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:
296     // ```
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);
302     //     g(p);
303     // }
304     // int main() { return 0; }
305     // EOF
306     // ```
307     // The following tests demonstrate that calls to an external `alloca`
308     // function in Rust also doesn't trigger stack protection.
309
310     fn alloca(size: usize) -> *mut ();
311 }
312
313 // CHECK-LABEL: alloca_small_compile_time_constant_arg
314 #[no_mangle]
315 pub fn alloca_small_compile_time_constant_arg(f: fn(*mut ())) {
316     f(unsafe { alloca(8) });
317
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
323 }
324
325 // CHECK-LABEL: alloca_large_compile_time_constant_arg
326 #[no_mangle]
327 pub fn alloca_large_compile_time_constant_arg(f: fn(*mut ())) {
328     f(unsafe { alloca(9) });
329
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
335 }
336
337
338 // CHECK-LABEL: alloca_dynamic_arg
339 #[no_mangle]
340 pub fn alloca_dynamic_arg(f: fn(*mut ()), n: usize) {
341     f(unsafe { alloca(n) });
342
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
348 }
349
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.
356
357
358 // CHECK-LABEL: unsized_fn_param
359 #[no_mangle]
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
363
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`
369     // heuristics.
370
371
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
377 }
378
379 // CHECK-LABEL: unsized_local
380 #[no_mangle]
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
384     f(&mut a);
385
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.
390
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
396 }