summaryrefslogtreecommitdiffstats
path: root/tests/ui/abi/stack-protector.rs
blob: e94aa816d90a15c3178da6036c186b3945f2ca6f (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
// run-pass
// only-x86_64-unknown-linux-gnu
// revisions: ssp no-ssp
// [ssp] compile-flags: -Z stack-protector=all
// compile-flags: -C opt-level=2
// compile-flags: -g

use std::env;
use std::process::{Command, ExitStatus};

fn main() {
    if env::args().len() == 1 {
        // The test is initially run without arguments. Start the process again,
        // this time *with* an argument; in this configuration, the test program
        // will deliberately smash the stack.
        let cur_argv0 = env::current_exe().unwrap();
        let mut child = Command::new(&cur_argv0);
        child.arg("stacksmash");

        if cfg!(ssp) {
            assert_stack_smash_prevented(&mut child);
        } else {
            assert_stack_smashed(&mut child);
        }
    } else {
        vulnerable_function();
        // If we return here the test is broken: it should either have called
        // malicious_code() which terminates the process, or be caught by the
        // stack check which also terminates the process.
        panic!("TEST BUG: stack smash unsuccessful");
    }
}

// Avoid inlining to make sure the return address is pushed to stack.
#[inline(never)]
fn vulnerable_function() {
    let mut x = 5usize;
    let stackaddr = &mut x as *mut usize;
    let bad_code_ptr = malicious_code as usize;
    // Overwrite the on-stack return address with the address of `malicious_code()`,
    // thereby jumping to that function when returning from `vulnerable_function()`.
    unsafe { fill(stackaddr, bad_code_ptr, 20); }
    // Capture the address, so the write is not optimized away.
    std::hint::black_box(stackaddr);
}

// Use an uninlined function with its own stack frame to make sure that we don't
// clobber e.g. the counter or address local variable.
#[inline(never)]
unsafe fn fill(addr: *mut usize, val: usize, count: usize) {
    let mut addr = addr;
    for _ in 0..count {
        *addr = val;
        addr = addr.add(1);
    }
}

// We jump to malicious_code() having wreaked havoc with the previous stack
// frame and not setting up a new one. This function is therefore constrained,
// e.g. both println!() and std::process::exit() segfaults if called. We
// therefore keep the amount of work to a minimum by calling POSIX functions
// directly.
// The function is un-inlined just to make it possible to set a breakpoint here.
#[inline(never)]
fn malicious_code() {
    let msg = [112u8, 119u8, 110u8, 101u8, 100u8, 33u8, 0u8]; // "pwned!\0" ascii
    unsafe {
        write(1, &msg as *const u8, msg.len());
        _exit(0);
    }
}
extern "C" {
    fn write(fd: i32, buf: *const u8, count: usize) -> isize;
    fn _exit(status: i32) -> !;
}


fn assert_stack_smash_prevented(cmd: &mut Command) {
    let (status, stdout, stderr) = run(cmd);
    assert!(!status.success());
    assert!(stdout.is_empty());
    assert!(stderr.contains("stack smashing detected"));
}

fn assert_stack_smashed(cmd: &mut Command) {
    let (status, stdout, stderr) = run(cmd);
    assert!(status.success());
    assert!(stdout.contains("pwned!"));
    assert!(stderr.is_empty());
}


fn run(cmd: &mut Command) -> (ExitStatus, String, String) {
    let output = cmd.output().unwrap();
    let stdout = String::from_utf8_lossy(&output.stdout);
    let stderr = String::from_utf8_lossy(&output.stderr);
    println!("status: {}", output.status);
    println!("stdout: {}", stdout);
    println!("stderr: {}", stderr);
    (output.status, stdout.to_string(), stderr.to_string())
}