Skip to content

Commit a01382d

Browse files
committedOct 19, 2023
Auto merge of #116037 - wesleywiser:stack_protector_test_windows, r=cuviper
Add `-Zstack-protector` test for Windows targets Add variants of the `stack-protector-heuristics-effect.rs` test for 32-bit and 64-bit MSVC Windows and update the original test to run on GNU Windows targets. I added two tests instead of trying to modify the original because: - MSVC uses a different function name (`__security_check_cookie` to perform the test rather than doing the test inline and calling `__stack_chk_fail`). - LLVM's stack protection pass doesn't currently support generating checks for [frames with funclet based EH personality](https://github.com/llvm/llvm-project/blob/37fd3c96b917096d8a550038f6e61cdf0fc4174f/llvm/lib/CodeGen/StackProtector.cpp#L103C1-L109C4). - 32-bit Windows uses classic EH while 64-bit Windows uses table-based EH which results in slightly different codegen. [CI run with test passing on {i686,x86_64}-{msvc,mingw}](https://github.com/rust-lang/rust/actions/runs/6275450644/job/17042958375?pr=116037)
2 parents 36b61e5 + 316c9a9 commit a01382d

3 files changed

+821
-1
lines changed
 
Lines changed: 406 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,406 @@
1+
// revisions: all strong basic none missing
2+
// assembly-output: emit-asm
3+
// only-windows
4+
// only-msvc
5+
// ignore-64bit 64-bit table based SEH has slightly different behaviors than classic SEH
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: __security_check_cookie
23+
// strong-NOT: __security_check_cookie
24+
// basic-NOT: __security_check_cookie
25+
// none-NOT: __security_check_cookie
26+
// missing-NOT: __security_check_cookie
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: __security_check_cookie
55+
// strong: __security_check_cookie
56+
// basic-NOT: __security_check_cookie
57+
// none-NOT: __security_check_cookie
58+
// missing-NOT: __security_check_cookie
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: __security_check_cookie
71+
// strong: __security_check_cookie
72+
// basic-NOT: __security_check_cookie
73+
// none-NOT: __security_check_cookie
74+
// missing-NOT: __security_check_cookie
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: __security_check_cookie
88+
// strong: __security_check_cookie
89+
// basic-NOT: __security_check_cookie
90+
// none-NOT: __security_check_cookie
91+
// missing-NOT: __security_check_cookie
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: __security_check_cookie
104+
// strong: __security_check_cookie
105+
// basic: __security_check_cookie
106+
// none-NOT: __security_check_cookie
107+
// missing-NOT: __security_check_cookie
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: __security_check_cookie
123+
// strong: __security_check_cookie
124+
// basic: __security_check_cookie
125+
// none-NOT: __security_check_cookie
126+
// missing-NOT: __security_check_cookie
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: __security_check_cookie
150+
// strong: __security_check_cookie
151+
// basic-NOT: __security_check_cookie
152+
// none-NOT: __security_check_cookie
153+
// missing-NOT: __security_check_cookie
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: __security_check_cookie
182+
// strong-NOT: __security_check_cookie
183+
// basic-NOT: __security_check_cookie
184+
// none-NOT: __security_check_cookie
185+
// missing-NOT: __security_check_cookie
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: __security_check_cookie
211+
// strong-NOT: __security_check_cookie
212+
// basic-NOT: __security_check_cookie
213+
// none-NOT: __security_check_cookie
214+
// missing-NOT: __security_check_cookie
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: __security_check_cookie
248+
// strong: __security_check_cookie
249+
// basic-NOT: __security_check_cookie
250+
// none-NOT: __security_check_cookie
251+
// missing-NOT: __security_check_cookie
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: __security_check_cookie
278+
// strong: __security_check_cookie
279+
// basic-NOT: __security_check_cookie
280+
// none-NOT: __security_check_cookie
281+
// missing-NOT: __security_check_cookie
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: __security_check_cookie
319+
// strong-NOT: __security_check_cookie
320+
// basic-NOT: __security_check_cookie
321+
// none-NOT: __security_check_cookie
322+
// missing-NOT: __security_check_cookie
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: __security_check_cookie
331+
// strong-NOT: __security_check_cookie
332+
// basic-NOT: __security_check_cookie
333+
// none-NOT: __security_check_cookie
334+
// missing-NOT: __security_check_cookie
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: __security_check_cookie
344+
// strong-NOT: __security_check_cookie
345+
// basic-NOT: __security_check_cookie
346+
// none-NOT: __security_check_cookie
347+
// missing-NOT: __security_check_cookie
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+
// We should have a __security_check_cookie call in `all` and `strong` modes but
373+
// LLVM does not support generating stack protectors in functions with funclet
374+
// based EH personalities.
375+
// https://github.com/llvm/llvm-project/blob/37fd3c96b917096d8a550038f6e61cdf0fc4174f/llvm/lib/CodeGen/StackProtector.cpp#L103C1-L109C4
376+
// all-NOT: __security_check_cookie
377+
// strong-NOT: __security_check_cookie
378+
379+
// basic-NOT: __security_check_cookie
380+
// none-NOT: __security_check_cookie
381+
// missing-NOT: __security_check_cookie
382+
}
383+
384+
// CHECK-LABEL: unsized_local
385+
#[no_mangle]
386+
pub fn unsized_local(s: &[u8], l: bool, f: fn(&mut [u8])) {
387+
let n = if l { 1 } else { 2 };
388+
let mut a: [u8] = *Box::<[u8]>::from(&s[0..n]); // slice-copy with Box::from
389+
f(&mut a);
390+
391+
// This function allocates a slice as a local variable in its stack
392+
// frame. Since the size is not a compile-time constant, an array
393+
// alloca is required, and the function is protected by both the
394+
// `strong` and `basic` heuristic.
395+
396+
// We should have a __security_check_cookie call in `all`, `strong` and `basic` modes but
397+
// LLVM does not support generating stack protectors in functions with funclet
398+
// based EH personalities.
399+
// https://github.com/llvm/llvm-project/blob/37fd3c96b917096d8a550038f6e61cdf0fc4174f/llvm/lib/CodeGen/StackProtector.cpp#L103C1-L109C4
400+
// all-NOT: __security_check_cookie
401+
// strong-NOT: __security_check_cookie
402+
// basic-NOT: __security_check_cookie
403+
404+
// none-NOT: __security_check_cookie
405+
// missing-NOT: __security_check_cookie
406+
}
Lines changed: 414 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,414 @@
1+
// revisions: all strong basic none missing
2+
// assembly-output: emit-asm
3+
// only-windows
4+
// only-msvc
5+
// ignore-32bit 64-bit table based SEH has slightly different behaviors than classic SEH
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: __security_check_cookie
23+
// strong-NOT: __security_check_cookie
24+
// basic-NOT: __security_check_cookie
25+
// none-NOT: __security_check_cookie
26+
// missing-NOT: __security_check_cookie
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: __security_check_cookie
55+
// strong: __security_check_cookie
56+
// basic-NOT: __security_check_cookie
57+
// none-NOT: __security_check_cookie
58+
// missing-NOT: __security_check_cookie
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: __security_check_cookie
71+
// strong: __security_check_cookie
72+
// basic-NOT: __security_check_cookie
73+
// none-NOT: __security_check_cookie
74+
// missing-NOT: __security_check_cookie
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: __security_check_cookie
88+
// strong: __security_check_cookie
89+
// basic-NOT: __security_check_cookie
90+
// none-NOT: __security_check_cookie
91+
// missing-NOT: __security_check_cookie
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: __security_check_cookie
104+
// strong: __security_check_cookie
105+
// basic: __security_check_cookie
106+
// none-NOT: __security_check_cookie
107+
// missing-NOT: __security_check_cookie
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: __security_check_cookie
123+
// strong: __security_check_cookie
124+
// basic: __security_check_cookie
125+
// none-NOT: __security_check_cookie
126+
// missing-NOT: __security_check_cookie
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: __security_check_cookie
150+
// strong: __security_check_cookie
151+
// basic-NOT: __security_check_cookie
152+
// none-NOT: __security_check_cookie
153+
// missing-NOT: __security_check_cookie
154+
}
155+
156+
157+
// CHECK-LABEL: local_string_addr_taken
158+
#[no_mangle]
159+
pub fn local_string_addr_taken(f: fn(&String)) {
160+
// CHECK-DAG: .seh_endprologue
161+
let x = String::new();
162+
f(&x);
163+
164+
// Taking the address of the local variable `x` leads to stack smash
165+
// protection with the `strong` heuristic, but not with the `basic`
166+
// heuristic. It does not matter that the reference is not mut.
167+
//
168+
// An interesting note is that a similar function in C++ *would* be
169+
// protected by the `basic` heuristic, because `std::string` has a char
170+
// array internally as a small object optimization:
171+
// ```
172+
// cat <<EOF | clang++ -O2 -fstack-protector -S -x c++ - -o - | grep stack_chk
173+
// #include <string>
174+
// void f(void (*g)(const std::string&)) {
175+
// std::string x;
176+
// g(x);
177+
// }
178+
// EOF
179+
// ```
180+
//
181+
182+
// We should have a __security_check_cookie call in `all` and `strong` modes but
183+
// LLVM does not support generating stack protectors in functions with funclet
184+
// based EH personalities.
185+
// https://github.com/llvm/llvm-project/blob/37fd3c96b917096d8a550038f6e61cdf0fc4174f/llvm/lib/CodeGen/StackProtector.cpp#L103C1-L109C4
186+
// all-NOT: __security_check_cookie
187+
// strong-NOT: __security_check_cookie
188+
189+
// basic-NOT: __security_check_cookie
190+
// none-NOT: __security_check_cookie
191+
// missing-NOT: __security_check_cookie
192+
193+
// CHECK-DAG: .seh_endproc
194+
}
195+
196+
pub trait SelfByRef {
197+
fn f(&self) -> i32;
198+
}
199+
200+
impl SelfByRef for i32 {
201+
fn f(&self) -> i32 {
202+
return self + 1;
203+
}
204+
}
205+
206+
// CHECK-LABEL: local_var_addr_taken_used_locally_only
207+
#[no_mangle]
208+
pub fn local_var_addr_taken_used_locally_only(factory: fn() -> i32, sink: fn(i32)) {
209+
let x = factory();
210+
let g = x.f();
211+
sink(g);
212+
213+
// Even though the local variable conceptually has its address taken, as
214+
// it's passed by reference to the trait function, the use of the reference
215+
// is easily inlined. There is therefore no stack smash protection even with
216+
// the `strong` heuristic.
217+
218+
// all: __security_check_cookie
219+
// strong-NOT: __security_check_cookie
220+
// basic-NOT: __security_check_cookie
221+
// none-NOT: __security_check_cookie
222+
// missing-NOT: __security_check_cookie
223+
}
224+
225+
pub struct Gigastruct {
226+
does: u64,
227+
not: u64,
228+
have: u64,
229+
array: u64,
230+
members: u64
231+
}
232+
233+
// CHECK-LABEL: local_large_var_moved
234+
#[no_mangle]
235+
pub fn local_large_var_moved(f: fn(Gigastruct)) {
236+
let x = Gigastruct { does: 0, not: 1, have: 2, array: 3, members: 4 };
237+
f(x);
238+
239+
// Even though the local variable conceptually doesn't have its address
240+
// taken, it's so large that the "move" is implemented with a reference to a
241+
// stack-local variable in the ABI. Consequently, this function *is*
242+
// protected by the `strong` heuristic. This is also the case for
243+
// rvalue-references in C++, regardless of struct size:
244+
// ```
245+
// cat <<EOF | clang++ -O2 -fstack-protector-strong -S -x c++ - -o - | grep stack_chk
246+
// #include <cstdint>
247+
// #include <utility>
248+
// void f(void (*g)(uint64_t&&)) {
249+
// uint64_t x;
250+
// g(std::move(x));
251+
// }
252+
// EOF
253+
// ```
254+
255+
// all: __security_check_cookie
256+
// strong: __security_check_cookie
257+
// basic-NOT: __security_check_cookie
258+
// none-NOT: __security_check_cookie
259+
// missing-NOT: __security_check_cookie
260+
}
261+
262+
// CHECK-LABEL: local_large_var_cloned
263+
#[no_mangle]
264+
pub fn local_large_var_cloned(f: fn(Gigastruct)) {
265+
f(Gigastruct { does: 0, not: 1, have: 2, array: 3, members: 4 });
266+
267+
// A new instance of `Gigastruct` is passed to `f()`, without any apparent
268+
// connection to this stack frame. Still, since instances of `Gigastruct`
269+
// are sufficiently large, it is allocated in the caller stack frame and
270+
// passed as a pointer. As such, this function is *also* protected by the
271+
// `strong` heuristic, just like `local_large_var_moved`. This is also the
272+
// case for pass-by-value of sufficiently large structs in C++:
273+
// ```
274+
// cat <<EOF | clang++ -O2 -fstack-protector-strong -S -x c++ - -o - | grep stack_chk
275+
// #include <cstdint>
276+
// #include <utility>
277+
// struct Gigastruct { uint64_t a, b, c, d, e; };
278+
// void f(void (*g)(Gigastruct)) {
279+
// g(Gigastruct{});
280+
// }
281+
// EOF
282+
// ```
283+
284+
285+
// all: __security_check_cookie
286+
// strong: __security_check_cookie
287+
// basic-NOT: __security_check_cookie
288+
// none-NOT: __security_check_cookie
289+
// missing-NOT: __security_check_cookie
290+
}
291+
292+
293+
extern "C" {
294+
// A call to an external `alloca` function is *not* recognized as an
295+
// `alloca(3)` operation. This function is a compiler built-in, as the
296+
// man page explains. Clang translates it to an LLVM `alloca`
297+
// instruction with a count argument, which is also what the LLVM stack
298+
// protector heuristics looks for. The man page for `alloca(3)` details
299+
// a way to avoid using the compiler built-in: pass a -std=c11
300+
// argument, *and* don't include <alloca.h>. Though this leads to an
301+
// external alloca() function being called, it doesn't lead to stack
302+
// protection being included. It even fails with a linker error
303+
// "undefined reference to `alloca'". Example:
304+
// ```
305+
// cat<<EOF | clang -fstack-protector-strong -x c -std=c11 - -o /dev/null
306+
// #include <stdlib.h>
307+
// void * alloca(size_t);
308+
// void f(void (*g)(void*)) {
309+
// void * p = alloca(10);
310+
// g(p);
311+
// }
312+
// int main() { return 0; }
313+
// EOF
314+
// ```
315+
// The following tests demonstrate that calls to an external `alloca`
316+
// function in Rust also doesn't trigger stack protection.
317+
318+
fn alloca(size: usize) -> *mut ();
319+
}
320+
321+
// CHECK-LABEL: alloca_small_compile_time_constant_arg
322+
#[no_mangle]
323+
pub fn alloca_small_compile_time_constant_arg(f: fn(*mut ())) {
324+
f(unsafe { alloca(8) });
325+
326+
// all: __security_check_cookie
327+
// strong-NOT: __security_check_cookie
328+
// basic-NOT: __security_check_cookie
329+
// none-NOT: __security_check_cookie
330+
// missing-NOT: __security_check_cookie
331+
}
332+
333+
// CHECK-LABEL: alloca_large_compile_time_constant_arg
334+
#[no_mangle]
335+
pub fn alloca_large_compile_time_constant_arg(f: fn(*mut ())) {
336+
f(unsafe { alloca(9) });
337+
338+
// all: __security_check_cookie
339+
// strong-NOT: __security_check_cookie
340+
// basic-NOT: __security_check_cookie
341+
// none-NOT: __security_check_cookie
342+
// missing-NOT: __security_check_cookie
343+
}
344+
345+
346+
// CHECK-LABEL: alloca_dynamic_arg
347+
#[no_mangle]
348+
pub fn alloca_dynamic_arg(f: fn(*mut ()), n: usize) {
349+
f(unsafe { alloca(n) });
350+
351+
// all: __security_check_cookie
352+
// strong-NOT: __security_check_cookie
353+
// basic-NOT: __security_check_cookie
354+
// none-NOT: __security_check_cookie
355+
// missing-NOT: __security_check_cookie
356+
}
357+
358+
// The question then is: in what ways can Rust code generate array-`alloca`
359+
// LLVM instructions? This appears to only be generated by
360+
// rustc_codegen_ssa::traits::Builder::array_alloca() through
361+
// rustc_codegen_ssa::mir::operand::OperandValue::store_unsized(). FWICT
362+
// this is support for the "unsized locals" unstable feature:
363+
// https://doc.rust-lang.org/unstable-book/language-features/unsized-locals.html.
364+
365+
366+
// CHECK-LABEL: unsized_fn_param
367+
#[no_mangle]
368+
pub fn unsized_fn_param(s: [u8], l: bool, f: fn([u8])) {
369+
let n = if l { 1 } else { 2 };
370+
f(*Box::<[u8]>::from(&s[0..n])); // slice-copy with Box::from
371+
372+
// Even though slices are conceptually passed by-value both into this
373+
// function and into `f()`, this is implemented with pass-by-reference
374+
// using a suitably constructed fat-pointer (as if the functions
375+
// accepted &[u8]). This function therefore doesn't need dynamic array
376+
// alloca, and is therefore not protected by the `strong` or `basic`
377+
// heuristics.
378+
379+
380+
// We should have a __security_check_cookie call in `all` and `strong` modes but
381+
// LLVM does not support generating stack protectors in functions with funclet
382+
// based EH personalities.
383+
// https://github.com/llvm/llvm-project/blob/37fd3c96b917096d8a550038f6e61cdf0fc4174f/llvm/lib/CodeGen/StackProtector.cpp#L103C1-L109C4
384+
// all-NOT: __security_check_cookie
385+
// strong-NOT: __security_check_cookie
386+
387+
// basic-NOT: __security_check_cookie
388+
// none-NOT: __security_check_cookie
389+
// missing-NOT: __security_check_cookie
390+
}
391+
392+
// CHECK-LABEL: unsized_local
393+
#[no_mangle]
394+
pub fn unsized_local(s: &[u8], l: bool, f: fn(&mut [u8])) {
395+
let n = if l { 1 } else { 2 };
396+
let mut a: [u8] = *Box::<[u8]>::from(&s[0..n]); // slice-copy with Box::from
397+
f(&mut a);
398+
399+
// This function allocates a slice as a local variable in its stack
400+
// frame. Since the size is not a compile-time constant, an array
401+
// alloca is required, and the function is protected by both the
402+
// `strong` and `basic` heuristic.
403+
404+
// We should have a __security_check_cookie call in `all`, `strong` and `basic` modes but
405+
// LLVM does not support generating stack protectors in functions with funclet
406+
// based EH personalities.
407+
// https://github.com/llvm/llvm-project/blob/37fd3c96b917096d8a550038f6e61cdf0fc4174f/llvm/lib/CodeGen/StackProtector.cpp#L103C1-L109C4
408+
// all-NOT: __security_check_cookie
409+
// strong-NOT: __security_check_cookie
410+
// basic-NOT: __security_check_cookie
411+
412+
// none-NOT: __security_check_cookie
413+
// missing-NOT: __security_check_cookie
414+
}

‎tests/assembly/stack-protector/stack-protector-heuristics-effect.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
// revisions: all strong basic none missing
22
// assembly-output: emit-asm
33
// ignore-macos slightly different policy on stack protection of arrays
4-
// ignore-windows stack check code uses different function names
4+
// ignore-msvc stack check code uses different function names
55
// ignore-nvptx64 stack protector is not supported
66
// ignore-wasm32-bare
77
// [all] compile-flags: -Z stack-protector=all

0 commit comments

Comments
 (0)
Please sign in to comment.