1 // To handle out-of-bounds reads and writes we use segfaults right now. We only
2 // want to catch a subset of segfaults, however, rather than all segfaults
3 // happening everywhere. The purpose of this test is to ensure that we *don't*
4 // catch segfaults if it happens in a random place in the code, but we instead
5 // bail out of our segfault handler early.
6 //
7 // This is sort of hard to test for but the general idea here is that we confirm
8 // that execution made it to our `segfault` function by printing something, and
9 // then we also make sure that stderr is empty to confirm that no weird panics
10 // happened or anything like that.
11 
12 use std::env;
13 use std::process::{Command, ExitStatus};
14 use wasmtime::*;
15 
16 const VAR_NAME: &str = "__TEST_TO_RUN";
17 const CONFIRM: &str = "well at least we ran up to the segfault\n";
18 
segfault() -> !19 fn segfault() -> ! {
20     unsafe {
21         print!("{}", CONFIRM);
22         *(0x4 as *mut i32) = 3;
23         unreachable!()
24     }
25 }
26 
overrun_the_stack() -> usize27 fn overrun_the_stack() -> usize {
28     let mut a = [0u8; 1024];
29     if a.as_mut_ptr() as usize == 1 {
30         return 1;
31     } else {
32         return a.as_mut_ptr() as usize + overrun_the_stack();
33     }
34 }
35 
main()36 fn main() {
37     // Skip this tests if it looks like we're in a cross-compiled situation and
38     // we're emulating this test for a different platform. In that scenario
39     // emulators (like QEMU) tend to not report signals the same way and such.
40     if std::env::vars()
41         .filter(|(k, _v)| k.starts_with("CARGO_TARGET") && k.ends_with("RUNNER"))
42         .count()
43         > 0
44     {
45         return;
46     }
47 
48     let tests: &[(&str, fn())] = &[
49         ("normal segfault", || segfault()),
50         ("make instance then segfault", || {
51             let engine = Engine::default();
52             let store = Store::new(&engine);
53             let module = Module::new(&engine, "(module)").unwrap();
54             let _instance = Instance::new(&store, &module, &[]).unwrap();
55             segfault();
56         }),
57         ("make instance then overrun the stack", || {
58             let engine = Engine::default();
59             let store = Store::new(&engine);
60             let module = Module::new(&engine, "(module)").unwrap();
61             let _instance = Instance::new(&store, &module, &[]).unwrap();
62             println!("stack overrun: {}", overrun_the_stack());
63         }),
64         ("segfault in a host function", || {
65             let engine = Engine::default();
66             let store = Store::new(&engine);
67             let module = Module::new(&engine, r#"(import "" "" (func)) (start 0)"#).unwrap();
68             let segfault = Func::wrap(&store, || segfault());
69             Instance::new(&store, &module, &[segfault.into()]).unwrap();
70         }),
71     ];
72     match env::var(VAR_NAME) {
73         Ok(s) => {
74             let test = tests
75                 .iter()
76                 .find(|p| p.0 == s)
77                 .expect("failed to find test")
78                 .1;
79             test();
80         }
81         Err(_) => {
82             for (name, _test) in tests {
83                 runtest(name);
84             }
85         }
86     }
87 }
88 
runtest(name: &str)89 fn runtest(name: &str) {
90     let me = env::current_exe().unwrap();
91     let mut cmd = Command::new(me);
92     cmd.env(VAR_NAME, name);
93     let output = cmd.output().expect("failed to spawn subprocess");
94     let stdout = String::from_utf8_lossy(&output.stdout);
95     let stderr = String::from_utf8_lossy(&output.stderr);
96     let mut desc = format!("got status: {}", output.status);
97     if !stdout.trim().is_empty() {
98         desc.push_str("\nstdout: ----\n");
99         desc.push_str("    ");
100         desc.push_str(&stdout.replace("\n", "\n    "));
101     }
102     if !stderr.trim().is_empty() {
103         desc.push_str("\nstderr: ----\n");
104         desc.push_str("    ");
105         desc.push_str(&stderr.replace("\n", "\n    "));
106     }
107     if is_segfault(&output.status) {
108         assert!(
109             stdout.ends_with(CONFIRM) && stderr.is_empty(),
110             "failed to find confirmation in test `{}`\n{}",
111             name,
112             desc
113         );
114     } else if name.contains("overrun the stack") {
115         assert!(
116             stderr.contains("thread 'main' has overflowed its stack"),
117             "bad stderr: {}",
118             stderr
119         );
120     } else {
121         panic!("\n\nexpected a segfault on `{}`\n{}\n\n", name, desc);
122     }
123 }
124 
125 #[cfg(unix)]
is_segfault(status: &ExitStatus) -> bool126 fn is_segfault(status: &ExitStatus) -> bool {
127     use std::os::unix::prelude::*;
128 
129     match status.signal() {
130         Some(libc::SIGSEGV) | Some(libc::SIGBUS) => true,
131         _ => false,
132     }
133 }
134 
135 #[cfg(windows)]
is_segfault(status: &ExitStatus) -> bool136 fn is_segfault(status: &ExitStatus) -> bool {
137     match status.code().map(|s| s as u32) {
138         Some(0xc0000005) => true,
139         _ => false,
140     }
141 }
142