]> git.lizzy.rs Git - rust.git/blob - tests/ui/abi/stack-protector.rs
Rollup merge of #107194 - xfix:remove-slice-internals-dependency-in-rustc-ast, r...
[rust.git] / tests / ui / abi / stack-protector.rs
1 // run-pass
2 // only-x86_64-unknown-linux-gnu
3 // revisions: ssp no-ssp
4 // [ssp] compile-flags: -Z stack-protector=all
5 // compile-flags: -C opt-level=2
6 // compile-flags: -g
7
8 use std::env;
9 use std::process::{Command, ExitStatus};
10
11 fn main() {
12     if env::args().len() == 1 {
13         // The test is initially run without arguments. Start the process again,
14         // this time *with* an argument; in this configuration, the test program
15         // will deliberately smash the stack.
16         let cur_argv0 = env::current_exe().unwrap();
17         let mut child = Command::new(&cur_argv0);
18         child.arg("stacksmash");
19
20         if cfg!(ssp) {
21             assert_stack_smash_prevented(&mut child);
22         } else {
23             assert_stack_smashed(&mut child);
24         }
25     } else {
26         vulnerable_function();
27         // If we return here the test is broken: it should either have called
28         // malicious_code() which terminates the process, or be caught by the
29         // stack check which also terminates the process.
30         panic!("TEST BUG: stack smash unsuccessful");
31     }
32 }
33
34 // Avoid inlining to make sure the return address is pushed to stack.
35 #[inline(never)]
36 fn vulnerable_function() {
37     let mut x = 5usize;
38     let stackaddr = &mut x as *mut usize;
39     let bad_code_ptr = malicious_code as usize;
40     // Overwrite the on-stack return address with the address of `malicious_code()`,
41     // thereby jumping to that function when returning from `vulnerable_function()`.
42     unsafe { fill(stackaddr, bad_code_ptr, 20); }
43 }
44
45 // Use an uninlined function with its own stack frame to make sure that we don't
46 // clobber e.g. the counter or address local variable.
47 #[inline(never)]
48 unsafe fn fill(addr: *mut usize, val: usize, count: usize) {
49     let mut addr = addr;
50     for _ in 0..count {
51         *addr = val;
52         addr = addr.add(1);
53     }
54 }
55
56 // We jump to malicious_code() having wreaked havoc with the previous stack
57 // frame and not setting up a new one. This function is therefore constrained,
58 // e.g. both println!() and std::process::exit() segfaults if called. We
59 // therefore keep the amount of work to a minimum by calling POSIX functions
60 // directly.
61 // The function is un-inlined just to make it possible to set a breakpoint here.
62 #[inline(never)]
63 fn malicious_code() {
64     let msg = [112u8, 119u8, 110u8, 101u8, 100u8, 33u8, 0u8]; // "pwned!\0" ascii
65     unsafe {
66         write(1, &msg as *const u8, msg.len());
67         _exit(0);
68     }
69 }
70 extern "C" {
71     fn write(fd: i32, buf: *const u8, count: usize) -> isize;
72     fn _exit(status: i32) -> !;
73 }
74
75
76 fn assert_stack_smash_prevented(cmd: &mut Command) {
77     let (status, stdout, stderr) = run(cmd);
78     assert!(!status.success());
79     assert!(stdout.is_empty());
80     assert!(stderr.contains("stack smashing detected"));
81 }
82
83 fn assert_stack_smashed(cmd: &mut Command) {
84     let (status, stdout, stderr) = run(cmd);
85     assert!(status.success());
86     assert!(stdout.contains("pwned!"));
87     assert!(stderr.is_empty());
88 }
89
90
91 fn run(cmd: &mut Command) -> (ExitStatus, String, String) {
92     let output = cmd.output().unwrap();
93     let stdout = String::from_utf8_lossy(&output.stdout);
94     let stderr = String::from_utf8_lossy(&output.stderr);
95     println!("status: {}", output.status);
96     println!("stdout: {}", stdout);
97     println!("stderr: {}", stderr);
98     (output.status, stdout.to_string(), stderr.to_string())
99 }