|
| 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 | +} |
0 commit comments