From dd0b5fa74fbb8656f3e24df148053cf6600d696c Mon Sep 17 00:00:00 2001 From: s1341 Date: Sun, 7 Nov 2021 16:32:43 +0200 Subject: [PATCH] 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 --- libafl/Cargo.toml | 9 +- libafl/src/bolts/minibsod.rs | 259 ++++++++++++++++++++++++++++ libafl/src/bolts/mod.rs | 2 + libafl/src/bolts/os/unix_signals.rs | 35 +++- libafl/src/executors/inprocess.rs | 93 +++------- 5 files changed, 322 insertions(+), 76 deletions(-) create mode 100644 libafl/src/bolts/minibsod.rs diff --git a/libafl/Cargo.toml b/libafl/Cargo.toml index e5939a8cc1..f0b02df3b7 100644 --- a/libafl/Cargo.toml +++ b/libafl/Cargo.toml @@ -28,7 +28,7 @@ nautilus = ["grammartec", "std", "serde_json/std"] # 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_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 [build-dependencies] @@ -78,17 +78,12 @@ z3 = { version = "0.11", features = ["static-link-z3"], optional = true } # for # AGPL 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] libc = "0.2" # For (*nix) libc uds = "0.2.3" lock_api = "0.4.3" regex = "1.4.5" +backtrace = "0.3" [target.'cfg(windows)'.dependencies] windows = "0.18.0" diff --git a/libafl/src/bolts/minibsod.rs b/libafl/src/bolts/minibsod.rs new file mode 100644 index 0000000000..44c28970d2 --- /dev/null +++ b/libafl/src/bolts/minibsod.rs @@ -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( + writer: &mut BufWriter, + 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( + writer: &mut BufWriter, + 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( + writer: &mut BufWriter, + 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( + writer: &mut BufWriter, + 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( + writer: &mut BufWriter, + _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( + writer: &mut BufWriter, + 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( + writer: &mut BufWriter, + 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( + writer: &mut BufWriter, + 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( + writer: &mut BufWriter, + 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( + writer: &mut BufWriter, + 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( + writer: &mut BufWriter, + 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(); + } +} diff --git a/libafl/src/bolts/mod.rs b/libafl/src/bolts/mod.rs index b60b6f3e04..0c1f0c9221 100644 --- a/libafl/src/bolts/mod.rs +++ b/libafl/src/bolts/mod.rs @@ -9,6 +9,8 @@ pub mod fs; #[cfg(feature = "std")] pub mod launcher; pub mod llmp; +#[cfg(all(feature = "std", unix))] +pub mod minibsod; pub mod os; pub mod ownedref; pub mod rands; diff --git a/libafl/src/bolts/os/unix_signals.rs b/libafl/src/bolts/os/unix_signals.rs index 9c9c96a0ef..1bf1b89d01 100644 --- a/libafl/src/bolts/os/unix_signals.rs +++ b/libafl/src/bolts/os/unix_signals.rs @@ -3,11 +3,15 @@ use alloc::vec::Vec; use core::{ cell::UnsafeCell, fmt::{self, Display, Formatter}, - mem, ptr, + mem, + mem::MaybeUninit, + ptr, ptr::write_volatile, sync::atomic::{compiler_fence, Ordering}, }; +#[cfg(feature = "std")] +use nix::errno::{errno, Errno}; #[cfg(feature = "std")] use std::ffi::CString; @@ -63,6 +67,10 @@ use crate::Error; pub use libc::{c_void, siginfo_t}; +extern "C" { + fn getcontext(ucp: *mut ucontext_t) -> c_int; +} + /// All signals on this system, as `enum`. #[derive(IntoPrimitive, TryFromPrimitive, Clone, Copy)] #[repr(i32)] @@ -233,3 +241,28 @@ pub unsafe fn setup_signal_handler(handler: &mut T) -> Res 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 { + 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()) + ))) + } +} diff --git a/libafl/src/executors/inprocess.rs b/libafl/src/executors/inprocess.rs index 30ca20c4ac..89018517aa 100644 --- a/libafl/src/executors/inprocess.rs +++ b/libafl/src/executors/inprocess.rs @@ -482,31 +482,34 @@ mod unix_signal_handler { unix_remove_timeout(); #[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); #[cfg(feature = "std")] - println!("Crashed with {}", signal); + eprintln!("Crashed with {}", signal); if data.current_input_ptr.is_null() { #[cfg(feature = "std")] { - println!("Double crash\n"); + eprintln!("Double crash\n"); #[cfg(target_os = "android")] let si_addr = (_info._pad[0] as i64) | ((_info._pad[1] as i64) << 32); #[cfg(not(target_os = "android"))] 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.", 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")] { 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 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(); - // Make sure we don't crash in the crash handler forever. 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 .objective_mut() .is_interesting(state, event_mgr, input, observers, &ExitKind::Crash)