From b69a061d23a108e2f25be9a7c86ea164aad4c08f Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Tue, 18 Feb 2020 16:22:18 -0600 Subject: [PATCH] Add a test that segfault handlers ignore non-wasm segfaults (#941) This is the subject of #940 which while fixed is good to have a regression test for! --- crates/api/Cargo.toml | 4 ++ crates/api/tests/host-segfault.rs | 100 ++++++++++++++++++++++++++++++ 2 files changed, 104 insertions(+) create mode 100644 crates/api/tests/host-segfault.rs diff --git a/crates/api/Cargo.toml b/crates/api/Cargo.toml index acd2736dae..8dd7873c73 100644 --- a/crates/api/Cargo.toml +++ b/crates/api/Cargo.toml @@ -44,3 +44,7 @@ default = ['wat'] # to cranelift. Requires Nightly Rust currently, and this is not enabled by # default. lightbeam = ["wasmtime-jit/lightbeam"] + +[[test]] +name = "host-segfault" +harness = false diff --git a/crates/api/tests/host-segfault.rs b/crates/api/tests/host-segfault.rs new file mode 100644 index 0000000000..127a6d7943 --- /dev/null +++ b/crates/api/tests/host-segfault.rs @@ -0,0 +1,100 @@ +// To handle out-of-bounds reads and writes we use segfaults right now. We only +// want to catch a subset of segfaults, however, rather than all segfaults +// happening everywhere. The purpose of this test is to ensure that we *don't* +// catch segfaults if it happens in a random place in the code, but we instead +// bail out of our segfault handler early. +// +// This is sort of hard to test for but the general idea here is that we confirm +// that execution made it to our `segfault` function by printing something, and +// then we also make sure that stderr is empty to confirm that no weird panics +// happened or anything like that. + +use std::env; +use std::process::{Command, ExitStatus}; +use wasmtime::*; + +const VAR_NAME: &str = "__TEST_TO_RUN"; +const CONFIRM: &str = "well at least we ran up to the segfault\n"; + +fn segfault() -> ! { + unsafe { + print!("{}", CONFIRM); + *(0x4 as *mut i32) = 3; + unreachable!() + } +} + +fn main() { + let tests: &[(&str, fn())] = &[ + ("normal segfault", || segfault()), + ("make instance then segfault", || { + let store = Store::default(); + let module = Module::new(&store, "(module)").unwrap(); + let _instance = Instance::new(&module, &[]).unwrap(); + segfault(); + }), + ]; + match env::var(VAR_NAME) { + Ok(s) => { + let test = tests + .iter() + .find(|p| p.0 == s) + .expect("failed to find test") + .1; + test(); + } + Err(_) => { + for (name, _test) in tests { + runtest(name); + } + } + } +} + +fn runtest(name: &str) { + let me = env::current_exe().unwrap(); + let mut cmd = Command::new(me); + cmd.env(VAR_NAME, name); + let output = cmd.output().expect("failed to spawn subprocess"); + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + let mut desc = format!("got status: {}", output.status); + if !stdout.trim().is_empty() { + desc.push_str("\nstdout: ----\n"); + desc.push_str(" "); + desc.push_str(&stdout.replace("\n", "\n ")); + } + if !stderr.trim().is_empty() { + desc.push_str("\nstderr: ----\n"); + desc.push_str(" "); + desc.push_str(&stderr.replace("\n", "\n ")); + } + if is_segfault(&output.status) { + assert!( + stdout.ends_with(CONFIRM) && stderr.is_empty(), + "failed to find confirmation in test `{}`\n{}", + name, + desc + ); + } else { + panic!("\n\nexpected a segfault on `{}`\n{}\n\n", name, desc); + } +} + +#[cfg(unix)] +fn is_segfault(status: &ExitStatus) -> bool { + use std::os::unix::prelude::*; + + match status.signal() { + Some(libc::SIGSEGV) | Some(libc::SIGBUS) => true, + _ => false, + } +} + +#[cfg(windows)] +fn is_segfault(status: &ExitStatus) -> bool { + match status.code().map(|s| s as u32) { + Some(0xc0000005) => true, + _ => false, + } +}