Add minibsod (#362)

* Add minibsod

* fmt'

* clippy

* nostd/mac fixes

* windows fix

* woops. Mac fixes

* Get rid of unneccesary sleep

* Fix missing unsafe

* clippy fixes

* make ucontext,siginfo not a reference

* fmt

* fix _context

* Add stubs for non-apple, non-linux, non-android; add a todo

* Fmt

* macos x64, testcase, cleanup

* no_std

* added fault address to minibsod for apple x64

* added err, hexlified values (as per mac panic)

* informing user about lack of registers

Co-authored-by: Dominik Maier <domenukk@gmail.com>
This commit is contained in:
s1341 2021-11-07 16:32:43 +02:00 committed by GitHub
parent 32b8f838ae
commit dd0b5fa74f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 322 additions and 76 deletions

View File

@ -28,7 +28,7 @@ nautilus = ["grammartec", "std", "serde_json/std"]
# LLMP features # LLMP features
llmp_bind_public = [] # If set, llmp will bind to 0.0.0.0, allowing cross-device communication. Binds to localhost by default. llmp_bind_public = [] # If set, llmp will bind to 0.0.0.0, allowing cross-device communication. Binds to localhost by default.
llmp_compression = ["miniz_oxide"] # llmp compression using GZip llmp_compression = ["miniz_oxide"] # llmp compression using GZip
llmp_debug = ["backtrace"] # Enables debug output for LLMP llmp_debug = [] # Enables debug output for LLMP
llmp_small_maps = [] # reduces initial map size for llmp llmp_small_maps = [] # reduces initial map size for llmp
[build-dependencies] [build-dependencies]
@ -78,17 +78,12 @@ z3 = { version = "0.11", features = ["static-link-z3"], optional = true } # for
# AGPL # AGPL
grammartec = { git = "https://github.com/andreafioraldi/nautilus", optional = true } grammartec = { git = "https://github.com/andreafioraldi/nautilus", optional = true }
[target.'cfg(target_os = "android")'.dependencies]
backtrace = { version = "0.3", optional = true, default-features = false, features = ["std", "libbacktrace"] } # for llmp_debug
[target.'cfg(not(target_os = "android"))'.dependencies]
backtrace = { version = "0.3", optional = true } # for llmp_debug
[target.'cfg(unix)'.dependencies] [target.'cfg(unix)'.dependencies]
libc = "0.2" # For (*nix) libc libc = "0.2" # For (*nix) libc
uds = "0.2.3" uds = "0.2.3"
lock_api = "0.4.3" lock_api = "0.4.3"
regex = "1.4.5" regex = "1.4.5"
backtrace = "0.3"
[target.'cfg(windows)'.dependencies] [target.'cfg(windows)'.dependencies]
windows = "0.18.0" windows = "0.18.0"

View File

@ -0,0 +1,259 @@
//! Implements a mini-bsod generator
use libc::siginfo_t;
use std::io::{BufWriter, Write};
use crate::bolts::os::unix_signals::{ucontext_t, Signal};
#[cfg(all(target_os = "linux", target_arch = "x86_64"))]
fn dump_registers<W: Write>(
writer: &mut BufWriter<W>,
ucontext: &ucontext_t,
) -> Result<(), std::io::Error> {
use libc::{
REG_EFL, REG_R10, REG_R11, REG_R12, REG_R13, REG_R14, REG_R15, REG_R8, REG_R9, REG_RAX,
REG_RBP, REG_RBX, REG_RCX, REG_RDI, REG_RDX, REG_RIP, REG_RSI, REG_RSP,
};
let mcontext = &ucontext.uc_mcontext;
write!(writer, "r8 : {:#016x}, ", mcontext.gregs[REG_R8 as usize])?;
write!(writer, "r9 : {:#016x}, ", mcontext.gregs[REG_R9 as usize])?;
write!(writer, "r10: {:#016x}, ", mcontext.gregs[REG_R10 as usize])?;
writeln!(writer, "r11: {:#016x}, ", mcontext.gregs[REG_R11 as usize])?;
write!(writer, "r12: {:#016x}, ", mcontext.gregs[REG_R12 as usize])?;
write!(writer, "r13: {:#016x}, ", mcontext.gregs[REG_R13 as usize])?;
write!(writer, "r14: {:#016x}, ", mcontext.gregs[REG_R14 as usize])?;
writeln!(writer, "r15: {:#016x}, ", mcontext.gregs[REG_R15 as usize])?;
write!(writer, "rdi: {:#016x}, ", mcontext.gregs[REG_RDI as usize])?;
write!(writer, "rsi: {:#016x}, ", mcontext.gregs[REG_RSI as usize])?;
write!(writer, "rbp: {:#016x}, ", mcontext.gregs[REG_RBP as usize])?;
writeln!(writer, "rbx: {:#016x}, ", mcontext.gregs[REG_RBX as usize])?;
write!(writer, "rdx: {:#016x}, ", mcontext.gregs[REG_RDX as usize])?;
write!(writer, "rax: {:#016x}, ", mcontext.gregs[REG_RAX as usize])?;
write!(writer, "rcx: {:#016x}, ", mcontext.gregs[REG_RCX as usize])?;
writeln!(writer, "rsp: {:#016x}, ", mcontext.gregs[REG_RSP as usize])?;
write!(writer, "rip: {:#016x}, ", mcontext.gregs[REG_RIP as usize])?;
writeln!(writer, "efl: {:#016x}, ", mcontext.gregs[REG_EFL as usize])?;
Ok(())
}
#[cfg(all(
any(target_os = "linux", target_os = "android"),
target_arch = "aarch64"
))]
fn dump_registers<W: Write>(
writer: &mut BufWriter<W>,
ucontext: &ucontext_t,
) -> Result<(), std::io::Error> {
for reg in 0..31 {
write!(
writer,
"x{:02}: 0x{:016x} ",
reg, ucontext.uc_mcontext.regs[reg as usize]
)?;
if reg % 4 == 3 {
writeln!(writer)?;
}
}
writeln!(writer, "pc : 0x{:016x} ", ucontext.uc_mcontext.pc)?;
Ok(())
}
#[cfg(all(target_vendor = "apple", target_arch = "aarch64"))]
fn dump_registers<W: Write>(
writer: &mut BufWriter<W>,
ucontext: &ucontext_t,
) -> Result<(), std::io::Error> {
let mcontext = unsafe { *ucontext.uc_mcontext };
for reg in 0..29 {
writeln!(
writer,
"x{:02}: 0x{:016x} ",
reg, mcontext.__ss.__x[reg as usize]
);
if reg % 4 == 3 {
writeln!(writer);
}
}
write!(writer, "fp: 0x{:016x} ", mcontext.__ss.__fp);
write!(writer, "lr: 0x{:016x} ", mcontext.__ss.__lr);
write!(writer, "pc: 0x{:016x} ", mcontext.__ss.__pc);
Ok(())
}
#[allow(clippy::unnecessary_wraps, clippy::similar_names)]
#[cfg(all(target_vendor = "apple", target_arch = "x86_64"))]
fn dump_registers<W: Write>(
writer: &mut BufWriter<W>,
ucontext: &ucontext_t,
) -> Result<(), std::io::Error> {
let mcontext = unsafe { *ucontext.uc_mcontext };
let ss = mcontext.__ss;
write!(writer, "r8 : {:#016x}, ", ss.__r8)?;
write!(writer, "r9 : {:#016x}, ", ss.__r9)?;
write!(writer, "r10: {:#016x}, ", ss.__r10)?;
writeln!(writer, "r11: {:#016x}, ", ss.__r11)?;
write!(writer, "r12: {:#016x}, ", ss.__r12)?;
write!(writer, "r13: {:#016x}, ", ss.__r13)?;
write!(writer, "r14: {:#016x}, ", ss.__r14)?;
writeln!(writer, "r15: {:#016x}, ", ss.__r15)?;
write!(writer, "rdi: {:#016x}, ", ss.__rdi)?;
write!(writer, "rsi: {:#016x}, ", ss.__rsi)?;
write!(writer, "rbp: {:#016x}, ", ss.__rbp)?;
writeln!(writer, "rbx: {:#016x}, ", ss.__rbx)?;
write!(writer, "rdx: {:#016x}, ", ss.__rdx)?;
write!(writer, "rax: {:#016x}, ", ss.__rax)?;
write!(writer, "rcx: {:#016x}, ", ss.__rcx)?;
writeln!(writer, "rsp: {:#016x}, ", ss.__rsp)?;
write!(writer, "rip: {:#016x}, ", ss.__rip)?;
writeln!(writer, "efl: {:#016x}, ", ss.__rflags)?;
Ok(())
}
#[allow(clippy::unnecessary_wraps)]
#[cfg(not(any(target_vendor = "apple", target_os = "linux", target_os = "android")))]
fn dump_registers<W: Write>(
writer: &mut BufWriter<W>,
_ucontext: &ucontext_t,
) -> Result<(), std::io::Error> {
// TODO: Implement dump registers
writeln!(
writer,
"< Dumping registers is not yet supported on platform {:?}. Please add it to `minibsod.rs` >",
std::env::consts::OS
)?;
Ok(())
}
#[cfg(all(target_os = "linux", target_arch = "x86_64"))]
fn write_crash<W: Write>(
writer: &mut BufWriter<W>,
signal: Signal,
ucontext: &ucontext_t,
) -> Result<(), std::io::Error> {
writeln!(
writer,
"Received signal {} at {:#016x}, fault address: {:#016x}",
signal,
ucontext.uc_mcontext.gregs[libc::REG_RIP as usize],
ucontext.uc_mcontext.gregs[libc::REG_CR2 as usize]
)?;
Ok(())
}
#[cfg(all(
any(target_os = "linux", target_os = "android"),
target_arch = "aarch64"
))]
fn write_crash<W: Write>(
writer: &mut BufWriter<W>,
signal: Signal,
ucontext: &ucontext_t,
) -> Result<(), std::io::Error> {
writeln!(
writer,
"Received signal {} at 0x{:016x}, fault address: 0x{:016x}",
signal, ucontext.uc_mcontext.pc, ucontext.uc_mcontext.fault_address
)?;
Ok(())
}
#[cfg(all(target_vendor = "apple", target_arch = "aarch64"))]
fn write_crash<W: Write>(
writer: &mut BufWriter<W>,
signal: Signal,
ucontext: &ucontext_t,
) -> Result<(), std::io::Error> {
let mcontext = unsafe { *ucontext.uc_mcontext };
writeln!(
writer,
"Received signal {} at 0x{:016x}, fault address: 0x{:016x}",
signal, mcontext.__ss.__pc, mcontext.__es.__far
)?;
Ok(())
}
#[cfg(all(target_vendor = "apple", target_arch = "x86_64"))]
#[allow(clippy::similar_names)]
fn write_crash<W: Write>(
writer: &mut BufWriter<W>,
signal: Signal,
ucontext: &ucontext_t,
) -> Result<(), std::io::Error> {
let mcontext = unsafe { *ucontext.uc_mcontext };
writeln!(
writer,
"Received signal {} at 0x{:016x}, fault address: 0x{:016x}, trapno: 0x{:x}, err: 0x{:x}",
signal,
mcontext.__ss.__rip,
mcontext.__es.__faultvaddr,
mcontext.__es.__trapno,
mcontext.__es.__err
)?;
Ok(())
}
#[cfg(not(any(target_vendor = "apple", target_os = "linux", target_os = "android")))]
fn write_crash<W: Write>(
writer: &mut BufWriter<W>,
signal: Signal,
_ucontext: &ucontext_t,
) -> Result<(), std::io::Error> {
// TODO add fault addr for other platforms.
writeln!(writer, "Received signal {}", signal,)?;
Ok(())
}
/// Generates a mini-BSOD given a signal and context.
#[cfg(unix)]
pub fn generate_minibsod<W: Write>(
writer: &mut BufWriter<W>,
signal: Signal,
_siginfo: siginfo_t,
ucontext: &ucontext_t,
) -> Result<(), std::io::Error> {
writeln!(writer, "{:━^100}", " CRASH ")?;
write_crash(writer, signal, ucontext)?;
writeln!(writer, "{:━^100}", " REGISTERS ")?;
dump_registers(writer, ucontext)?;
writeln!(writer, "{:━^100}", " BACKTRACE ")?;
writeln!(writer, "{:?}", backtrace::Backtrace::new())?;
#[cfg(any(target_os = "linux", target_os = "android"))]
{
writeln!(writer, "{:━^100}", " MAPS ")?;
match std::fs::read_to_string("/proc/self/maps") {
Ok(maps) => writer.write_all(maps.as_bytes())?,
Err(e) => writeln!(writer, "Couldn't load mappings: {:?}", e)?,
};
}
Ok(())
}
#[cfg(test)]
mod tests {
use std::io::{stdout, BufWriter};
use crate::bolts::{minibsod::dump_registers, os::unix_signals::ucontext};
#[test]
pub fn test_dump_registers() {
let ucontext = ucontext().unwrap();
let mut writer = BufWriter::new(stdout());
dump_registers(&mut writer, &ucontext).unwrap();
}
}

View File

@ -9,6 +9,8 @@ pub mod fs;
#[cfg(feature = "std")] #[cfg(feature = "std")]
pub mod launcher; pub mod launcher;
pub mod llmp; pub mod llmp;
#[cfg(all(feature = "std", unix))]
pub mod minibsod;
pub mod os; pub mod os;
pub mod ownedref; pub mod ownedref;
pub mod rands; pub mod rands;

View File

@ -3,11 +3,15 @@ use alloc::vec::Vec;
use core::{ use core::{
cell::UnsafeCell, cell::UnsafeCell,
fmt::{self, Display, Formatter}, fmt::{self, Display, Formatter},
mem, ptr, mem,
mem::MaybeUninit,
ptr,
ptr::write_volatile, ptr::write_volatile,
sync::atomic::{compiler_fence, Ordering}, sync::atomic::{compiler_fence, Ordering},
}; };
#[cfg(feature = "std")]
use nix::errno::{errno, Errno};
#[cfg(feature = "std")] #[cfg(feature = "std")]
use std::ffi::CString; use std::ffi::CString;
@ -63,6 +67,10 @@ use crate::Error;
pub use libc::{c_void, siginfo_t}; pub use libc::{c_void, siginfo_t};
extern "C" {
fn getcontext(ucp: *mut ucontext_t) -> c_int;
}
/// All signals on this system, as `enum`. /// All signals on this system, as `enum`.
#[derive(IntoPrimitive, TryFromPrimitive, Clone, Copy)] #[derive(IntoPrimitive, TryFromPrimitive, Clone, Copy)]
#[repr(i32)] #[repr(i32)]
@ -233,3 +241,28 @@ pub unsafe fn setup_signal_handler<T: 'static + Handler>(handler: &mut T) -> Res
Ok(()) Ok(())
} }
/// Function to get the current [`ucontext_t`] for this process.
/// This calls the libc `getcontext` function under the hood.
/// We wrap it here, as it seems to be (currently)
/// not available on `MacOS` in the `libc` crate.
#[cfg(unix)]
pub fn ucontext() -> Result<ucontext_t, Error> {
let mut ucontext = unsafe { MaybeUninit::zeroed().assume_init() };
if unsafe { getcontext(&mut ucontext) } == 0 {
Ok(ucontext)
} else {
#[cfg(not(feature = "std"))]
unsafe {
libc::perror(b"Failed to get ucontext\n".as_ptr() as _)
};
#[cfg(not(feature = "std"))]
return Err(Error::Unknown("Failed to get ucontex".into()));
#[cfg(feature = "std")]
Err(Error::Unknown(format!(
"Failed to get ucontext: {:?}",
Errno::from_i32(errno())
)))
}
}

View File

@ -482,31 +482,34 @@ mod unix_signal_handler {
unix_remove_timeout(); unix_remove_timeout();
#[cfg(all(target_os = "android", target_arch = "aarch64"))] #[cfg(all(target_os = "android", target_arch = "aarch64"))]
let _context = *(((_context as *mut _ as *mut libc::c_void as usize) + 128) let _context = &mut *(((_context as *mut _ as *mut libc::c_void as usize) + 128)
as *mut libc::c_void as *mut ucontext_t); as *mut libc::c_void as *mut ucontext_t);
#[cfg(feature = "std")] #[cfg(feature = "std")]
println!("Crashed with {}", signal); eprintln!("Crashed with {}", signal);
if data.current_input_ptr.is_null() { if data.current_input_ptr.is_null() {
#[cfg(feature = "std")] #[cfg(feature = "std")]
{ {
println!("Double crash\n"); eprintln!("Double crash\n");
#[cfg(target_os = "android")] #[cfg(target_os = "android")]
let si_addr = (_info._pad[0] as i64) | ((_info._pad[1] as i64) << 32); let si_addr = (_info._pad[0] as i64) | ((_info._pad[1] as i64) << 32);
#[cfg(not(target_os = "android"))] #[cfg(not(target_os = "android"))]
let si_addr = { _info.si_addr() as usize }; let si_addr = { _info.si_addr() as usize };
println!( eprintln!(
"We crashed at addr 0x{:x}, but are not in the target... Bug in the fuzzer? Exiting.", "We crashed at addr 0x{:x}, but are not in the target... Bug in the fuzzer? Exiting.",
si_addr si_addr
); );
#[cfg(all(feature = "std", unix))]
{
let mut writer = std::io::BufWriter::new(std::io::stderr());
crate::bolts::minibsod::generate_minibsod(&mut writer, signal, _info, _context)
.unwrap();
writer.flush().unwrap();
}
} }
// let's yolo-cat the maps for debugging, if possible.
#[cfg(all(any(target_os = "linux", target_os = "netbsd"), feature = "std"))]
match std::fs::read_to_string("/proc/self/maps") {
Ok(maps) => println!("maps:\n{}", maps),
Err(e) => println!("Couldn't load mappings: {:?}", e),
};
#[cfg(feature = "std")] #[cfg(feature = "std")]
{ {
println!("Type QUIT to restart the child"); println!("Type QUIT to restart the child");
@ -524,67 +527,21 @@ mod unix_signal_handler {
let executor = (data.executor_ptr as *const E).as_ref().unwrap(); let executor = (data.executor_ptr as *const E).as_ref().unwrap();
let observers = executor.observers(); let observers = executor.observers();
#[cfg(feature = "std")]
println!("Child crashed!");
#[allow(clippy::non_ascii_literal)]
#[cfg(all(
feature = "std",
any(target_os = "linux", target_os = "android"),
target_arch = "aarch64"
))]
{
println!("{:━^100}", " CRASH ");
println!(
"Received signal {} at 0x{:016x}, fault address: 0x{:016x}",
signal, _context.uc_mcontext.pc, _context.uc_mcontext.fault_address
);
println!("{:━^100}", " REGISTERS ");
for reg in 0..31 {
print!(
"x{:02}: 0x{:016x} ",
reg, _context.uc_mcontext.regs[reg as usize]
);
if reg % 4 == 3 {
println!();
}
}
println!("pc : 0x{:016x} ", _context.uc_mcontext.pc);
//println!("{:━^100}", " BACKTRACE ");
//println!("{:?}", backtrace::Backtrace::new())
}
#[allow(clippy::non_ascii_literal)]
#[cfg(all(feature = "std", target_vendor = "apple", target_arch = "aarch64"))]
{
let mcontext = *_context.uc_mcontext;
println!("{:━^100}", " CRASH ");
println!(
"Received signal {} at 0x{:016x}, fault address: 0x{:016x}",
signal, mcontext.__ss.__pc, mcontext.__es.__far
);
println!("{:━^100}", " REGISTERS ");
for reg in 0..29 {
print!("x{:02}: 0x{:016x} ", reg, mcontext.__ss.__x[reg as usize]);
if reg % 4 == 3 {
println!();
}
}
print!("fp: 0x{:016x} ", mcontext.__ss.__fp);
print!("lr: 0x{:016x} ", mcontext.__ss.__lr);
print!("pc: 0x{:016x} ", mcontext.__ss.__pc);
}
#[cfg(feature = "std")]
let _res = stdout().flush();
let input = (data.current_input_ptr as *const I).as_ref().unwrap(); let input = (data.current_input_ptr as *const I).as_ref().unwrap();
// Make sure we don't crash in the crash handler forever.
data.current_input_ptr = ptr::null(); data.current_input_ptr = ptr::null();
#[cfg(feature = "std")]
eprintln!("Child crashed!");
#[cfg(all(feature = "std", unix))]
{
let mut writer = std::io::BufWriter::new(std::io::stderr());
writeln!(writer, "input: {:?}", input.generate_name(0)).unwrap();
crate::bolts::minibsod::generate_minibsod(&mut writer, signal, _info, _context)
.unwrap();
writer.flush().unwrap();
}
let interesting = fuzzer let interesting = fuzzer
.objective_mut() .objective_mut()
.is_interesting(state, event_mgr, input, observers, &ExitKind::Crash) .is_interesting(state, event_mgr, input, observers, &ExitKind::Crash)