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