From 44c841ffb1d029cec43f105bf75d794a3f503d6c Mon Sep 17 00:00:00 2001 From: Romain Malmain Date: Fri, 22 Mar 2024 18:03:29 +0100 Subject: [PATCH] WIP: QEMU exit handler (#1745) * Added paging filtering. Reworked address range filtering to fit with new generic code. * Fix: renamed remaining QemuInstrumentationFilter instances. * Renamed sync breakpoint to sync exit. * Split emu in systemmode.rs / usermode.rs for specific code. EmuExitHandler implementation. * sync_backdoor.rs removal. Formatting. * Updated `bindgen` and `which`. Adapting code to work with update. * fix: reconfigure cleanly if prior configure was interrupted abruptly. * Enable sanitizers in QEMU during debug. * Added target-usable files. * Added breakpoint structure. * Adapted other files to work with ExitHandler. * Adapted existing fuzzer to work with new exit handler. * fix: use get to avoid crashes. * Updated README to indicate cargo-make should be installed. * Added QEMU internal exit handler. * Adapted qemu_systemmode example with new exit handler. * Fixed fuzzers to work with new exit handler. * Trying to fix CI (#1739) * test * dummy * dummy * Added new examples. * Forgot to add build scripts. * format * format * clang-format * python emulator adaptation. * fixed python bindings. * clippy fixes. * python bindings. * fix qemu_sugar. * fix fuzzbench. * fixed import issues. * misc fixes. * renamed crate. * Updated x86_64 stub bindings. * Fixed static naming. * binding fmt * clippy * clippy * Removed useless return statement. * removed advice to install cargo-make in individual repositories. * symcc_update (#1749) * Remove unused create_anymap_for_trait macro (fixes #1719) (#1752) * Fix `as_object` UB discussed in #1748 (#1751) * Fix as_object UB discussed in #1748 * More cleanup, more less UB * Fix fixes * Added uninit_on_shmem api * clippy * fmt * trying to fix fuzzers, libfuzzer wrapper * Add OwnedRefMit::owned constructor, libfuzzer fix * Some more fixes * Add BacktaceObserver::owned fn * fmt * more fmt * Ignore SigPipe by default (#1741) * Ignore SigPipe by default * Fix no_std * fmt * Fix incorrect imports (#1758) * Fix incorrect imports https://doc.rust-lang.org/core/simd/trait.SimdOrd.html * Fix * Try fix ci * Documentation fixes (#1761) * Documentation fixes * Fix InProcessExecutor url * Update all urls to latest * Miri ignores for M1 regex (#1762) * Enabling DrCov on Windows (#1765) * Enabling DrCov for Windows * Dedup common code in scheduler (#1702) * dedup common code in scheduler * del eco * fixing * fix * replace `Emulator::new_empty` by `Emulator::get` calls outside of `emu.rs` for safety. (#1763) * Add mute_inprocess_target fn, SimpleFdLogger::set_logger, and more (#1754) * Add mute_inprocess_target fn, SimpleFdLogger::set_logger, set_error_print_panic_hook * Trying to fix #1753 * typo * More fix * Fix test? * more testcase fixes * Fix: renamed remaining QemuInstrumentationFilter instances. * Split emu in systemmode.rs / usermode.rs for specific code. EmuExitHandler implementation. * format * format * format * Replace sync_exit with sync_backdoor. * Rework command system. * fix bad import. * format. * cargo fmt * disable af-xdp as well to avoid linking errors. * End of merging. * format. * Adaptation for usermode. * format. * injection support. * usermode fixes. format. * clippy * clippy + format * Do not unwrap emu + format. * fix: entry_point breakpoint * inital commit. * clippy * tests * clippy * adapt example * systemmode. * renaming * fmt * fix lints. * more lint fix. * even more lint fixes. * always more lint fixes. * lint fix. * allow unused qualifications for crate when it could be confusing. * Still lint fixes. * Lint fixes on generated code. * Some lint fixes. * merge continue. * renamed modules as well. * fixing merge. * systemmode compiling. * fmt * fix early emulator drop. * fmt * fix cast to c_void of the wrong object. * Added global enum for snapshot managers. Some renaming. * move things around. * WIP: generic inclusion of QEMU Executor in exit handler. * * Moved extern calls to `libafl_qemu_sys` * Replaced old `Emulator` by `Qemu` and only kept C functions wrappers * Now `Emulator` is for higher-level interactions with QEMU. Kept old functions for compatibility calling to `Qemu` functions * A direct side effect of this slit is the removal of the `IsEmuExitHandler` trait dependency added in many parts of the code. * Removed old dirty casting for `QemuExecutor` helpers and used the brand-new access to `QemuExecutorState` instead. * Minor changes to `Qemu` and `Emulator` `get` methods for cleaner getters. * Add missing `Qemu` function. * Updated `qemu_systemmode` example. * Adapted QEMU fuzzers + renaming. * Fixed python. * fix libafl_sugar with new implementation. * fix dangling RefCell. adapt new examples. TODO: merge `libafl_systemmode.*` examples. * clippy. * fix more fuzzers. * clippy. * Implement `HasInstrumentationFilter` generically. Updated `StdInstrumentationFilter` accordingly. * Renamed breakpoint functions for QEMU. `qemu.run()` handling. * Removed OnceCell / RefCell in signature. more explicit `MmapPerms` method names. * minor code refactoring * Emulator::run_handle refactoring * deprecated Emulator functions calling directly to QEMU functions. * IsSnapshotManager -> SnapshotManager * IsEmuExitHandler -> EmuExitHandler + fmt * Generic register when it makes sense. * reverted IsSnapshotManager -> SnapshotManager because of a collision. * fix syntax + clippy * fmt --------- Co-authored-by: Dongjia "toka" Zhang Co-authored-by: Dominik Maier Co-authored-by: lazymio Co-authored-by: Bet4 <0xbet4@gmail.com> Co-authored-by: mkravchik --- fuzzers/fuzzbench_fork_qemu/src/fuzzer.rs | 54 +- fuzzers/fuzzbench_qemu/src/fuzzer.rs | 55 +- fuzzers/qemu_cmin/src/fuzzer.rs | 51 +- fuzzers/qemu_coverage/src/fuzzer.rs | 52 +- fuzzers/qemu_launcher/src/client.rs | 37 +- fuzzers/qemu_launcher/src/harness.rs | 30 +- fuzzers/qemu_launcher/src/instance.rs | 8 +- fuzzers/qemu_systemmode/Cargo.toml | 1 + fuzzers/qemu_systemmode/src/fuzzer.rs | 43 +- .../qemu_systemmode_breakpoints/.gitignore | 1 + .../qemu_systemmode_breakpoints/Cargo.toml | 21 + fuzzers/qemu_systemmode_breakpoints/README.md | 26 + .../qemu_systemmode_breakpoints/corpus/random | 1 + .../qemu_systemmode_breakpoints/corpus/zero | Bin 0 -> 5 bytes .../example/build.sh | 2 + .../example/main.c | 33 + .../example/mps2_m3.ld | 143 ++ .../example/startup.c | 107 + .../qemu_systemmode_breakpoints/src/fuzzer.rs | 272 +++ .../qemu_systemmode_breakpoints/src/main.rs | 13 + .../qemu_systemmode_sync_backdoor/.gitignore | 1 + .../qemu_systemmode_sync_backdoor/Cargo.toml | 21 + .../qemu_systemmode_sync_backdoor/README.md | 26 + .../corpus/random | 1 + .../qemu_systemmode_sync_backdoor/corpus/zero | Bin 0 -> 5 bytes .../example/build.sh | 2 + .../example/main.c | 37 + .../example/mps2_m3.ld | 143 ++ .../example/startup.c | 107 + .../src/fuzzer.rs | 213 ++ .../qemu_systemmode_sync_backdoor/src/main.rs | 13 + libafl/src/monitors/disk.rs | 2 +- libafl_bolts/src/cli.rs | 4 +- libafl_qemu/Cargo.toml | 3 +- libafl_qemu/build_linux.rs | 26 +- libafl_qemu/libafl_qemu_build/src/bindings.rs | 1 + libafl_qemu/libafl_qemu_sys/Cargo.toml | 9 + libafl_qemu/libafl_qemu_sys/src/lib.rs | 238 +++ libafl_qemu/libafl_qemu_sys/src/systemmode.rs | 16 + libafl_qemu/libafl_qemu_sys/src/usermode.rs | 36 + libafl_qemu/runtime/libafl_exit.h | 176 ++ libafl_qemu/runtime/libafl_exit_windows.asm | 63 + libafl_qemu/src/aarch64.rs | 24 +- libafl_qemu/src/arm.rs | 24 +- libafl_qemu/src/asan.rs | 308 ++- libafl_qemu/src/breakpoint.rs | 95 + libafl_qemu/src/calls.rs | 54 +- libafl_qemu/src/cmplog.rs | 36 +- libafl_qemu/src/command.rs | 660 ++++++ libafl_qemu/src/drcov.rs | 23 +- libafl_qemu/src/edges.rs | 118 +- libafl_qemu/src/elf.rs | 3 +- libafl_qemu/src/emu.rs | 1847 ++++++++--------- libafl_qemu/src/emu/systemmode.rs | 451 ++++ libafl_qemu/src/emu/usermode.rs | 491 +++++ libafl_qemu/src/executor/mod.rs | 36 +- libafl_qemu/src/executor/stateful.rs | 14 +- libafl_qemu/src/helper.rs | 94 +- libafl_qemu/src/hexagon.rs | 24 +- libafl_qemu/src/hooks.rs | 78 +- libafl_qemu/src/i386.rs | 24 +- libafl_qemu/src/injections.rs | 19 +- libafl_qemu/src/lib.rs | 6 +- libafl_qemu/src/mips.rs | 24 +- libafl_qemu/src/ppc.rs | 24 +- libafl_qemu/src/snapshot.rs | 66 +- libafl_qemu/src/sync_backdoor.rs | 221 +- libafl_qemu/src/x86_64.rs | 24 +- libafl_sugar/src/qemu.rs | 18 +- 69 files changed, 5211 insertions(+), 1683 deletions(-) create mode 100644 fuzzers/qemu_systemmode_breakpoints/.gitignore create mode 100644 fuzzers/qemu_systemmode_breakpoints/Cargo.toml create mode 100644 fuzzers/qemu_systemmode_breakpoints/README.md create mode 100644 fuzzers/qemu_systemmode_breakpoints/corpus/random create mode 100644 fuzzers/qemu_systemmode_breakpoints/corpus/zero create mode 100755 fuzzers/qemu_systemmode_breakpoints/example/build.sh create mode 100644 fuzzers/qemu_systemmode_breakpoints/example/main.c create mode 100644 fuzzers/qemu_systemmode_breakpoints/example/mps2_m3.ld create mode 100644 fuzzers/qemu_systemmode_breakpoints/example/startup.c create mode 100644 fuzzers/qemu_systemmode_breakpoints/src/fuzzer.rs create mode 100644 fuzzers/qemu_systemmode_breakpoints/src/main.rs create mode 100644 fuzzers/qemu_systemmode_sync_backdoor/.gitignore create mode 100644 fuzzers/qemu_systemmode_sync_backdoor/Cargo.toml create mode 100644 fuzzers/qemu_systemmode_sync_backdoor/README.md create mode 100644 fuzzers/qemu_systemmode_sync_backdoor/corpus/random create mode 100644 fuzzers/qemu_systemmode_sync_backdoor/corpus/zero create mode 100755 fuzzers/qemu_systemmode_sync_backdoor/example/build.sh create mode 100644 fuzzers/qemu_systemmode_sync_backdoor/example/main.c create mode 100644 fuzzers/qemu_systemmode_sync_backdoor/example/mps2_m3.ld create mode 100644 fuzzers/qemu_systemmode_sync_backdoor/example/startup.c create mode 100644 fuzzers/qemu_systemmode_sync_backdoor/src/fuzzer.rs create mode 100644 fuzzers/qemu_systemmode_sync_backdoor/src/main.rs create mode 100644 libafl_qemu/libafl_qemu_sys/src/systemmode.rs create mode 100644 libafl_qemu/libafl_qemu_sys/src/usermode.rs create mode 100644 libafl_qemu/runtime/libafl_exit.h create mode 100755 libafl_qemu/runtime/libafl_exit_windows.asm create mode 100644 libafl_qemu/src/breakpoint.rs create mode 100644 libafl_qemu/src/command.rs create mode 100644 libafl_qemu/src/emu/systemmode.rs create mode 100644 libafl_qemu/src/emu/usermode.rs diff --git a/fuzzers/fuzzbench_fork_qemu/src/fuzzer.rs b/fuzzers/fuzzbench_fork_qemu/src/fuzzer.rs index 49bacd7d08..5b3875ac7c 100644 --- a/fuzzers/fuzzbench_fork_qemu/src/fuzzer.rs +++ b/fuzzers/fuzzbench_fork_qemu/src/fuzzer.rs @@ -39,7 +39,7 @@ use libafl::{ }; use libafl_bolts::{ current_nanos, current_time, - os::dup2, + os::{dup2, unix_signals::Signal}, rands::StdRand, shmem::{ShMemProvider, StdShMemProvider}, tuples::{tuple_list, Merge}, @@ -49,10 +49,10 @@ use libafl_qemu::{ cmplog::{CmpLogMap, CmpLogObserver, QemuCmpLogChildHelper}, edges::{QemuEdgeCoverageChildHelper, EDGES_MAP_PTR, EDGES_MAP_SIZE}, elf::EasyElf, - emu::Emulator, filter_qemu_args, hooks::QemuHooks, - GuestReg, MmapPerms, QemuForkExecutor, Regs, + GuestReg, MmapPerms, Qemu, QemuExitReason, QemuExitReasonError, QemuForkExecutor, + QemuShutdownCause, Regs, }; #[cfg(unix)] use nix::{self, unistd::dup}; @@ -148,33 +148,38 @@ fn fuzz( let args: Vec = env::args().collect(); let env: Vec<(String, String)> = env::vars().collect(); - let emu = Emulator::new(&args, &env)?; + let qemu = Qemu::init(&args, &env)?; let mut elf_buffer = Vec::new(); - let elf = EasyElf::from_file(emu.binary_path(), &mut elf_buffer)?; + let elf = EasyElf::from_file(qemu.binary_path(), &mut elf_buffer)?; let test_one_input_ptr = elf - .resolve_symbol("LLVMFuzzerTestOneInput", emu.load_addr()) + .resolve_symbol("LLVMFuzzerTestOneInput", qemu.load_addr()) .expect("Symbol LLVMFuzzerTestOneInput not found"); println!("LLVMFuzzerTestOneInput @ {test_one_input_ptr:#x}"); - emu.set_breakpoint(test_one_input_ptr); // LLVMFuzzerTestOneInput - unsafe { emu.run() }; + qemu.set_breakpoint(test_one_input_ptr); // LLVMFuzzerTestOneInput + unsafe { + match qemu.run() { + Ok(QemuExitReason::Breakpoint(_)) => {} + _ => panic!("Unexpected QEMU exit."), + } + } - println!("Break at {:#x}", emu.read_reg::<_, u64>(Regs::Rip).unwrap()); + println!("Break at {:#x}", qemu.read_reg::<_, u64>(Regs::Pc).unwrap()); - let stack_ptr: u64 = emu.read_reg(Regs::Rsp).unwrap(); + let stack_ptr: u64 = qemu.read_reg(Regs::Sp).unwrap(); let mut ret_addr = [0; 8]; - unsafe { emu.read_mem(stack_ptr, &mut ret_addr) }; + unsafe { qemu.read_mem(stack_ptr, &mut ret_addr) }; let ret_addr = u64::from_le_bytes(ret_addr); println!("Stack pointer = {stack_ptr:#x}"); println!("Return address = {ret_addr:#x}"); - emu.remove_breakpoint(test_one_input_ptr); // LLVMFuzzerTestOneInput - emu.set_breakpoint(ret_addr); // LLVMFuzzerTestOneInput ret addr + qemu.remove_breakpoint(test_one_input_ptr); // LLVMFuzzerTestOneInput + qemu.set_breakpoint(ret_addr); // LLVMFuzzerTestOneInput ret addr - let input_addr = emu.map_private(0, 4096, MmapPerms::ReadWrite).unwrap(); + let input_addr = qemu.map_private(0, 4096, MmapPerms::ReadWrite).unwrap(); println!("Placing input at {input_addr:#x}"); let log = RefCell::new( @@ -314,21 +319,28 @@ fn fuzz( } unsafe { - emu.write_mem(input_addr, buf); + qemu.write_mem(input_addr, buf); - emu.write_reg(Regs::Rdi, input_addr).unwrap(); - emu.write_reg(Regs::Rsi, len as GuestReg).unwrap(); - emu.write_reg(Regs::Rip, test_one_input_ptr).unwrap(); - emu.write_reg(Regs::Rsp, stack_ptr).unwrap(); + qemu.write_reg(Regs::Rdi, input_addr).unwrap(); + qemu.write_reg(Regs::Rsi, len as GuestReg).unwrap(); + qemu.write_reg(Regs::Rip, test_one_input_ptr).unwrap(); + qemu.write_reg(Regs::Rsp, stack_ptr).unwrap(); - emu.run(); + match qemu.run() { + Ok(QemuExitReason::Breakpoint(_)) => {} + Ok(QemuExitReason::End(QemuShutdownCause::HostSignal(Signal::SigInterrupt))) => { + process::exit(0) + } + Err(QemuExitReasonError::UnexpectedExit) => return ExitKind::Crash, + _ => panic!("Unexpected QEMU exit."), + } } ExitKind::Ok }; let mut hooks = QemuHooks::new( - emu.clone(), + qemu.clone(), tuple_list!( QemuEdgeCoverageChildHelper::default(), QemuCmpLogChildHelper::default(), diff --git a/fuzzers/fuzzbench_qemu/src/fuzzer.rs b/fuzzers/fuzzbench_qemu/src/fuzzer.rs index b2bedb50cb..06f3a1bb39 100644 --- a/fuzzers/fuzzbench_qemu/src/fuzzer.rs +++ b/fuzzers/fuzzbench_qemu/src/fuzzer.rs @@ -38,7 +38,7 @@ use libafl::{ }; use libafl_bolts::{ current_nanos, current_time, - os::dup2, + os::{dup2, unix_signals::Signal}, rands::StdRand, shmem::{ShMemProvider, StdShMemProvider}, tuples::{tuple_list, Merge}, @@ -53,11 +53,14 @@ use libafl_qemu::{ elf::EasyElf, filter_qemu_args, hooks::QemuHooks, - Emulator, GuestReg, //snapshot::QemuSnapshotHelper, MmapPerms, + Qemu, QemuExecutor, + QemuExitReason, + QemuExitReasonError, + QemuShutdownCause, Regs, }; #[cfg(unix)] @@ -172,34 +175,39 @@ fn fuzz( let args: Vec = env::args().collect(); let env: Vec<(String, String)> = env::vars().collect(); - let emu = Emulator::new(&args, &env).unwrap(); + let qemu = Qemu::init(&args, &env).unwrap(); // let (emu, asan) = init_with_asan(&mut args, &mut env).unwrap(); let mut elf_buffer = Vec::new(); - let elf = EasyElf::from_file(emu.binary_path(), &mut elf_buffer)?; + let elf = EasyElf::from_file(qemu.binary_path(), &mut elf_buffer)?; let test_one_input_ptr = elf - .resolve_symbol("LLVMFuzzerTestOneInput", emu.load_addr()) + .resolve_symbol("LLVMFuzzerTestOneInput", qemu.load_addr()) .expect("Symbol LLVMFuzzerTestOneInput not found"); println!("LLVMFuzzerTestOneInput @ {test_one_input_ptr:#x}"); - emu.set_breakpoint(test_one_input_ptr); // LLVMFuzzerTestOneInput - unsafe { emu.run() }; + qemu.set_breakpoint(test_one_input_ptr); // LLVMFuzzerTestOneInput + unsafe { + match qemu.run() { + Ok(QemuExitReason::Breakpoint(_)) => {} + _ => panic!("Unexpected QEMU exit."), + } + } - println!("Break at {:#x}", emu.read_reg::<_, u64>(Regs::Rip).unwrap()); + println!("Break at {:#x}", qemu.read_reg::<_, u64>(Regs::Pc).unwrap()); - let stack_ptr: u64 = emu.read_reg(Regs::Rsp).unwrap(); + let stack_ptr: u64 = qemu.read_reg(Regs::Sp).unwrap(); let mut ret_addr = [0; 8]; - unsafe { emu.read_mem(stack_ptr, &mut ret_addr) }; + unsafe { qemu.read_mem(stack_ptr, &mut ret_addr) }; let ret_addr = u64::from_le_bytes(ret_addr); println!("Stack pointer = {stack_ptr:#x}"); println!("Return address = {ret_addr:#x}"); - emu.remove_breakpoint(test_one_input_ptr); // LLVMFuzzerTestOneInput - emu.set_breakpoint(ret_addr); // LLVMFuzzerTestOneInput ret addr + qemu.remove_breakpoint(test_one_input_ptr); // LLVMFuzzerTestOneInput + qemu.set_breakpoint(ret_addr); // LLVMFuzzerTestOneInput ret addr - let input_addr = emu + let input_addr = qemu .map_private(0, MAX_INPUT_SIZE, MmapPerms::ReadWrite) .unwrap(); println!("Placing input at {input_addr:#x}"); @@ -328,21 +336,28 @@ fn fuzz( } unsafe { - emu.write_mem(input_addr, buf); + qemu.write_mem(input_addr, buf); - emu.write_reg(Regs::Rdi, input_addr).unwrap(); - emu.write_reg(Regs::Rsi, len as GuestReg).unwrap(); - emu.write_reg(Regs::Rip, test_one_input_ptr).unwrap(); - emu.write_reg(Regs::Rsp, stack_ptr).unwrap(); + qemu.write_reg(Regs::Rdi, input_addr).unwrap(); + qemu.write_reg(Regs::Rsi, len as GuestReg).unwrap(); + qemu.write_reg(Regs::Rip, test_one_input_ptr).unwrap(); + qemu.write_reg(Regs::Rsp, stack_ptr).unwrap(); - emu.run(); + match qemu.run() { + Ok(QemuExitReason::Breakpoint(_)) => {} + Ok(QemuExitReason::End(QemuShutdownCause::HostSignal(Signal::SigInterrupt))) => { + process::exit(0) + } + Err(QemuExitReasonError::UnexpectedExit) => return ExitKind::Crash, + _ => panic!("Unexpected QEMU exit."), + } } ExitKind::Ok }; let mut hooks = QemuHooks::new( - emu.clone(), + qemu.clone(), tuple_list!( QemuEdgeCoverageHelper::default(), QemuCmpLogHelper::default(), diff --git a/fuzzers/qemu_cmin/src/fuzzer.rs b/fuzzers/qemu_cmin/src/fuzzer.rs index 6a7e5e5133..8787b053a7 100644 --- a/fuzzers/qemu_cmin/src/fuzzer.rs +++ b/fuzzers/qemu_cmin/src/fuzzer.rs @@ -21,6 +21,7 @@ use libafl::{ use libafl_bolts::{ core_affinity::Cores, current_nanos, + os::unix_signals::Signal, rands::StdRand, shmem::{ShMemProvider, StdShMemProvider}, tuples::tuple_list, @@ -29,9 +30,8 @@ use libafl_bolts::{ use libafl_qemu::{ edges::{QemuEdgeCoverageChildHelper, EDGES_MAP_PTR, EDGES_MAP_SIZE}, elf::EasyElf, - emu::Emulator, - ArchExtras, CallingConvention, GuestAddr, GuestReg, MmapPerms, QemuForkExecutor, QemuHooks, - Regs, + ArchExtras, CallingConvention, GuestAddr, GuestReg, MmapPerms, Qemu, QemuExitReason, + QemuExitReasonError, QemuForkExecutor, QemuHooks, QemuShutdownCause, Regs, }; #[derive(Default)] @@ -113,31 +113,31 @@ pub fn fuzz() -> Result<(), Error> { env::remove_var("LD_LIBRARY_PATH"); let env: Vec<(String, String)> = env::vars().collect(); - let emu = Emulator::new(&options.args, &env).unwrap(); + let qemu = Qemu::init(&options.args, &env).unwrap(); let mut elf_buffer = Vec::new(); - let elf = EasyElf::from_file(emu.binary_path(), &mut elf_buffer).unwrap(); + let elf = EasyElf::from_file(qemu.binary_path(), &mut elf_buffer).unwrap(); let test_one_input_ptr = elf - .resolve_symbol("LLVMFuzzerTestOneInput", emu.load_addr()) + .resolve_symbol("LLVMFuzzerTestOneInput", qemu.load_addr()) .expect("Symbol LLVMFuzzerTestOneInput not found"); log::debug!("LLVMFuzzerTestOneInput @ {test_one_input_ptr:#x}"); - emu.entry_break(test_one_input_ptr); + qemu.entry_break(test_one_input_ptr); - let pc: GuestReg = emu.read_reg(Regs::Pc).unwrap(); + let pc: GuestReg = qemu.read_reg(Regs::Pc).unwrap(); log::debug!("Break at {pc:#x}"); - let ret_addr: GuestAddr = emu.read_return_address().unwrap(); + let ret_addr: GuestAddr = qemu.read_return_address().unwrap(); log::debug!("Return address = {ret_addr:#x}"); - emu.set_breakpoint(ret_addr); + qemu.set_breakpoint(ret_addr); - let input_addr = emu + let input_addr = qemu .map_private(0, MAX_INPUT_SIZE, MmapPerms::ReadWrite) .unwrap(); log::debug!("Placing input at {input_addr:#x}"); - let stack_ptr: GuestAddr = emu.read_reg(Regs::Sp).unwrap(); + let stack_ptr: GuestAddr = qemu.read_reg(Regs::Sp).unwrap(); let mut shmem_provider = StdShMemProvider::new().expect("Failed to init shared memory"); @@ -201,24 +201,29 @@ pub fn fuzz() -> Result<(), Error> { let len = len as GuestReg; unsafe { - emu.write_mem(input_addr, buf); - emu.write_reg(Regs::Pc, test_one_input_ptr).unwrap(); - emu.write_reg(Regs::Sp, stack_ptr).unwrap(); - emu.write_return_address(ret_addr).unwrap(); - emu.write_function_argument(CallingConvention::Cdecl, 0, input_addr) + qemu.write_mem(input_addr, buf); + qemu.write_reg(Regs::Pc, test_one_input_ptr).unwrap(); + qemu.write_reg(Regs::Sp, stack_ptr).unwrap(); + qemu.write_return_address(ret_addr).unwrap(); + qemu.write_function_argument(CallingConvention::Cdecl, 0, input_addr) .unwrap(); - emu.write_function_argument(CallingConvention::Cdecl, 1, len) + qemu.write_function_argument(CallingConvention::Cdecl, 1, len) .unwrap(); - emu.run(); + + match qemu.run() { + Ok(QemuExitReason::Breakpoint(_)) => {} + Ok(QemuExitReason::End(QemuShutdownCause::HostSignal(Signal::SigInterrupt))) => { + process::exit(0) + } + Err(QemuExitReasonError::UnexpectedExit) => return ExitKind::Crash, + _ => panic!("Unexpected QEMU exit."), + } } ExitKind::Ok }; - let mut hooks = QemuHooks::new( - emu.clone(), - tuple_list!(QemuEdgeCoverageChildHelper::default(),), - ); + let mut hooks = QemuHooks::new(qemu, tuple_list!(QemuEdgeCoverageChildHelper::default(),)); let mut executor = QemuForkExecutor::new( &mut hooks, diff --git a/fuzzers/qemu_coverage/src/fuzzer.rs b/fuzzers/qemu_coverage/src/fuzzer.rs index 12e473f951..698fc59883 100644 --- a/fuzzers/qemu_coverage/src/fuzzer.rs +++ b/fuzzers/qemu_coverage/src/fuzzer.rs @@ -20,14 +20,16 @@ use libafl::{ use libafl_bolts::{ core_affinity::Cores, current_nanos, + os::unix_signals::Signal, rands::StdRand, shmem::{ShMemProvider, StdShMemProvider}, tuples::tuple_list, AsSlice, }; use libafl_qemu::{ - drcov::QemuDrCovHelper, elf::EasyElf, emu::Emulator, ArchExtras, CallingConvention, GuestAddr, - GuestReg, MmapPerms, QemuExecutor, QemuHooks, QemuInstrumentationAddressRangeFilter, Regs, + drcov::QemuDrCovHelper, elf::EasyElf, ArchExtras, CallingConvention, GuestAddr, GuestReg, + MmapPerms, Qemu, QemuExecutor, QemuExitReason, QemuHooks, + QemuInstrumentationAddressRangeFilter, QemuShutdownCause, Regs, }; use rangemap::RangeMap; @@ -118,19 +120,19 @@ pub fn fuzz() { env::remove_var("LD_LIBRARY_PATH"); let env: Vec<(String, String)> = env::vars().collect(); - let emu = Emulator::new(&options.args, &env).unwrap(); + let qemu = Qemu::init(&options.args, &env).unwrap(); let mut elf_buffer = Vec::new(); - let elf = EasyElf::from_file(emu.binary_path(), &mut elf_buffer).unwrap(); + let elf = EasyElf::from_file(qemu.binary_path(), &mut elf_buffer).unwrap(); let test_one_input_ptr = elf - .resolve_symbol("LLVMFuzzerTestOneInput", emu.load_addr()) + .resolve_symbol("LLVMFuzzerTestOneInput", qemu.load_addr()) .expect("Symbol LLVMFuzzerTestOneInput not found"); log::debug!("LLVMFuzzerTestOneInput @ {test_one_input_ptr:#x}"); - emu.entry_break(test_one_input_ptr); + qemu.entry_break(test_one_input_ptr); - for m in emu.mappings() { + for m in qemu.mappings() { log::debug!( "Mapping: 0x{:016x}-0x{:016x}, {}", m.start(), @@ -139,30 +141,38 @@ pub fn fuzz() { ); } - let pc: GuestReg = emu.read_reg(Regs::Pc).unwrap(); + let pc: GuestReg = qemu.read_reg(Regs::Pc).unwrap(); log::debug!("Break at {pc:#x}"); - let ret_addr: GuestAddr = emu.read_return_address().unwrap(); + let ret_addr: GuestAddr = qemu.read_return_address().unwrap(); log::debug!("Return address = {ret_addr:#x}"); - emu.set_breakpoint(ret_addr); + qemu.set_breakpoint(ret_addr); - let input_addr = emu + let input_addr = qemu .map_private(0, MAX_INPUT_SIZE, MmapPerms::ReadWrite) .unwrap(); log::debug!("Placing input at {input_addr:#x}"); - let stack_ptr: GuestAddr = emu.read_reg(Regs::Sp).unwrap(); + let stack_ptr: GuestAddr = qemu.read_reg(Regs::Sp).unwrap(); let reset = |buf: &[u8], len: GuestReg| -> Result<(), String> { unsafe { - emu.write_mem(input_addr, buf); - emu.write_reg(Regs::Pc, test_one_input_ptr)?; - emu.write_reg(Regs::Sp, stack_ptr)?; - emu.write_return_address(ret_addr)?; - emu.write_function_argument(CallingConvention::Cdecl, 0, input_addr)?; - emu.write_function_argument(CallingConvention::Cdecl, 1, len)?; - emu.run(); + qemu.write_mem(input_addr, buf); + qemu.write_reg(Regs::Pc, test_one_input_ptr)?; + qemu.write_reg(Regs::Sp, stack_ptr)?; + qemu.write_return_address(ret_addr)?; + qemu.write_function_argument(CallingConvention::Cdecl, 0, input_addr)?; + qemu.write_function_argument(CallingConvention::Cdecl, 1, len)?; + + match qemu.run() { + Ok(QemuExitReason::Breakpoint(_)) => {} + Ok(QemuExitReason::End(QemuShutdownCause::HostSignal(Signal::SigInterrupt))) => { + process::exit(0) + } + _ => panic!("Unexpected QEMU exit."), + } + Ok(()) } }; @@ -218,7 +228,7 @@ pub fn fuzz() { let scheduler = QueueScheduler::new(); let mut fuzzer = StdFuzzer::new(scheduler, feedback, objective); - let rangemap = emu + let rangemap = qemu .mappings() .filter_map(|m| { m.path() @@ -241,7 +251,7 @@ pub fn fuzz() { coverage.set_file_name(format!("{coverage_name}-{core:03}.{coverage_extension}")); let mut hooks = QemuHooks::new( - emu.clone(), + qemu, tuple_list!(QemuDrCovHelper::new( QemuInstrumentationAddressRangeFilter::None, rangemap, diff --git a/fuzzers/qemu_launcher/src/client.rs b/fuzzers/qemu_launcher/src/client.rs index 39e2827dae..5604fef72b 100644 --- a/fuzzers/qemu_launcher/src/client.rs +++ b/fuzzers/qemu_launcher/src/client.rs @@ -11,11 +11,11 @@ use libafl_bolts::{core_affinity::CoreId, rands::StdRand, tuples::tuple_list}; #[cfg(feature = "injections")] use libafl_qemu::injections::QemuInjectionHelper; use libafl_qemu::{ - asan::{init_with_asan, QemuAsanHelper}, + asan::{init_qemu_with_asan, QemuAsanHelper}, cmplog::QemuCmpLogHelper, edges::QemuEdgeCoverageHelper, elf::EasyElf, - ArchExtras, Emulator, GuestAddr, QemuInstrumentationAddressRangeFilter, + ArchExtras, GuestAddr, Qemu, QemuInstrumentationAddressRangeFilter, }; use crate::{ @@ -53,21 +53,18 @@ impl<'a> Client<'a> { .collect::>() } - fn start_pc(emu: &Emulator) -> Result { + fn start_pc(qemu: &Qemu) -> Result { let mut elf_buffer = Vec::new(); - let elf = EasyElf::from_file(emu.binary_path(), &mut elf_buffer)?; + let elf = EasyElf::from_file(qemu.binary_path(), &mut elf_buffer)?; let start_pc = elf - .resolve_symbol("LLVMFuzzerTestOneInput", emu.load_addr()) + .resolve_symbol("LLVMFuzzerTestOneInput", qemu.load_addr()) .ok_or_else(|| Error::empty_optional("Symbol LLVMFuzzerTestOneInput not found"))?; Ok(start_pc) } #[allow(clippy::similar_names)] // elf != self - fn coverage_filter( - &self, - emu: &Emulator, - ) -> Result { + fn coverage_filter(&self, qemu: &Qemu) -> Result { /* Conversion is required on 32-bit targets, but not on 64-bit ones */ if let Some(includes) = &self.options.include { #[cfg_attr(target_pointer_width = "64", allow(clippy::useless_conversion))] @@ -91,9 +88,9 @@ impl<'a> Client<'a> { Ok(QemuInstrumentationAddressRangeFilter::DenyList(rules)) } else { let mut elf_buffer = Vec::new(); - let elf = EasyElf::from_file(emu.binary_path(), &mut elf_buffer)?; + let elf = EasyElf::from_file(qemu.binary_path(), &mut elf_buffer)?; let range = elf - .get_section(".text", emu.load_addr()) + .get_section(".text", qemu.load_addr()) .ok_or_else(|| Error::key_not_found("Failed to find .text section"))?; Ok(QemuInstrumentationAddressRangeFilter::AllowList(vec![ range, @@ -113,16 +110,16 @@ impl<'a> Client<'a> { let mut env = self.env(); log::debug!("ENV: {:#?}", env); - let (emu, mut asan) = { + let (qemu, mut asan) = { if self.options.is_asan_core(core_id) { - let (emu, asan) = init_with_asan(&mut args, &mut env)?; + let (emu, asan) = init_qemu_with_asan(&mut args, &mut env)?; (emu, Some(asan)) } else { - (Emulator::new(&args, &env)?, None) + (Qemu::init(&args, &env)?, None) } }; - let start_pc = Self::start_pc(&emu)?; + let start_pc = Self::start_pc(&qemu)?; log::debug!("start_pc @ {start_pc:#x}"); #[cfg(not(feature = "injections"))] @@ -146,22 +143,22 @@ impl<'a> Client<'a> { let extra_tokens = injection_helper.as_ref().map(|h| h.tokens.clone()); - emu.entry_break(start_pc); + qemu.entry_break(start_pc); - let ret_addr: GuestAddr = emu + let ret_addr: GuestAddr = qemu .read_return_address() .map_err(|e| Error::unknown(format!("Failed to read return address: {e:}")))?; log::debug!("ret_addr = {ret_addr:#x}"); - emu.set_breakpoint(ret_addr); + qemu.set_breakpoint(ret_addr); let is_asan = self.options.is_asan_core(core_id); let is_cmplog = self.options.is_cmplog_core(core_id); - let edge_coverage_helper = QemuEdgeCoverageHelper::new(self.coverage_filter(&emu)?); + let edge_coverage_helper = QemuEdgeCoverageHelper::new(self.coverage_filter(&qemu)?); let instance = Instance::builder() .options(self.options) - .emu(&emu) + .qemu(&qemu) .mgr(mgr) .core_id(core_id) .extra_tokens(extra_tokens); diff --git a/fuzzers/qemu_launcher/src/harness.rs b/fuzzers/qemu_launcher/src/harness.rs index 43b2aabb33..c8272b4a41 100644 --- a/fuzzers/qemu_launcher/src/harness.rs +++ b/fuzzers/qemu_launcher/src/harness.rs @@ -4,10 +4,10 @@ use libafl::{ Error, }; use libafl_bolts::AsSlice; -use libafl_qemu::{ArchExtras, CallingConvention, Emulator, GuestAddr, GuestReg, MmapPerms, Regs}; +use libafl_qemu::{ArchExtras, CallingConvention, GuestAddr, GuestReg, MmapPerms, Qemu, Regs}; pub struct Harness<'a> { - emu: &'a Emulator, + qemu: &'a Qemu, input_addr: GuestAddr, pc: GuestAddr, stack_ptr: GuestAddr, @@ -17,25 +17,25 @@ pub struct Harness<'a> { pub const MAX_INPUT_SIZE: usize = 1_048_576; // 1MB impl<'a> Harness<'a> { - pub fn new(emu: &Emulator) -> Result { - let input_addr = emu + pub fn new(qemu: &Qemu) -> Result { + let input_addr = qemu .map_private(0, MAX_INPUT_SIZE, MmapPerms::ReadWrite) .map_err(|e| Error::unknown(format!("Failed to map input buffer: {e:}")))?; - let pc: GuestReg = emu + let pc: GuestReg = qemu .read_reg(Regs::Pc) .map_err(|e| Error::unknown(format!("Failed to read PC: {e:}")))?; - let stack_ptr: GuestAddr = emu + let stack_ptr: GuestAddr = qemu .read_reg(Regs::Sp) .map_err(|e| Error::unknown(format!("Failed to read stack pointer: {e:}")))?; - let ret_addr: GuestAddr = emu + let ret_addr: GuestAddr = qemu .read_return_address() .map_err(|e| Error::unknown(format!("Failed to read return address: {e:}")))?; Ok(Harness { - emu, + qemu, input_addr, pc, stack_ptr, @@ -58,29 +58,29 @@ impl<'a> Harness<'a> { } let len = len as GuestReg; - unsafe { self.emu.write_mem(self.input_addr, buf) }; + unsafe { self.qemu.write_mem(self.input_addr, buf) }; - self.emu + self.qemu .write_reg(Regs::Pc, self.pc) .map_err(|e| Error::unknown(format!("Failed to write PC: {e:}")))?; - self.emu + self.qemu .write_reg(Regs::Sp, self.stack_ptr) .map_err(|e| Error::unknown(format!("Failed to write SP: {e:}")))?; - self.emu + self.qemu .write_return_address(self.ret_addr) .map_err(|e| Error::unknown(format!("Failed to write return address: {e:}")))?; - self.emu + self.qemu .write_function_argument(CallingConvention::Cdecl, 0, self.input_addr) .map_err(|e| Error::unknown(format!("Failed to write argument 0: {e:}")))?; - self.emu + self.qemu .write_function_argument(CallingConvention::Cdecl, 1, len) .map_err(|e| Error::unknown(format!("Failed to write argument 1: {e:}")))?; unsafe { - let _ = self.emu.run(); + let _ = self.qemu.run(); }; Ok(()) } diff --git a/fuzzers/qemu_launcher/src/instance.rs b/fuzzers/qemu_launcher/src/instance.rs index f122452c50..a026e36827 100644 --- a/fuzzers/qemu_launcher/src/instance.rs +++ b/fuzzers/qemu_launcher/src/instance.rs @@ -41,7 +41,7 @@ use libafl_qemu::{ cmplog::CmpLogObserver, edges::{edges_map_mut_slice, MAX_EDGES_NUM}, helper::QemuHelperTuple, - Emulator, QemuExecutor, QemuHooks, + Qemu, QemuExecutor, QemuHooks, }; use typed_builder::TypedBuilder; @@ -59,7 +59,7 @@ pub type ClientMgr = #[derive(TypedBuilder)] pub struct Instance<'a, M: Monitor> { options: &'a FuzzerOptions, - emu: &'a Emulator, + qemu: &'a Qemu, mgr: ClientMgr, core_id: CoreId, extra_tokens: Option>, @@ -72,7 +72,7 @@ impl<'a, M: Monitor> Instance<'a, M> { where QT: QemuHelperTuple + Debug, { - let mut hooks = QemuHooks::new(self.emu.clone(), helpers); + let mut hooks = QemuHooks::new(self.qemu.clone(), helpers); // Create an observation channel using the coverage map let edges_observer = unsafe { @@ -147,7 +147,7 @@ impl<'a, M: Monitor> Instance<'a, M> { state.add_metadata(tokens); - let harness = Harness::new(self.emu)?; + let harness = Harness::new(self.qemu)?; let mut harness = |input: &BytesInput| harness.run(input); // A fuzzer with feedbacks and a corpus scheduler diff --git a/fuzzers/qemu_systemmode/Cargo.toml b/fuzzers/qemu_systemmode/Cargo.toml index 519a978b55..c0dea615da 100644 --- a/fuzzers/qemu_systemmode/Cargo.toml +++ b/fuzzers/qemu_systemmode/Cargo.toml @@ -18,4 +18,5 @@ codegen-units = 1 libafl = { path = "../../libafl/" } libafl_bolts = { path = "../../libafl_bolts/" } libafl_qemu = { path = "../../libafl_qemu/", features = ["arm", "systemmode"] } +libafl_qemu_sys = { path = "../../libafl_qemu/libafl_qemu_sys", features = ["arm", "systemmode"] } env_logger = "*" diff --git a/fuzzers/qemu_systemmode/src/fuzzer.rs b/fuzzers/qemu_systemmode/src/fuzzer.rs index 75cc20bd9b..d0e2c9d510 100644 --- a/fuzzers/qemu_systemmode/src/fuzzer.rs +++ b/fuzzers/qemu_systemmode/src/fuzzer.rs @@ -22,6 +22,7 @@ use libafl::{ use libafl_bolts::{ core_affinity::Cores, current_nanos, + os::unix_signals::Signal, rands::StdRand, shmem::{ShMemProvider, StdShMemProvider}, tuples::tuple_list, @@ -30,9 +31,10 @@ use libafl_bolts::{ use libafl_qemu::{ edges::{edges_map_mut_slice, QemuEdgeCoverageHelper, MAX_EDGES_NUM}, elf::EasyElf, - emu::Emulator, - GuestPhysAddr, QemuExecutor, QemuHooks, Regs, + emu::Qemu, + QemuExecutor, QemuExitReason, QemuExitReasonError, QemuHooks, QemuShutdownCause, Regs, }; +use libafl_qemu_sys::GuestPhysAddr; pub static mut MAX_INPUT_SIZE: usize = 50; @@ -83,17 +85,20 @@ pub fn fuzz() { // Initialize QEMU let args: Vec = env::args().collect(); let env: Vec<(String, String)> = env::vars().collect(); - let emu = Emulator::new(&args, &env).unwrap(); + let qemu = Qemu::init(&args, &env).unwrap(); - emu.set_breakpoint(main_addr); + qemu.set_breakpoint(main_addr); unsafe { - emu.run().unwrap(); + match qemu.run() { + Ok(QemuExitReason::Breakpoint(_)) => {} + _ => panic!("Unexpected QEMU exit."), + } } - emu.remove_breakpoint(main_addr); + qemu.remove_breakpoint(main_addr); - emu.set_breakpoint(breakpoint); // BREAKPOINT + qemu.set_breakpoint(breakpoint); // BREAKPOINT - let devices = emu.list_devices(); + let devices = qemu.list_devices(); println!("Devices = {devices:?}"); // let saved_cpu_states: Vec<_> = (0..emu.num_cpus()) @@ -102,7 +107,7 @@ pub fn fuzz() { // emu.save_snapshot("start", true); - let snap = emu.create_fast_snapshot(true); + let snap = qemu.create_fast_snapshot(true); // The wrapped harness function, calling out to the LLVM-style harness let mut harness = |input: &BytesInput| { @@ -115,13 +120,20 @@ pub fn fuzz() { // len = MAX_INPUT_SIZE; } - emu.write_phys_mem(input_addr, buf); + qemu.write_phys_mem(input_addr, buf); - let _ = emu.run(); + match qemu.run() { + Ok(QemuExitReason::Breakpoint(_)) => {} + Ok(QemuExitReason::End(QemuShutdownCause::HostSignal( + Signal::SigInterrupt, + ))) => process::exit(0), + Err(QemuExitReasonError::UnexpectedExit) => return ExitKind::Crash, + _ => panic!("Unexpected QEMU exit."), + } // If the execution stops at any point other then the designated breakpoint (e.g. a breakpoint on a panic method) we consider it a crash - let mut pcs = (0..emu.num_cpus()) - .map(|i| emu.cpu_from_index(i)) + let mut pcs = (0..qemu.num_cpus()) + .map(|i| qemu.cpu_from_index(i)) .map(|cpu| -> Result { cpu.read_reg(Regs::Pc) }); let ret = match pcs .find(|pc| (breakpoint..breakpoint + 5).contains(pc.as_ref().unwrap_or(&0))) @@ -139,7 +151,7 @@ pub fn fuzz() { // emu.load_snapshot("start", true); // OPTION 3: restore a fast devices+mem snapshot - emu.restore_fast_snapshot(snap); + qemu.restore_fast_snapshot(snap); ret } @@ -194,7 +206,8 @@ pub fn fuzz() { // A fuzzer with feedbacks and a corpus scheduler let mut fuzzer = StdFuzzer::new(scheduler, feedback, objective); - let mut hooks = QemuHooks::new(emu.clone(), tuple_list!(QemuEdgeCoverageHelper::default())); + let mut hooks = + QemuHooks::new(qemu.clone(), tuple_list!(QemuEdgeCoverageHelper::default())); // Create a QEMU in-process executor let mut executor = QemuExecutor::new( diff --git a/fuzzers/qemu_systemmode_breakpoints/.gitignore b/fuzzers/qemu_systemmode_breakpoints/.gitignore new file mode 100644 index 0000000000..b511ae114b --- /dev/null +++ b/fuzzers/qemu_systemmode_breakpoints/.gitignore @@ -0,0 +1 @@ +*.qcow2 diff --git a/fuzzers/qemu_systemmode_breakpoints/Cargo.toml b/fuzzers/qemu_systemmode_breakpoints/Cargo.toml new file mode 100644 index 0000000000..45fc940353 --- /dev/null +++ b/fuzzers/qemu_systemmode_breakpoints/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "qemu_systemmode_breakpoints" +version = "0.11.1" +authors = ["Andrea Fioraldi ", "Dominik Maier ", "Romain Malmain "] +edition = "2021" + +[features] +default = ["std"] +std = [] + +[profile.release] +incremental = true +debug = true +lto = "fat" +codegen-units = 1 + +[dependencies] +libafl = { path = "../../libafl/" } +libafl_bolts = { path = "../../libafl_bolts/" } +libafl_qemu = { path = "../../libafl_qemu/", features = ["arm", "systemmode"] } +env_logger = "*" diff --git a/fuzzers/qemu_systemmode_breakpoints/README.md b/fuzzers/qemu_systemmode_breakpoints/README.md new file mode 100644 index 0000000000..14098dc09c --- /dev/null +++ b/fuzzers/qemu_systemmode_breakpoints/README.md @@ -0,0 +1,26 @@ +# Qemu systemmode with launcher + +This folder contains an example fuzzer for the qemu systemmode, using LLMP for fast multi-process fuzzing and crash detection. + +## Build + +To build this example, run + +```bash +cargo build --release +cd example; sh build.sh; cd .. +``` + +This will build the the fuzzer (src/fuzzer.rs) and a small example binary based on FreeRTOS, which can run under a qemu emulation target. + +## Run + +Since the instrumentation is based on snapshtos QEMU needs a virtual drive (even if it is unused...). +Create on and then run the fuzzer: +```bash +# create an image +qemu-img create -f qcow2 dummy.qcow2 32M +# run the fuzzer +KERNEL=./example/example.elf target/release/qemu_systemmode -icount shift=auto,align=off,sleep=off -machine mps2-an385 -monitor null -kernel ./example/example.elf -serial null -nographic -snapshot -drive if=none,format=qcow2,file=dummy.qcow2 -S +``` +Currently the ``KERNEL`` variable is needed because the fuzzer does not parse QEMUs arguments to find the binary. \ No newline at end of file diff --git a/fuzzers/qemu_systemmode_breakpoints/corpus/random b/fuzzers/qemu_systemmode_breakpoints/corpus/random new file mode 100644 index 0000000000..25175d51d3 --- /dev/null +++ b/fuzzers/qemu_systemmode_breakpoints/corpus/random @@ -0,0 +1 @@ +yJv lpZGօrsY˗CMRպ}S7;Io76l1ޘ1R^Όp>G&_|1;ro4ƯUE<`L"6VƷk4/"tf7Fގd]ܮ%|8#wNUn8%4o˗ diff --git a/fuzzers/qemu_systemmode_breakpoints/corpus/zero b/fuzzers/qemu_systemmode_breakpoints/corpus/zero new file mode 100644 index 0000000000000000000000000000000000000000..112363ac1917b417ffbd7f376ca786a1e5fa7490 GIT binary patch literal 5 McmZQzU|`?^000jF3jhEB literal 0 HcmV?d00001 diff --git a/fuzzers/qemu_systemmode_breakpoints/example/build.sh b/fuzzers/qemu_systemmode_breakpoints/example/build.sh new file mode 100755 index 0000000000..ceb387b19a --- /dev/null +++ b/fuzzers/qemu_systemmode_breakpoints/example/build.sh @@ -0,0 +1,2 @@ +#!/bin/sh +arm-none-eabi-gcc -ggdb -ffreestanding -nostartfiles -lgcc -T mps2_m3.ld -mcpu=cortex-m3 main.c startup.c -o example.elf \ No newline at end of file diff --git a/fuzzers/qemu_systemmode_breakpoints/example/main.c b/fuzzers/qemu_systemmode_breakpoints/example/main.c new file mode 100644 index 0000000000..13777fb35a --- /dev/null +++ b/fuzzers/qemu_systemmode_breakpoints/example/main.c @@ -0,0 +1,33 @@ +int __attribute__((noinline)) BREAKPOINT() { + for (;;) {} +} + +int LLVMFuzzerTestOneInput(unsigned int *Data, unsigned int Size) { + if (Data[3] == 0) { + while (1) {} + } // cause a timeout + for (int i = 0; i < Size; i++) { + // if (Data[i] > 0xFFd0 && Data[i] < 0xFFFF) {return 1;} // cause qemu to + // crash + for (int j = i + 1; j < Size; j++) { + if (Data[j] == 0) { continue; } + if (Data[j] > Data[i]) { + int tmp = Data[i]; + Data[i] = Data[j]; + Data[j] = tmp; + if (Data[i] <= 100) { j--; } + } + } + } + return BREAKPOINT(); +} +unsigned int FUZZ_INPUT[] = { + 101, 201, 700, 230, 860, 234, 980, 200, 340, 678, 230, 134, 900, + 236, 900, 123, 800, 123, 658, 607, 246, 804, 567, 568, 207, 407, + 246, 678, 457, 892, 834, 456, 878, 246, 699, 854, 234, 844, 290, + 125, 324, 560, 852, 928, 910, 790, 853, 345, 234, 586, +}; + +int main() { + LLVMFuzzerTestOneInput(FUZZ_INPUT, 50); +} diff --git a/fuzzers/qemu_systemmode_breakpoints/example/mps2_m3.ld b/fuzzers/qemu_systemmode_breakpoints/example/mps2_m3.ld new file mode 100644 index 0000000000..adfc15ed78 --- /dev/null +++ b/fuzzers/qemu_systemmode_breakpoints/example/mps2_m3.ld @@ -0,0 +1,143 @@ +/* + * FreeRTOS V202112.00 + * Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * https://www.FreeRTOS.org + * https://github.com/FreeRTOS + * + */ + +MEMORY +{ + RAM (xrw) : ORIGIN = 0x00000000, LENGTH = 4M + /* Originally */ + /* FLASH (xr) : ORIGIN = 0x00000000, LENGTH = 4M */ + /* RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 4M */ +} +ENTRY(Reset_Handler) + +_Min_Heap_Size = 0x300000 ; /* Required amount of heap. */ +_Min_Stack_Size = 0x4000 ; /* Required amount of stack. */ +M_VECTOR_RAM_SIZE = (16 + 48) * 4; +_estack = ORIGIN(RAM) + LENGTH(RAM); + +SECTIONS +{ + + .isr_vector : + { + __vector_table = .; + KEEP(*(.isr_vector)) + . = ALIGN(4); + } > RAM /* FLASH */ + + .text : + { + . = ALIGN(4); + *(.text*) + KEEP (*(.init)) + KEEP (*(.fini)) + KEEP(*(.eh_frame)) + *(.rodata*) + . = ALIGN(4); + _etext = .; + } > RAM /* FLASH */ + + .ARM.extab : + { + . = ALIGN(4); + *(.ARM.extab* .gnu.linkonce.armextab.*) + . = ALIGN(4); + } >RAM /* FLASH */ + + .ARM : + { + . = ALIGN(4); + __exidx_start = .; + *(.ARM.exidx* .gnu.linkonce.armexidx.*) + __exidx_end = .; + . = ALIGN(4); + } >RAM /* FLASH */ + + .interrupts_ram : + { + . = ALIGN(4); + __VECTOR_RAM__ = .; + __interrupts_ram_start__ = .; + . += M_VECTOR_RAM_SIZE; + . = ALIGN(4); + __interrupts_ram_end = .; + } > RAM + + _sidata = LOADADDR(.data); + + .data : /* AT ( _sidata ) */ + { + . = ALIGN(4); + _sdata = .; + *(.data*) + . = ALIGN(4); + _edata = .; + } > RAM /* RAM AT > FLASH */ + + .uninitialized (NOLOAD): + { + . = ALIGN(32); + __uninitialized_start = .; + *(.uninitialized) + KEEP(*(.keep.uninitialized)) + . = ALIGN(32); + __uninitialized_end = .; + } > RAM + + .bss : + { + . = ALIGN(4); + _sbss = .; + __bss_start__ = _sbss; + *(.bss*) + *(COMMON) + . = ALIGN(4); + _ebss = .; + __bss_end__ = _ebss; + } >RAM + + .heap : + { + . = ALIGN(8); + PROVIDE ( end = . ); + PROVIDE ( _end = . ); + _heap_bottom = .; + . = . + _Min_Heap_Size; + _heap_top = .; + . = . + _Min_Stack_Size; + . = ALIGN(8); + } >RAM + + /* Set stack top to end of RAM, and stack limit move down by + * size of stack_dummy section */ + __StackTop = ORIGIN(RAM) + LENGTH(RAM); + __StackLimit = __StackTop - _Min_Stack_Size; + PROVIDE(__stack = __StackTop); + + /* Check if data + heap + stack exceeds RAM limit */ + ASSERT(__StackLimit >= _heap_top, "region RAM overflowed with stack") +} + diff --git a/fuzzers/qemu_systemmode_breakpoints/example/startup.c b/fuzzers/qemu_systemmode_breakpoints/example/startup.c new file mode 100644 index 0000000000..36be3cf55a --- /dev/null +++ b/fuzzers/qemu_systemmode_breakpoints/example/startup.c @@ -0,0 +1,107 @@ +/* + * FreeRTOS V202112.00 + * Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * https://www.FreeRTOS.org + * https://github.com/FreeRTOS + * + */ + +typedef unsigned int uint32_t; + +extern int main(); + +extern uint32_t _estack, _sidata, _sdata, _edata, _sbss, _ebss; + +/* Prevent optimization so gcc does not replace code with memcpy */ +__attribute__((optimize("O0"))) __attribute__((naked)) void Reset_Handler( + void) { + /* set stack pointer */ + __asm volatile("ldr r0, =_estack"); + __asm volatile("mov sp, r0"); + + /* copy .data section from flash to RAM */ + // Not needed for this example, see linker script + // for( uint32_t * src = &_sidata, * dest = &_sdata; dest < &_edata; ) + // { + // *dest++ = *src++; + // } + + /* zero out .bss section */ + for (uint32_t *dest = &_sbss; dest < &_ebss;) { + *dest++ = 0; + } + + /* jump to board initialisation */ + void _start(void); + _start(); +} + +const uint32_t *isr_vector[] __attribute__((section(".isr_vector"))) = { + (uint32_t *)&_estack, + (uint32_t *)&Reset_Handler, /* Reset -15 */ + 0, /* NMI_Handler -14 */ + 0, /* HardFault_Handler -13 */ + 0, /* MemManage_Handler -12 */ + 0, /* BusFault_Handler -11 */ + 0, /* UsageFault_Handler -10 */ + 0, /* reserved */ + 0, /* reserved */ + 0, /* reserved */ + 0, /* reserved -6 */ + 0, /* SVC_Handler -5 */ + 0, /* DebugMon_Handler -4 */ + 0, /* reserved */ + 0, /* PendSV handler -2 */ + 0, /* SysTick_Handler -1 */ + 0, /* uart0 receive 0 */ + 0, /* uart0 transmit */ + 0, /* uart1 receive */ + 0, /* uart1 transmit */ + 0, /* uart 2 receive */ + 0, /* uart 2 transmit */ + 0, /* GPIO 0 combined interrupt */ + 0, /* GPIO 2 combined interrupt */ + 0, /* Timer 0 */ + 0, /* Timer 1 */ + 0, /* Dial Timer */ + 0, /* SPI0 SPI1 */ + 0, /* uart overflow 1, 2,3 */ + 0, /* Ethernet 13 */ +}; + +__attribute__((naked)) void exit(__attribute__((unused)) int status) { + /* Force qemu to exit using ARM Semihosting */ + __asm volatile( + "mov r1, r0\n" + "cmp r1, #0\n" + "bne .notclean\n" + "ldr r1, =0x20026\n" /* ADP_Stopped_ApplicationExit, a clean exit */ + ".notclean:\n" + "movs r0, #0x18\n" /* SYS_EXIT */ + "bkpt 0xab\n" + "end: b end\n"); +} + +void _start(void) { + main(); + exit(0); +} diff --git a/fuzzers/qemu_systemmode_breakpoints/src/fuzzer.rs b/fuzzers/qemu_systemmode_breakpoints/src/fuzzer.rs new file mode 100644 index 0000000000..9dceeb89b7 --- /dev/null +++ b/fuzzers/qemu_systemmode_breakpoints/src/fuzzer.rs @@ -0,0 +1,272 @@ +//! A fuzzer using qemu in systemmode for binary-only coverage of kernels +//! +use core::{ptr::addr_of_mut, time::Duration}; +use std::{env, path::PathBuf, process}; + +use libafl::{ + corpus::{Corpus, InMemoryCorpus, OnDiskCorpus}, + events::{launcher::Launcher, EventConfig}, + executors::ExitKind, + feedback_or, feedback_or_fast, + feedbacks::{CrashFeedback, MaxMapFeedback, TimeFeedback, TimeoutFeedback}, + fuzzer::{Fuzzer, StdFuzzer}, + inputs::BytesInput, + monitors::MultiMonitor, + mutators::scheduled::{havoc_mutations, StdScheduledMutator}, + observers::{HitcountsMapObserver, TimeObserver, VariableMapObserver}, + schedulers::{IndexesLenTimeMinimizerScheduler, QueueScheduler}, + stages::{CalibrationStage, StdMutationalStage}, + state::{HasCorpus, StdState}, + Error, +}; +use libafl_bolts::{ + core_affinity::Cores, + current_nanos, + rands::StdRand, + shmem::{ShMemProvider, StdShMemProvider}, + tuples::tuple_list, +}; +use libafl_qemu::{ + breakpoint::Breakpoint, + command::{Command, EmulatorMemoryChunk, EndCommand, StartCommand}, + edges::{edges_map_mut_slice, QemuEdgeCoverageHelper, MAX_EDGES_NUM}, + elf::EasyElf, + emu::Emulator, + executor::{stateful::StatefulQemuExecutor, QemuExecutorState}, + EmuExitReasonError, FastSnapshotManager, GuestPhysAddr, GuestReg, HandlerError, HandlerResult, + QemuHooks, StdEmuExitHandler, +}; + +// use libafl_qemu::QemuSnapshotBuilder; // for normal qemu snapshot + +pub static mut MAX_INPUT_SIZE: usize = 50; + +pub fn fuzz() { + env_logger::init(); + + if let Ok(s) = env::var("FUZZ_SIZE") { + str::parse::(&s).expect("FUZZ_SIZE was not a number"); + }; + // Hardcoded parameters + let timeout = Duration::from_secs(3); + let broker_port = 1337; + let cores = Cores::from_cmdline("1").unwrap(); + let corpus_dirs = [PathBuf::from("./corpus")]; + let objective_dir = PathBuf::from("./crashes"); + + let mut elf_buffer = Vec::new(); + let elf = EasyElf::from_file( + env::var("KERNEL").expect("KERNEL env not set"), + &mut elf_buffer, + ) + .unwrap(); + + let input_addr = elf + .resolve_symbol( + &env::var("FUZZ_INPUT").unwrap_or_else(|_| "FUZZ_INPUT".to_owned()), + 0, + ) + .expect("Symbol or env FUZZ_INPUT not found") as GuestPhysAddr; + println!("FUZZ_INPUT @ {input_addr:#x}"); + + let main_addr = elf + .resolve_symbol("main", 0) + .expect("Symbol main not found"); + println!("main address = {main_addr:#x}"); + + let breakpoint = elf + .resolve_symbol( + &env::var("BREAKPOINT").unwrap_or_else(|_| "BREAKPOINT".to_owned()), + 0, + ) + .expect("Symbol or env BREAKPOINT not found"); + println!("Breakpoint address = {breakpoint:#x}"); + + let mut run_client = |state: Option<_>, mut mgr, _core_id| { + // Initialize QEMU + let args: Vec = env::args().collect(); + let env: Vec<(String, String)> = env::vars().collect(); + + // Choose Snapshot Builder + // let emu_snapshot_manager = QemuSnapshotBuilder::new(true); + let emu_snapshot_manager = FastSnapshotManager::new(false); + + // Choose Exit Handler + let emu_exit_handler = StdEmuExitHandler::new(emu_snapshot_manager); + + // Create emulator + let emu = Emulator::new(&args, &env, emu_exit_handler).unwrap(); + + // Set breakpoints of interest with corresponding commands. + emu.add_breakpoint( + Breakpoint::with_command( + main_addr, + Command::StartCommand(StartCommand::new(EmulatorMemoryChunk::phys( + input_addr, + unsafe { MAX_INPUT_SIZE } as GuestReg, + None, + ))), + true, + ), + true, + ); + emu.add_breakpoint( + Breakpoint::with_command( + breakpoint, + Command::EndCommand(EndCommand::new(Some(ExitKind::Ok))), + false, + ), + true, + ); + + let devices = emu.list_devices(); + println!("Devices = {:?}", devices); + + // The wrapped harness function, calling out to the LLVM-style harness + let mut harness = + |input: &BytesInput, qemu_executor_state: &mut QemuExecutorState<_, _>| unsafe { + match emu.run(input, qemu_executor_state) { + Ok(handler_result) => match handler_result { + HandlerResult::UnhandledExit(unhandled_exit) => { + panic!("Unhandled exit: {}", unhandled_exit) + } + HandlerResult::EndOfRun(exit_kind) => return exit_kind, + HandlerResult::Interrupted => { + std::process::exit(0); + } + }, + Err(handler_error) => match handler_error { + HandlerError::QemuExitReasonError(emu_exit_reason_error) => { + match emu_exit_reason_error { + EmuExitReasonError::UnknownKind => panic!("unknown kind"), + EmuExitReasonError::UnexpectedExit => return ExitKind::Crash, + _ => { + panic!("Emu Exit unhandled error: {:?}", emu_exit_reason_error) + } + } + } + _ => panic!("Unhandled error: {:?}", handler_error), + }, + } + }; + + // Create an observation channel using the coverage map + let edges_observer = unsafe { + HitcountsMapObserver::new(VariableMapObserver::from_mut_slice( + "edges", + edges_map_mut_slice(), + addr_of_mut!(MAX_EDGES_NUM), + )) + }; + + // Create an observation channel to keep track of the execution time + let time_observer = TimeObserver::new("time"); + + // Feedback to rate the interestingness of an input + // This one is composed by two Feedbacks in OR + let mut feedback = feedback_or!( + // New maximization map feedback linked to the edges observer and the feedback state + MaxMapFeedback::tracking(&edges_observer, true, true), + // Time feedback, this one does not need a feedback state + TimeFeedback::with_observer(&time_observer) + ); + + // A feedback to choose if an input is a solution or not + let mut objective = feedback_or_fast!(CrashFeedback::new(), TimeoutFeedback::new()); + + // If not restarting, create a State from scratch + let mut state = state.unwrap_or_else(|| { + StdState::new( + // RNG + StdRand::with_seed(current_nanos()), + // Corpus that will be evolved, we keep it in memory for performance + InMemoryCorpus::new(), + // Corpus in which we store solutions (crashes in this example), + // on disk so the user can get them after stopping the fuzzer + OnDiskCorpus::new(objective_dir.clone()).unwrap(), + // States of the feedbacks. + // The feedbacks can report the data that should persist in the State. + &mut feedback, + // Same for objective feedbacks + &mut objective, + ) + .unwrap() + }); + + // A minimization+queue policy to get testcasess from the corpus + let scheduler = IndexesLenTimeMinimizerScheduler::new(QueueScheduler::new()); + + // A fuzzer with feedbacks and a corpus scheduler + let mut fuzzer = StdFuzzer::new(scheduler, feedback, objective); + + let mut hooks = QemuHooks::new( + emu.qemu().clone(), + tuple_list!(QemuEdgeCoverageHelper::default()), + ); + + // Setup an havoc mutator with a mutational stage + let mutator = StdScheduledMutator::new(havoc_mutations()); + let calibration_feedback = MaxMapFeedback::tracking(&edges_observer, true, true); + let mut stages = tuple_list!( + StdMutationalStage::new(mutator), + CalibrationStage::new(&calibration_feedback) + ); + + // Create a QEMU in-process executor + let mut executor = StatefulQemuExecutor::new( + &mut hooks, + &mut harness, + tuple_list!(edges_observer, time_observer), + &mut fuzzer, + &mut state, + &mut mgr, + timeout, + ) + .expect("Failed to create QemuExecutor"); + + // Instead of calling the timeout handler and restart the process, trigger a breakpoint ASAP + executor.break_on_timeout(); + + if state.must_load_initial_inputs() { + state + .load_initial_inputs(&mut fuzzer, &mut executor, &mut mgr, &corpus_dirs) + .unwrap_or_else(|_| { + println!("Failed to load initial corpus at {:?}", &corpus_dirs); + process::exit(0); + }); + println!("We imported {} inputs from disk.", state.corpus().count()); + } + + fuzzer + .fuzz_loop(&mut stages, &mut executor, &mut state, &mut mgr) + .unwrap(); + Ok(()) + }; + + // The shared memory allocator + let shmem_provider = StdShMemProvider::new().expect("Failed to init shared memory"); + + // The stats reporter for the broker + let monitor = MultiMonitor::new(|s| println!("{s}")); + + // let monitor = SimpleMonitor::new(|s| println!("{s}")); + // let mut mgr = SimpleEventManager::new(monitor); + // run_client(None, mgr, 0); + + // Build and run a Launcher + match Launcher::builder() + .shmem_provider(shmem_provider) + .broker_port(broker_port) + .configuration(EventConfig::from_build_id()) + .monitor(monitor) + .run_client(&mut run_client) + .cores(&cores) + // .stdout_file(Some("/dev/null")) + .build() + .launch() + { + Ok(()) => (), + Err(Error::ShuttingDown) => println!("Fuzzing stopped by user. Good bye."), + Err(err) => panic!("Failed to run launcher: {err:?}"), + } +} diff --git a/fuzzers/qemu_systemmode_breakpoints/src/main.rs b/fuzzers/qemu_systemmode_breakpoints/src/main.rs new file mode 100644 index 0000000000..bc1e80f767 --- /dev/null +++ b/fuzzers/qemu_systemmode_breakpoints/src/main.rs @@ -0,0 +1,13 @@ +//! A libfuzzer-like fuzzer using qemu for binary-only coverage +#[cfg(target_os = "linux")] +mod fuzzer; + +#[cfg(target_os = "linux")] +pub fn main() { + fuzzer::fuzz(); +} + +#[cfg(not(target_os = "linux"))] +pub fn main() { + panic!("qemu-user and libafl_qemu is only supported on linux!"); +} diff --git a/fuzzers/qemu_systemmode_sync_backdoor/.gitignore b/fuzzers/qemu_systemmode_sync_backdoor/.gitignore new file mode 100644 index 0000000000..b511ae114b --- /dev/null +++ b/fuzzers/qemu_systemmode_sync_backdoor/.gitignore @@ -0,0 +1 @@ +*.qcow2 diff --git a/fuzzers/qemu_systemmode_sync_backdoor/Cargo.toml b/fuzzers/qemu_systemmode_sync_backdoor/Cargo.toml new file mode 100644 index 0000000000..2a1455ee8c --- /dev/null +++ b/fuzzers/qemu_systemmode_sync_backdoor/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "qemu_systemmode_sync_backdoor" +version = "0.11.1" +authors = ["Andrea Fioraldi ", "Dominik Maier ", "Romain Malmain "] +edition = "2021" + +[features] +default = ["std"] +std = [] + +[profile.release] +incremental = true +debug = true +lto = "fat" +codegen-units = 1 + +[dependencies] +libafl = { path = "../../libafl/" } +libafl_bolts = { path = "../../libafl_bolts/" } +libafl_qemu = { path = "../../libafl_qemu/", features = ["arm", "systemmode"] } +env_logger = "*" diff --git a/fuzzers/qemu_systemmode_sync_backdoor/README.md b/fuzzers/qemu_systemmode_sync_backdoor/README.md new file mode 100644 index 0000000000..14098dc09c --- /dev/null +++ b/fuzzers/qemu_systemmode_sync_backdoor/README.md @@ -0,0 +1,26 @@ +# Qemu systemmode with launcher + +This folder contains an example fuzzer for the qemu systemmode, using LLMP for fast multi-process fuzzing and crash detection. + +## Build + +To build this example, run + +```bash +cargo build --release +cd example; sh build.sh; cd .. +``` + +This will build the the fuzzer (src/fuzzer.rs) and a small example binary based on FreeRTOS, which can run under a qemu emulation target. + +## Run + +Since the instrumentation is based on snapshtos QEMU needs a virtual drive (even if it is unused...). +Create on and then run the fuzzer: +```bash +# create an image +qemu-img create -f qcow2 dummy.qcow2 32M +# run the fuzzer +KERNEL=./example/example.elf target/release/qemu_systemmode -icount shift=auto,align=off,sleep=off -machine mps2-an385 -monitor null -kernel ./example/example.elf -serial null -nographic -snapshot -drive if=none,format=qcow2,file=dummy.qcow2 -S +``` +Currently the ``KERNEL`` variable is needed because the fuzzer does not parse QEMUs arguments to find the binary. \ No newline at end of file diff --git a/fuzzers/qemu_systemmode_sync_backdoor/corpus/random b/fuzzers/qemu_systemmode_sync_backdoor/corpus/random new file mode 100644 index 0000000000..25175d51d3 --- /dev/null +++ b/fuzzers/qemu_systemmode_sync_backdoor/corpus/random @@ -0,0 +1 @@ +yJv lpZGօrsY˗CMRպ}S7;Io76l1ޘ1R^Όp>G&_|1;ro4ƯUE<`L"6VƷk4/"tf7Fގd]ܮ%|8#wNUn8%4o˗ diff --git a/fuzzers/qemu_systemmode_sync_backdoor/corpus/zero b/fuzzers/qemu_systemmode_sync_backdoor/corpus/zero new file mode 100644 index 0000000000000000000000000000000000000000..112363ac1917b417ffbd7f376ca786a1e5fa7490 GIT binary patch literal 5 McmZQzU|`?^000jF3jhEB literal 0 HcmV?d00001 diff --git a/fuzzers/qemu_systemmode_sync_backdoor/example/build.sh b/fuzzers/qemu_systemmode_sync_backdoor/example/build.sh new file mode 100755 index 0000000000..e3ca03467f --- /dev/null +++ b/fuzzers/qemu_systemmode_sync_backdoor/example/build.sh @@ -0,0 +1,2 @@ +#!/bin/sh +arm-none-eabi-gcc -ggdb -ffreestanding -nostartfiles -lgcc -T mps2_m3.ld -mcpu=cortex-m3 -I../../../libafl_qemu/runtime main.c startup.c -o example.elf \ No newline at end of file diff --git a/fuzzers/qemu_systemmode_sync_backdoor/example/main.c b/fuzzers/qemu_systemmode_sync_backdoor/example/main.c new file mode 100644 index 0000000000..ed6779ca25 --- /dev/null +++ b/fuzzers/qemu_systemmode_sync_backdoor/example/main.c @@ -0,0 +1,37 @@ +#include "libafl_exit.h" + +int __attribute__((noinline)) BREAKPOINT() { + for (;;) {} +} + +int LLVMFuzzerTestOneInput(unsigned int *Data, unsigned int Size) { + LIBAFL_EXIT_START_PHYS((unsigned int) Data, Size); + // if (Data[3] == 0) { + // while (1) {} + // } // cause a timeout + for (int i = 0; i < Size; i++) { + // if (Data[i] > 0xFFd0 && Data[i] < 0xFFFF) {return 1;} // cause qemu to + // crash + for (int j = i + 1; j < Size; j++) { + if (Data[j] == 0) { continue; } + if (Data[j] > Data[i]) { + int tmp = Data[i]; + Data[i] = Data[j]; + Data[j] = tmp; + if (Data[i] <= 100) { j--; } + } + } + } + LIBAFL_EXIT_END(LIBAFL_EXIT_END_OK); + return BREAKPOINT(); +} +unsigned int FUZZ_INPUT[] = { + 101, 201, 700, 230, 860, 234, 980, 200, 340, 678, 230, 134, 900, + 236, 900, 123, 800, 123, 658, 607, 246, 804, 567, 568, 207, 407, + 246, 678, 457, 892, 834, 456, 878, 246, 699, 854, 234, 844, 290, + 125, 324, 560, 852, 928, 910, 790, 853, 345, 234, 586, +}; + +int main() { + LLVMFuzzerTestOneInput(FUZZ_INPUT, 50); +} diff --git a/fuzzers/qemu_systemmode_sync_backdoor/example/mps2_m3.ld b/fuzzers/qemu_systemmode_sync_backdoor/example/mps2_m3.ld new file mode 100644 index 0000000000..adfc15ed78 --- /dev/null +++ b/fuzzers/qemu_systemmode_sync_backdoor/example/mps2_m3.ld @@ -0,0 +1,143 @@ +/* + * FreeRTOS V202112.00 + * Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * https://www.FreeRTOS.org + * https://github.com/FreeRTOS + * + */ + +MEMORY +{ + RAM (xrw) : ORIGIN = 0x00000000, LENGTH = 4M + /* Originally */ + /* FLASH (xr) : ORIGIN = 0x00000000, LENGTH = 4M */ + /* RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 4M */ +} +ENTRY(Reset_Handler) + +_Min_Heap_Size = 0x300000 ; /* Required amount of heap. */ +_Min_Stack_Size = 0x4000 ; /* Required amount of stack. */ +M_VECTOR_RAM_SIZE = (16 + 48) * 4; +_estack = ORIGIN(RAM) + LENGTH(RAM); + +SECTIONS +{ + + .isr_vector : + { + __vector_table = .; + KEEP(*(.isr_vector)) + . = ALIGN(4); + } > RAM /* FLASH */ + + .text : + { + . = ALIGN(4); + *(.text*) + KEEP (*(.init)) + KEEP (*(.fini)) + KEEP(*(.eh_frame)) + *(.rodata*) + . = ALIGN(4); + _etext = .; + } > RAM /* FLASH */ + + .ARM.extab : + { + . = ALIGN(4); + *(.ARM.extab* .gnu.linkonce.armextab.*) + . = ALIGN(4); + } >RAM /* FLASH */ + + .ARM : + { + . = ALIGN(4); + __exidx_start = .; + *(.ARM.exidx* .gnu.linkonce.armexidx.*) + __exidx_end = .; + . = ALIGN(4); + } >RAM /* FLASH */ + + .interrupts_ram : + { + . = ALIGN(4); + __VECTOR_RAM__ = .; + __interrupts_ram_start__ = .; + . += M_VECTOR_RAM_SIZE; + . = ALIGN(4); + __interrupts_ram_end = .; + } > RAM + + _sidata = LOADADDR(.data); + + .data : /* AT ( _sidata ) */ + { + . = ALIGN(4); + _sdata = .; + *(.data*) + . = ALIGN(4); + _edata = .; + } > RAM /* RAM AT > FLASH */ + + .uninitialized (NOLOAD): + { + . = ALIGN(32); + __uninitialized_start = .; + *(.uninitialized) + KEEP(*(.keep.uninitialized)) + . = ALIGN(32); + __uninitialized_end = .; + } > RAM + + .bss : + { + . = ALIGN(4); + _sbss = .; + __bss_start__ = _sbss; + *(.bss*) + *(COMMON) + . = ALIGN(4); + _ebss = .; + __bss_end__ = _ebss; + } >RAM + + .heap : + { + . = ALIGN(8); + PROVIDE ( end = . ); + PROVIDE ( _end = . ); + _heap_bottom = .; + . = . + _Min_Heap_Size; + _heap_top = .; + . = . + _Min_Stack_Size; + . = ALIGN(8); + } >RAM + + /* Set stack top to end of RAM, and stack limit move down by + * size of stack_dummy section */ + __StackTop = ORIGIN(RAM) + LENGTH(RAM); + __StackLimit = __StackTop - _Min_Stack_Size; + PROVIDE(__stack = __StackTop); + + /* Check if data + heap + stack exceeds RAM limit */ + ASSERT(__StackLimit >= _heap_top, "region RAM overflowed with stack") +} + diff --git a/fuzzers/qemu_systemmode_sync_backdoor/example/startup.c b/fuzzers/qemu_systemmode_sync_backdoor/example/startup.c new file mode 100644 index 0000000000..36be3cf55a --- /dev/null +++ b/fuzzers/qemu_systemmode_sync_backdoor/example/startup.c @@ -0,0 +1,107 @@ +/* + * FreeRTOS V202112.00 + * Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * https://www.FreeRTOS.org + * https://github.com/FreeRTOS + * + */ + +typedef unsigned int uint32_t; + +extern int main(); + +extern uint32_t _estack, _sidata, _sdata, _edata, _sbss, _ebss; + +/* Prevent optimization so gcc does not replace code with memcpy */ +__attribute__((optimize("O0"))) __attribute__((naked)) void Reset_Handler( + void) { + /* set stack pointer */ + __asm volatile("ldr r0, =_estack"); + __asm volatile("mov sp, r0"); + + /* copy .data section from flash to RAM */ + // Not needed for this example, see linker script + // for( uint32_t * src = &_sidata, * dest = &_sdata; dest < &_edata; ) + // { + // *dest++ = *src++; + // } + + /* zero out .bss section */ + for (uint32_t *dest = &_sbss; dest < &_ebss;) { + *dest++ = 0; + } + + /* jump to board initialisation */ + void _start(void); + _start(); +} + +const uint32_t *isr_vector[] __attribute__((section(".isr_vector"))) = { + (uint32_t *)&_estack, + (uint32_t *)&Reset_Handler, /* Reset -15 */ + 0, /* NMI_Handler -14 */ + 0, /* HardFault_Handler -13 */ + 0, /* MemManage_Handler -12 */ + 0, /* BusFault_Handler -11 */ + 0, /* UsageFault_Handler -10 */ + 0, /* reserved */ + 0, /* reserved */ + 0, /* reserved */ + 0, /* reserved -6 */ + 0, /* SVC_Handler -5 */ + 0, /* DebugMon_Handler -4 */ + 0, /* reserved */ + 0, /* PendSV handler -2 */ + 0, /* SysTick_Handler -1 */ + 0, /* uart0 receive 0 */ + 0, /* uart0 transmit */ + 0, /* uart1 receive */ + 0, /* uart1 transmit */ + 0, /* uart 2 receive */ + 0, /* uart 2 transmit */ + 0, /* GPIO 0 combined interrupt */ + 0, /* GPIO 2 combined interrupt */ + 0, /* Timer 0 */ + 0, /* Timer 1 */ + 0, /* Dial Timer */ + 0, /* SPI0 SPI1 */ + 0, /* uart overflow 1, 2,3 */ + 0, /* Ethernet 13 */ +}; + +__attribute__((naked)) void exit(__attribute__((unused)) int status) { + /* Force qemu to exit using ARM Semihosting */ + __asm volatile( + "mov r1, r0\n" + "cmp r1, #0\n" + "bne .notclean\n" + "ldr r1, =0x20026\n" /* ADP_Stopped_ApplicationExit, a clean exit */ + ".notclean:\n" + "movs r0, #0x18\n" /* SYS_EXIT */ + "bkpt 0xab\n" + "end: b end\n"); +} + +void _start(void) { + main(); + exit(0); +} diff --git a/fuzzers/qemu_systemmode_sync_backdoor/src/fuzzer.rs b/fuzzers/qemu_systemmode_sync_backdoor/src/fuzzer.rs new file mode 100644 index 0000000000..53e77e06fa --- /dev/null +++ b/fuzzers/qemu_systemmode_sync_backdoor/src/fuzzer.rs @@ -0,0 +1,213 @@ +//! A fuzzer using qemu in systemmode for binary-only coverage of kernels +//! +use core::{ptr::addr_of_mut, time::Duration}; +use std::{env, path::PathBuf, process}; + +use libafl::{ + corpus::{Corpus, InMemoryCorpus, OnDiskCorpus}, + events::{launcher::Launcher, EventConfig}, + executors::ExitKind, + feedback_or, feedback_or_fast, + feedbacks::{CrashFeedback, MaxMapFeedback, TimeFeedback, TimeoutFeedback}, + fuzzer::{Fuzzer, StdFuzzer}, + inputs::BytesInput, + monitors::MultiMonitor, + mutators::scheduled::{havoc_mutations, StdScheduledMutator}, + observers::{HitcountsMapObserver, TimeObserver, VariableMapObserver}, + schedulers::{IndexesLenTimeMinimizerScheduler, QueueScheduler}, + stages::{CalibrationStage, StdMutationalStage}, + state::{HasCorpus, StdState}, + Error, +}; +use libafl_bolts::{ + core_affinity::Cores, + current_nanos, + rands::StdRand, + shmem::{ShMemProvider, StdShMemProvider}, + tuples::tuple_list, +}; +use libafl_qemu::{ + edges::{edges_map_mut_slice, QemuEdgeCoverageHelper, MAX_EDGES_NUM}, + emu::Emulator, + executor::{stateful::StatefulQemuExecutor, QemuExecutorState}, + EmuExitReasonError, FastSnapshotManager, HandlerError, HandlerResult, QemuHooks, + StdEmuExitHandler, +}; + +// use libafl_qemu::QemuSnapshotBuilder; for normal qemu snapshot + +pub fn fuzz() { + env_logger::init(); + + if let Ok(s) = env::var("FUZZ_SIZE") { + str::parse::(&s).expect("FUZZ_SIZE was not a number"); + }; + // Hardcoded parameters + let timeout = Duration::from_secs(3); + let broker_port = 1337; + let cores = Cores::from_cmdline("1").unwrap(); + let corpus_dirs = [PathBuf::from("./corpus")]; + let objective_dir = PathBuf::from("./crashes"); + + let mut run_client = |state: Option<_>, mut mgr, _core_id| { + // Initialize QEMU + let args: Vec = env::args().collect(); + let env: Vec<(String, String)> = env::vars().collect(); + // let emu_snapshot_manager = QemuSnapshotBuilder::new(true); + let emu_snapshot_manager = FastSnapshotManager::new(false); // Create a snapshot manager (normal or fast for now). + let emu_exit_handler: StdEmuExitHandler = + StdEmuExitHandler::new(emu_snapshot_manager); // Create an exit handler: it is the entity taking the decision of what should be done when QEMU returns. + let emu = Emulator::new(&args, &env, emu_exit_handler).unwrap(); // Create the emulator + + let devices = emu.list_devices(); + println!("Devices = {:?}", devices); + + // The wrapped harness function, calling out to the LLVM-style harness + let mut harness = + |input: &BytesInput, qemu_executor_state: &mut QemuExecutorState<_, _>| unsafe { + match emu.run(input, qemu_executor_state) { + Ok(handler_result) => match handler_result { + HandlerResult::UnhandledExit(unhandled_exit) => { + panic!("Unhandled exit: {}", unhandled_exit) + } + HandlerResult::EndOfRun(exit_kind) => exit_kind, + HandlerResult::Interrupted => { + println!("Interrupted."); + std::process::exit(0); + } + }, + Err(handler_error) => match handler_error { + HandlerError::QemuExitReasonError(emu_exit_reason_error) => { + match emu_exit_reason_error { + EmuExitReasonError::UnknownKind => panic!("unknown kind"), + EmuExitReasonError::UnexpectedExit => ExitKind::Crash, + _ => { + panic!("Emu Exit unhandled error: {:?}", emu_exit_reason_error) + } + } + } + _ => panic!("Unhandled error: {:?}", handler_error), + }, + } + }; + + // Create an observation channel using the coverage map + let edges_observer = unsafe { + HitcountsMapObserver::new(VariableMapObserver::from_mut_slice( + "edges", + edges_map_mut_slice(), + addr_of_mut!(MAX_EDGES_NUM), + )) + }; + + // Create an observation channel to keep track of the execution time + let time_observer = TimeObserver::new("time"); + + // Feedback to rate the interestingness of an input + // This one is composed by two Feedbacks in OR + let mut feedback = feedback_or!( + // New maximization map feedback linked to the edges observer and the feedback state + MaxMapFeedback::tracking(&edges_observer, true, true), + // Time feedback, this one does not need a feedback state + TimeFeedback::with_observer(&time_observer) + ); + + // A feedback to choose if an input is a solution or not + let mut objective = feedback_or_fast!(CrashFeedback::new(), TimeoutFeedback::new()); + + // If not restarting, create a State from scratch + let mut state = state.unwrap_or_else(|| { + StdState::new( + // RNG + StdRand::with_seed(current_nanos()), + // Corpus that will be evolved, we keep it in memory for performance + InMemoryCorpus::new(), + // Corpus in which we store solutions (crashes in this example), + // on disk so the user can get them after stopping the fuzzer + OnDiskCorpus::new(objective_dir.clone()).unwrap(), + // States of the feedbacks. + // The feedbacks can report the data that should persist in the State. + &mut feedback, + // Same for objective feedbacks + &mut objective, + ) + .unwrap() + }); + + // A minimization+queue policy to get testcasess from the corpus + let scheduler = IndexesLenTimeMinimizerScheduler::new(QueueScheduler::new()); + + // A fuzzer with feedbacks and a corpus scheduler + let mut fuzzer = StdFuzzer::new(scheduler, feedback, objective); + + let mut hooks = QemuHooks::new( + emu.qemu().clone(), + tuple_list!(QemuEdgeCoverageHelper::default()), + ); + + // Setup an havoc mutator with a mutational stage + let mutator = StdScheduledMutator::new(havoc_mutations()); + let calibration_feedback = MaxMapFeedback::tracking(&edges_observer, true, true); + let mut stages = tuple_list!( + StdMutationalStage::new(mutator), + CalibrationStage::new(&calibration_feedback) + ); + + // Create a QEMU in-process executor + let mut executor = StatefulQemuExecutor::new( + &mut hooks, + &mut harness, + tuple_list!(edges_observer, time_observer), + &mut fuzzer, + &mut state, + &mut mgr, + timeout, + ) + .expect("Failed to create QemuExecutor"); + + // Instead of calling the timeout handler and restart the process, trigger a breakpoint ASAP + executor.break_on_timeout(); + + if state.must_load_initial_inputs() { + state + .load_initial_inputs(&mut fuzzer, &mut executor, &mut mgr, &corpus_dirs) + .unwrap_or_else(|_| { + println!("Failed to load initial corpus at {:?}", &corpus_dirs); + process::exit(0); + }); + println!("We imported {} inputs from disk.", state.corpus().count()); + } + + fuzzer + .fuzz_loop(&mut stages, &mut executor, &mut state, &mut mgr) + .unwrap(); + Ok(()) + }; + + // The shared memory allocator + let shmem_provider = StdShMemProvider::new().expect("Failed to init shared memory"); + + // The stats reporter for the broker + let monitor = MultiMonitor::new(|s| println!("{s}")); + + // let monitor = SimpleMonitor::new(|s| println!("{s}")); + // let mut mgr = SimpleEventManager::new(monitor); + // run_client(None, mgr, 0); + + // Build and run a Launcher + match Launcher::builder() + .shmem_provider(shmem_provider) + .broker_port(broker_port) + .configuration(EventConfig::from_build_id()) + .monitor(monitor) + .run_client(&mut run_client) + .cores(&cores) + // .stdout_file(Some("/dev/null")) + .build() + .launch() + { + Ok(()) => (), + Err(Error::ShuttingDown) => println!("Fuzzing stopped by user. Good bye."), + Err(err) => panic!("Failed to run launcher: {err:?}"), + } +} diff --git a/fuzzers/qemu_systemmode_sync_backdoor/src/main.rs b/fuzzers/qemu_systemmode_sync_backdoor/src/main.rs new file mode 100644 index 0000000000..bc1e80f767 --- /dev/null +++ b/fuzzers/qemu_systemmode_sync_backdoor/src/main.rs @@ -0,0 +1,13 @@ +//! A libfuzzer-like fuzzer using qemu for binary-only coverage +#[cfg(target_os = "linux")] +mod fuzzer; + +#[cfg(target_os = "linux")] +pub fn main() { + fuzzer::fuzz(); +} + +#[cfg(not(target_os = "linux"))] +pub fn main() { + panic!("qemu-user and libafl_qemu is only supported on linux!"); +} diff --git a/libafl/src/monitors/disk.rs b/libafl/src/monitors/disk.rs index 449687bf4a..4419f97d0f 100644 --- a/libafl/src/monitors/disk.rs +++ b/libafl/src/monitors/disk.rs @@ -216,7 +216,7 @@ where "objectives": self.base.objective_size(), "executions": self.base.total_execs(), "exec_sec": self.base.execs_per_sec(), - "clients": &self.client_stats()[1..] + "clients": &self.client_stats().get(1..) }); writeln!(&file, "{line}").expect("Unable to write JSON to file"); } diff --git a/libafl_bolts/src/cli.rs b/libafl_bolts/src/cli.rs index 43239e7d98..920c5a4c35 100644 --- a/libafl_bolts/src/cli.rs +++ b/libafl_bolts/src/cli.rs @@ -34,14 +34,14 @@ //! use std::env; //! //! // make sure to add `features = ["qemu_cli"]` to the `libafl` crate in `Cargo.toml` -//! use libafl_qemu::Emulator; +//! use libafl_qemu::Qemu; //! //! fn fuzz_with_qemu(mut options: FuzzerOptions) { //! env::remove_var("LD_LIBRARY_PATH"); //! //! let env: Vec<(String, String)> = env::vars().collect(); //! -//! let emu = Emulator::new(&mut options.qemu_args.to_vec(), &mut env).unwrap(); +//! let qemu = Qemu::init(&mut options.qemu_args.to_vec(), &mut env).unwrap(); //! // do other stuff... //! } //! diff --git a/libafl_qemu/Cargo.toml b/libafl_qemu/Cargo.toml index bc0c799171..5f34422e9a 100644 --- a/libafl_qemu/Cargo.toml +++ b/libafl_qemu/Cargo.toml @@ -25,7 +25,7 @@ document-features = ["dep:document-features"] ## Find injections during fuzzing injections = ["serde_yaml", "toml"] ## Python bindings support -python = ["pyo3", "pyo3-build-config"] +python = ["pyo3", "pyo3-build-config", "libafl_qemu_sys/python"] ## Fork support fork = ["libafl/fork"] ## Build libqasan for address sanitization @@ -94,6 +94,7 @@ document-features = { version = "0.2", optional = true } [build-dependencies] pyo3-build-config = { version = "0.18", optional = true } rustversion = "1.0" +bindgen = "0.69" [lib] name = "libafl_qemu" diff --git a/libafl_qemu/build_linux.rs b/libafl_qemu/build_linux.rs index f6dcbd0b60..620d3e8a04 100644 --- a/libafl_qemu/build_linux.rs +++ b/libafl_qemu/build_linux.rs @@ -1,5 +1,6 @@ -use std::{env, fs, path::Path, process::Command}; +use std::{env, fs, path::{Path, PathBuf}, process::Command}; +#[allow(clippy::too_many_lines)] pub fn build() { // Note: Unique features are checked in libafl_qemu_sys @@ -15,11 +16,15 @@ pub fn build() { let build_libqasan = cfg!(all(feature = "build_libqasan", not(feature = "hexagon"))); + let exit_hdr_dir = PathBuf::from("runtime"); + let exit_hdr = exit_hdr_dir.join("libafl_exit.h"); + println!("cargo:rustc-cfg=emulation_mode=\"{emulation_mode}\""); println!("cargo:rerun-if-env-changed=EMULATION_MODE"); println!("cargo:rerun-if-changed=build.rs"); println!("cargo:rerun-if-changed=build_linux.rs"); + println!("cargo:rerun-if-changed={}", exit_hdr_dir.display()); let cpu_target = if cfg!(feature = "x86_64") { "x86_64".to_string() @@ -62,11 +67,28 @@ pub fn build() { let out_dir = env::var("OUT_DIR").unwrap(); let out_dir_path = Path::new(&out_dir); - let mut target_dir = out_dir_path.to_path_buf(); + let out_dir_path_buf = out_dir_path.to_path_buf(); + let mut target_dir = out_dir_path_buf.clone(); target_dir.pop(); target_dir.pop(); target_dir.pop(); + let binding_file = out_dir_path_buf.join("backdoor_bindings.rs"); + bindgen::Builder::default() + .derive_debug(true) + .derive_default(true) + .impl_debug(true) + .generate_comments(true) + .default_enum_style(bindgen::EnumVariation::NewType { + is_global: true, + is_bitfield: true, + }) + .header(exit_hdr.display().to_string()) + .generate() + .expect("Exit bindings generation failed.") + .write_to_file(binding_file) + .expect("Could not write bindings."); + if (emulation_mode == "usermode") && build_libqasan { let qasan_dir = Path::new("libqasan"); let qasan_dir = fs::canonicalize(qasan_dir).unwrap(); diff --git a/libafl_qemu/libafl_qemu_build/src/bindings.rs b/libafl_qemu/libafl_qemu_build/src/bindings.rs index 23904d46f6..a0930e8e14 100644 --- a/libafl_qemu/libafl_qemu_build/src/bindings.rs +++ b/libafl_qemu/libafl_qemu_build/src/bindings.rs @@ -136,6 +136,7 @@ pub fn generate( .allowlist_type("MemOpIdx") .allowlist_type("MemOp") .allowlist_type("DeviceSnapshotKind") + .allowlist_type("ShutdownCause") .allowlist_type("libafl_exit_reason") .allowlist_type("libafl_exit_reason_kind") .allowlist_type("libafl_exit_reason_sync_backdoor") diff --git a/libafl_qemu/libafl_qemu_sys/Cargo.toml b/libafl_qemu/libafl_qemu_sys/Cargo.toml index 1b8c309496..1d1dd57bfb 100644 --- a/libafl_qemu/libafl_qemu_sys/Cargo.toml +++ b/libafl_qemu/libafl_qemu_sys/Cargo.toml @@ -30,12 +30,21 @@ be = [] usermode = [] systemmode = [] +python = ["pyo3", "pyo3-build-config"] + slirp = [ "systemmode", "libafl_qemu_build/slirp" ] # build qemu with host libslirp (for user networking) shared = [ "libafl_qemu_build/shared" ] clippy = [ "libafl_qemu_build/clippy" ] # special feature for clippy, don't use in normal projects [dependencies] +paste = "1" +num_enum = "0.7" +libc = "0.2" +strum = "0.25" +strum_macros = "0.25" +pyo3 = { version = "0.18", optional = true } [build-dependencies] libafl_qemu_build = { path = "../libafl_qemu_build", version = "0.11.2" } +pyo3-build-config = { version = "0.18", optional = true } diff --git a/libafl_qemu/libafl_qemu_sys/src/lib.rs b/libafl_qemu/libafl_qemu_sys/src/lib.rs index a0eef89ae8..9007c629fa 100644 --- a/libafl_qemu/libafl_qemu_sys/src/lib.rs +++ b/libafl_qemu/libafl_qemu_sys/src/lib.rs @@ -19,12 +19,131 @@ include!(concat!(env!("OUT_DIR"), "/bindings.rs")); #[cfg(all(feature = "clippy", target_os = "linux"))] mod x86_64_stub_bindings; +#[cfg(emulation_mode = "usermode")] +mod usermode; +#[cfg(emulation_mode = "usermode")] +pub use usermode::*; + +#[cfg(emulation_mode = "systemmode")] +mod systemmode; +use num_enum::{IntoPrimitive, TryFromPrimitive}; +use paste::paste; +use strum_macros::EnumIter; +#[cfg(emulation_mode = "systemmode")] +pub use systemmode::*; + +/// Safe linking with of extern "C" functions. +/// This macro makes sure the declared symbol is defined *at link time*, avoiding declaring non-existant symbols +/// that could be silently ignored during linking if unused. +/// +/// This macro relies on a nightly feature, and can only be used in this mode +/// It is (nearly) a drop-in replacement for extern "C" { } blocks containing function and static declarations, and will have the same effect in practice. +#[macro_export] +macro_rules! extern_c_checked { + () => {}; + + ($visibility:vis fn $c_fn:ident($($param_ident:ident : $param_ty:ty),*) $( -> $ret_ty:ty )?; $($tail:tt)*) => { + paste! { + #[cfg_attr(nightly, used(linker))] + static [<__ $c_fn:upper __>]: unsafe extern "C" fn($($param_ty),*) $( -> $ret_ty )? = $c_fn; + } + + extern "C" { + $visibility fn $c_fn($($param_ident : $param_ty),*) $( -> $ret_ty )?; + } + + extern_c_checked!($($tail)*); + }; + + ($visibility:vis static $c_var:ident : $c_var_ty:ty; $($tail:tt)*) => { + paste! { + #[allow(non_camel_case_types)] + #[allow(unused)] + struct [<__ $c_var:upper _STRUCT__>] { member: *const $c_var_ty } + + unsafe impl Sync for [<__ $c_var:upper _STRUCT__>] {} + + #[cfg_attr(nightly, used(linker))] + static [<__ $c_var:upper __>]: [<__ $c_var:upper _STRUCT__>] = unsafe { [<__ $c_var:upper _STRUCT__>] { member: core::ptr::addr_of!($c_var) } }; + } + + extern "C" { + $visibility static $c_var: $c_var_ty; + } + + extern_c_checked!($($tail)*); + }; + + ($visibility:vis static mut $c_var:ident : $c_var_ty:ty; $($tail:tt)*) => { + paste! { + #[allow(non_camel_case_types)] + #[allow(unused)] + struct [<__ $c_var:upper _STRUCT__>] { member: *const $c_var_ty } + + unsafe impl Sync for [<__ $c_var:upper _STRUCT__>] {} + + #[cfg_attr(nightly, used(linker))] + static mut [<__ $c_var:upper __>]: [<__ $c_var:upper _STRUCT__>] = unsafe { [<__ $c_var:upper _STRUCT__>] { member: core::ptr::addr_of!($c_var) } }; + } + + extern "C" { + $visibility static mut $c_var: $c_var_ty; + } + + extern_c_checked!($($tail)*); + }; +} + #[cfg(target_os = "linux")] use core::ops::BitAnd; +use std::{ffi::c_void, slice::from_raw_parts, str::from_utf8_unchecked}; +#[cfg(feature = "python")] +use pyo3::{pyclass, pymethods, IntoPy, PyObject, Python}; #[cfg(all(feature = "clippy", target_os = "linux"))] pub use x86_64_stub_bindings::*; +pub type CPUStatePtr = *mut crate::CPUState; +pub type CPUArchStatePtr = *mut crate::CPUArchState; +pub type ExitReasonPtr = *mut crate::libafl_exit_reason; + +pub type GuestUsize = crate::target_ulong; +pub type GuestIsize = crate::target_long; + +pub type GuestAddr = crate::target_ulong; +pub type GuestPhysAddr = crate::hwaddr; +pub type GuestVirtAddr = crate::vaddr; + +pub type GuestHwAddrInfo = crate::qemu_plugin_hwaddr; + +#[repr(C)] +#[cfg_attr(feature = "python", pyclass(unsendable))] +pub struct MapInfo { + start: GuestAddr, + end: GuestAddr, + offset: GuestAddr, + path: *const u8, + flags: i32, + is_priv: i32, +} + +#[repr(C)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct FatPtr(pub *const c_void, pub *const c_void); + +#[derive(IntoPrimitive, TryFromPrimitive, Debug, Clone, Copy, EnumIter, PartialEq, Eq)] +#[repr(i32)] +pub enum MmapPerms { + None = 0, + Read = libc::PROT_READ, + Write = libc::PROT_WRITE, + Execute = libc::PROT_EXEC, + ReadWrite = libc::PROT_READ | libc::PROT_WRITE, + ReadExecute = libc::PROT_READ | libc::PROT_EXEC, + WriteExecute = libc::PROT_WRITE | libc::PROT_EXEC, + ReadWriteExecute = libc::PROT_READ | libc::PROT_WRITE | libc::PROT_EXEC, +} + // from include/exec/memop.h #[cfg(target_os = "linux")] @@ -50,3 +169,122 @@ pub fn make_plugin_meminfo(oi: MemOpIdx, rw: qemu_plugin_mem_rw) -> qemu_plugin_ pub fn cpu_env(cpu: *mut CPUState) -> *mut CPUArchState { unsafe { cpu.add(1) as *mut CPUArchState } } + +extern_c_checked! { + //static libafl_page_size: GuestUsize; + pub fn libafl_page_from_addr(addr: GuestAddr) -> GuestAddr; + + // CPUState* libafl_qemu_get_cpu(int cpu_index); + pub fn libafl_qemu_get_cpu(cpu_index: i32) -> CPUStatePtr; + // int libafl_qemu_num_cpus(void); + pub fn libafl_qemu_num_cpus() -> i32; + // CPUState* libafl_qemu_current_cpu(void); + pub fn libafl_qemu_current_cpu() -> CPUStatePtr; + + // struct libafl_exit_reason* libafl_get_exit_reason(void); + // fn libafl_get_exit_reason() -> ExitReasonPtr; + + pub fn libafl_qemu_cpu_index(cpu: CPUStatePtr) -> i32; + + pub fn libafl_qemu_write_reg(cpu: CPUStatePtr, reg: i32, val: *const u8) -> i32; + pub fn libafl_qemu_read_reg(cpu: CPUStatePtr, reg: i32, val: *mut u8) -> i32; + pub fn libafl_qemu_num_regs(cpu: CPUStatePtr) -> i32; + + // fn libafl_qemu_set_breakpoint(addr: u64) -> i32; + // fn libafl_qemu_remove_breakpoint(addr: u64) -> i32; + pub fn libafl_flush_jit(); + // fn libafl_qemu_trigger_breakpoint(cpu: CPUStatePtr); + + pub fn strlen(s: *const u8) -> usize; + + pub fn libafl_qemu_add_gdb_cmd( + callback: extern "C" fn(*const (), *const u8, usize) -> i32, + data: *const () + ); + pub fn libafl_qemu_gdb_reply(buf: *const u8, len: usize); +} + +#[cfg_attr(feature = "python", pymethods)] +impl MapInfo { + #[must_use] + pub fn start(&self) -> GuestAddr { + self.start + } + + #[must_use] + pub fn end(&self) -> GuestAddr { + self.end + } + + #[must_use] + pub fn offset(&self) -> GuestAddr { + self.offset + } + + #[must_use] + pub fn path(&self) -> Option<&str> { + if self.path.is_null() { + None + } else { + unsafe { + Some(from_utf8_unchecked(from_raw_parts( + self.path, + strlen(self.path), + ))) + } + } + } + + #[must_use] + pub fn flags(&self) -> MmapPerms { + MmapPerms::try_from(self.flags).unwrap() + } + + #[must_use] + pub fn is_priv(&self) -> bool { + self.is_priv != 0 + } +} + +impl MmapPerms { + #[must_use] + pub fn readable(&self) -> bool { + matches!( + self, + MmapPerms::Read + | MmapPerms::ReadWrite + | MmapPerms::ReadExecute + | MmapPerms::ReadWriteExecute + ) + } + + #[must_use] + pub fn writable(&self) -> bool { + matches!( + self, + MmapPerms::Write + | MmapPerms::ReadWrite + | MmapPerms::WriteExecute + | MmapPerms::ReadWriteExecute + ) + } + + #[must_use] + pub fn executable(&self) -> bool { + matches!( + self, + MmapPerms::Execute + | MmapPerms::ReadExecute + | MmapPerms::WriteExecute + | MmapPerms::ReadWriteExecute + ) + } +} + +#[cfg(feature = "python")] +impl IntoPy for MmapPerms { + fn into_py(self, py: Python) -> PyObject { + let n: i32 = self.into(); + n.into_py(py) + } +} diff --git a/libafl_qemu/libafl_qemu_sys/src/systemmode.rs b/libafl_qemu/libafl_qemu_sys/src/systemmode.rs new file mode 100644 index 0000000000..85b02d58d4 --- /dev/null +++ b/libafl_qemu/libafl_qemu_sys/src/systemmode.rs @@ -0,0 +1,16 @@ +use paste::paste; + +use crate::{extern_c_checked, CPUStatePtr, GuestPhysAddr}; + +extern_c_checked! { + pub fn qemu_init(argc: i32, argv: *const *const u8, envp: *const *const u8); + + pub fn vm_start(); + pub fn qemu_main_loop(); + pub fn qemu_cleanup(); + + pub fn libafl_save_qemu_snapshot(name: *const u8, sync: bool); + pub fn libafl_load_qemu_snapshot(name: *const u8, sync: bool); + + pub fn libafl_qemu_current_paging_id(cpu: CPUStatePtr) -> GuestPhysAddr; +} diff --git a/libafl_qemu/libafl_qemu_sys/src/usermode.rs b/libafl_qemu/libafl_qemu_sys/src/usermode.rs new file mode 100644 index 0000000000..bec4527bc2 --- /dev/null +++ b/libafl_qemu/libafl_qemu_sys/src/usermode.rs @@ -0,0 +1,36 @@ +use core::ffi::c_void; + +use num_enum::{IntoPrimitive, TryFromPrimitive}; +use paste::paste; +use strum_macros::EnumIter; + +use crate::{extern_c_checked, GuestAddr, MapInfo}; + +extern_c_checked! { + pub fn qemu_user_init(argc: i32, argv: *const *const u8, envp: *const *const u8) -> i32; + + pub fn libafl_qemu_run() -> i32; + + pub fn libafl_load_addr() -> u64; + pub fn libafl_get_brk() -> u64; + pub fn libafl_set_brk(brk: u64) -> u64; + + pub fn read_self_maps() -> *const c_void; + pub fn free_self_maps(map_info: *const c_void); + + pub fn libafl_maps_next(map_info: *const c_void, ret: *mut MapInfo) -> *const c_void; + + pub static exec_path: *const u8; + pub static guest_base: usize; + pub static mut mmap_next_start: GuestAddr; + + pub static mut libafl_dump_core_hook: unsafe extern "C" fn(i32); + pub static mut libafl_force_dfl: i32; +} + +#[derive(IntoPrimitive, TryFromPrimitive, Debug, Clone, Copy, EnumIter, PartialEq, Eq)] +#[repr(i32)] +pub enum VerifyAccess { + Read = libc::PROT_READ, + Write = libc::PROT_READ | libc::PROT_WRITE, +} diff --git a/libafl_qemu/runtime/libafl_exit.h b/libafl_qemu/runtime/libafl_exit.h new file mode 100644 index 0000000000..885290dbbe --- /dev/null +++ b/libafl_qemu/runtime/libafl_exit.h @@ -0,0 +1,176 @@ +#ifndef LIBAFL_EXIT_H +#define LIBAFL_EXIT_H + +#define STRINGIFY(s) #s +#define XSTRINGIFY(s) STRINGIFY(s) + +// Target Specific imports / definitions +#ifdef _WIN32 + #include + #include + +typedef UINT64 libafl_word; + #define LIBAFL_CALLING_CONVENTION __fastcall + +#else + #ifdef __x86_64__ + #include + +typedef uint64_t libafl_word; + #define LIBAFL_CALLING_CONVENTION __attribute__(()) + #endif + + #ifdef __arm__ + #include + +typedef uint32_t libafl_word; + #define LIBAFL_CALLING_CONVENTION __attribute__(()) + #endif +#endif + +#define LIBAFL_EXIT_OPCODE 0x66f23a0f +#define LIBAFL_EXIT_VERSION_NUMBER 0111 // TODO: find a nice way to set it. + +typedef enum LibaflExit { + LIBAFL_EXIT_START_VIRT = 0, + LIBAFL_EXIT_START_PHYS = 1, + LIBAFL_EXIT_INPUT_VIRT = 2, + LIBAFL_EXIT_INPUT_PHYS = 3, + LIBAFL_EXIT_END = 4, + LIBAFL_EXIT_SAVE = 5, + LIBAFL_EXIT_LOAD = 6, + LIBAFL_EXIT_VERSION = 7, + LIBAFL_EXIT_VADDR_FILTER_ALLOW = 8, +} LibaflExit; + +typedef enum LibaflExitEndStatus { + LIBAFL_EXIT_END_UNKNOWN = 0, + LIBAFL_EXIT_END_OK = 1, + LIBAFL_EXIT_END_CRASH = 2, +} LibaflExitEndParams; + +#ifdef _WIN32 + #ifdef __cplusplus +extern "C" { + #endif +libafl_word LIBAFL_CALLING_CONVENTION _libafl_exit_call0(libafl_word action); +libafl_word LIBAFL_CALLING_CONVENTION _libafl_exit_call1(libafl_word action, + libafl_word arg1); +libafl_word LIBAFL_CALLING_CONVENTION _libafl_exit_call2(libafl_word action, + libafl_word arg1, + libafl_word arg2); + #ifdef __cplusplus +} + #endif +#else + + #ifdef __x86_64__ +libafl_word LIBAFL_CALLING_CONVENTION _libafl_exit_call0(libafl_word action) { + libafl_word ret; + __asm__ volatile ( + "mov %1, %%rax\n" + ".dword " XSTRINGIFY(LIBAFL_EXIT_OPCODE) "\n" + "mov %%rax, %0\n" + : "=g"(ret) + : "g"(action) + : "%rax" + ); + return ret; +} + +libafl_word LIBAFL_CALLING_CONVENTION _libafl_exit_call1(libafl_word action, + libafl_word arg1) { + libafl_word ret; + __asm__ volatile ( + "mov %1, %%rax\n" + "mov %2, %%rdi\n" + ".dword " XSTRINGIFY(LIBAFL_EXIT_OPCODE) "\n" + "mov %%rax, %0\n" + : "=g"(ret) + : "g"(action), "g"(arg1) + : "%rax", "%rdi" + ); + return ret; +} + +libafl_word LIBAFL_CALLING_CONVENTION _libafl_exit_call2(libafl_word action, + libafl_word arg1, + libafl_word arg2) { + libafl_word ret; + __asm__ volatile ( + "mov %1, %%rax\n" + "mov %2, %%rdi\n" + "mov %3, %%rsi\n" + ".dword " XSTRINGIFY(LIBAFL_EXIT_OPCODE) "\n" + "mov %%rax, %0\n" + : "=g"(ret) + : "g"(action), "g"(arg1), "g"(arg2) + : "%rax", "%rdi", "%rsi" + ); + return ret; +} + #endif + + #ifdef __arm__ +libafl_word LIBAFL_CALLING_CONVENTION _libafl_exit_call0(libafl_word action) { + libafl_word ret; + __asm__ volatile ( + "mov r0, %1\n" + ".word " XSTRINGIFY(LIBAFL_EXIT_OPCODE) "\n" + "mov %0, r0\n" + : "=r"(ret) + : "r"(action) + : "r0" + ); + return ret; +} + +libafl_word LIBAFL_CALLING_CONVENTION _libafl_exit_call1(libafl_word action, + libafl_word arg1) { + libafl_word ret; + __asm__ volatile ( + "mov r0, %1\n" + "mov r1, %2\n" + ".word " XSTRINGIFY(LIBAFL_EXIT_OPCODE) "\n" + "mov %0, r0\n" + : "=r"(ret) + : "r"(action), "r"(arg1) + : "r0", "r1" + ); + return ret; +} + +libafl_word LIBAFL_CALLING_CONVENTION _libafl_exit_call2(libafl_word action, + libafl_word arg1, + libafl_word arg2) { + libafl_word ret; + __asm__ volatile ( + "mov r0, %1\n" + "mov r1, %2\n" + "mov r2, %3\n" + ".word " XSTRINGIFY(LIBAFL_EXIT_OPCODE) "\n" + "mov %0, r0\n" + : "=r"(ret) + : "r"(action), "r"(arg1), "r"(arg2) + : "r0", "r1", "r2" + ); + return ret; +} + #endif + +#endif + +#define LIBAFL_EXIT_START_VIRT(buf_vaddr, max_len) \ + _libafl_exit_call2(LIBAFL_EXIT_START_VIRT, buf_vaddr, max_len) +#define LIBAFL_EXIT_START_PHYS(buf_paddr, max_len) \ + _libafl_exit_call2(LIBAFL_EXIT_START_PHYS, buf_paddr, max_len) +#define LIBAFL_EXIT_INPUT_VIRT(buf_vaddr, max_len) \ + _libafl_exit_call2(LIBAFL_EXIT_INPUT_VIRT, buf_vaddr, max_len) +#define LIBAFL_EXIT_INPUT_PHYS(buf_paddr, max_len) \ + _libafl_exit_call2(LIBAFL_EXIT_INPUT_PHYS, buf_paddr, max_len) +#define LIBAFL_EXIT_END(status) _libafl_exit_call1(LIBAFL_EXIT_END, status) +#define LIBAFL_EXIT_SAVE() _libafl_exit_call0(LIBAFL_EXIT_SAVE) +#define LIBAFL_EXIT_LOAD() _libafl_exit_call0(LIBAFL_EXIT_LOAD) +#define LIBAFL_EXIT_VERSION() _libafl_exit_call0(LIBAFL_EXIT_VERSION_NUMBER) + +#endif \ No newline at end of file diff --git a/libafl_qemu/runtime/libafl_exit_windows.asm b/libafl_qemu/runtime/libafl_exit_windows.asm new file mode 100755 index 0000000000..39be791fc9 --- /dev/null +++ b/libafl_qemu/runtime/libafl_exit_windows.asm @@ -0,0 +1,63 @@ +PUBLIC _libafl_exit_call0, _libafl_exit_call1, _libafl_exit_call2 + +LIBAFL_EXIT_OPCODE MACRO + dd 66f23a0fh +ENDM + +.code + +; Execute LibAFL backdoor (no argument) +; Parameters: +; [RAX, OUT] Hook return value +; [RCX, IN] LibAFL Backdorr operation +_libafl_exit_call0: + mov rax, rcx + +IFNDEF _DEBUG + LIBAFL_EXIT_OPCODE +ENDIF + + ret + +; Execute LibAFL backdoor (one argument) +; Parameters: +; [RAX, OUT] Hook return value +; [RCX, IN] LibAFL Backdorr operation +; [RDX, IN] Arg1 +_libafl_exit_call1: + push rdi + + mov rax, rcx + mov rdi, rdx + +IFNDEF _DEBUG + LIBAFL_EXIT_OPCODE +ENDIF + + pop rdi + + ret + +; Execute LibAFL backdoor (two arguments) +; Parameters: +; [RAX, OUT] Hook return value +; [RCX, IN] LibAFL Backdorr operation +; [RDX, IN] Arg1 +; [R8, IN] Arg2 +_libafl_exit_call2: + push rdi + push rsi + + mov rax, rcx + mov rdi, rdx + mov rsi, r8 + +IFNDEF _DEBUG + LIBAFL_EXIT_OPCODE +ENDIF + pop rsi + pop rdi + + ret + +END \ No newline at end of file diff --git a/libafl_qemu/src/aarch64.rs b/libafl_qemu/src/aarch64.rs index 4489b57171..3793146d4a 100644 --- a/libafl_qemu/src/aarch64.rs +++ b/libafl_qemu/src/aarch64.rs @@ -8,7 +8,7 @@ use pyo3::prelude::*; pub use strum_macros::EnumIter; pub use syscall_numbers::aarch64::*; -use crate::{sync_backdoor::SyncBackdoorArgs, CallingConvention}; +use crate::{sync_backdoor::BackdoorArgs, CallingConvention}; #[derive(IntoPrimitive, TryFromPrimitive, Debug, Clone, Copy, EnumIter)] #[repr(i32)] @@ -49,19 +49,19 @@ pub enum Regs { Pstate = 33, } -static SYNC_BACKDOOR_ARCH_REGS: OnceLock> = OnceLock::new(); +static BACKDOOR_ARCH_REGS: OnceLock> = OnceLock::new(); -pub fn get_sync_backdoor_arch_regs() -> &'static EnumMap { - SYNC_BACKDOOR_ARCH_REGS.get_or_init(|| { +pub fn get_backdoor_arch_regs() -> &'static EnumMap { + BACKDOOR_ARCH_REGS.get_or_init(|| { enum_map! { - SyncBackdoorArgs::Ret => Regs::X0, - SyncBackdoorArgs::Cmd => Regs::X0, - SyncBackdoorArgs::Arg1 => Regs::X1, - SyncBackdoorArgs::Arg2 => Regs::X2, - SyncBackdoorArgs::Arg3 => Regs::X3, - SyncBackdoorArgs::Arg4 => Regs::X4, - SyncBackdoorArgs::Arg5 => Regs::X5, - SyncBackdoorArgs::Arg6 => Regs::X6, + BackdoorArgs::Ret => Regs::X0, + BackdoorArgs::Cmd => Regs::X0, + BackdoorArgs::Arg1 => Regs::X1, + BackdoorArgs::Arg2 => Regs::X2, + BackdoorArgs::Arg3 => Regs::X3, + BackdoorArgs::Arg4 => Regs::X4, + BackdoorArgs::Arg5 => Regs::X5, + BackdoorArgs::Arg6 => Regs::X6, } }) } diff --git a/libafl_qemu/src/arm.rs b/libafl_qemu/src/arm.rs index 926f8bef89..fcdf9b1a06 100644 --- a/libafl_qemu/src/arm.rs +++ b/libafl_qemu/src/arm.rs @@ -8,7 +8,7 @@ use pyo3::prelude::*; pub use strum_macros::EnumIter; pub use syscall_numbers::arm::*; -use crate::{sync_backdoor::SyncBackdoorArgs, CallingConvention}; +use crate::{sync_backdoor::BackdoorArgs, CallingConvention}; /// Registers for the ARM instruction set. #[derive(IntoPrimitive, TryFromPrimitive, Debug, Clone, Copy, EnumIter)] @@ -33,19 +33,19 @@ pub enum Regs { R25 = 25, } -static SYNC_BACKDOOR_ARCH_REGS: OnceLock> = OnceLock::new(); +static BACKDOOR_ARCH_REGS: OnceLock> = OnceLock::new(); -pub fn get_sync_backdoor_arch_regs() -> &'static EnumMap { - SYNC_BACKDOOR_ARCH_REGS.get_or_init(|| { +pub fn get_backdoor_arch_regs() -> &'static EnumMap { + BACKDOOR_ARCH_REGS.get_or_init(|| { enum_map! { - SyncBackdoorArgs::Ret => Regs::R0, - SyncBackdoorArgs::Cmd => Regs::R0, - SyncBackdoorArgs::Arg1 => Regs::R1, - SyncBackdoorArgs::Arg2 => Regs::R2, - SyncBackdoorArgs::Arg3 => Regs::R3, - SyncBackdoorArgs::Arg4 => Regs::R4, - SyncBackdoorArgs::Arg5 => Regs::R5, - SyncBackdoorArgs::Arg6 => Regs::R6, + BackdoorArgs::Ret => Regs::R0, + BackdoorArgs::Cmd => Regs::R0, + BackdoorArgs::Arg1 => Regs::R1, + BackdoorArgs::Arg2 => Regs::R2, + BackdoorArgs::Arg3 => Regs::R3, + BackdoorArgs::Arg4 => Regs::R4, + BackdoorArgs::Arg5 => Regs::R5, + BackdoorArgs::Arg6 => Regs::R6, } }) } diff --git a/libafl_qemu/src/asan.rs b/libafl_qemu/src/asan.rs index 3da6ff4f13..443342553e 100644 --- a/libafl_qemu/src/asan.rs +++ b/libafl_qemu/src/asan.rs @@ -11,6 +11,7 @@ use addr2line::object::{Object, ObjectSection}; use libafl::{ executors::ExitKind, inputs::UsesInput, observers::ObserversTuple, state::HasMetadata, }; +use libafl_qemu_sys::GuestAddr; use libc::{ c_void, MAP_ANON, MAP_FAILED, MAP_FIXED, MAP_NORESERVE, MAP_PRIVATE, PROT_READ, PROT_WRITE, }; @@ -20,14 +21,14 @@ use rangemap::RangeMap; use crate::{ calls::FullBacktraceCollector, - emu::{EmuError, Emulator, MemAccessInfo, SyscallHookResult}, + emu::{EmuError, MemAccessInfo, SyscallHookResult}, helper::{ HasInstrumentationFilter, IsFilter, QemuHelper, QemuHelperTuple, QemuInstrumentationAddressRangeFilter, }, hooks::{Hook, QemuHooks}, snapshot::QemuSnapshotHelper, - GuestAddr, Regs, + Qemu, Regs, }; // TODO at some point, merge parts with libafl_frida @@ -131,7 +132,7 @@ impl core::fmt::Display for AsanError { } } -pub type AsanErrorCallback = Box; +pub type AsanErrorCallback = Box; #[derive(Debug, Clone)] pub struct AllocTreeItem { @@ -209,7 +210,7 @@ impl AsanGiovese { } #[must_use] - fn new(emu: &Emulator) -> Pin> { + fn new(emu: Qemu) -> Pin> { let res = Self { alloc_tree: Mutex::new(IntervalTree::new()), saved_tree: IntervalTree::new(), @@ -237,34 +238,34 @@ impl AsanGiovese { ) -> SyscallHookResult { if sys_num == QASAN_FAKESYS_NR { let mut r = 0; - let emulator = Emulator::get().unwrap(); + let qemu = Qemu::get().unwrap(); match QasanAction::try_from(a0).expect("Invalid QASan action number") { QasanAction::Poison => { self.poison( - &emulator, + qemu, a1, a2 as usize, PoisonKind::try_from(a3 as i8).unwrap().into(), ); } QasanAction::UserPoison => { - self.poison(&emulator, a1, a2 as usize, PoisonKind::User.into()); + self.poison(qemu, a1, a2 as usize, PoisonKind::User.into()); } QasanAction::UnPoison => { - Self::unpoison(&emulator, a1, a2 as usize); + Self::unpoison(qemu, a1, a2 as usize); } QasanAction::IsPoison => { - if Self::is_invalid_access(&emulator, a1, a2 as usize) { + if Self::is_invalid_access(qemu, a1, a2 as usize) { r = 1; } } QasanAction::Alloc => { - let pc: GuestAddr = emulator.read_reg(Regs::Pc).unwrap(); + let pc: GuestAddr = qemu.read_reg(Regs::Pc).unwrap(); self.allocation(pc, a1, a2); } QasanAction::Dealloc => { - let pc: GuestAddr = emulator.read_reg(Regs::Pc).unwrap(); - self.deallocation(&emulator, pc, a1); + let pc: GuestAddr = qemu.read_reg(Regs::Pc).unwrap(); + self.deallocation(qemu, pc, a1); } _ => (), } @@ -284,9 +285,9 @@ impl AsanGiovese { #[inline] #[must_use] - pub fn is_invalid_access_1(emu: &Emulator, addr: GuestAddr) -> bool { + pub fn is_invalid_access_1(qemu: Qemu, addr: GuestAddr) -> bool { unsafe { - let h = emu.g2h::<*const c_void>(addr) as isize; + let h = qemu.g2h::<*const c_void>(addr) as isize; let shadow_addr = ((h >> 3) as *mut i8).offset(SHADOW_OFFSET); let k = *shadow_addr as isize; k != 0 && (h & 7).wrapping_add(1) > k @@ -295,9 +296,9 @@ impl AsanGiovese { #[inline] #[must_use] - pub fn is_invalid_access_2(emu: &Emulator, addr: GuestAddr) -> bool { + pub fn is_invalid_access_2(qemu: Qemu, addr: GuestAddr) -> bool { unsafe { - let h = emu.g2h::<*const c_void>(addr) as isize; + let h = qemu.g2h::<*const c_void>(addr) as isize; let shadow_addr = ((h >> 3) as *mut i8).offset(SHADOW_OFFSET); let k = *shadow_addr as isize; k != 0 && (h & 7).wrapping_add(2) > k @@ -306,9 +307,9 @@ impl AsanGiovese { #[inline] #[must_use] - pub fn is_invalid_access_4(emu: &Emulator, addr: GuestAddr) -> bool { + pub fn is_invalid_access_4(qemu: Qemu, addr: GuestAddr) -> bool { unsafe { - let h = emu.g2h::<*const c_void>(addr) as isize; + let h = qemu.g2h::<*const c_void>(addr) as isize; let shadow_addr = ((h >> 3) as *mut i8).offset(SHADOW_OFFSET); let k = *shadow_addr as isize; k != 0 && (h & 7).wrapping_add(4) > k @@ -317,9 +318,9 @@ impl AsanGiovese { #[inline] #[must_use] - pub fn is_invalid_access_8(emu: &Emulator, addr: GuestAddr) -> bool { + pub fn is_invalid_access_8(qemu: Qemu, addr: GuestAddr) -> bool { unsafe { - let h = emu.g2h::<*const c_void>(addr) as isize; + let h = qemu.g2h::<*const c_void>(addr) as isize; let shadow_addr = ((h >> 3) as *mut i8).offset(SHADOW_OFFSET); *shadow_addr != 0 } @@ -328,7 +329,7 @@ impl AsanGiovese { #[inline] #[must_use] #[allow(clippy::cast_sign_loss)] - pub fn is_invalid_access(emu: &Emulator, addr: GuestAddr, n: usize) -> bool { + pub fn is_invalid_access(qemu: Qemu, addr: GuestAddr, n: usize) -> bool { unsafe { if n == 0 { return false; @@ -343,12 +344,12 @@ impl AsanGiovese { let next_8 = (start & !7).wrapping_add(8); let first_size = next_8.wrapping_sub(start) as isize; if n <= first_size { - let h = emu.g2h::<*const c_void>(start) as isize; + let h = qemu.g2h::<*const c_void>(start) as isize; let shadow_addr = ((h >> 3) as *mut i8).offset(SHADOW_OFFSET); let k = *shadow_addr as isize; return k != 0 && (h & 7).wrapping_add(n) > k; } - let h = emu.g2h::<*const c_void>(start) as isize; + let h = qemu.g2h::<*const c_void>(start) as isize; let shadow_addr = ((h >> 3) as *mut i8).offset(SHADOW_OFFSET); let k = *shadow_addr as isize; if k != 0 && (h & 7).wrapping_add(first_size) > k { @@ -358,7 +359,7 @@ impl AsanGiovese { } while start < last_8 { - let h = emu.g2h::<*const c_void>(start) as isize; + let h = qemu.g2h::<*const c_void>(start) as isize; let shadow_addr = ((h >> 3) as *mut i8).offset(SHADOW_OFFSET); if *shadow_addr != 0 { return true; @@ -367,7 +368,7 @@ impl AsanGiovese { } if last_8 != end { - let h = emu.g2h::<*const c_void>(start) as isize; + let h = qemu.g2h::<*const c_void>(start) as isize; let last_size = end.wrapping_sub(last_8) as isize; let shadow_addr = ((h >> 3) as *mut i8).offset(SHADOW_OFFSET); let k = *shadow_addr as isize; @@ -380,7 +381,7 @@ impl AsanGiovese { #[inline] #[allow(clippy::cast_sign_loss)] - pub fn poison(&mut self, emu: &Emulator, addr: GuestAddr, n: usize, poison_byte: i8) -> bool { + pub fn poison(&mut self, qemu: Qemu, addr: GuestAddr, n: usize, poison_byte: i8) -> bool { unsafe { if n == 0 { return false; @@ -406,14 +407,14 @@ impl AsanGiovese { if n < first_size { return false; } - let h = emu.g2h::<*const c_void>(start) as isize; + let h = qemu.g2h::<*const c_void>(start) as isize; let shadow_addr = ((h >> 3) as *mut i8).offset(SHADOW_OFFSET); *shadow_addr = (8isize).wrapping_sub(first_size) as i8; start = next_8; } while start < last_8 { - let h = emu.g2h::<*const c_void>(start) as isize; + let h = qemu.g2h::<*const c_void>(start) as isize; let shadow_addr = ((h >> 3) as *mut i8).offset(SHADOW_OFFSET); *shadow_addr = poison_byte; start = (start).wrapping_add(8); @@ -426,14 +427,14 @@ impl AsanGiovese { #[inline] #[allow(clippy::must_use_candidate)] #[allow(clippy::cast_sign_loss)] - pub fn unpoison(emu: &Emulator, addr: GuestAddr, n: usize) -> bool { + pub fn unpoison(qemu: Qemu, addr: GuestAddr, n: usize) -> bool { unsafe { let n = n as isize; let mut start = addr; let end = start.wrapping_add(n as GuestAddr); while start < end { - let h = emu.g2h::<*const c_void>(start) as isize; + let h = qemu.g2h::<*const c_void>(start) as isize; let shadow_addr = ((h >> 3) as *mut i8).offset(SHADOW_OFFSET); *shadow_addr = 0; start = (start).wrapping_add(8); @@ -443,9 +444,9 @@ impl AsanGiovese { } #[inline] - pub fn unpoison_page(emu: &Emulator, page: GuestAddr) { + pub fn unpoison_page(qemu: Qemu, page: GuestAddr) { unsafe { - let h = emu.g2h::<*const c_void>(page) as isize; + let h = qemu.g2h::<*const c_void>(page) as isize; let shadow_addr = ((h >> 3) as *mut i8).offset(SHADOW_OFFSET); shadow_addr.write_bytes(0, SHADOW_PAGE_SIZE); } @@ -453,26 +454,26 @@ impl AsanGiovese { #[inline] #[allow(clippy::mut_from_ref)] - fn get_shadow_page(emu: &Emulator, page: GuestAddr) -> &mut [i8] { + fn get_shadow_page(qemu: &Qemu, page: GuestAddr) -> &mut [i8] { unsafe { - let h = emu.g2h::<*const c_void>(page) as isize; + let h = qemu.g2h::<*const c_void>(page) as isize; let shadow_addr = ((h >> 3) as *mut i8).offset(SHADOW_OFFSET); std::slice::from_raw_parts_mut(shadow_addr, SHADOW_PAGE_SIZE) } } - pub fn report_or_crash(&mut self, emu: &Emulator, pc: GuestAddr, error: AsanError) { + pub fn report_or_crash(&mut self, qemu: Qemu, pc: GuestAddr, error: AsanError) { if let Some(mut cb) = self.error_callback.take() { - (cb)(self, emu, pc, error); + (cb)(self, qemu, pc, error); self.error_callback = Some(cb); } else { std::process::abort(); } } - pub fn report(&mut self, emu: &Emulator, pc: GuestAddr, error: AsanError) { + pub fn report(&mut self, qemu: Qemu, pc: GuestAddr, error: AsanError) { if let Some(mut cb) = self.error_callback.take() { - (cb)(self, emu, pc, error); + (cb)(self, qemu, pc, error); self.error_callback = Some(cb); } } @@ -502,7 +503,7 @@ impl AsanGiovese { } } - pub fn alloc_free(&mut self, emulator: &Emulator, pc: GuestAddr, addr: GuestAddr) { + pub fn alloc_free(&mut self, qemu: Qemu, pc: GuestAddr, addr: GuestAddr) { let mut chunk = None; self.alloc_map_mut(addr, |interval, item| { chunk = Some(*interval); @@ -518,11 +519,11 @@ impl AsanGiovese { if let Some(ck) = chunk { if ck.start != addr { // Free not the start of the chunk - self.report_or_crash(emulator, pc, AsanError::BadFree(addr, Some(ck))); + self.report_or_crash(qemu, pc, AsanError::BadFree(addr, Some(ck))); } } else { // Free of wild ptr - self.report_or_crash(emulator, pc, AsanError::BadFree(addr, None)); + self.report_or_crash(qemu, pc, AsanError::BadFree(addr, None)); } } @@ -596,16 +597,16 @@ impl AsanGiovese { self.alloc_insert(pc, start, end); } - pub fn deallocation(&mut self, emulator: &Emulator, pc: GuestAddr, addr: GuestAddr) { - self.alloc_free(emulator, pc, addr); + pub fn deallocation(&mut self, qemu: Qemu, pc: GuestAddr, addr: GuestAddr) { + self.alloc_free(qemu, pc, addr); } - pub fn snapshot(&mut self, emu: &Emulator) { + pub fn snapshot(&mut self, qemu: Qemu) { if self.snapshot_shadow { let set = self.dirty_shadow.lock().unwrap(); for &page in &*set { - let data = Self::get_shadow_page(emu, page).to_vec(); + let data = Self::get_shadow_page(&qemu, page).to_vec(); self.saved_shadow.insert(page, data); } @@ -614,7 +615,7 @@ impl AsanGiovese { } } - pub fn rollback(&mut self, emu: &Emulator, detect_leaks: bool) -> AsanRollback { + pub fn rollback(&mut self, qemu: Qemu, detect_leaks: bool) -> AsanRollback { let mut leaks = vec![]; { @@ -637,10 +638,10 @@ impl AsanGiovese { for &page in &*set { let original = self.saved_shadow.get(&page); if let Some(data) = original { - let cur = Self::get_shadow_page(emu, page); + let cur = Self::get_shadow_page(&qemu, page); cur.copy_from_slice(data); } else { - Self::unpoison_page(emu, page); + Self::unpoison_page(qemu, page); } } @@ -655,8 +656,8 @@ impl AsanGiovese { for interval in leaks { self.report( - emu, - emu.read_reg(Regs::Pc).unwrap(), + qemu, + qemu.read_reg(Regs::Pc).unwrap(), AsanError::MemLeak(interval), ); } @@ -667,10 +668,10 @@ impl AsanGiovese { static mut ASAN_INITED: bool = false; -pub fn init_with_asan( +pub fn init_qemu_with_asan( args: &mut Vec, env: &mut [(String, String)], -) -> Result<(Emulator, Pin>), EmuError> { +) -> Result<(Qemu, Pin>), EmuError> { let current = env::current_exe().unwrap(); let asan_lib = fs::canonicalize(current) .unwrap() @@ -716,10 +717,10 @@ pub fn init_with_asan( ASAN_INITED = true; } - let emu = Emulator::new(args, env)?; - let rt = AsanGiovese::new(&emu); + let qemu = Qemu::init(args, env)?; + let rt = AsanGiovese::new(qemu); - Ok((emu, rt)) + Ok((qemu, rt)) } pub enum QemuAsanOptions { @@ -756,7 +757,7 @@ impl QemuAsanHelper { filter: QemuInstrumentationAddressRangeFilter, options: QemuAsanOptions, ) -> Self { - assert!(unsafe { ASAN_INITED }, "The ASan runtime is not initialized, use init_with_asan(...) instead of just Emulator::new(...)"); + assert!(unsafe { ASAN_INITED }, "The ASan runtime is not initialized, use init_qemu_with_asan(...) instead of just Qemu::init(...)"); let (snapshot, detect_leaks) = match options { QemuAsanOptions::None => (false, false), QemuAsanOptions::Snapshot => (true, false), @@ -780,7 +781,7 @@ impl QemuAsanHelper { error_callback: AsanErrorCallback, options: QemuAsanOptions, ) -> Self { - assert!(unsafe { ASAN_INITED }, "The ASan runtime is not initialized, use init_with_asan(...) instead of just Emulator::new(...)"); + assert!(unsafe { ASAN_INITED }, "The ASan runtime is not initialized, use init_qemu_with_asan(...) instead of just Qemu::init(...)"); let (snapshot, detect_leaks) = match options { QemuAsanOptions::None => (false, false), QemuAsanOptions::Snapshot => (true, false), @@ -825,107 +826,95 @@ impl QemuAsanHelper { self.rt.allocation(pc, start, end); } - pub fn dealloc(&mut self, emulator: &Emulator, pc: GuestAddr, addr: GuestAddr) { - self.rt.deallocation(emulator, pc, addr); + pub fn dealloc(&mut self, qemu: Qemu, pc: GuestAddr, addr: GuestAddr) { + self.rt.deallocation(qemu, pc, addr); } #[allow(clippy::unused_self)] #[must_use] - pub fn is_poisoned(&self, emulator: &Emulator, addr: GuestAddr, size: usize) -> bool { - AsanGiovese::is_invalid_access(emulator, addr, size) + pub fn is_poisoned(&self, qemu: Qemu, addr: GuestAddr, size: usize) -> bool { + AsanGiovese::is_invalid_access(qemu, addr, size) } - pub fn read_1(&mut self, emulator: &Emulator, pc: GuestAddr, addr: GuestAddr) { - if self.enabled() && AsanGiovese::is_invalid_access_1(emulator, addr) { - self.rt - .report_or_crash(emulator, pc, AsanError::Read(addr, 1)); + pub fn read_1(&mut self, qemu: Qemu, pc: GuestAddr, addr: GuestAddr) { + if self.enabled() && AsanGiovese::is_invalid_access_1(qemu, addr) { + self.rt.report_or_crash(qemu, pc, AsanError::Read(addr, 1)); } } - pub fn read_2(&mut self, emulator: &Emulator, pc: GuestAddr, addr: GuestAddr) { - if self.enabled() && AsanGiovese::is_invalid_access_2(emulator, addr) { - self.rt - .report_or_crash(emulator, pc, AsanError::Read(addr, 2)); + pub fn read_2(&mut self, qemu: Qemu, pc: GuestAddr, addr: GuestAddr) { + if self.enabled() && AsanGiovese::is_invalid_access_2(qemu, addr) { + self.rt.report_or_crash(qemu, pc, AsanError::Read(addr, 2)); } } - pub fn read_4(&mut self, emulator: &Emulator, pc: GuestAddr, addr: GuestAddr) { - if self.enabled() && AsanGiovese::is_invalid_access_4(emulator, addr) { - self.rt - .report_or_crash(emulator, pc, AsanError::Read(addr, 4)); + pub fn read_4(&mut self, qemu: Qemu, pc: GuestAddr, addr: GuestAddr) { + if self.enabled() && AsanGiovese::is_invalid_access_4(qemu, addr) { + self.rt.report_or_crash(qemu, pc, AsanError::Read(addr, 4)); } } - pub fn read_8(&mut self, emulator: &Emulator, pc: GuestAddr, addr: GuestAddr) { - if self.enabled() && AsanGiovese::is_invalid_access_8(emulator, addr) { - self.rt - .report_or_crash(emulator, pc, AsanError::Read(addr, 8)); + pub fn read_8(&mut self, qemu: Qemu, pc: GuestAddr, addr: GuestAddr) { + if self.enabled() && AsanGiovese::is_invalid_access_8(qemu, addr) { + self.rt.report_or_crash(qemu, pc, AsanError::Read(addr, 8)); } } - pub fn read_n(&mut self, emulator: &Emulator, pc: GuestAddr, addr: GuestAddr, size: usize) { - if self.enabled() && AsanGiovese::is_invalid_access(emulator, addr, size) { + pub fn read_n(&mut self, qemu: Qemu, pc: GuestAddr, addr: GuestAddr, size: usize) { + if self.enabled() && AsanGiovese::is_invalid_access(qemu, addr, size) { self.rt - .report_or_crash(emulator, pc, AsanError::Read(addr, size)); + .report_or_crash(qemu, pc, AsanError::Read(addr, size)); } } - pub fn write_1(&mut self, emulator: &Emulator, pc: GuestAddr, addr: GuestAddr) { - if self.enabled() && AsanGiovese::is_invalid_access_1(emulator, addr) { - self.rt - .report_or_crash(emulator, pc, AsanError::Write(addr, 1)); + pub fn write_1(&mut self, qemu: Qemu, pc: GuestAddr, addr: GuestAddr) { + if self.enabled() && AsanGiovese::is_invalid_access_1(qemu, addr) { + self.rt.report_or_crash(qemu, pc, AsanError::Write(addr, 1)); } } - pub fn write_2(&mut self, emulator: &Emulator, pc: GuestAddr, addr: GuestAddr) { - if self.enabled() && AsanGiovese::is_invalid_access_2(emulator, addr) { - self.rt - .report_or_crash(emulator, pc, AsanError::Write(addr, 2)); + pub fn write_2(&mut self, qemu: Qemu, pc: GuestAddr, addr: GuestAddr) { + if self.enabled() && AsanGiovese::is_invalid_access_2(qemu, addr) { + self.rt.report_or_crash(qemu, pc, AsanError::Write(addr, 2)); } } - pub fn write_4(&mut self, emulator: &Emulator, pc: GuestAddr, addr: GuestAddr) { - if self.enabled() && AsanGiovese::is_invalid_access_4(emulator, addr) { - self.rt - .report_or_crash(emulator, pc, AsanError::Write(addr, 4)); + pub fn write_4(&mut self, qemu: Qemu, pc: GuestAddr, addr: GuestAddr) { + if self.enabled() && AsanGiovese::is_invalid_access_4(qemu, addr) { + self.rt.report_or_crash(qemu, pc, AsanError::Write(addr, 4)); } } - pub fn write_8(&mut self, emulator: &Emulator, pc: GuestAddr, addr: GuestAddr) { - if self.enabled() && AsanGiovese::is_invalid_access_8(emulator, addr) { - self.rt - .report_or_crash(emulator, pc, AsanError::Write(addr, 8)); + pub fn write_8(&mut self, qemu: Qemu, pc: GuestAddr, addr: GuestAddr) { + if self.enabled() && AsanGiovese::is_invalid_access_8(qemu, addr) { + self.rt.report_or_crash(qemu, pc, AsanError::Write(addr, 8)); } } - pub fn write_n(&mut self, emulator: &Emulator, pc: GuestAddr, addr: GuestAddr, size: usize) { - if self.enabled() && AsanGiovese::is_invalid_access(emulator, addr, size) { + pub fn write_n(&mut self, qemu: Qemu, pc: GuestAddr, addr: GuestAddr, size: usize) { + if self.enabled() && AsanGiovese::is_invalid_access(qemu, addr, size) { self.rt - .report_or_crash(emulator, pc, AsanError::Write(addr, size)); + .report_or_crash(qemu, pc, AsanError::Write(addr, size)); } } - pub fn poison( - &mut self, - emulator: &Emulator, - addr: GuestAddr, - size: usize, - poison: PoisonKind, - ) { - self.rt.poison(emulator, addr, size, poison.into()); + pub fn poison(&mut self, qemu: Qemu, addr: GuestAddr, size: usize, poison: PoisonKind) { + self.rt.poison(qemu, addr, size, poison.into()); } #[allow(clippy::unused_self)] - pub fn unpoison(&mut self, emulator: &Emulator, addr: GuestAddr, size: usize) { - AsanGiovese::unpoison(emulator, addr, size); + pub fn unpoison(&mut self, qemu: Qemu, addr: GuestAddr, size: usize) { + AsanGiovese::unpoison(qemu, addr, size); } - pub fn reset(&mut self, emulator: &Emulator) -> AsanRollback { - self.rt.rollback(emulator, self.detect_leaks) + pub fn reset(&mut self, qemu: Qemu) -> AsanRollback { + self.rt.rollback(qemu, self.detect_leaks) } } -impl HasInstrumentationFilter for QemuAsanHelper { +impl HasInstrumentationFilter + for QemuAsanHelper +{ fn filter(&self) -> &QemuInstrumentationAddressRangeFilter { &self.filter } @@ -987,23 +976,23 @@ where } } - fn pre_exec(&mut self, emulator: &Emulator, _input: &S::Input) { + fn pre_exec(&mut self, qemu: Qemu, _input: &S::Input) { if self.empty { - self.rt.snapshot(emulator); + self.rt.snapshot(qemu); self.empty = false; } } fn post_exec( &mut self, - emulator: &Emulator, + qemu: Qemu, _input: &S::Input, _observers: &mut OT, exit_kind: &mut ExitKind, ) where OT: ObserversTuple, { - if self.reset(emulator) == AsanRollback::HasLeaks { + if self.reset(qemu) == AsanRollback::HasLeaks { *exit_kind = ExitKind::Crash; } } @@ -1014,10 +1003,10 @@ where S: UsesInput, QT: QemuHelperTuple, { - let emu = hooks.emulator().clone(); + let qemu = *hooks.qemu(); let h = hooks.match_helper_mut::().unwrap(); - let pc: GuestAddr = emu.read_reg(Regs::Pc).unwrap(); - h.rt.report(&emu, pc, AsanError::Signal(target_sig)); + let pc: GuestAddr = qemu.read_reg(Regs::Pc).unwrap(); + h.rt.report(qemu, pc, AsanError::Signal(target_sig)); } pub fn gen_readwrite_asan( @@ -1047,9 +1036,9 @@ pub fn trace_read1_asan( S: UsesInput, QT: QemuHelperTuple, { - let emulator = hooks.emulator().clone(); + let qemu = *hooks.qemu(); let h = hooks.match_helper_mut::().unwrap(); - h.read_1(&emulator, id as GuestAddr, addr); + h.read_1(qemu, id as GuestAddr, addr); } pub fn trace_read2_asan( @@ -1061,9 +1050,9 @@ pub fn trace_read2_asan( S: UsesInput, QT: QemuHelperTuple, { - let emulator = hooks.emulator().clone(); + let qemu = *hooks.qemu(); let h = hooks.match_helper_mut::().unwrap(); - h.read_2(&emulator, id as GuestAddr, addr); + h.read_2(qemu, id as GuestAddr, addr); } pub fn trace_read4_asan( @@ -1075,9 +1064,9 @@ pub fn trace_read4_asan( S: UsesInput, QT: QemuHelperTuple, { - let emulator = hooks.emulator().clone(); + let qemu = *hooks.qemu(); let h = hooks.match_helper_mut::().unwrap(); - h.read_4(&emulator, id as GuestAddr, addr); + h.read_4(qemu, id as GuestAddr, addr); } pub fn trace_read8_asan( @@ -1089,9 +1078,9 @@ pub fn trace_read8_asan( S: UsesInput, QT: QemuHelperTuple, { - let emulator = hooks.emulator().clone(); + let qemu = *hooks.qemu(); let h = hooks.match_helper_mut::().unwrap(); - h.read_8(&emulator, id as GuestAddr, addr); + h.read_8(qemu, id as GuestAddr, addr); } pub fn trace_read_n_asan( @@ -1104,9 +1093,9 @@ pub fn trace_read_n_asan( S: UsesInput, QT: QemuHelperTuple, { - let emulator = hooks.emulator().clone(); + let qemu = *hooks.qemu(); let h = hooks.match_helper_mut::().unwrap(); - h.read_n(&emulator, id as GuestAddr, addr, size); + h.read_n(qemu, id as GuestAddr, addr, size); } pub fn trace_write1_asan( @@ -1118,9 +1107,9 @@ pub fn trace_write1_asan( S: UsesInput, QT: QemuHelperTuple, { - let emulator = hooks.emulator().clone(); + let qemu = *hooks.qemu(); let h = hooks.match_helper_mut::().unwrap(); - h.write_1(&emulator, id as GuestAddr, addr); + h.write_1(qemu, id as GuestAddr, addr); } pub fn trace_write2_asan( @@ -1132,9 +1121,9 @@ pub fn trace_write2_asan( S: UsesInput, QT: QemuHelperTuple, { - let emulator = hooks.emulator().clone(); + let qemu = *hooks.qemu(); let h = hooks.match_helper_mut::().unwrap(); - h.write_2(&emulator, id as GuestAddr, addr); + h.write_2(qemu, id as GuestAddr, addr); } pub fn trace_write4_asan( @@ -1146,9 +1135,9 @@ pub fn trace_write4_asan( S: UsesInput, QT: QemuHelperTuple, { - let emulator = hooks.emulator().clone(); + let qemu = *hooks.qemu(); let h = hooks.match_helper_mut::().unwrap(); - h.write_4(&emulator, id as GuestAddr, addr); + h.write_4(qemu, id as GuestAddr, addr); } pub fn trace_write8_asan( @@ -1160,9 +1149,9 @@ pub fn trace_write8_asan( S: UsesInput, QT: QemuHelperTuple, { - let emulator = hooks.emulator().clone(); + let qemu = *hooks.qemu(); let h = hooks.match_helper_mut::().unwrap(); - h.write_8(&emulator, id as GuestAddr, addr); + h.write_8(qemu, id as GuestAddr, addr); } pub fn trace_write_n_asan( @@ -1175,9 +1164,9 @@ pub fn trace_write_n_asan( S: UsesInput, QT: QemuHelperTuple, { - let emulator = hooks.emulator().clone(); + let qemu = *hooks.qemu(); let h = hooks.match_helper_mut::().unwrap(); - h.read_n(&emulator, id as GuestAddr, addr, size); + h.read_n(qemu, id as GuestAddr, addr, size); } pub fn gen_write_asan_snapshot( @@ -1208,9 +1197,9 @@ pub fn trace_write1_asan_snapshot( QT: QemuHelperTuple, { if id != 0 { - let emulator = hooks.emulator().clone(); + let qemu = *hooks.qemu(); let h = hooks.match_helper_mut::().unwrap(); - h.write_1(&emulator, id as GuestAddr, addr); + h.write_1(qemu, id as GuestAddr, addr); } let h = hooks.match_helper_mut::().unwrap(); h.access(addr, 1); @@ -1226,9 +1215,9 @@ pub fn trace_write2_asan_snapshot( QT: QemuHelperTuple, { if id != 0 { - let emulator = hooks.emulator().clone(); + let qemu = *hooks.qemu(); let h = hooks.match_helper_mut::().unwrap(); - h.write_2(&emulator, id as GuestAddr, addr); + h.write_2(qemu, id as GuestAddr, addr); } let h = hooks.match_helper_mut::().unwrap(); h.access(addr, 2); @@ -1244,9 +1233,9 @@ pub fn trace_write4_asan_snapshot( QT: QemuHelperTuple, { if id != 0 { - let emulator = hooks.emulator().clone(); + let qemu = *hooks.qemu(); let h = hooks.match_helper_mut::().unwrap(); - h.write_4(&emulator, id as GuestAddr, addr); + h.write_4(qemu, id as GuestAddr, addr); } let h = hooks.match_helper_mut::().unwrap(); h.access(addr, 4); @@ -1262,9 +1251,9 @@ pub fn trace_write8_asan_snapshot( QT: QemuHelperTuple, { if id != 0 { - let emulator = hooks.emulator().clone(); + let qemu = *hooks.qemu(); let h = hooks.match_helper_mut::().unwrap(); - h.write_8(&emulator, id as GuestAddr, addr); + h.write_8(qemu, id as GuestAddr, addr); } let h = hooks.match_helper_mut::().unwrap(); h.access(addr, 8); @@ -1281,9 +1270,9 @@ pub fn trace_write_n_asan_snapshot( QT: QemuHelperTuple, { if id != 0 { - let emulator = hooks.emulator().clone(); + let qemu = *hooks.qemu(); let h = hooks.match_helper_mut::().unwrap(); - h.read_n(&emulator, id as GuestAddr, addr, size); + h.read_n(qemu, id as GuestAddr, addr, size); } let h = hooks.match_helper_mut::().unwrap(); h.access(addr, size); @@ -1308,16 +1297,16 @@ where QT: QemuHelperTuple, { if sys_num == QASAN_FAKESYS_NR { - let emulator = hooks.emulator().clone(); + let qemu = *hooks.qemu(); let h = hooks.match_helper_mut::().unwrap(); match QasanAction::try_from(a0).expect("Invalid QASan action number") { QasanAction::CheckLoad => { - let pc: GuestAddr = emulator.read_reg(Regs::Pc).unwrap(); - h.read_n(&emulator, pc, a1, a2 as usize); + let pc: GuestAddr = qemu.read_reg(Regs::Pc).unwrap(); + h.read_n(qemu, pc, a1, a2 as usize); } QasanAction::CheckStore => { - let pc: GuestAddr = emulator.read_reg(Regs::Pc).unwrap(); - h.write_n(&emulator, pc, a1, a2 as usize); + let pc: GuestAddr = qemu.read_reg(Regs::Pc).unwrap(); + h.write_n(qemu, pc, a1, a2 as usize); } QasanAction::Enable => { h.set_enabled(true); @@ -1358,9 +1347,9 @@ fn load_file_section<'input, 'arena, Endian: addr2line::gimli::Endianity>( #[allow(clippy::unnecessary_cast)] #[allow(clippy::too_many_lines)] -pub fn asan_report(rt: &AsanGiovese, emu: &Emulator, pc: GuestAddr, err: AsanError) { +pub fn asan_report(rt: &AsanGiovese, qemu: Qemu, pc: GuestAddr, err: AsanError) { let mut regions = std::collections::HashMap::new(); - for region in emu.mappings() { + for region in qemu.mappings() { if let Some(path) = region.path() { let start = region.start(); let end = region.end(); @@ -1562,6 +1551,9 @@ pub fn asan_report(rt: &AsanGiovese, emu: &Emulator, pc: GuestAddr, err: AsanErr } // fix pc in case it is not synced (in hooks) - emu.write_reg(Regs::Pc, pc).unwrap(); - eprint!("Context:\n{}", emu.current_cpu().unwrap().display_context()); + qemu.write_reg(Regs::Pc, pc).unwrap(); + eprint!( + "Context:\n{}", + qemu.current_cpu().unwrap().display_context() + ); } diff --git a/libafl_qemu/src/breakpoint.rs b/libafl_qemu/src/breakpoint.rs new file mode 100644 index 0000000000..ec9ed7ff94 --- /dev/null +++ b/libafl_qemu/src/breakpoint.rs @@ -0,0 +1,95 @@ +use std::{ + borrow::Borrow, + fmt::{Display, Formatter}, + hash::{Hash, Hasher}, +}; + +use libafl_qemu_sys::GuestAddr; + +use crate::{command::Command, Qemu}; + +// TODO: distinguish breakpoints with IDs instead of addresses to avoid collisions. +#[derive(Debug, Clone)] +pub struct Breakpoint { + addr: GuestAddr, + cmd: Option, + disable_on_trigger: bool, + enabled: bool, +} + +impl Hash for Breakpoint { + fn hash(&self, state: &mut H) { + self.addr.hash(state); + } +} + +impl PartialEq for Breakpoint { + fn eq(&self, other: &Self) -> bool { + self.addr == other.addr + } +} + +impl Eq for Breakpoint {} + +impl Display for Breakpoint { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "Breakpoint @vaddr 0x{:x}", self.addr) + } +} + +impl Borrow for Breakpoint { + fn borrow(&self) -> &GuestAddr { + &self.addr + } +} + +impl Breakpoint { + // Emu will return with the breakpoint as exit reason. + #[must_use] + pub fn without_command(addr: GuestAddr, disable_on_trigger: bool) -> Self { + Self { + addr, + cmd: None, + disable_on_trigger, + enabled: false, + } + } + + // Emu will execute the command when it meets the breakpoint. + #[must_use] + pub fn with_command(addr: GuestAddr, cmd: Command, disable_on_trigger: bool) -> Self { + Self { + addr, + cmd: Some(cmd), + disable_on_trigger, + enabled: false, + } + } + + #[must_use] + pub fn addr(&self) -> GuestAddr { + self.addr + } + + pub fn enable(&mut self, qemu: &Qemu) { + if !self.enabled { + qemu.set_breakpoint(self.addr); + self.enabled = true; + } + } + + pub fn disable(&mut self, qemu: &Qemu) { + if self.enabled { + qemu.remove_breakpoint(self.addr.into()); + self.enabled = false; + } + } + + pub fn trigger(&mut self, qemu: &Qemu) -> Option<&Command> { + if self.disable_on_trigger { + self.disable(qemu); + } + + self.cmd.as_ref() + } +} diff --git a/libafl_qemu/src/calls.rs b/libafl_qemu/src/calls.rs index f024260c6c..9cc87f7d77 100644 --- a/libafl_qemu/src/calls.rs +++ b/libafl_qemu/src/calls.rs @@ -7,17 +7,18 @@ use libafl::{ observers::{stacktrace::BacktraceObserver, ObserversTuple}, }; use libafl_bolts::{tuples::MatchFirstType, Named}; +use libafl_qemu_sys::GuestAddr; use thread_local::ThreadLocal; use crate::{ capstone, - emu::{ArchExtras, Emulator}, + emu::ArchExtras, helper::{ HasInstrumentationFilter, IsFilter, QemuHelper, QemuHelperTuple, QemuInstrumentationAddressRangeFilter, }, hooks::{Hook, QemuHooks}, - GuestAddr, + Qemu, }; pub trait CallTraceCollector: 'static + Debug { @@ -42,7 +43,7 @@ pub trait CallTraceCollector: 'static + Debug { QT: QemuHelperTuple; // Frowarded from the `QemuCallTracerHelper` - fn pre_exec(&mut self, _emulator: &Emulator, _input: &I) + fn pre_exec(&mut self, _qemu: Qemu, _input: &I) where I: Input, { @@ -50,7 +51,7 @@ pub trait CallTraceCollector: 'static + Debug { fn post_exec( &mut self, - _emulator: &Emulator, + _qemu: Qemu, _input: &S::Input, _observers: &mut OT, _exit_kind: &mut ExitKind, @@ -82,13 +83,13 @@ pub trait CallTraceCollectorTuple: 'static + MatchFirstType + Debug { S: UsesInput, QT: QemuHelperTuple; - fn pre_exec_all(&mut self, _emulator: &Emulator, input: &I) + fn pre_exec_all(&mut self, _qemu: Qemu, input: &I) where I: Input; fn post_exec_all( &mut self, - _emulator: &Emulator, + _qemu: Qemu, input: &S::Input, _observers: &mut OT, _exit_kind: &mut ExitKind, @@ -122,7 +123,7 @@ impl CallTraceCollectorTuple for () { { } - fn pre_exec_all(&mut self, _emulator: &Emulator, _input: &I) + fn pre_exec_all(&mut self, _qemu: Qemu, _input: &I) where I: Input, { @@ -130,7 +131,7 @@ impl CallTraceCollectorTuple for () { fn post_exec_all( &mut self, - _emulator: &Emulator, + _emulator: Qemu, _input: &S::Input, _observers: &mut OT, _exit_kind: &mut ExitKind, @@ -190,17 +191,17 @@ where self.1.on_ret_all(hooks, state, pc, ret_addr); } - fn pre_exec_all(&mut self, emulator: &Emulator, input: &I) + fn pre_exec_all(&mut self, qemu: Qemu, input: &I) where I: Input, { - self.0.pre_exec(emulator, input); - self.1.pre_exec_all(emulator, input); + self.0.pre_exec(qemu, input); + self.1.pre_exec_all(qemu, input); } fn post_exec_all( &mut self, - emulator: &Emulator, + qemu: Qemu, input: &S::Input, observers: &mut OT, exit_kind: &mut ExitKind, @@ -208,8 +209,8 @@ where OT: ObserversTuple, S: UsesInput, { - self.0.post_exec(emulator, input, observers, exit_kind); - self.1.post_exec_all(emulator, input, observers, exit_kind); + self.0.post_exec(qemu, input, observers, exit_kind); + self.1.post_exec_all(qemu, input, observers, exit_kind); } } @@ -246,7 +247,7 @@ where S: UsesInput, QT: QemuHelperTuple, { - let ret_addr: GuestAddr = hooks.emulator().read_return_address().unwrap(); + let ret_addr: GuestAddr = hooks.qemu().read_return_address().unwrap(); // log::info!("RET @ 0x{:#x}", ret_addr); @@ -292,7 +293,7 @@ where .unwrap(); } - let emu = hooks.emulator(); + let emu = hooks.qemu(); if let Some(h) = hooks.helpers().match_first_type::() { #[allow(unused_mut)] @@ -383,9 +384,11 @@ where } } -impl HasInstrumentationFilter for QemuCallTracerHelper +impl HasInstrumentationFilter + for QemuCallTracerHelper where T: CallTraceCollectorTuple, + S: UsesInput, { fn filter(&self) -> &QemuInstrumentationAddressRangeFilter { &self.filter @@ -412,16 +415,13 @@ where ); } - fn pre_exec(&mut self, emulator: &Emulator, input: &S::Input) { - self.collectors - .as_mut() - .unwrap() - .pre_exec_all(emulator, input); + fn pre_exec(&mut self, qemu: Qemu, input: &S::Input) { + self.collectors.as_mut().unwrap().pre_exec_all(qemu, input); } fn post_exec( &mut self, - emulator: &Emulator, + qemu: Qemu, input: &S::Input, observers: &mut OT, exit_kind: &mut ExitKind, @@ -431,7 +431,7 @@ where self.collectors .as_mut() .unwrap() - .post_exec_all(emulator, input, observers, exit_kind); + .post_exec_all(qemu, input, observers, exit_kind); } } @@ -498,7 +498,7 @@ impl CallTraceCollector for OnCrashBacktraceCollector { self.callstack_hash ^= ret_addr as u64; } - fn pre_exec(&mut self, _emulator: &Emulator, _input: &I) + fn pre_exec(&mut self, _qemu: Qemu, _input: &I) where I: Input, { @@ -507,7 +507,7 @@ impl CallTraceCollector for OnCrashBacktraceCollector { fn post_exec( &mut self, - _emulator: &Emulator, + _qemu: Qemu, _input: &S::Input, observers: &mut OT, exit_kind: &mut ExitKind, @@ -602,7 +602,7 @@ impl CallTraceCollector for FullBacktraceCollector { } } - fn pre_exec(&mut self, _emulator: &Emulator, _input: &I) + fn pre_exec(&mut self, _qemu: Qemu, _input: &I) where I: Input, { diff --git a/libafl_qemu/src/cmplog.rs b/libafl_qemu/src/cmplog.rs index 7651356ed1..102bf559ea 100644 --- a/libafl_qemu/src/cmplog.rs +++ b/libafl_qemu/src/cmplog.rs @@ -2,6 +2,7 @@ use capstone::{arch::BuildsCapstone, Capstone, InsnDetail}; use hashbrown::HashMap; use libafl::{inputs::UsesInput, state::HasMetadata}; +use libafl_qemu_sys::GuestAddr; pub use libafl_targets::{ cmps::{ __libafl_targets_cmplog_instructions, __libafl_targets_cmplog_routines, CMPLOG_ENABLED, @@ -11,18 +12,13 @@ pub use libafl_targets::{ use serde::{Deserialize, Serialize}; #[cfg(emulation_mode = "usermode")] -use crate::{ - capstone, - emu::{ArchExtras, Emulator}, - CallingConvention, -}; +use crate::{capstone, emu::ArchExtras, CallingConvention, Qemu}; use crate::{ helper::{ hash_me, HasInstrumentationFilter, IsFilter, QemuHelper, QemuHelperTuple, QemuInstrumentationAddressRangeFilter, }, hooks::{Hook, QemuHooks}, - GuestAddr, }; #[cfg_attr( @@ -70,7 +66,9 @@ impl Default for QemuCmpLogHelper { } } -impl HasInstrumentationFilter for QemuCmpLogHelper { +impl HasInstrumentationFilter + for QemuCmpLogHelper +{ fn filter(&self) -> &QemuInstrumentationAddressRangeFilter { &self.filter } @@ -246,12 +244,12 @@ impl QemuCmpLogRoutinesHelper { } } - let emu = Emulator::get().unwrap(); + let qemu = Qemu::get().unwrap(); - let a0: GuestAddr = emu + let a0: GuestAddr = qemu .read_function_argument(CallingConvention::Cdecl, 0) .unwrap_or(0); - let a1: GuestAddr = emu + let a1: GuestAddr = qemu .read_function_argument(CallingConvention::Cdecl, 1) .unwrap_or(0); @@ -262,7 +260,7 @@ impl QemuCmpLogRoutinesHelper { // if !emu.access_ok(VerifyAccess::Read, a0, 0x20) || !emu.access_ok(VerifyAccess::Read, a1, 0x20) { return; } unsafe { - __libafl_targets_cmplog_routines(k as usize, emu.g2h(a0), emu.g2h(a1)); + __libafl_targets_cmplog_routines(k as usize, qemu.g2h(a0), qemu.g2h(a1)); } } @@ -289,21 +287,21 @@ impl QemuCmpLogRoutinesHelper { .unwrap(); } - let emu = hooks.emulator(); + let qemu = hooks.qemu(); if let Some(h) = hooks.helpers().match_first_type::() { #[allow(unused_mut)] let mut code = { #[cfg(emulation_mode = "usermode")] unsafe { - std::slice::from_raw_parts(emu.g2h(pc), 512) + std::slice::from_raw_parts(qemu.g2h(pc), 512) } #[cfg(emulation_mode = "systemmode")] &mut [0; 512] }; #[cfg(emulation_mode = "systemmode")] unsafe { - emu.read_mem(pc, code) + qemu.read_mem(pc, code) }; // TODO handle faults let mut iaddr = pc; @@ -318,7 +316,7 @@ impl QemuCmpLogRoutinesHelper { match u32::from(detail.0) { capstone::InsnGroupType::CS_GRP_CALL => { let k = (hash_me(pc.into())) & (CMPLOG_MAP_W as u64 - 1); - emu.set_hook(k, insn.address() as GuestAddr, Self::on_call, false); + qemu.set_hook(k, insn.address() as GuestAddr, Self::on_call, false); } capstone::InsnGroupType::CS_GRP_RET | capstone::InsnGroupType::CS_GRP_INVALID @@ -335,11 +333,11 @@ impl QemuCmpLogRoutinesHelper { #[cfg(emulation_mode = "usermode")] unsafe { - code = std::slice::from_raw_parts(emu.g2h(iaddr), 512); + code = std::slice::from_raw_parts(qemu.g2h(iaddr), 512); } #[cfg(emulation_mode = "systemmode")] unsafe { - emu.read_mem(pc, code); + qemu.read_mem(pc, code); } // TODO handle faults } } @@ -349,7 +347,9 @@ impl QemuCmpLogRoutinesHelper { } #[cfg(emulation_mode = "usermode")] -impl HasInstrumentationFilter for QemuCmpLogRoutinesHelper { +impl HasInstrumentationFilter + for QemuCmpLogRoutinesHelper +{ fn filter(&self) -> &QemuInstrumentationAddressRangeFilter { &self.filter } diff --git a/libafl_qemu/src/command.rs b/libafl_qemu/src/command.rs new file mode 100644 index 0000000000..bb658d9b6c --- /dev/null +++ b/libafl_qemu/src/command.rs @@ -0,0 +1,660 @@ +#[cfg(emulation_mode = "systemmode")] +use std::collections::HashSet; +use std::fmt::{Debug, Display, Formatter}; + +use enum_map::Enum; +use libafl::{ + executors::ExitKind, + inputs::{BytesInput, HasBytesVec}, + state::{HasExecutions, State}, +}; +use libafl_qemu_sys::{GuestPhysAddr, GuestVirtAddr}; +use num_enum::TryFromPrimitive; + +#[cfg(emulation_mode = "systemmode")] +use crate::QemuInstrumentationPagingFilter; +use crate::{ + executor::QemuExecutorState, sync_backdoor::SyncBackdoorError, EmuExitHandler, Emulator, + GuestAddrKind, GuestReg, HandlerError, HasInstrumentationFilter, InnerHandlerResult, IsFilter, + IsSnapshotManager, Qemu, QemuHelperTuple, QemuInstrumentationAddressRangeFilter, Regs, + StdEmuExitHandler, StdInstrumentationFilter, CPU, +}; + +pub const VERSION: u64 = bindings::LIBAFL_EXIT_VERSION_NUMBER as u64; + +mod bindings { + #![allow(non_upper_case_globals)] + #![allow(non_camel_case_types)] + #![allow(non_snake_case)] + #![allow(improper_ctypes)] + #![allow(unused_mut)] + #![allow(unused)] + #![allow(unused_variables)] + #![allow(clippy::all)] + #![allow(clippy::pedantic)] + + include!(concat!(env!("OUT_DIR"), "/backdoor_bindings.rs")); +} + +#[derive(Debug, Clone, TryFromPrimitive)] +#[repr(u64)] +pub enum NativeBackdoorCommand { + StartVirt = bindings::LibaflExit_LIBAFL_EXIT_START_VIRT.0 as u64, // Shortcut for Save + InputVirt + StartPhys = bindings::LibaflExit_LIBAFL_EXIT_START_PHYS.0 as u64, // Shortcut for Save + InputPhys + InputVirt = bindings::LibaflExit_LIBAFL_EXIT_INPUT_VIRT.0 as u64, // The address is a virtual address using the paging currently running in the VM. + InputPhys = bindings::LibaflExit_LIBAFL_EXIT_INPUT_PHYS.0 as u64, // The address is a physical address + End = bindings::LibaflExit_LIBAFL_EXIT_END.0 as u64, // Implies reloading of the target. The first argument gives the exit status. + Save = bindings::LibaflExit_LIBAFL_EXIT_SAVE.0 as u64, // Save the VM + Load = bindings::LibaflExit_LIBAFL_EXIT_LOAD.0 as u64, // Reload the target without ending the run? + Version = bindings::LibaflExit_LIBAFL_EXIT_VERSION.0 as u64, // Version of the bindings used in the target + VaddrFilterAllowRange = bindings::LibaflExit_LIBAFL_EXIT_VADDR_FILTER_ALLOW.0 as u64, // Allow given address range +} + +#[derive(Debug, Clone, Enum, TryFromPrimitive)] +#[repr(u64)] +pub enum NativeExitKind { + Unknown = bindings::LibaflExitEndStatus_LIBAFL_EXIT_END_UNKNOWN.0 as u64, // Should not be used + Ok = bindings::LibaflExitEndStatus_LIBAFL_EXIT_END_OK.0 as u64, // Normal exit + Crash = bindings::LibaflExitEndStatus_LIBAFL_EXIT_END_CRASH.0 as u64, // Crash reported in the VM +} + +pub trait IsCommand +where + QT: QemuHelperTuple, + S: State + HasExecutions, + E: EmuExitHandler, +{ + /// Used to know whether the command can be run during a backdoor, or if it is necessary to go out of + /// the QEMU VM to run the command. + fn usable_at_runtime(&self) -> bool; + + /// Command handler. + /// - `input`: The input for the current emulator run. + /// - `ret_reg`: The register in which the guest return value should be written, if any. + /// Returns + /// - `InnerHandlerResult`: How the high-level handler should behave + fn run( + &self, + emu: &Emulator, + qemu_executor_state: &mut QemuExecutorState, + input: &BytesInput, + ret_reg: Option, + ) -> Result; +} + +#[cfg(emulation_mode = "systemmode")] +pub type PagingFilterCommand = FilterCommand; + +pub type AddressRangeFilterCommand = FilterCommand; + +#[derive(Debug, Clone)] +pub enum Command { + SaveCommand(SaveCommand), + LoadCommand(LoadCommand), + InputCommand(InputCommand), + StartCommand(StartCommand), + EndCommand(EndCommand), + VersionCommand(VersionCommand), + #[cfg(emulation_mode = "systemmode")] + PagingFilterCommand(PagingFilterCommand), + AddressRangeFilterCommand(AddressRangeFilterCommand), +} + +// TODO: Replace with enum_dispatch implementation +impl IsCommand> for Command +where + SM: IsSnapshotManager, + QT: QemuHelperTuple + StdInstrumentationFilter + Debug, + S: State + HasExecutions, +{ + fn usable_at_runtime(&self) -> bool { + match self { + Command::SaveCommand(cmd) => { + >>::usable_at_runtime(cmd) + } + Command::LoadCommand(cmd) => { + >>::usable_at_runtime(cmd) + } + Command::InputCommand(cmd) => { + >>::usable_at_runtime(cmd) + } + Command::StartCommand(cmd) => { + >>::usable_at_runtime(cmd) + } + Command::EndCommand(cmd) => { + >>::usable_at_runtime(cmd) + } + Command::VersionCommand(cmd) => { + >>::usable_at_runtime(cmd) + } + #[cfg(emulation_mode = "systemmode")] + Command::PagingFilterCommand(cmd) => { + >>::usable_at_runtime( + cmd, + ) + } + Command::AddressRangeFilterCommand(cmd) => , + >>::usable_at_runtime(cmd), + } + } + + fn run( + &self, + emu: &Emulator>, + qemu_executor_state: &mut QemuExecutorState, + input: &BytesInput, + ret_reg: Option, + ) -> Result { + match self { + Command::SaveCommand(cmd) => { + >>::run( + cmd, + emu, + qemu_executor_state, + input, + ret_reg, + ) + } + Command::LoadCommand(cmd) => { + >>::run( + cmd, + emu, + qemu_executor_state, + input, + ret_reg, + ) + } + Command::InputCommand(cmd) => , + >>::run( + cmd, emu, qemu_executor_state, input, ret_reg + ), + Command::StartCommand(cmd) => , + >>::run( + cmd, emu, qemu_executor_state, input, ret_reg + ), + Command::EndCommand(cmd) => { + >>::run( + cmd, + emu, + qemu_executor_state, + input, + ret_reg, + ) + } + Command::VersionCommand(cmd) => , + >>::run( + cmd, emu, qemu_executor_state, input, ret_reg + ), + #[cfg(emulation_mode = "systemmode")] + Command::PagingFilterCommand(cmd) => , + >>::run( + cmd, emu, qemu_executor_state, input, ret_reg + ), + Command::AddressRangeFilterCommand(cmd) => { + >>::run( + cmd, + emu, + qemu_executor_state, + input, + ret_reg, + ) + } + } + } +} + +#[derive(Debug, Clone)] +pub struct EmulatorMemoryChunk { + addr: GuestAddrKind, + size: GuestReg, + cpu: Option, +} + +#[derive(Debug, Clone)] +pub struct SaveCommand; + +impl IsCommand> for SaveCommand +where + SM: IsSnapshotManager, + QT: QemuHelperTuple + StdInstrumentationFilter + Debug, + S: State + HasExecutions, +{ + fn usable_at_runtime(&self) -> bool { + false + } + + fn run( + &self, + emu: &Emulator>, + #[cfg(emulation_mode = "systemmode")] qemu_executor_state: &mut QemuExecutorState, + #[cfg(not(emulation_mode = "systemmode"))] _qemu_executor_state: &mut QemuExecutorState< + QT, + S, + >, + _input: &BytesInput, + _ret_reg: Option, + ) -> Result { + let qemu = emu.qemu(); + let emu_exit_handler = emu.exit_handler().borrow_mut(); + + let snapshot_id = emu_exit_handler.snapshot_manager_borrow_mut().save(qemu); + emu_exit_handler + .set_snapshot_id(snapshot_id) + .map_err(|_| HandlerError::MultipleSnapshotDefinition)?; + + #[cfg(emulation_mode = "systemmode")] + { + let qemu_helpers = qemu_executor_state.hooks_mut().helpers_mut(); + + let mut allowed_paging_ids = HashSet::new(); + + let current_paging_id = qemu.current_cpu().unwrap().current_paging_id().unwrap(); + allowed_paging_ids.insert(current_paging_id); + + let paging_filter = + HasInstrumentationFilter::::filter_mut( + qemu_helpers, + ); + + *paging_filter = QemuInstrumentationPagingFilter::AllowList(allowed_paging_ids); + } + + Ok(InnerHandlerResult::Continue) + } +} + +#[derive(Debug, Clone)] +pub struct LoadCommand; + +impl IsCommand> for LoadCommand +where + SM: IsSnapshotManager, + QT: QemuHelperTuple + StdInstrumentationFilter + Debug, + S: State + HasExecutions, +{ + fn usable_at_runtime(&self) -> bool { + false + } + + fn run( + &self, + emu: &Emulator>, + _qemu_executor_state: &mut QemuExecutorState, + _input: &BytesInput, + _ret_reg: Option, + ) -> Result { + let qemu = emu.qemu(); + let emu_exit_handler = emu.exit_handler().borrow_mut(); + + let snapshot_id = emu_exit_handler + .snapshot_id() + .ok_or(HandlerError::SnapshotNotFound)?; + + emu_exit_handler + .snapshot_manager_borrow_mut() + .restore(&snapshot_id, qemu)?; + + Ok(InnerHandlerResult::Continue) + } +} + +#[derive(Debug, Clone)] +pub struct InputCommand { + location: EmulatorMemoryChunk, +} + +impl IsCommand> for InputCommand +where + SM: IsSnapshotManager, + QT: QemuHelperTuple + StdInstrumentationFilter + Debug, + S: State + HasExecutions, +{ + fn usable_at_runtime(&self) -> bool { + true + } + + fn run( + &self, + emu: &Emulator>, + _qemu_executor_state: &mut QemuExecutorState, + input: &BytesInput, + ret_reg: Option, + ) -> Result { + let qemu = emu.qemu(); + + let ret_value = self.location.write(qemu, input.bytes()); + + if let Some(reg) = ret_reg { + qemu.write_reg(reg, ret_value).unwrap(); + } + + Ok(InnerHandlerResult::Continue) + } +} + +#[derive(Debug, Clone)] +pub struct StartCommand { + input_location: EmulatorMemoryChunk, +} + +impl IsCommand> for StartCommand +where + SM: IsSnapshotManager, + QT: QemuHelperTuple + StdInstrumentationFilter + Debug, + S: State + HasExecutions, +{ + fn usable_at_runtime(&self) -> bool { + false + } + + fn run( + &self, + emu: &Emulator>, + _qemu_executor_state: &mut QemuExecutorState, + input: &BytesInput, + ret_reg: Option, + ) -> Result { + let emu_exit_handler = emu.exit_handler().borrow_mut(); + let qemu = emu.qemu(); + let snapshot_id = emu_exit_handler.snapshot_manager_borrow_mut().save(qemu); + + emu_exit_handler + .set_snapshot_id(snapshot_id) + .map_err(|_| HandlerError::MultipleSnapshotDefinition)?; + + emu_exit_handler + .set_input_location(self.input_location.clone(), ret_reg) + .unwrap(); + + let ret_value = self.input_location.write(qemu, input.bytes()); + + if let Some(reg) = ret_reg { + qemu.write_reg(reg, ret_value).unwrap(); + } + + Ok(InnerHandlerResult::Continue) + } +} + +#[derive(Debug, Clone)] +pub struct EndCommand(Option); + +impl IsCommand> for EndCommand +where + SM: IsSnapshotManager, + QT: QemuHelperTuple + StdInstrumentationFilter + Debug, + S: State + HasExecutions, +{ + fn usable_at_runtime(&self) -> bool { + false + } + + fn run( + &self, + emu: &Emulator>, + _qemu_executor_state: &mut QemuExecutorState, + _input: &BytesInput, + _ret_reg: Option, + ) -> Result { + let emu_exit_handler = emu.exit_handler().borrow_mut(); + + let snapshot_id = emu_exit_handler + .snapshot_id() + .ok_or(HandlerError::SnapshotNotFound)?; + + emu_exit_handler + .snapshot_manager_borrow_mut() + .restore(&snapshot_id, emu.qemu())?; + + Ok(InnerHandlerResult::EndOfRun(self.0.unwrap())) + } +} + +#[derive(Debug, Clone)] +pub struct VersionCommand(u64); + +impl IsCommand> for VersionCommand +where + SM: IsSnapshotManager, + QT: QemuHelperTuple + StdInstrumentationFilter + Debug, + S: State + HasExecutions, +{ + fn usable_at_runtime(&self) -> bool { + true + } + + fn run( + &self, + _emu: &Emulator>, + _qemu_executor_state: &mut QemuExecutorState, + _input: &BytesInput, + _ret_reg: Option, + ) -> Result { + let guest_version = self.0; + + if VERSION == guest_version { + Ok(InnerHandlerResult::Continue) + } else { + Err(HandlerError::SyncBackdoorError( + SyncBackdoorError::VersionDifference(guest_version), + )) + } + } +} + +#[derive(Debug, Clone)] +pub struct FilterCommand +where + T: IsFilter + Debug, +{ + filter: T, +} + +#[cfg(emulation_mode = "systemmode")] +impl IsCommand> for PagingFilterCommand +where + SM: IsSnapshotManager, + QT: QemuHelperTuple + StdInstrumentationFilter + Debug, + S: State + HasExecutions, +{ + fn usable_at_runtime(&self) -> bool { + true + } + + fn run( + &self, + _emu: &Emulator>, + qemu_executor_state: &mut QemuExecutorState, + _input: &BytesInput, + _ret_reg: Option, + ) -> Result { + let qemu_helpers = qemu_executor_state.hooks_mut().helpers_mut(); + + let paging_filter = + HasInstrumentationFilter::::filter_mut( + qemu_helpers, + ); + + *paging_filter = self.filter.clone(); + + Ok(InnerHandlerResult::Continue) + } +} + +impl IsCommand> for AddressRangeFilterCommand +where + SM: IsSnapshotManager, + QT: QemuHelperTuple + StdInstrumentationFilter + Debug, + S: State + HasExecutions, +{ + fn usable_at_runtime(&self) -> bool { + true + } + + #[allow(clippy::type_complexity)] // TODO: refactor with correct type. + fn run( + &self, + _emu: &Emulator>, + qemu_executor_state: &mut QemuExecutorState, + _input: &BytesInput, + _ret_reg: Option, + ) -> Result { + let qemu_helpers = qemu_executor_state.hooks_mut().helpers_mut(); + + let addr_range_filter = + HasInstrumentationFilter::::filter_mut( + qemu_helpers, + ); + + *addr_range_filter = self.filter.clone(); + + Ok(InnerHandlerResult::Continue) + } +} + +impl VersionCommand { + #[must_use] + pub fn new(version: u64) -> Self { + Self(version) + } +} + +impl FilterCommand +where + T: IsFilter + Debug, +{ + pub fn new(filter: T) -> Self { + Self { filter } + } +} + +// TODO: rewrite with display implementation for each command. +impl Display for Command { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + Command::SaveCommand(_) => write!(f, "Save VM"), + Command::LoadCommand(_) => write!(f, "Reload VM"), + Command::InputCommand(input_command) => { + write!(f, "Set fuzzing input @{}", input_command.location.addr) + } + Command::StartCommand(start_command) => { + write!( + f, + "Start fuzzing with input @{}", + start_command.input_location.addr + ) + } + Command::EndCommand(end_command) => write!(f, "Exit of kind {:?}", end_command.0), + Command::VersionCommand(version_command) => { + write!(f, "Client version: {}", version_command.0) + } + Command::AddressRangeFilterCommand(addr_range_filter) => { + write!(f, "Addr range filter: {:?}", addr_range_filter.filter,) + } + #[cfg(emulation_mode = "systemmode")] + Command::PagingFilterCommand(paging_filter) => { + write!(f, "Addr range filter: {:?}", paging_filter.filter,) + } + } + } +} + +impl StartCommand { + #[must_use] + pub fn new(input_location: EmulatorMemoryChunk) -> Self { + Self { input_location } + } +} + +impl EndCommand { + #[must_use] + pub fn new(exit_kind: Option) -> Self { + Self(exit_kind) + } +} + +impl InputCommand { + #[must_use] + pub fn new(location: EmulatorMemoryChunk) -> Self { + Self { location } + } +} + +impl EmulatorMemoryChunk { + #[must_use] + pub fn phys(addr: GuestPhysAddr, size: GuestReg, cpu: Option) -> Self { + Self { + addr: GuestAddrKind::Physical(addr), + size, + cpu, + } + } + + #[must_use] + pub fn virt(addr: GuestVirtAddr, size: GuestReg, cpu: CPU) -> Self { + Self { + addr: GuestAddrKind::Virtual(addr), + size, + cpu: Some(cpu), + } + } + + /// Returns the number of bytes effectively written. + #[must_use] + pub fn write(&self, qemu: &Qemu, input: &[u8]) -> GuestReg { + let max_len: usize = self.size.try_into().unwrap(); + + let input_sliced = if input.len() > max_len { + &input[0..max_len] + } else { + input + }; + + match self.addr { + GuestAddrKind::Physical(hwaddr) => unsafe { + #[cfg(emulation_mode = "usermode")] + { + // For now the default behaviour is to fall back to virtual addresses + qemu.write_mem(hwaddr.try_into().unwrap(), input_sliced); + } + #[cfg(emulation_mode = "systemmode")] + { + qemu.write_phys_mem(hwaddr, input_sliced); + } + }, + GuestAddrKind::Virtual(vaddr) => unsafe { + self.cpu + .as_ref() + .unwrap() + .write_mem(vaddr.try_into().unwrap(), input_sliced); + }, + }; + + input_sliced.len().try_into().unwrap() + } +} + +impl Display for InputCommand { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!( + f, + "{} (0x{:x} max nb bytes)", + self.location.addr, self.location.size + ) + } +} diff --git a/libafl_qemu/src/drcov.rs b/libafl_qemu/src/drcov.rs index 10f0fd1200..2f35eed599 100644 --- a/libafl_qemu/src/drcov.rs +++ b/libafl_qemu/src/drcov.rs @@ -4,18 +4,18 @@ use hashbrown::{hash_map::Entry, HashMap}; use libafl::{ executors::ExitKind, inputs::UsesInput, observers::ObserversTuple, state::HasMetadata, }; +use libafl_qemu_sys::{GuestAddr, GuestUsize}; use libafl_targets::drcov::{DrCovBasicBlock, DrCovWriter}; use rangemap::RangeMap; use serde::{Deserialize, Serialize}; use crate::{ - emu::{GuestAddr, GuestUsize}, helper::{ HasInstrumentationFilter, IsFilter, QemuHelper, QemuHelperTuple, QemuInstrumentationAddressRangeFilter, }, hooks::{Hook, QemuHooks}, - Emulator, + Qemu, }; static DRCOV_IDS: Mutex>> = Mutex::new(None); @@ -78,7 +78,11 @@ impl QemuDrCovHelper { } } -impl HasInstrumentationFilter for QemuDrCovHelper { +impl HasInstrumentationFilter + for QemuDrCovHelper +where + S: UsesInput, +{ fn filter(&self) -> &QemuInstrumentationAddressRangeFilter { &self.filter } @@ -103,11 +107,11 @@ where ); } - fn pre_exec(&mut self, _emulator: &Emulator, _input: &S::Input) {} + fn pre_exec(&mut self, _qemu: Qemu, _input: &S::Input) {} fn post_exec( &mut self, - _emulator: &Emulator, + _qemu: Qemu, _input: &S::Input, _observers: &mut OT, _exit_kind: &mut ExitKind, @@ -200,8 +204,7 @@ pub fn gen_unique_block_ids( pc: GuestAddr, ) -> Option where - S: HasMetadata, - S: UsesInput, + S: UsesInput + HasMetadata, QT: QemuHelperTuple, { let drcov_helper = hooks @@ -255,8 +258,7 @@ pub fn gen_block_lengths( pc: GuestAddr, block_length: GuestUsize, ) where - S: HasMetadata, - S: UsesInput, + S: UsesInput + HasMetadata, QT: QemuHelperTuple, { let drcov_helper = hooks @@ -276,9 +278,8 @@ pub fn gen_block_lengths( pub fn exec_trace_block(hooks: &mut QemuHooks, _state: Option<&mut S>, id: u64) where - S: HasMetadata, - S: UsesInput, QT: QemuHelperTuple, + S: UsesInput + HasMetadata, { if hooks .helpers() diff --git a/libafl_qemu/src/edges.rs b/libafl_qemu/src/edges.rs index 9664ddd348..9e612cfc27 100644 --- a/libafl_qemu/src/edges.rs +++ b/libafl_qemu/src/edges.rs @@ -2,14 +2,18 @@ use std::{cell::UnsafeCell, cmp::max}; use hashbrown::{hash_map::Entry, HashMap}; use libafl::{inputs::UsesInput, state::HasMetadata}; +use libafl_qemu_sys::GuestAddr; +#[cfg(emulation_mode = "systemmode")] +use libafl_qemu_sys::GuestPhysAddr; pub use libafl_targets::{ edges_map_mut_ptr, edges_map_mut_slice, edges_max_num, std_edges_map_observer, EDGES_MAP, EDGES_MAP_PTR, EDGES_MAP_PTR_NUM, EDGES_MAP_SIZE, MAX_EDGES_NUM, }; use serde::{Deserialize, Serialize}; +#[cfg(emulation_mode = "systemmode")] +use crate::helper::QemuInstrumentationPagingFilter; use crate::{ - emu::GuestAddr, helper::{ hash_me, HasInstrumentationFilter, QemuHelper, QemuHelperTuple, QemuInstrumentationAddressRangeFilter, @@ -17,8 +21,6 @@ use crate::{ hooks::{Hook, QemuHooks}, IsFilter, }; -#[cfg(emulation_mode = "systemmode")] -use crate::{helper::QemuInstrumentationPagingFilter, GuestPhysAddr}; #[cfg_attr( any(not(feature = "serdeany_autoreg"), miri), @@ -130,7 +132,9 @@ impl Default for QemuEdgeCoverageHelper { } } -impl HasInstrumentationFilter for QemuEdgeCoverageHelper { +impl HasInstrumentationFilter + for QemuEdgeCoverageHelper +{ fn filter(&self) -> &QemuInstrumentationAddressRangeFilter { &self.address_filter } @@ -141,7 +145,9 @@ impl HasInstrumentationFilter for QemuEdg } #[cfg(emulation_mode = "systemmode")] -impl HasInstrumentationFilter for QemuEdgeCoverageHelper { +impl HasInstrumentationFilter + for QemuEdgeCoverageHelper +{ fn filter(&self) -> &QemuInstrumentationPagingFilter { &self.paging_filter } @@ -277,7 +283,7 @@ impl Default for QemuEdgeCoverageChildHelper { } } -impl HasInstrumentationFilter +impl HasInstrumentationFilter for QemuEdgeCoverageChildHelper { fn filter(&self) -> &QemuInstrumentationAddressRangeFilter { @@ -290,7 +296,9 @@ impl HasInstrumentationFilter } #[cfg(emulation_mode = "systemmode")] -impl HasInstrumentationFilter for QemuEdgeCoverageChildHelper { +impl HasInstrumentationFilter + for QemuEdgeCoverageChildHelper +{ fn filter(&self) -> &QemuInstrumentationPagingFilter { &self.paging_filter } @@ -302,8 +310,7 @@ impl HasInstrumentationFilter for QemuEdgeCover impl QemuHelper for QemuEdgeCoverageChildHelper where - S: UsesInput, - S: HasMetadata, + S: UsesInput + HasMetadata, { const HOOKS_DO_SIDE_EFFECTS: bool = false; @@ -330,6 +337,7 @@ where pub struct QemuEdgeCoverageClassicHelper { address_filter: QemuInstrumentationAddressRangeFilter, use_hitcounts: bool, + use_jit: bool, } #[cfg(emulation_mode = "systemmode")] @@ -338,23 +346,29 @@ pub struct QemuEdgeCoverageClassicHelper { address_filter: QemuInstrumentationAddressRangeFilter, paging_filter: QemuInstrumentationPagingFilter, use_hitcounts: bool, + use_jit: bool, } #[cfg(emulation_mode = "usermode")] impl QemuEdgeCoverageClassicHelper { #[must_use] - pub fn new(address_filter: QemuInstrumentationAddressRangeFilter) -> Self { + pub fn new(address_filter: QemuInstrumentationAddressRangeFilter, use_jit: bool) -> Self { Self { address_filter, use_hitcounts: true, + use_jit, } } #[must_use] - pub fn without_hitcounts(address_filter: QemuInstrumentationAddressRangeFilter) -> Self { + pub fn without_hitcounts( + address_filter: QemuInstrumentationAddressRangeFilter, + use_jit: bool, + ) -> Self { Self { address_filter, use_hitcounts: false, + use_jit, } } @@ -370,11 +384,13 @@ impl QemuEdgeCoverageClassicHelper { pub fn new( address_filter: QemuInstrumentationAddressRangeFilter, paging_filter: QemuInstrumentationPagingFilter, + use_jit: bool, ) -> Self { Self { address_filter, paging_filter, use_hitcounts: true, + use_jit, } } @@ -382,11 +398,13 @@ impl QemuEdgeCoverageClassicHelper { pub fn without_hitcounts( address_filter: QemuInstrumentationAddressRangeFilter, paging_filter: QemuInstrumentationPagingFilter, + use_jit: bool, ) -> Self { Self { address_filter, paging_filter, use_hitcounts: false, + use_jit, } } @@ -399,7 +417,7 @@ impl QemuEdgeCoverageClassicHelper { #[cfg(emulation_mode = "usermode")] impl Default for QemuEdgeCoverageClassicHelper { fn default() -> Self { - Self::new(QemuInstrumentationAddressRangeFilter::None) + Self::new(QemuInstrumentationAddressRangeFilter::None, false) } } @@ -409,11 +427,12 @@ impl Default for QemuEdgeCoverageClassicHelper { Self::new( QemuInstrumentationAddressRangeFilter::None, QemuInstrumentationPagingFilter::None, + false, ) } } -impl HasInstrumentationFilter +impl HasInstrumentationFilter for QemuEdgeCoverageClassicHelper { fn filter(&self) -> &QemuInstrumentationAddressRangeFilter { @@ -426,7 +445,9 @@ impl HasInstrumentationFilter } #[cfg(emulation_mode = "systemmode")] -impl HasInstrumentationFilter for QemuEdgeCoverageClassicHelper { +impl HasInstrumentationFilter + for QemuEdgeCoverageClassicHelper +{ fn filter(&self) -> &QemuInstrumentationPagingFilter { &self.paging_filter } @@ -436,10 +457,10 @@ impl HasInstrumentationFilter for QemuEdgeCover } } +#[allow(clippy::collapsible_else_if)] impl QemuHelper for QemuEdgeCoverageClassicHelper where - S: UsesInput, - S: HasMetadata, + S: UsesInput + HasMetadata, { const HOOKS_DO_SIDE_EFFECTS: bool = false; @@ -448,17 +469,47 @@ where QT: QemuHelperTuple, { if self.use_hitcounts { - hooks.blocks( - Hook::Function(gen_hashed_block_ids::), - Hook::Empty, - Hook::Raw(trace_block_transition_hitcount), - ); + if self.use_jit { + let hook_id = hooks.blocks( + Hook::Function(gen_hashed_block_ids::), + Hook::Empty, + Hook::Empty, + ); + + unsafe { + libafl_qemu_sys::libafl_qemu_block_hook_set_jit( + hook_id.0, + Some(libafl_qemu_sys::libafl_jit_trace_block_hitcount), + ); + } + } else { + hooks.blocks( + Hook::Function(gen_hashed_block_ids::), + Hook::Empty, + Hook::Raw(trace_block_transition_hitcount), + ); + } } else { - hooks.blocks( - Hook::Function(gen_hashed_block_ids::), - Hook::Empty, - Hook::Raw(trace_block_transition_single), - ); + if self.use_jit { + let hook_id = hooks.blocks( + Hook::Function(gen_hashed_block_ids::), + Hook::Empty, + Hook::Empty, + ); + + unsafe { + libafl_qemu_sys::libafl_qemu_block_hook_set_jit( + hook_id.0, + Some(libafl_qemu_sys::libafl_jit_trace_block_single), + ); + } + } else { + hooks.blocks( + Hook::Function(gen_hashed_block_ids::), + Hook::Empty, + Hook::Raw(trace_block_transition_single), + ); + } } } } @@ -472,8 +523,7 @@ pub fn gen_unique_edge_ids( dest: GuestAddr, ) -> Option where - S: HasMetadata, - S: UsesInput, + S: UsesInput + HasMetadata, QT: QemuHelperTuple, { if let Some(h) = hooks.helpers().match_first_type::() { @@ -487,9 +537,9 @@ where #[cfg(emulation_mode = "systemmode")] { let paging_id = hooks - .emulator() + .qemu() .current_cpu() - .map(|cpu| cpu.get_current_paging_id()) + .map(|cpu| cpu.current_paging_id()) .flatten(); if !h.must_instrument(src, paging_id) && !h.must_instrument(dest, paging_id) { @@ -557,9 +607,9 @@ where #[cfg(emulation_mode = "systemmode")] { let paging_id = hooks - .emulator() + .qemu() .current_cpu() - .map(|cpu| cpu.get_current_paging_id()) + .map(|cpu| cpu.current_paging_id()) .flatten(); if !h.must_instrument(src, paging_id) && !h.must_instrument(dest, paging_id) { @@ -624,9 +674,9 @@ where #[cfg(emulation_mode = "systemmode")] { let paging_id = hooks - .emulator() + .qemu() .current_cpu() - .map(|cpu| cpu.get_current_paging_id()) + .map(|cpu| cpu.current_paging_id()) .flatten(); if !h.must_instrument(pc, paging_id) { diff --git a/libafl_qemu/src/elf.rs b/libafl_qemu/src/elf.rs index a69628103b..40f0726941 100644 --- a/libafl_qemu/src/elf.rs +++ b/libafl_qemu/src/elf.rs @@ -4,8 +4,7 @@ use std::{fs::File, io::Read, ops::Range, path::Path, str}; use goblin::elf::{header::ET_DYN, Elf}; use libafl::Error; - -use crate::GuestAddr; +use libafl_qemu_sys::GuestAddr; pub struct EasyElf<'a> { elf: Elf<'a>, diff --git a/libafl_qemu/src/emu.rs b/libafl_qemu/src/emu.rs index d589d53b96..2992ec4618 100644 --- a/libafl_qemu/src/emu.rs +++ b/libafl_qemu/src/emu.rs @@ -1,103 +1,80 @@ //! Expose QEMU user `LibAFL` C api to Rust use core::{ - ffi::c_void, fmt, + marker::PhantomData, mem::{transmute, MaybeUninit}, ptr::{addr_of, copy_nonoverlapping, null}, }; -#[cfg(emulation_mode = "usermode")] -use std::cell::OnceCell; +use std::{ + cell::{OnceCell, Ref, RefCell, RefMut}, + collections::HashSet, + ffi::CString, + fmt::{Debug, Display, Formatter}, + ptr, +}; + +use libafl::{executors::ExitKind, inputs::BytesInput}; #[cfg(emulation_mode = "systemmode")] -use std::{ffi::CStr, ptr::null_mut}; -use std::{ffi::CString, ptr, slice::from_raw_parts, str::from_utf8_unchecked}; +use libafl_qemu_sys::qemu_init; +#[cfg(emulation_mode = "usermode")] +use libafl_qemu_sys::{guest_base, qemu_user_init, VerifyAccess}; +use libafl_qemu_sys::{ + libafl_flush_jit, libafl_get_exit_reason, libafl_page_from_addr, libafl_qemu_add_gdb_cmd, + libafl_qemu_cpu_index, libafl_qemu_current_cpu, libafl_qemu_gdb_reply, libafl_qemu_get_cpu, + libafl_qemu_num_cpus, libafl_qemu_num_regs, libafl_qemu_read_reg, + libafl_qemu_remove_breakpoint, libafl_qemu_set_breakpoint, libafl_qemu_trigger_breakpoint, + libafl_qemu_write_reg, CPUStatePtr, FatPtr, GuestUsize, +}; +pub use libafl_qemu_sys::{GuestAddr, GuestPhysAddr, GuestVirtAddr}; +#[cfg(emulation_mode = "usermode")] +pub use libafl_qemu_sys::{MapInfo, MmapPerms, MmapPermsIter}; +use num_traits::Num; +use strum::IntoEnumIterator; + +use crate::{command::IsCommand, GuestReg, QemuHelperTuple, Regs, StdInstrumentationFilter}; + +#[cfg(emulation_mode = "systemmode")] +pub mod systemmode; +#[cfg(emulation_mode = "systemmode")] +pub use systemmode::*; #[cfg(emulation_mode = "usermode")] -use libc::c_int; -use num_enum::{IntoPrimitive, TryFromPrimitive}; -use num_traits::Num; -use paste::paste; -use strum::IntoEnumIterator; -use strum_macros::EnumIter; +pub mod usermode; +#[cfg(emulation_mode = "usermode")] +pub use usermode::*; -use crate::{GuestReg, Regs}; - -/// Safe linking with of extern "C" functions. -/// This macro makes sure the declared symbol is defined *at link time*, avoiding declaring non-existant symbols -/// that could be silently ignored during linking if unused. -/// -/// This macro relies on a nightly feature, and can only be used in this mode -/// It is (nearly) a drop-in replacement for extern "C" { } blocks containing function and static declarations, and will have the same effect in practice. -macro_rules! extern_c_checked { - () => {}; - - ($visibility:vis fn $c_fn:ident($($param_ident:ident : $param_ty:ty),*) $( -> $ret_ty:ty )?; $($tail:tt)*) => { - paste! { - #[cfg_attr(nightly, used(linker))] - static [<__ $c_fn:upper __>]: unsafe extern "C" fn($($param_ty),*) $( -> $ret_ty )? = $c_fn; - } - - extern "C" { - $visibility fn $c_fn($($param_ident : $param_ty),*) $( -> $ret_ty )?; - } - - extern_c_checked!($($tail)*); - }; - - ($visibility:vis static $c_var:ident : $c_var_ty:ty; $($tail:tt)*) => { - paste! { - #[allow(non_camel_case_types)] - #[allow(unused)] - struct [<__ $c_var:upper _STRUCT__>] { member: *const $c_var_ty } - - unsafe impl Sync for [<__ $c_var:upper _STRUCT__>] {} - - #[cfg_attr(nightly, used(linker))] - static [<__ $c_var:upper __>]: [<__ $c_var:upper _STRUCT__>] = unsafe { [<__ $c_var:upper _STRUCT__>] { member: core::ptr::addr_of!($c_var) } }; - } - - extern "C" { - $visibility static $c_var: $c_var_ty; - } - - extern_c_checked!($($tail)*); - }; - - ($visibility:vis static mut $c_var:ident : $c_var_ty:ty; $($tail:tt)*) => { - paste! { - #[allow(non_camel_case_types)] - #[allow(unused)] - struct [<__ $c_var:upper _STRUCT__>] { member: *const $c_var_ty } - - unsafe impl Sync for [<__ $c_var:upper _STRUCT__>] {} - - #[cfg_attr(nightly, used(linker))] - static mut [<__ $c_var:upper __>]: [<__ $c_var:upper _STRUCT__>] = unsafe { [<__ $c_var:upper _STRUCT__>] { member: core::ptr::addr_of!($c_var) } }; - } - - extern "C" { - $visibility static mut $c_var: $c_var_ty; - } - - extern_c_checked!($($tail)*); - }; -} - -pub type GuestAddr = libafl_qemu_sys::target_ulong; -pub type GuestUsize = libafl_qemu_sys::target_ulong; -pub type GuestIsize = libafl_qemu_sys::target_long; -pub type GuestVirtAddr = libafl_qemu_sys::vaddr; -pub type GuestPhysAddr = libafl_qemu_sys::hwaddr; - -pub type GuestHwAddrInfo = libafl_qemu_sys::qemu_plugin_hwaddr; - -#[derive(Debug, Clone)] +#[derive(Clone)] pub enum GuestAddrKind { Physical(GuestPhysAddr), Virtual(GuestVirtAddr), } -impl fmt::Display for GuestAddrKind { +impl Debug for GuestAddrKind { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + GuestAddrKind::Physical(paddr) => write!(f, "vaddr {paddr:x}"), + GuestAddrKind::Virtual(vaddr) => write!(f, "paddr {vaddr:x}"), + } + } +} + +#[derive(Debug, Clone)] +pub enum QemuShutdownCause { + None, + HostError, + HostQmpQuit, + HostQmpSystemReset, + HostSignal(Signal), + HostUi, + GuestShutdown, + GuestReset, + GuestPanic, + SubsystemReset, + SnapshotLoad, +} + +impl Display for GuestAddrKind { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { GuestAddrKind::Physical(phys_addr) => write!(f, "hwaddr 0x{phys_addr:x}"), @@ -106,40 +83,214 @@ impl fmt::Display for GuestAddrKind { } } -#[cfg(emulation_mode = "systemmode")] -pub type FastSnapshot = *mut libafl_qemu_sys::SyxSnapshot; - -#[cfg(emulation_mode = "systemmode")] -pub enum DeviceSnapshotFilter { - All, - AllowList(Vec), - DenyList(Vec), +#[derive(Debug, Clone)] +pub enum HandlerError { + QemuExitReasonError(EmuExitReasonError), + SMError(SnapshotManagerError), + SyncBackdoorError(SyncBackdoorError), + MultipleSnapshotDefinition, + MultipleInputDefinition, + SnapshotNotFound, } -#[cfg(emulation_mode = "systemmode")] -impl DeviceSnapshotFilter { - fn enum_id(&self) -> libafl_qemu_sys::DeviceSnapshotKind { - match self { - DeviceSnapshotFilter::All => libafl_qemu_sys::DeviceSnapshotKind_DEVICE_SNAPSHOT_ALL, - DeviceSnapshotFilter::AllowList(_) => { - libafl_qemu_sys::DeviceSnapshotKind_DEVICE_SNAPSHOT_ALLOWLIST - } - DeviceSnapshotFilter::DenyList(_) => { - libafl_qemu_sys::DeviceSnapshotKind_DEVICE_SNAPSHOT_DENYLIST - } +impl From for HandlerError { + fn from(sm_error: SnapshotManagerError) -> Self { + HandlerError::SMError(sm_error) + } +} + +#[derive(Debug, Clone)] +pub enum SnapshotManagerError { + SnapshotIdNotFound(SnapshotId), + MemoryInconsistencies(u64), +} + +#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)] +pub struct SnapshotId { + id: u64, +} + +pub trait IsSnapshotManager: Debug + Clone { + fn save(&mut self, qemu: &Qemu) -> SnapshotId; + fn restore( + &mut self, + snapshot_id: &SnapshotId, + qemu: &Qemu, + ) -> Result<(), SnapshotManagerError>; +} + +// TODO: Rework with generics for command handlers? +pub trait EmuExitHandler: Sized + Debug + Clone +where + QT: QemuHelperTuple, + S: State + HasExecutions, +{ + fn try_put_input( + emu: &Emulator, + qemu_executor_state: &mut QemuExecutorState, + input: &BytesInput, + ); + + fn handle( + emu: &Emulator, + exit_reason: Result, + qemu_executor_state: &mut QemuExecutorState, + input: &BytesInput, + ) -> Result; +} + +pub enum InnerHandlerResult { + EndOfRun(ExitKind), // The run is over and the emulator is ready for the next iteration. + ReturnToHarness(EmuExitReason), // Return to the harness immediately. Can happen at any point of the run when the handler is not supposed to handle a request. + Continue, // Resume QEMU and continue to run the handler. + Interrupt, // QEMU has been interrupted by user. +} + +/// Special kind of Exit handler with no data embedded. +/// As a result, it is safe to transmute from any `Emulator` implementing `EmuExitHandler` to this one, +/// since it won't use any data which could cause type confusion. +#[derive(Clone, Debug)] +pub struct NopEmuExitHandler; + +impl EmuExitHandler for NopEmuExitHandler +where + QT: QemuHelperTuple, + S: State + HasExecutions, +{ + fn try_put_input(_: &Emulator, _: &mut QemuExecutorState, _: &BytesInput) {} + + fn handle( + _: &Emulator, + exit_reason: Result, + _: &mut QemuExecutorState, + _: &BytesInput, + ) -> Result { + match exit_reason { + Ok(reason) => Ok(InnerHandlerResult::ReturnToHarness(reason)), + Err(error) => Err(error)?, + } + } +} + +/// Synchronous Exit handler maintaining only one snapshot. +#[derive(Debug, Clone)] +pub struct StdEmuExitHandler +where + SM: IsSnapshotManager + Clone, +{ + snapshot_manager: RefCell, + snapshot_id: OnceCell, + input_location: OnceCell<(EmulatorMemoryChunk, Option)>, +} + +impl StdEmuExitHandler +where + SM: IsSnapshotManager, +{ + pub fn new(snapshot_manager: SM) -> Self { + Self { + snapshot_manager: RefCell::new(snapshot_manager), + snapshot_id: OnceCell::new(), + input_location: OnceCell::new(), } } - fn devices(&self, v: &mut Vec<*mut i8>) -> *mut *mut i8 { - v.clear(); - match self { - DeviceSnapshotFilter::All => null_mut(), - DeviceSnapshotFilter::AllowList(l) | DeviceSnapshotFilter::DenyList(l) => { - for name in l { - v.push(name.as_bytes().as_ptr() as *mut i8); + pub fn set_input_location( + &self, + input_location: EmulatorMemoryChunk, + ret_reg: Option, + ) -> Result<(), (EmulatorMemoryChunk, Option)> { + self.input_location.set((input_location, ret_reg)) + } + + pub fn set_snapshot_id(&self, snapshot_id: SnapshotId) -> Result<(), SnapshotId> { + self.snapshot_id.set(snapshot_id) + } + + pub fn snapshot_id(&self) -> Option { + Some(*self.snapshot_id.get()?) + } + + pub fn snapshot_manager_borrow(&self) -> Ref { + self.snapshot_manager.borrow() + } + + pub fn snapshot_manager_borrow_mut(&self) -> RefMut { + self.snapshot_manager.borrow_mut() + } +} + +// TODO: replace handlers with generics to permit compile-time customization of handlers +impl EmuExitHandler for StdEmuExitHandler +where + SM: IsSnapshotManager, + QT: QemuHelperTuple + StdInstrumentationFilter + Debug, + S: State + HasExecutions, +{ + fn try_put_input( + emu: &Emulator, + qemu_executor_state: &mut QemuExecutorState, + input: &BytesInput, + ) { + let exit_handler = emu.state().exit_handler.borrow(); + + if let Some((input_location, ret_register)) = exit_handler.input_location.get() { + let input_command = InputCommand::new(input_location.clone()); + input_command + .run(emu, qemu_executor_state, input, *ret_register) + .unwrap(); + } + } + + fn handle( + emu: &Emulator, + exit_reason: Result, + qemu_executor_state: &mut QemuExecutorState, + input: &BytesInput, + ) -> Result { + let exit_handler = emu.exit_handler().borrow_mut(); + let qemu = emu.qemu(); + + let mut exit_reason = match exit_reason { + Ok(exit_reason) => exit_reason, + Err(exit_error) => match exit_error { + EmuExitReasonError::UnexpectedExit => { + if let Some(snapshot_id) = exit_handler.snapshot_id.get() { + exit_handler + .snapshot_manager + .borrow_mut() + .restore(snapshot_id, qemu)?; + } + return Ok(InnerHandlerResult::EndOfRun(ExitKind::Crash)); } - v.as_mut_ptr() + _ => Err(exit_error)?, + }, + }; + + let (command, ret_reg): (Option, Option) = match &mut exit_reason { + EmuExitReason::End(shutdown_cause) => match shutdown_cause { + QemuShutdownCause::HostSignal(Signal::SigInterrupt) => { + return Ok(InnerHandlerResult::Interrupt) + } + QemuShutdownCause::GuestPanic => { + return Ok(InnerHandlerResult::EndOfRun(ExitKind::Crash)) + } + _ => panic!("Unhandled QEMU shutdown cause: {shutdown_cause:?}."), + }, + EmuExitReason::Breakpoint(bp) => (bp.trigger(qemu).cloned(), None), + EmuExitReason::SyncBackdoor(sync_backdoor) => { + let command = sync_backdoor.command().clone(); + (Some(command), Some(sync_backdoor.ret_reg())) } + }; + + // manually drop ref cell here to avoid keeping it alive in cmd. + drop(exit_handler); + + if let Some(cmd) = command { + cmd.run(emu, qemu_executor_state, input, ret_reg) + } else { + Ok(InnerHandlerResult::ReturnToHarness(exit_reason)) } } } @@ -209,75 +360,6 @@ pub use libafl_qemu_sys::{CPUArchState, CPUState}; use crate::sync_backdoor::{SyncBackdoor, SyncBackdoorError}; -pub type CPUStatePtr = *mut libafl_qemu_sys::CPUState; -pub type CPUArchStatePtr = *mut libafl_qemu_sys::CPUArchState; - -pub type ExitReasonPtr = *mut libafl_qemu_sys::libafl_exit_reason; - -#[derive(IntoPrimitive, TryFromPrimitive, Debug, Clone, Copy, EnumIter, PartialEq, Eq)] -#[repr(i32)] -pub enum MmapPerms { - None = 0, - Read = libc::PROT_READ, - Write = libc::PROT_WRITE, - Execute = libc::PROT_EXEC, - ReadWrite = libc::PROT_READ | libc::PROT_WRITE, - ReadExecute = libc::PROT_READ | libc::PROT_EXEC, - WriteExecute = libc::PROT_WRITE | libc::PROT_EXEC, - ReadWriteExecute = libc::PROT_READ | libc::PROT_WRITE | libc::PROT_EXEC, -} - -impl MmapPerms { - #[must_use] - pub fn is_r(&self) -> bool { - matches!( - self, - MmapPerms::Read - | MmapPerms::ReadWrite - | MmapPerms::ReadExecute - | MmapPerms::ReadWriteExecute - ) - } - - #[must_use] - pub fn is_w(&self) -> bool { - matches!( - self, - MmapPerms::Write - | MmapPerms::ReadWrite - | MmapPerms::WriteExecute - | MmapPerms::ReadWriteExecute - ) - } - - #[must_use] - pub fn is_x(&self) -> bool { - matches!( - self, - MmapPerms::Execute - | MmapPerms::ReadExecute - | MmapPerms::WriteExecute - | MmapPerms::ReadWriteExecute - ) - } -} - -#[cfg(feature = "python")] -impl IntoPy for MmapPerms { - fn into_py(self, py: Python) -> PyObject { - let n: i32 = self.into(); - n.into_py(py) - } -} - -#[cfg(emulation_mode = "usermode")] -#[derive(IntoPrimitive, TryFromPrimitive, Debug, Clone, Copy, EnumIter, PartialEq, Eq)] -#[repr(i32)] -pub enum VerifyAccess { - Read = libc::PROT_READ, - Write = libc::PROT_READ | libc::PROT_WRITE, -} - // syshook_ret #[repr(C)] #[cfg_attr(feature = "python", pyclass)] @@ -323,218 +405,19 @@ impl SyscallHookResult { } } -#[repr(C)] -#[cfg_attr(feature = "python", pyclass(unsendable))] -pub struct MapInfo { - start: GuestAddr, - end: GuestAddr, - offset: GuestAddr, - path: *const u8, - flags: i32, - is_priv: i32, -} - -#[cfg_attr(feature = "python", pymethods)] -impl MapInfo { - #[must_use] - pub fn start(&self) -> GuestAddr { - self.start - } - - #[must_use] - pub fn end(&self) -> GuestAddr { - self.end - } - - #[must_use] - pub fn offset(&self) -> GuestAddr { - self.offset - } - - #[must_use] - pub fn path(&self) -> Option<&str> { - if self.path.is_null() { - None - } else { - unsafe { - Some(from_utf8_unchecked(from_raw_parts( - self.path, - strlen(self.path), - ))) - } - } - } - - #[must_use] - pub fn flags(&self) -> MmapPerms { - MmapPerms::try_from(self.flags).unwrap() - } - - #[must_use] - pub fn is_priv(&self) -> bool { - self.is_priv != 0 - } -} - -#[cfg(emulation_mode = "usermode")] -extern_c_checked! { - fn qemu_user_init(argc: i32, argv: *const *const u8, envp: *const *const u8) -> i32; - - fn libafl_qemu_run() -> i32; - - fn libafl_load_addr() -> u64; - fn libafl_get_brk() -> u64; - fn libafl_set_brk(brk: u64) -> u64; - - fn read_self_maps() -> *const c_void; - fn free_self_maps(map_info: *const c_void); - - fn libafl_maps_next(map_info: *const c_void, ret: *mut MapInfo) -> *const c_void; - - static exec_path: *const u8; - static guest_base: usize; - static mut mmap_next_start: GuestAddr; - - static mut libafl_dump_core_hook: unsafe extern "C" fn(i32); - static mut libafl_force_dfl: i32; -} - -#[cfg(emulation_mode = "systemmode")] -extern_c_checked! { - fn qemu_init(argc: i32, argv: *const *const u8, envp: *const *const u8); - - fn vm_start(); - fn qemu_main_loop(); - fn qemu_cleanup(); - - fn libafl_save_qemu_snapshot(name: *const u8, sync: bool); - fn libafl_load_qemu_snapshot(name: *const u8, sync: bool); - - fn libafl_qemu_current_paging_id(cpu: CPUStatePtr) -> GuestPhysAddr; -} - -#[cfg(emulation_mode = "systemmode")] -extern "C" fn qemu_cleanup_atexit() { - unsafe { - qemu_cleanup(); - } -} - -// TODO rely completely on libafl_qemu_sys -extern_c_checked! { - //static libafl_page_size: GuestUsize; - fn libafl_page_from_addr(addr: GuestAddr) -> GuestAddr; - - // CPUState* libafl_qemu_get_cpu(int cpu_index); - fn libafl_qemu_get_cpu(cpu_index: i32) -> CPUStatePtr; - // int libafl_qemu_num_cpus(void); - fn libafl_qemu_num_cpus() -> i32; - // CPUState* libafl_qemu_current_cpu(void); - fn libafl_qemu_current_cpu() -> CPUStatePtr; - - // struct libafl_exit_reason* libafl_get_exit_reason(void); - fn libafl_get_exit_reason() -> ExitReasonPtr; - - fn libafl_qemu_cpu_index(cpu: CPUStatePtr) -> i32; - - fn libafl_qemu_write_reg(cpu: CPUStatePtr, reg: i32, val: *const u8) -> i32; - fn libafl_qemu_read_reg(cpu: CPUStatePtr, reg: i32, val: *mut u8) -> i32; - fn libafl_qemu_num_regs(cpu: CPUStatePtr) -> i32; - - fn libafl_qemu_set_breakpoint(addr: u64) -> i32; - fn libafl_qemu_remove_breakpoint(addr: u64) -> i32; - fn libafl_flush_jit(); - fn libafl_qemu_trigger_breakpoint(cpu: CPUStatePtr); - - fn strlen(s: *const u8) -> usize; - - fn libafl_qemu_add_gdb_cmd( - callback: extern "C" fn(*const (), *const u8, usize) -> i32, - data: *const () - ); - fn libafl_qemu_gdb_reply(buf: *const u8, len: usize); -} - -#[cfg(emulation_mode = "usermode")] -#[cfg_attr(feature = "python", pyclass(unsendable))] -pub struct GuestMaps { - orig_c_iter: *const c_void, - c_iter: *const c_void, -} - -// Consider a private new only for Emulator -#[cfg(emulation_mode = "usermode")] -impl GuestMaps { - #[must_use] - pub(crate) fn new() -> Self { - unsafe { - let maps = read_self_maps(); - Self { - orig_c_iter: maps, - c_iter: maps, - } - } - } -} - -#[cfg(emulation_mode = "usermode")] -impl Iterator for GuestMaps { - type Item = MapInfo; - - #[allow(clippy::uninit_assumed_init)] - fn next(&mut self) -> Option { - if self.c_iter.is_null() { - return None; - } - unsafe { - let mut ret = MaybeUninit::uninit(); - self.c_iter = libafl_maps_next(self.c_iter, ret.as_mut_ptr()); - if self.c_iter.is_null() { - None - } else { - Some(ret.assume_init()) - } - } - } -} - -#[cfg(all(emulation_mode = "usermode", feature = "python"))] -#[pymethods] -impl GuestMaps { - fn __iter__(slf: PyRef) -> PyRef { - slf - } - fn __next__(mut slf: PyRefMut) -> Option { - Python::with_gil(|py| slf.next().map(|x| x.into_py(py))) - } -} - -#[cfg(emulation_mode = "usermode")] -impl Drop for GuestMaps { - fn drop(&mut self) { - unsafe { - free_self_maps(self.orig_c_iter); - } - } -} - -#[repr(C)] -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub(crate) struct FatPtr(pub *const c_void, pub *const c_void); - #[allow(clippy::vec_box)] static mut GDB_COMMANDS: Vec> = vec![]; extern "C" fn gdb_cmd(data: *const (), buf: *const u8, len: usize) -> i32 { unsafe { - let closure = &mut *(data as *mut Box FnMut(&Emulator, &'r str) -> bool>); + let closure = &mut *(data as *mut Box FnMut(&Qemu, &'r str) -> bool>); let cmd = std::str::from_utf8_unchecked(std::slice::from_raw_parts(buf, len)); - let emu = Emulator::new_empty(); - i32::from(closure(&emu, cmd)) + let qemu = Qemu::get_unchecked(); + i32::from(closure(&qemu, cmd)) } } -#[derive(Debug)] +#[derive(Debug, Clone)] #[repr(transparent)] pub struct CPU { ptr: CPUStatePtr, @@ -568,8 +451,8 @@ pub trait ArchExtras { #[allow(clippy::unused_self)] impl CPU { #[must_use] - pub fn emulator(&self) -> Emulator { - unsafe { Emulator::new_empty() } + pub fn qemu(&self) -> Qemu { + unsafe { Qemu::get_unchecked() } } #[must_use] @@ -605,111 +488,8 @@ impl CPU { } } - #[cfg(emulation_mode = "systemmode")] - #[must_use] - pub fn get_phys_addr(&self, vaddr: GuestAddr) -> Option { - unsafe { - let page = libafl_page_from_addr(vaddr); - let mut attrs = MaybeUninit::::uninit(); - let paddr = libafl_qemu_sys::cpu_get_phys_page_attrs_debug( - self.ptr, - page as GuestVirtAddr, - attrs.as_mut_ptr(), - ); - if paddr == (-1i64 as GuestPhysAddr) { - None - } else { - Some(paddr) - } - } - } - - #[cfg(emulation_mode = "systemmode")] - #[must_use] - pub fn get_phys_addr_tlb( - &self, - vaddr: GuestAddr, - info: MemAccessInfo, - is_store: bool, - ) -> Option { - unsafe { - let pminfo = libafl_qemu_sys::make_plugin_meminfo( - info.oi, - if is_store { - libafl_qemu_sys::qemu_plugin_mem_rw_QEMU_PLUGIN_MEM_W - } else { - libafl_qemu_sys::qemu_plugin_mem_rw_QEMU_PLUGIN_MEM_R - }, - ); - let phwaddr = libafl_qemu_sys::qemu_plugin_get_hwaddr(pminfo, vaddr as GuestVirtAddr); - if phwaddr.is_null() { - None - } else { - Some(libafl_qemu_sys::qemu_plugin_hwaddr_phys_addr(phwaddr) as GuestPhysAddr) - } - } - } - - #[cfg(emulation_mode = "systemmode")] - #[must_use] - pub fn get_current_paging_id(&self) -> Option { - let paging_id = unsafe { libafl_qemu_current_paging_id(self.ptr) }; - - if paging_id == 0 { - None - } else { - Some(paging_id) - } - } - // TODO expose tlb_set_dirty and tlb_reset_dirty - /// Write a value to a guest address. - /// - /// # Safety - /// This will write to a translated guest address (using `g2h`). - /// It just adds `guest_base` and writes to that location, without checking the bounds. - /// This may only be safely used for valid guest addresses! - pub unsafe fn write_mem(&self, addr: GuestAddr, buf: &[u8]) { - #[cfg(emulation_mode = "usermode")] - { - let host_addr = Emulator::new_empty().g2h(addr); - copy_nonoverlapping(buf.as_ptr(), host_addr, buf.len()); - } - // TODO use gdbstub's target_cpu_memory_rw_debug - #[cfg(emulation_mode = "systemmode")] - libafl_qemu_sys::cpu_memory_rw_debug( - self.ptr, - addr as GuestVirtAddr, - buf.as_ptr() as *mut _, - buf.len(), - true, - ); - } - - /// Read a value from a guest address. - /// - /// # Safety - /// This will read from a translated guest address (using `g2h`). - /// It just adds `guest_base` and writes to that location, without checking the bounds. - /// This may only be safely used for valid guest addresses! - pub unsafe fn read_mem(&self, addr: GuestAddr, buf: &mut [u8]) { - #[cfg(emulation_mode = "usermode")] - { - let host_addr = Emulator::new_empty().g2h(addr); - copy_nonoverlapping(host_addr, buf.as_mut_ptr(), buf.len()); - } - // TODO use gdbstub's target_cpu_memory_rw_debug - #[cfg(emulation_mode = "systemmode")] - libafl_qemu_sys::cpu_memory_rw_debug( - self.ptr, - addr as GuestVirtAddr, - buf.as_mut_ptr() as *mut _, - buf.len(), - false, - ); - } - #[must_use] pub fn num_regs(&self) -> i32 { unsafe { libafl_qemu_num_regs(self.ptr) } @@ -788,28 +568,6 @@ impl CPU { self.ptr } - #[must_use] - pub fn page_size(&self) -> usize { - #[cfg(emulation_mode = "usermode")] - { - thread_local! { - static PAGE_SIZE: OnceCell = const { OnceCell::new() }; - } - - PAGE_SIZE.with(|s| { - *s.get_or_init(|| { - unsafe { libc::sysconf(libc::_SC_PAGE_SIZE) } - .try_into() - .expect("Invalid page size") - }) - }) - } - #[cfg(emulation_mode = "systemmode")] - { - unsafe { libafl_qemu_sys::qemu_target_page_size() } - } - } - #[must_use] pub fn display_context(&self) -> String { let mut display = String::new(); @@ -872,7 +630,16 @@ create_hook_id!(PreSyscall, libafl_qemu_remove_pre_syscall_hook, false); create_hook_id!(PostSyscall, libafl_qemu_remove_post_syscall_hook, false); create_hook_id!(NewThread, libafl_qemu_remove_new_thread_hook, false); -use std::pin::Pin; +use std::{pin::Pin, ptr::NonNull}; + +use libafl::state::{HasExecutions, State}; +use libafl_bolts::os::unix_signals::Signal; + +use crate::{ + breakpoint::Breakpoint, + command::{Command, EmulatorMemoryChunk, InputCommand}, + executor::QemuExecutorState, +}; #[derive(Debug)] pub struct HookData(u64); @@ -946,16 +713,55 @@ pub enum EmuError { #[derive(Debug, Clone)] pub enum EmuExitReason { - End, // QEMU ended for some reason. - Breakpoint(GuestVirtAddr), // Breakpoint triggered. Contains the virtual address of the trigger. + End(QemuShutdownCause), // QEMU ended for some reason. + Breakpoint(Breakpoint), // Breakpoint triggered. Contains the address of the trigger. SyncBackdoor(SyncBackdoor), // Synchronous backdoor: The guest triggered a backdoor and should return to LibAFL. } -impl fmt::Display for EmuExitReason { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { +#[derive(Debug, Clone)] +pub enum QemuExitReason { + End(QemuShutdownCause), // QEMU ended for some reason. + Breakpoint(GuestAddr), // Breakpoint triggered. Contains the address of the trigger. + SyncBackdoor, // Synchronous backdoor: The guest triggered a backdoor and should return to LibAFL. +} + +/// High level result when finishing to handle requests +#[derive(Debug, Clone)] +pub enum HandlerResult { + UnhandledExit(EmuExitReason), // QEMU exit not handled by the current exit handler. + EndOfRun(ExitKind), // QEMU ended the current run and should pass some exit kind. + Interrupted, // User sent an interrupt signal +} + +impl From for HandlerError { + fn from(error: EmuExitReasonError) -> Self { + HandlerError::QemuExitReasonError(error) + } +} + +impl From for HandlerError { + fn from(error: SyncBackdoorError) -> Self { + HandlerError::SyncBackdoorError(error) + } +} + +impl Display for QemuExitReason { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { match self { - EmuExitReason::End => write!(f, "End"), - EmuExitReason::Breakpoint(vaddr) => write!(f, "Breakpoint @vaddr 0x{vaddr:x}"), + QemuExitReason::End(shutdown_cause) => write!(f, "End: {shutdown_cause:?}"), + QemuExitReason::Breakpoint(bp) => write!(f, "Breakpoint: {bp}"), + QemuExitReason::SyncBackdoor => write!(f, "Sync Backdoor"), // QemuExitReason::SyncBackdoor(sync_backdoor) => { + // write!(f, "Sync backdoor exit: {sync_backdoor}") + // } + } + } +} + +impl Display for EmuExitReason { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + match self { + EmuExitReason::End(shutdown_cause) => write!(f, "End: {shutdown_cause:?}"), + EmuExitReason::Breakpoint(bp) => write!(f, "{bp}"), EmuExitReason::SyncBackdoor(sync_backdoor) => { write!(f, "Sync backdoor exit: {sync_backdoor}") } @@ -963,11 +769,18 @@ impl fmt::Display for EmuExitReason { } } +#[derive(Debug, Clone)] +pub enum QemuExitReasonError { + UnknownKind, // Exit reason was not NULL, but exit kind is unknown. Should never happen. + UnexpectedExit, // Qemu exited without going through an expected exit point. Can be caused by a crash for example. +} + #[derive(Debug, Clone)] pub enum EmuExitReasonError { - UnknownKind(), + UnknownKind, UnexpectedExit, SyncBackdoorError(SyncBackdoorError), + BreakpointNotFound(GuestAddr), } impl From for EmuExitReasonError { @@ -976,31 +789,9 @@ impl From for EmuExitReasonError { } } -impl TryFrom<&Emulator> for EmuExitReason { - type Error = EmuExitReasonError; - fn try_from(emu: &Emulator) -> Result { - let exit_reason = unsafe { libafl_get_exit_reason() }; - if exit_reason.is_null() { - Err(EmuExitReasonError::UnexpectedExit) - } else { - let exit_reason: &mut libafl_qemu_sys::libafl_exit_reason = - unsafe { transmute(&mut *exit_reason) }; - Ok(match exit_reason.kind { - libafl_qemu_sys::libafl_exit_reason_kind_BREAKPOINT => unsafe { - EmuExitReason::Breakpoint(exit_reason.data.breakpoint.addr.into()) - }, - libafl_qemu_sys::libafl_exit_reason_kind_SYNC_BACKDOOR => { - EmuExitReason::SyncBackdoor(emu.try_into()?) - } - _ => return Err(EmuExitReasonError::UnknownKind()), - }) - } - } -} - impl std::error::Error for EmuError {} -impl fmt::Display for EmuError { +impl Display for EmuError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { EmuError::MultipleInstances => { @@ -1025,17 +816,43 @@ impl From for libafl::Error { } } -static mut EMULATOR_IS_INITIALIZED: bool = false; +static mut EMULATOR_STATE: *mut () = ptr::null_mut(); +static mut QEMU_IS_INITIALIZED: bool = false; -#[derive(Clone, Debug)] -pub struct Emulator { +/// The thin wrapper around QEMU. +/// It is considered unsafe to use it directly. +/// Prefer using `Emulator` instead in case of doubt. +#[derive(Clone, Copy, Debug)] +pub struct Qemu { _private: (), } +pub struct EmulatorState +where + QT: QemuHelperTuple, + S: State + HasExecutions, + E: EmuExitHandler, +{ + exit_handler: RefCell, + breakpoints: RefCell>, + _phantom: PhantomData<(QT, S)>, +} + +#[derive(Clone, Debug)] +pub struct Emulator +where + QT: QemuHelperTuple, + S: State + HasExecutions, + E: EmuExitHandler, +{ + state: ptr::NonNull>, + qemu: Qemu, +} + #[allow(clippy::unused_self)] -impl Emulator { +impl Qemu { #[allow(clippy::must_use_candidate, clippy::similar_names)] - pub fn new(args: &[String], env: &[(String, String)]) -> Result { + pub fn init(args: &[String], env: &[(String, String)]) -> Result { if args.is_empty() { return Err(EmuError::EmptyArgs); } @@ -1046,10 +863,10 @@ impl Emulator { } unsafe { - if EMULATOR_IS_INITIALIZED { + if QEMU_IS_INITIALIZED { return Err(EmuError::MultipleInstances); } - EMULATOR_IS_INITIALIZED = true; + QEMU_IS_INITIALIZED = true; } #[allow(clippy::cast_possible_wrap)] @@ -1081,36 +898,95 @@ impl Emulator { libafl_qemu_sys::syx_snapshot_init(true); } } - Ok(Emulator { _private: () }) + + Ok(Qemu { _private: () }) + } + + /// Get a QEMU object. + /// Same as `Qemu::get`, but without checking whether QEMU has been correctly initialized. + /// + /// # Safety + /// + /// Should not be used if `Qemu::init` has never been used before (otherwise QEMU will not be initialized, and a crash will occur). + /// Prefer `Qemu::get` for a safe version of this method. + #[must_use] + pub unsafe fn get_unchecked() -> Self { + Qemu { _private: () } } #[must_use] pub fn get() -> Option { unsafe { - if EMULATOR_IS_INITIALIZED { - Some(Self::new_empty()) + if QEMU_IS_INITIALIZED { + Some(Qemu { _private: () }) } else { None } } } - /// Get an empty emulator. - /// - /// # Safety - /// - /// Should not be used if `Emulator::new` has never been used before (otherwise QEMU will not be initialized). - /// Prefer `Emulator::get` for a safe version of this method. - #[must_use] - pub unsafe fn new_empty() -> Emulator { - Emulator { _private: () } - } + fn post_run(&self) -> Result { + let exit_reason = unsafe { libafl_get_exit_reason() }; + if exit_reason.is_null() { + Err(QemuExitReasonError::UnexpectedExit) + } else { + let exit_reason: &mut libafl_qemu_sys::libafl_exit_reason = + unsafe { transmute(&mut *exit_reason) }; + Ok(match exit_reason.kind { + libafl_qemu_sys::libafl_exit_reason_kind_INTERNAL => unsafe { + let qemu_shutdown_cause: QemuShutdownCause = + match exit_reason.data.internal.cause { + libafl_qemu_sys::ShutdownCause_SHUTDOWN_CAUSE_NONE => { + QemuShutdownCause::None + } + libafl_qemu_sys::ShutdownCause_SHUTDOWN_CAUSE_HOST_ERROR => { + QemuShutdownCause::HostError + } + libafl_qemu_sys::ShutdownCause_SHUTDOWN_CAUSE_HOST_QMP_QUIT => { + QemuShutdownCause::HostQmpQuit + } + libafl_qemu_sys::ShutdownCause_SHUTDOWN_CAUSE_HOST_QMP_SYSTEM_RESET => { + QemuShutdownCause::HostQmpSystemReset + } + libafl_qemu_sys::ShutdownCause_SHUTDOWN_CAUSE_HOST_SIGNAL => { + QemuShutdownCause::HostSignal( + Signal::try_from(exit_reason.data.internal.signal).unwrap(), + ) + } + libafl_qemu_sys::ShutdownCause_SHUTDOWN_CAUSE_HOST_UI => { + QemuShutdownCause::HostUi + } + libafl_qemu_sys::ShutdownCause_SHUTDOWN_CAUSE_GUEST_SHUTDOWN => { + QemuShutdownCause::GuestShutdown + } + libafl_qemu_sys::ShutdownCause_SHUTDOWN_CAUSE_GUEST_RESET => { + QemuShutdownCause::GuestReset + } + libafl_qemu_sys::ShutdownCause_SHUTDOWN_CAUSE_GUEST_PANIC => { + QemuShutdownCause::GuestPanic + } + libafl_qemu_sys::ShutdownCause_SHUTDOWN_CAUSE_SUBSYSTEM_RESET => { + QemuShutdownCause::SubsystemReset + } + libafl_qemu_sys::ShutdownCause_SHUTDOWN_CAUSE_SNAPSHOT_LOAD => { + QemuShutdownCause::SnapshotLoad + } - /// This function gets the memory mappings from the emulator. - #[cfg(emulation_mode = "usermode")] - #[must_use] - pub fn mappings(&self) -> GuestMaps { - GuestMaps::new() + _ => panic!("shutdown cause not handled."), + }; + + QemuExitReason::End(qemu_shutdown_cause) + }, + libafl_qemu_sys::libafl_exit_reason_kind_BREAKPOINT => unsafe { + let bp_addr = exit_reason.data.breakpoint.addr; + QemuExitReason::Breakpoint(bp_addr) + }, + libafl_qemu_sys::libafl_exit_reason_kind_SYNC_BACKDOOR => { + QemuExitReason::SyncBackdoor + } + _ => return Err(QemuExitReasonError::UnknownKind), + }) + } } #[must_use] @@ -1141,7 +1017,7 @@ impl Emulator { } #[must_use] - pub fn page_from_addr(addr: GuestAddr) -> GuestAddr { + pub fn page_from_addr(&self, addr: GuestAddr) -> GuestAddr { unsafe { libafl_page_from_addr(addr) } } @@ -1150,26 +1026,6 @@ impl Emulator { unsafe { libafl_page_size } }*/ - #[cfg(emulation_mode = "usermode")] - #[must_use] - pub fn g2h(&self, addr: GuestAddr) -> *mut T { - unsafe { (addr as usize + guest_base) as *mut T } - } - - #[cfg(emulation_mode = "usermode")] - #[must_use] - pub fn h2g(&self, addr: *const T) -> GuestAddr { - unsafe { (addr as usize - guest_base) as GuestAddr } - } - - #[cfg(emulation_mode = "usermode")] - #[must_use] - pub fn access_ok(&self, kind: VerifyAccess, addr: GuestAddr, size: usize) -> bool { - self.current_cpu() - .unwrap_or_else(|| self.cpu_from_index(0)) - .access_ok(kind, addr, size) - } - pub unsafe fn write_mem(&self, addr: GuestAddr, buf: &[u8]) { self.current_cpu() .unwrap_or_else(|| self.cpu_from_index(0)) @@ -1182,28 +1038,6 @@ impl Emulator { .read_mem(addr, buf); } - /// Write a value to a phsical guest address, including ROM areas. - #[cfg(emulation_mode = "systemmode")] - pub unsafe fn write_phys_mem(&self, paddr: GuestPhysAddr, buf: &[u8]) { - libafl_qemu_sys::cpu_physical_memory_rw( - paddr, - buf.as_ptr() as *mut _, - buf.len() as u64, - true, - ); - } - - /// Read a value from a physical guest address. - #[cfg(emulation_mode = "systemmode")] - pub unsafe fn read_phys_mem(&self, paddr: GuestPhysAddr, buf: &mut [u8]) { - libafl_qemu_sys::cpu_physical_memory_rw( - paddr, - buf.as_mut_ptr() as *mut _, - buf.len() as u64, - false, - ); - } - #[must_use] pub fn num_regs(&self) -> i32 { self.current_cpu().unwrap().num_regs() @@ -1240,137 +1074,14 @@ impl Emulator { pub fn entry_break(&self, addr: GuestAddr) { self.set_breakpoint(addr); unsafe { - // TODO: decide what to do with sync exit here: ignore or check for bp exit? - let _ = self.run(); + match self.run() { + Ok(QemuExitReason::Breakpoint(_)) => {} + _ => panic!("Unexpected QEMU exit."), + } } self.remove_breakpoint(addr); } - #[cfg(emulation_mode = "usermode")] - pub fn force_dfl(&self) { - unsafe { - libafl_force_dfl = 1; - } - } - /// This function will run the emulator until the next breakpoint, or until finish. - /// # Safety - /// - /// Should, in general, be safe to call. - /// Of course, the emulated target is not contained securely and can corrupt state or interact with the operating system. - pub unsafe fn run(&self) -> Result { - #[cfg(emulation_mode = "usermode")] - libafl_qemu_run(); - #[cfg(emulation_mode = "systemmode")] - { - vm_start(); - qemu_main_loop(); - } - EmuExitReason::try_from(self) - } - - #[cfg(emulation_mode = "usermode")] - #[must_use] - pub fn binary_path<'a>(&self) -> &'a str { - unsafe { from_utf8_unchecked(from_raw_parts(exec_path, strlen(exec_path))) } - } - - #[cfg(emulation_mode = "usermode")] - #[must_use] - pub fn load_addr(&self) -> GuestAddr { - unsafe { libafl_load_addr() as GuestAddr } - } - - #[cfg(emulation_mode = "usermode")] - #[must_use] - pub fn get_brk(&self) -> GuestAddr { - unsafe { libafl_get_brk() as GuestAddr } - } - - #[cfg(emulation_mode = "usermode")] - pub fn set_brk(&self, brk: GuestAddr) { - unsafe { libafl_set_brk(brk.into()) }; - } - - #[cfg(emulation_mode = "usermode")] - #[must_use] - pub fn get_mmap_start(&self) -> GuestAddr { - unsafe { mmap_next_start } - } - - #[cfg(emulation_mode = "usermode")] - pub fn set_mmap_start(&self, start: GuestAddr) { - unsafe { mmap_next_start = start }; - } - - #[cfg(emulation_mode = "usermode")] - #[allow(clippy::cast_sign_loss)] - fn mmap( - &self, - addr: GuestAddr, - size: usize, - perms: MmapPerms, - flags: c_int, - ) -> Result { - let res = unsafe { - libafl_qemu_sys::target_mmap(addr, size as GuestUsize, perms.into(), flags, -1, 0) - }; - if res <= 0 { - Err(()) - } else { - Ok(res as GuestAddr) - } - } - - #[cfg(emulation_mode = "usermode")] - pub fn map_private( - &self, - addr: GuestAddr, - size: usize, - perms: MmapPerms, - ) -> Result { - self.mmap(addr, size, perms, libc::MAP_PRIVATE | libc::MAP_ANONYMOUS) - .map_err(|()| format!("Failed to map {addr}")) - .map(|addr| addr as GuestAddr) - } - - #[cfg(emulation_mode = "usermode")] - pub fn map_fixed( - &self, - addr: GuestAddr, - size: usize, - perms: MmapPerms, - ) -> Result { - self.mmap( - addr, - size, - perms, - libc::MAP_FIXED | libc::MAP_PRIVATE | libc::MAP_ANONYMOUS, - ) - .map_err(|()| format!("Failed to map {addr}")) - .map(|addr| addr as GuestAddr) - } - - #[cfg(emulation_mode = "usermode")] - pub fn mprotect(&self, addr: GuestAddr, size: usize, perms: MmapPerms) -> Result<(), String> { - let res = unsafe { - libafl_qemu_sys::target_mprotect(addr.into(), size as GuestUsize, perms.into()) - }; - if res == 0 { - Ok(()) - } else { - Err(format!("Failed to mprotect {addr}")) - } - } - - #[cfg(emulation_mode = "usermode")] - pub fn unmap(&self, addr: GuestAddr, size: usize) -> Result<(), String> { - if unsafe { libafl_qemu_sys::target_munmap(addr.into(), size as GuestUsize) } == 0 { - Ok(()) - } else { - Err(format!("Failed to unmap {addr}")) - } - } - pub fn flush_jit(&self) { unsafe { libafl_flush_jit(); @@ -1534,167 +1245,6 @@ impl Emulator { } } - #[cfg(emulation_mode = "usermode")] - #[allow(clippy::type_complexity)] - pub fn add_pre_syscall_hook>( - &self, - data: T, - callback: extern "C" fn( - T, - i32, - GuestAddr, - GuestAddr, - GuestAddr, - GuestAddr, - GuestAddr, - GuestAddr, - GuestAddr, - GuestAddr, - ) -> SyscallHookResult, - ) -> PreSyscallHookId { - unsafe { - let data: u64 = data.into().0; - let callback: extern "C" fn( - u64, - i32, - GuestAddr, - GuestAddr, - GuestAddr, - GuestAddr, - GuestAddr, - GuestAddr, - GuestAddr, - GuestAddr, - ) -> libafl_qemu_sys::syshook_ret = core::mem::transmute(callback); - let num = libafl_qemu_sys::libafl_add_pre_syscall_hook(Some(callback), data); - PreSyscallHookId(num) - } - } - - #[cfg(emulation_mode = "usermode")] - #[allow(clippy::type_complexity)] - pub fn add_post_syscall_hook>( - &self, - data: T, - callback: extern "C" fn( - T, - GuestAddr, - i32, - GuestAddr, - GuestAddr, - GuestAddr, - GuestAddr, - GuestAddr, - GuestAddr, - GuestAddr, - GuestAddr, - ) -> GuestAddr, - ) -> PostSyscallHookId { - unsafe { - let data: u64 = data.into().0; - let callback: extern "C" fn( - u64, - GuestAddr, - i32, - GuestAddr, - GuestAddr, - GuestAddr, - GuestAddr, - GuestAddr, - GuestAddr, - GuestAddr, - GuestAddr, - ) -> GuestAddr = core::mem::transmute(callback); - let num = libafl_qemu_sys::libafl_add_post_syscall_hook(Some(callback), data); - PostSyscallHookId(num) - } - } - - #[cfg(emulation_mode = "usermode")] - pub fn add_new_thread_hook>( - &self, - data: T, - callback: extern "C" fn(T, tid: u32) -> bool, - ) -> NewThreadHookId { - unsafe { - let data: u64 = data.into().0; - let callback: extern "C" fn(u64, u32) -> bool = core::mem::transmute(callback); - let num = libafl_qemu_sys::libafl_add_new_thread_hook(Some(callback), data); - NewThreadHookId(num) - } - } - - #[cfg(emulation_mode = "systemmode")] - pub fn save_snapshot(&self, name: &str, sync: bool) { - let s = CString::new(name).expect("Invalid snapshot name"); - unsafe { libafl_save_qemu_snapshot(s.as_ptr() as *const _, sync) }; - } - - #[cfg(emulation_mode = "systemmode")] - pub fn load_snapshot(&self, name: &str, sync: bool) { - let s = CString::new(name).expect("Invalid snapshot name"); - unsafe { libafl_load_qemu_snapshot(s.as_ptr() as *const _, sync) }; - } - - #[cfg(emulation_mode = "systemmode")] - #[must_use] - pub fn create_fast_snapshot(&self, track: bool) -> FastSnapshot { - unsafe { - libafl_qemu_sys::syx_snapshot_new( - track, - true, - libafl_qemu_sys::DeviceSnapshotKind_DEVICE_SNAPSHOT_ALL, - null_mut(), - ) - } - } - - #[cfg(emulation_mode = "systemmode")] - #[must_use] - pub fn create_fast_snapshot_filter( - &self, - track: bool, - device_filter: &DeviceSnapshotFilter, - ) -> FastSnapshot { - let mut v = vec![]; - unsafe { - libafl_qemu_sys::syx_snapshot_new( - track, - true, - device_filter.enum_id(), - device_filter.devices(&mut v), - ) - } - } - - #[cfg(emulation_mode = "systemmode")] - pub fn restore_fast_snapshot(&self, snapshot: FastSnapshot) { - unsafe { libafl_qemu_sys::syx_snapshot_root_restore(snapshot) } - } - - #[cfg(emulation_mode = "systemmode")] - pub fn list_devices(&self) -> Vec { - let mut r = vec![]; - unsafe { - let devices = libafl_qemu_sys::device_list_all(); - if devices.is_null() { - return r; - } - - let mut ptr = devices; - while !(*ptr).is_null() { - let c_str: &CStr = CStr::from_ptr(*ptr); - let name = c_str.to_str().unwrap().to_string(); - r.push(name); - - ptr = ptr.add(1); - } - - libc::free(devices as *mut c_void); - r - } - } - #[allow(clippy::type_complexity)] pub fn add_gdb_cmd(&self, callback: Box bool>) { unsafe { @@ -1707,17 +1257,9 @@ impl Emulator { pub fn gdb_reply(&self, output: &str) { unsafe { libafl_qemu_gdb_reply(output.as_bytes().as_ptr(), output.len()) }; } - - #[cfg(emulation_mode = "usermode")] - #[allow(clippy::type_complexity)] - pub fn set_crash_hook(&self, callback: extern "C" fn(i32)) { - unsafe { - libafl_dump_core_hook = callback; - } - } } -impl ArchExtras for Emulator { +impl ArchExtras for Qemu { fn read_return_address(&self) -> Result where T: From, @@ -1760,6 +1302,439 @@ impl ArchExtras for Emulator { } } +#[allow(clippy::unused_self)] +impl Emulator +where + QT: QemuHelperTuple, + S: State + HasExecutions, + E: EmuExitHandler, +{ + #[allow(clippy::must_use_candidate, clippy::similar_names)] + pub fn new( + args: &[String], + env: &[(String, String)], + exit_handler: E, + ) -> Result { + let qemu = Qemu::init(args, env)?; + + Self::new_with_qemu(qemu, exit_handler) + } + + pub fn new_with_qemu(qemu: Qemu, exit_handler: E) -> Result { + let emu_state = Box::new(EmulatorState { + exit_handler: RefCell::new(exit_handler), + breakpoints: RefCell::new(HashSet::new()), + _phantom: PhantomData, + }); + + let emu_state_ptr = unsafe { + let emu_ptr = NonNull::from(Box::leak(emu_state)); + EMULATOR_STATE = emu_ptr.as_ptr() as *mut (); + emu_ptr + }; + + Ok(Emulator { + state: emu_state_ptr, + qemu, + }) + } + + #[must_use] + pub fn qemu(&self) -> &Qemu { + &self.qemu + } + + #[must_use] + pub fn state(&self) -> &EmulatorState { + unsafe { self.state.as_ref() } + } + + #[must_use] + pub fn state_mut(&mut self) -> &mut EmulatorState { + unsafe { self.state.as_mut() } + } + + #[must_use] + pub fn exit_handler(&self) -> &RefCell { + &self.state().exit_handler + } + + #[must_use] + pub fn get() -> Option> { + unsafe { + if QEMU_IS_INITIALIZED { + Some(Emulator::::get_unchecked()) + } else { + None + } + } + } + + /// Get an empty emulator. + /// Same as `Emulator::get`, but without checking whether QEMU has been correctly initialized. + /// + /// # Safety + /// + /// Should not be used if `Qemu::init` or `Emulator::new` has never been used before (otherwise QEMU will not be initialized, and a crash will occur). + /// Prefer `Emulator::get` for a safe version of this method. + #[must_use] + pub unsafe fn get_unchecked() -> Emulator { + Emulator { + state: NonNull::dangling(), + qemu: Qemu::get_unchecked(), + } + } + + #[must_use] + #[allow(clippy::cast_possible_wrap)] + #[allow(clippy::cast_sign_loss)] + #[deprecated( + note = "This function has been moved to the `Qemu` low-level structure. Please access it through `emu.qemu()`." + )] + pub fn num_cpus(&self) -> usize { + self.qemu.num_cpus() + } + + #[must_use] + #[deprecated( + note = "This function has been moved to the `Qemu` low-level structure. Please access it through `emu.qemu()`." + )] + pub fn current_cpu(&self) -> Option { + self.qemu.current_cpu() + } + + #[must_use] + #[allow(clippy::cast_possible_wrap)] + #[deprecated( + note = "This function has been moved to the `Qemu` low-level structure. Please access it through `emu.qemu()`." + )] + pub fn cpu_from_index(&self, index: usize) -> CPU { + self.qemu.cpu_from_index(index) + } + + #[must_use] + #[deprecated( + note = "This function has been moved to the `Qemu` low-level structure. Please access it through `emu.qemu()`." + )] + pub fn page_from_addr(&self, addr: GuestAddr) -> GuestAddr { + self.qemu.page_from_addr(addr) + } + + //#[must_use] + /*pub fn page_size() -> GuestUsize { + unsafe { libafl_page_size } + }*/ + + #[deprecated( + note = "This function has been moved to the `Qemu` low-level structure. Please access it through `emu.qemu()`." + )] + pub unsafe fn write_mem(&self, addr: GuestAddr, buf: &[u8]) { + self.qemu.write_mem(addr, buf); + } + + #[deprecated( + note = "This function has been moved to the `Qemu` low-level structure. Please access it through `emu.qemu()`." + )] + pub unsafe fn read_mem(&self, addr: GuestAddr, buf: &mut [u8]) { + self.qemu.read_mem(addr, buf); + } + + #[must_use] + #[deprecated( + note = "This function has been moved to the `Qemu` low-level structure. Please access it through `emu.qemu()`." + )] + pub fn num_regs(&self) -> i32 { + self.qemu.num_regs() + } + + #[deprecated( + note = "This function has been moved to the `Qemu` low-level structure. Please access it through `emu.qemu()`." + )] + pub fn write_reg(&self, reg: R, val: T) -> Result<(), String> + where + T: Num + PartialOrd + Copy + Into, + R: Into, + { + self.qemu.write_reg(reg, val) + } + + #[deprecated( + note = "This function has been moved to the `Qemu` low-level structure. Please access it through `emu.qemu()`." + )] + pub fn read_reg(&self, reg: R) -> Result + where + T: Num + PartialOrd + Copy + From, + R: Into, + { + self.qemu.read_reg(reg) + } + + pub fn add_breakpoint(&self, mut bp: Breakpoint, enable: bool) { + if enable { + bp.enable(&self.qemu); + } + + self.state().breakpoints.borrow_mut().insert(bp); + } + + pub fn remove_breakpoint(&self, bp: &mut Breakpoint) { + bp.disable(&self.qemu); + + self.state().breakpoints.borrow_mut().remove(bp); + } + + #[deprecated( + note = "This function has been moved to the `Qemu` low-level structure. Please access it through `emu.qemu()`." + )] + pub fn entry_break(&self, addr: GuestAddr) { + self.qemu.entry_break(addr); + } + + /// This function will run the emulator until the next breakpoint, or until finish. + /// # Safety + /// + /// Should, in general, be safe to call. + /// Of course, the emulated target is not contained securely and can corrupt state or interact with the operating system. + unsafe fn run_qemu(&self) -> Result { + match self.qemu.run() { + Ok(qemu_exit_reason) => Ok(match qemu_exit_reason { + QemuExitReason::End(qemu_shutdown_cause) => EmuExitReason::End(qemu_shutdown_cause), + QemuExitReason::Breakpoint(bp_addr) => { + let bp = self + .state() + .breakpoints + .borrow() + .get(&bp_addr) + .ok_or(EmuExitReasonError::BreakpointNotFound(bp_addr))? + .clone(); + EmuExitReason::Breakpoint(bp) + } + QemuExitReason::SyncBackdoor => EmuExitReason::SyncBackdoor(self.try_into()?), + }), + Err(qemu_exit_reason_error) => Err(match qemu_exit_reason_error { + QemuExitReasonError::UnexpectedExit => EmuExitReasonError::UnexpectedExit, + QemuExitReasonError::UnknownKind => EmuExitReasonError::UnknownKind, + }), + } + } + + /// This function will run the emulator until the exit handler decides to stop the execution for + /// whatever reason, depending on the choosen handler. + /// It is a higher-level abstraction of [`Emulator::run`] that will take care of some part of the runtime logic, + /// returning only when something interesting happen. + /// + /// # Safety + /// Should, in general, be safe to call. + /// Of course, the emulated target is not contained securely and can corrupt state or interact with the operating system. + pub unsafe fn run( + &self, + input: &BytesInput, + qemu_executor_state: &mut QemuExecutorState, + ) -> Result { + loop { + // Insert input if the location is already known + E::try_put_input(self, qemu_executor_state, input); + + // Run QEMU + let exit_reason = self.run_qemu(); + + // Handle QEMU exit + let handler_res = E::handle(self, exit_reason, qemu_executor_state, input)?; + + // Return to harness + match handler_res { + InnerHandlerResult::ReturnToHarness(exit_reason) => { + return Ok(HandlerResult::UnhandledExit(exit_reason)) + } + InnerHandlerResult::EndOfRun(exit_kind) => { + return Ok(HandlerResult::EndOfRun(exit_kind)) + } + InnerHandlerResult::Interrupt => return Ok(HandlerResult::Interrupted), + InnerHandlerResult::Continue => {} + } + } + } + + #[deprecated( + note = "This function has been moved to the `Qemu` low-level structure. Please access it through `emu.qemu()`." + )] + pub fn flush_jit(&self) { + self.qemu.flush_jit(); + } + + // TODO set T lifetime to be like Emulator + #[deprecated( + note = "This function has been moved to the `Qemu` low-level structure. Please access it through `emu.qemu()`." + )] + pub fn set_hook>( + &self, + data: T, + addr: GuestAddr, + callback: extern "C" fn(T, GuestAddr), + invalidate_block: bool, + ) -> InstructionHookId { + self.qemu.set_hook(data, addr, callback, invalidate_block) + } + + #[must_use] + #[deprecated( + note = "This function has been moved to the `Qemu` low-level structure. Please access it through `emu.qemu()`." + )] + pub fn remove_hook(&self, id: impl HookId, invalidate_block: bool) -> bool { + self.qemu.remove_hook(id, invalidate_block) + } + + #[must_use] + #[deprecated( + note = "This function has been moved to the `Qemu` low-level structure. Please access it through `emu.qemu()`." + )] + pub fn remove_hooks_at(&self, addr: GuestAddr, invalidate_block: bool) -> usize { + self.qemu.remove_hooks_at(addr, invalidate_block) + } + + #[deprecated( + note = "This function has been moved to the `Qemu` low-level structure. Please access it through `emu.qemu()`." + )] + pub fn add_edge_hooks>( + &self, + data: T, + gen: Option u64>, + exec: Option, + ) -> EdgeHookId { + self.qemu.add_edge_hooks(data, gen, exec) + } + + #[deprecated( + note = "This function has been moved to the `Qemu` low-level structure. Please access it through `emu.qemu()`." + )] + pub fn add_block_hooks>( + &self, + data: T, + gen: Option u64>, + post_gen: Option, + exec: Option, + ) -> BlockHookId { + self.qemu.add_block_hooks(data, gen, post_gen, exec) + } + + #[deprecated( + note = "This function has been moved to the `Qemu` low-level structure. Please access it through `emu.qemu()`." + )] + pub fn add_read_hooks>( + &self, + data: T, + gen: Option u64>, + exec1: Option, + exec2: Option, + exec4: Option, + exec8: Option, + exec_n: Option, + ) -> ReadHookId { + self.qemu + .add_read_hooks(data, gen, exec1, exec2, exec4, exec8, exec_n) + } + + // TODO add MemOp info + #[deprecated( + note = "This function has been moved to the `Qemu` low-level structure. Please access it through `emu.qemu()`." + )] + pub fn add_write_hooks>( + &self, + data: T, + gen: Option u64>, + exec1: Option, + exec2: Option, + exec4: Option, + exec8: Option, + exec_n: Option, + ) -> WriteHookId { + self.qemu + .add_write_hooks(data, gen, exec1, exec2, exec4, exec8, exec_n) + } + + #[deprecated( + note = "This function has been moved to the `Qemu` low-level structure. Please access it through `emu.qemu()`." + )] + pub fn add_cmp_hooks>( + &self, + data: T, + gen: Option u64>, + exec1: Option, + exec2: Option, + exec4: Option, + exec8: Option, + ) -> CmpHookId { + self.qemu + .add_cmp_hooks(data, gen, exec1, exec2, exec4, exec8) + } + + #[deprecated( + note = "This function has been moved to the `Qemu` low-level structure. Please access it through `emu.qemu()`." + )] + pub fn add_backdoor_hook>( + &self, + data: T, + callback: extern "C" fn(T, GuestAddr), + ) -> BackdoorHookId { + self.qemu.add_backdoor_hook(data, callback) + } + + #[allow(clippy::type_complexity)] + #[deprecated( + note = "This function has been moved to the `Qemu` low-level structure. Please access it through `emu.qemu()`." + )] + pub fn add_gdb_cmd(&self, callback: Box bool>) { + self.qemu.add_gdb_cmd(callback); + } + + #[deprecated( + note = "This function has been moved to the `Qemu` low-level structure. Please access it through `emu.qemu()`." + )] + pub fn gdb_reply(&self, output: &str) { + self.qemu.gdb_reply(output); + } +} + +// impl ArchExtras for Emulator +// where +// QT: QemuHelperTuple, +// S: State + HasExecutions, +// E: EmuExitHandler, +// { +// fn read_return_address(&self) -> Result +// where +// T: From, +// { +// self.qemu.read_return_address() +// } +// +// fn write_return_address(&self, val: T) -> Result<(), String> +// where +// T: Into, +// { +// self.qemu.write_return_address(val) +// } +// +// fn read_function_argument(&self, conv: CallingConvention, idx: u8) -> Result +// where +// T: From, +// { +// self.qemu.read_function_argument(conv, idx) +// } +// +// fn write_function_argument( +// &self, +// conv: CallingConvention, +// idx: i32, +// val: T, +// ) -> Result<(), String> +// where +// T: Into, +// { +// self.qemu.write_function_argument(conv, idx, val) +// } +// } + #[cfg(feature = "python")] pub mod pybind { use pyo3::{exceptions::PyValueError, prelude::*, types::PyInt}; @@ -1814,87 +1789,82 @@ pub mod pybind { } #[pyclass(unsendable)] - pub struct Emulator { - pub emu: super::Emulator, + pub struct Qemu { + pub qemu: super::Qemu, } #[pymethods] - impl Emulator { + impl Qemu { #[allow(clippy::needless_pass_by_value)] #[new] - fn new(args: Vec, env: Vec<(String, String)>) -> PyResult { - let emu = super::Emulator::new(&args, &env) + fn new(args: Vec, env: Vec<(String, String)>) -> PyResult { + let qemu = super::Qemu::init(&args, &env) .map_err(|e| PyValueError::new_err(format!("{e}")))?; - Ok(Emulator { emu }) + + Ok(Qemu { qemu }) } fn write_mem(&self, addr: GuestAddr, buf: &[u8]) { unsafe { - self.emu.write_mem(addr, buf); + self.qemu.write_mem(addr, buf); } } fn read_mem(&self, addr: GuestAddr, size: usize) -> Vec { let mut buf = vec![0; size]; unsafe { - self.emu.read_mem(addr, &mut buf); + self.qemu.read_mem(addr, &mut buf); } buf } fn num_regs(&self) -> i32 { - self.emu.num_regs() + self.qemu.num_regs() } fn write_reg(&self, reg: i32, val: GuestUsize) -> PyResult<()> { - self.emu.write_reg(reg, val).map_err(PyValueError::new_err) + self.qemu.write_reg(reg, val).map_err(PyValueError::new_err) } fn read_reg(&self, reg: i32) -> PyResult { - self.emu.read_reg(reg).map_err(PyValueError::new_err) + self.qemu.read_reg(reg).map_err(PyValueError::new_err) } fn set_breakpoint(&self, addr: GuestAddr) { - self.emu.set_breakpoint(addr); + self.qemu.set_breakpoint(addr); } fn entry_break(&self, addr: GuestAddr) { - self.emu.entry_break(addr); + self.qemu.entry_break(addr); } fn remove_breakpoint(&self, addr: GuestAddr) { - self.emu.remove_breakpoint(addr); - } - - fn run(&self) { - unsafe { - self.emu.run().unwrap(); - } + self.qemu.remove_breakpoint(addr); } fn g2h(&self, addr: GuestAddr) -> u64 { - self.emu.g2h::<*const u8>(addr) as u64 + self.qemu.g2h::<*const u8>(addr) as u64 } fn h2g(&self, addr: u64) -> GuestAddr { - self.emu.h2g(addr as *const u8) + self.qemu.h2g(addr as *const u8) } fn binary_path(&self) -> String { - self.emu.binary_path().to_owned() + self.qemu.binary_path().to_owned() } fn load_addr(&self) -> GuestAddr { - self.emu.load_addr() + self.qemu.load_addr() } fn flush_jit(&self) { - self.emu.flush_jit(); + self.qemu.flush_jit(); } fn map_private(&self, addr: GuestAddr, size: usize, perms: i32) -> PyResult { if let Ok(p) = MmapPerms::try_from(perms) { - self.emu + self.qemu .map_private(addr, size, p) .map_err(PyValueError::new_err) } else { @@ -1904,7 +1874,7 @@ pub mod pybind { fn map_fixed(&self, addr: GuestAddr, size: usize, perms: i32) -> PyResult { if let Ok(p) = MmapPerms::try_from(perms) { - self.emu + self.qemu .map_fixed(addr, size, p) .map_err(PyValueError::new_err) } else { @@ -1914,7 +1884,7 @@ pub mod pybind { fn mprotect(&self, addr: GuestAddr, size: usize, perms: i32) -> PyResult<()> { if let Ok(p) = MmapPerms::try_from(perms) { - self.emu + self.qemu .mprotect(addr, size, p) .map_err(PyValueError::new_err) } else { @@ -1923,21 +1893,22 @@ pub mod pybind { } fn unmap(&self, addr: GuestAddr, size: usize) -> PyResult<()> { - self.emu.unmap(addr, size).map_err(PyValueError::new_err) + self.qemu.unmap(addr, size).map_err(PyValueError::new_err) } fn set_syscall_hook(&self, hook: PyObject) { unsafe { PY_SYSCALL_HOOK = Some(hook); } - self.emu.add_pre_syscall_hook(0u64, py_syscall_hook_wrapper); + self.qemu + .add_pre_syscall_hook(0u64, py_syscall_hook_wrapper); } fn set_hook(&self, addr: GuestAddr, hook: PyObject) { unsafe { let idx = PY_GENERIC_HOOKS.len(); PY_GENERIC_HOOKS.push((addr, hook)); - self.emu + self.qemu .set_hook(idx as u64, addr, py_generic_hook_wrapper, true); } } @@ -1946,7 +1917,7 @@ pub mod pybind { unsafe { PY_GENERIC_HOOKS.retain(|(a, _)| *a != addr); } - self.emu.remove_hooks_at(addr, true) + self.qemu.remove_hooks_at(addr, true) } } } diff --git a/libafl_qemu/src/emu/systemmode.rs b/libafl_qemu/src/emu/systemmode.rs new file mode 100644 index 0000000000..4db07028dc --- /dev/null +++ b/libafl_qemu/src/emu/systemmode.rs @@ -0,0 +1,451 @@ +use std::{ + collections::HashMap, + ffi::{c_void, CStr, CString}, + fmt::Debug, + mem::MaybeUninit, + ptr::null_mut, + sync::atomic::{AtomicU64, Ordering}, +}; + +use libafl::state::{HasExecutions, State}; +use libafl_qemu_sys::{ + libafl_load_qemu_snapshot, libafl_qemu_current_paging_id, libafl_save_qemu_snapshot, + qemu_cleanup, qemu_main_loop, vm_start, GuestAddr, GuestPhysAddr, GuestVirtAddr, +}; + +use crate::{ + emu::{libafl_page_from_addr, IsSnapshotManager}, + EmuExitHandler, Emulator, MemAccessInfo, Qemu, QemuExitReason, QemuExitReasonError, + QemuHelperTuple, SnapshotId, SnapshotManagerError, CPU, +}; + +impl SnapshotId { + fn gen_unique_id() -> SnapshotId { + static UNIQUE_ID: AtomicU64 = AtomicU64::new(0); + + let unique_id = UNIQUE_ID.fetch_add(1, Ordering::SeqCst); + + SnapshotId { + id: unique_id.clone(), + } + } + + fn inner(&self) -> u64 { + self.id + } +} + +#[derive(Debug, Clone)] +pub enum SnapshotManager { + Qemu(QemuSnapshotManager), + Fast(FastSnapshotManager), +} + +impl IsSnapshotManager for SnapshotManager { + fn save(&mut self, qemu: &Qemu) -> SnapshotId { + match self { + SnapshotManager::Qemu(qemu_sm) => qemu_sm.save(qemu), + SnapshotManager::Fast(fast_sm) => fast_sm.save(qemu), + } + } + + fn restore( + &mut self, + snapshot_id: &SnapshotId, + qemu: &Qemu, + ) -> Result<(), SnapshotManagerError> { + match self { + SnapshotManager::Qemu(qemu_sm) => qemu_sm.restore(snapshot_id, qemu), + SnapshotManager::Fast(fast_sm) => fast_sm.restore(snapshot_id, qemu), + } + } +} + +pub type FastSnapshotPtr = *mut libafl_qemu_sys::SyxSnapshot; + +#[derive(Debug, Clone)] +pub struct FastSnapshotManager { + snapshots: HashMap, + check_memory_consistency: bool, +} + +impl Default for FastSnapshotManager { + fn default() -> Self { + Self::new(false) + } +} + +impl FastSnapshotManager { + pub fn new(check_memory_consistency: bool) -> Self { + Self { + snapshots: HashMap::new(), + check_memory_consistency, + } + } + + pub unsafe fn get(&self, id: &SnapshotId) -> FastSnapshotPtr { + self.snapshots.get(id).unwrap().clone() + } +} + +#[derive(Debug, Clone)] +pub struct QemuSnapshotManager { + is_sync: bool, +} + +impl QemuSnapshotManager { + pub fn new(is_sync: bool) -> Self { + Self { is_sync } + } + + pub fn snapshot_id_to_name(&self, snapshot_id: &SnapshotId) -> String { + format!("__libafl_qemu_snapshot_{}", snapshot_id.inner()) + } +} + +impl IsSnapshotManager for QemuSnapshotManager { + fn save(&mut self, qemu: &Qemu) -> SnapshotId { + let snapshot_id = SnapshotId::gen_unique_id(); + qemu.save_snapshot( + self.snapshot_id_to_name(&snapshot_id).as_str(), + self.is_sync, + ); + snapshot_id + } + + fn restore( + &mut self, + snapshot_id: &SnapshotId, + qemu: &Qemu, + ) -> Result<(), SnapshotManagerError> { + qemu.load_snapshot(self.snapshot_id_to_name(snapshot_id).as_str(), self.is_sync); + Ok(()) + } +} + +impl IsSnapshotManager for FastSnapshotManager { + fn save(&mut self, qemu: &Qemu) -> SnapshotId { + let snapshot_id = SnapshotId::gen_unique_id(); + self.snapshots + .insert(snapshot_id, qemu.create_fast_snapshot(true)); + snapshot_id + } + + fn restore( + &mut self, + snapshot_id: &SnapshotId, + qemu: &Qemu, + ) -> Result<(), SnapshotManagerError> { + let fast_snapshot_ptr = self + .snapshots + .get(snapshot_id) + .ok_or(SnapshotManagerError::SnapshotIdNotFound( + snapshot_id.clone(), + ))? + .clone(); + + qemu.restore_fast_snapshot(fast_snapshot_ptr); + + if self.check_memory_consistency { + let nb_inconsistencies = qemu.check_fast_snapshot_memory_consistency(fast_snapshot_ptr); + + if nb_inconsistencies > 0 { + return Err(SnapshotManagerError::MemoryInconsistencies( + nb_inconsistencies, + )); + } + } + + Ok(()) + } +} + +pub enum DeviceSnapshotFilter { + All, + AllowList(Vec), + DenyList(Vec), +} + +impl DeviceSnapshotFilter { + fn enum_id(&self) -> libafl_qemu_sys::DeviceSnapshotKind { + match self { + DeviceSnapshotFilter::All => libafl_qemu_sys::DeviceSnapshotKind_DEVICE_SNAPSHOT_ALL, + DeviceSnapshotFilter::AllowList(_) => { + libafl_qemu_sys::DeviceSnapshotKind_DEVICE_SNAPSHOT_ALLOWLIST + } + DeviceSnapshotFilter::DenyList(_) => { + libafl_qemu_sys::DeviceSnapshotKind_DEVICE_SNAPSHOT_DENYLIST + } + } + } + + fn devices(&self, v: &mut Vec<*mut i8>) -> *mut *mut i8 { + v.clear(); + match self { + DeviceSnapshotFilter::All => null_mut(), + DeviceSnapshotFilter::AllowList(l) | DeviceSnapshotFilter::DenyList(l) => { + for name in l { + v.push(name.as_bytes().as_ptr() as *mut i8); + } + v.as_mut_ptr() + } + } + } +} + +pub(super) extern "C" fn qemu_cleanup_atexit() { + unsafe { + qemu_cleanup(); + } +} + +impl CPU { + #[must_use] + pub fn get_phys_addr(&self, vaddr: GuestAddr) -> Option { + unsafe { + let page = libafl_page_from_addr(vaddr); + let mut attrs = MaybeUninit::::uninit(); + let paddr = libafl_qemu_sys::cpu_get_phys_page_attrs_debug( + self.ptr, + page as GuestVirtAddr, + attrs.as_mut_ptr(), + ); + if paddr == (-1i64 as GuestPhysAddr) { + None + } else { + Some(paddr) + } + } + } + + #[must_use] + pub fn get_phys_addr_tlb( + &self, + vaddr: GuestAddr, + info: MemAccessInfo, + is_store: bool, + ) -> Option { + unsafe { + let pminfo = libafl_qemu_sys::make_plugin_meminfo( + info.oi, + if is_store { + libafl_qemu_sys::qemu_plugin_mem_rw_QEMU_PLUGIN_MEM_W + } else { + libafl_qemu_sys::qemu_plugin_mem_rw_QEMU_PLUGIN_MEM_R + }, + ); + let phwaddr = libafl_qemu_sys::qemu_plugin_get_hwaddr(pminfo, vaddr as GuestVirtAddr); + if phwaddr.is_null() { + None + } else { + Some(libafl_qemu_sys::qemu_plugin_hwaddr_phys_addr(phwaddr) as GuestPhysAddr) + } + } + } + + #[must_use] + pub fn current_paging_id(&self) -> Option { + let paging_id = unsafe { libafl_qemu_current_paging_id(self.ptr) }; + + if paging_id == 0 { + None + } else { + Some(paging_id) + } + } + + /// Write a value to a guest address. + /// + /// # Safety + /// This will write to a translated guest address (using `g2h`). + /// It just adds `guest_base` and writes to that location, without checking the bounds. + /// This may only be safely used for valid guest addresses! + pub unsafe fn write_mem(&self, addr: GuestAddr, buf: &[u8]) { + // TODO use gdbstub's target_cpu_memory_rw_debug + libafl_qemu_sys::cpu_memory_rw_debug( + self.ptr, + addr as GuestVirtAddr, + buf.as_ptr() as *mut _, + buf.len(), + true, + ); + } + + /// Read a value from a guest address. + /// + /// # Safety + /// This will read from a translated guest address (using `g2h`). + /// It just adds `guest_base` and writes to that location, without checking the bounds. + /// This may only be safely used for valid guest addresses! + pub unsafe fn read_mem(&self, addr: GuestAddr, buf: &mut [u8]) { + // TODO use gdbstub's target_cpu_memory_rw_debug + libafl_qemu_sys::cpu_memory_rw_debug( + self.ptr, + addr as GuestVirtAddr, + buf.as_mut_ptr() as *mut _, + buf.len(), + false, + ); + } + + #[must_use] + pub fn page_size(&self) -> usize { + unsafe { libafl_qemu_sys::qemu_target_page_size() } + } +} + +#[allow(clippy::unused_self)] +impl Qemu { + /// Write a value to a phsical guest address, including ROM areas. + pub unsafe fn write_phys_mem(&self, paddr: GuestPhysAddr, buf: &[u8]) { + libafl_qemu_sys::cpu_physical_memory_rw( + paddr, + buf.as_ptr() as *mut _, + buf.len() as u64, + true, + ); + } + + /// Read a value from a physical guest address. + pub unsafe fn read_phys_mem(&self, paddr: GuestPhysAddr, buf: &mut [u8]) { + libafl_qemu_sys::cpu_physical_memory_rw( + paddr, + buf.as_mut_ptr() as *mut _, + buf.len() as u64, + false, + ); + } + + /// This function will run the emulator until the next breakpoint / sync exit, or until finish. + /// It is a low-level function and simply kicks QEMU. + /// # Safety + /// + /// Should, in general, be safe to call. + /// Of course, the emulated target is not contained securely and can corrupt state or interact with the operating system. + pub unsafe fn run(&self) -> Result { + vm_start(); + qemu_main_loop(); + + self.post_run() + } + + pub fn save_snapshot(&self, name: &str, sync: bool) { + let s = CString::new(name).expect("Invalid snapshot name"); + unsafe { libafl_save_qemu_snapshot(s.as_ptr() as *const _, sync) }; + } + + pub fn load_snapshot(&self, name: &str, sync: bool) { + let s = CString::new(name).expect("Invalid snapshot name"); + unsafe { libafl_load_qemu_snapshot(s.as_ptr() as *const _, sync) }; + } + + #[must_use] + pub fn create_fast_snapshot(&self, track: bool) -> FastSnapshotPtr { + unsafe { + libafl_qemu_sys::syx_snapshot_new( + track, + true, + libafl_qemu_sys::DeviceSnapshotKind_DEVICE_SNAPSHOT_ALL, + null_mut(), + ) + } + } + + #[must_use] + pub fn create_fast_snapshot_filter( + &self, + track: bool, + device_filter: &DeviceSnapshotFilter, + ) -> FastSnapshotPtr { + let mut v = vec![]; + unsafe { + libafl_qemu_sys::syx_snapshot_new( + track, + true, + device_filter.enum_id(), + device_filter.devices(&mut v), + ) + } + } + + pub fn restore_fast_snapshot(&self, snapshot: FastSnapshotPtr) { + unsafe { libafl_qemu_sys::syx_snapshot_root_restore(snapshot) } + } + + pub fn check_fast_snapshot_memory_consistency(&self, snapshot: FastSnapshotPtr) -> u64 { + unsafe { libafl_qemu_sys::syx_snapshot_check_memory_consistency(snapshot) } + } + + pub fn list_devices(&self) -> Vec { + let mut r = vec![]; + unsafe { + let devices = libafl_qemu_sys::device_list_all(); + if devices.is_null() { + return r; + } + + let mut ptr = devices; + while !(*ptr).is_null() { + let c_str: &CStr = CStr::from_ptr(*ptr); + let name = c_str.to_str().unwrap().to_string(); + r.push(name); + + ptr = ptr.add(1); + } + + libc::free(devices as *mut c_void); + r + } + } +} + +impl Emulator +where + QT: QemuHelperTuple, + S: State + HasExecutions, + E: EmuExitHandler, +{ + /// Write a value to a phsical guest address, including ROM areas. + pub unsafe fn write_phys_mem(&self, paddr: GuestPhysAddr, buf: &[u8]) { + self.qemu.write_phys_mem(paddr, buf) + } + + /// Read a value from a physical guest address. + pub unsafe fn read_phys_mem(&self, paddr: GuestPhysAddr, buf: &mut [u8]) { + self.qemu.read_phys_mem(paddr, buf) + } + + pub fn save_snapshot(&self, name: &str, sync: bool) { + self.qemu.save_snapshot(name, sync) + } + + pub fn load_snapshot(&self, name: &str, sync: bool) { + self.qemu.load_snapshot(name, sync) + } + + #[must_use] + pub fn create_fast_snapshot(&self, track: bool) -> FastSnapshotPtr { + self.qemu.create_fast_snapshot(track) + } + + #[must_use] + pub fn create_fast_snapshot_filter( + &self, + track: bool, + device_filter: &DeviceSnapshotFilter, + ) -> FastSnapshotPtr { + self.qemu.create_fast_snapshot_filter(track, device_filter) + } + + pub fn restore_fast_snapshot(&self, snapshot: FastSnapshotPtr) { + self.qemu.restore_fast_snapshot(snapshot) + } + + pub fn check_fast_snapshot_memory_consistency(&self, snapshot: FastSnapshotPtr) -> u64 { + self.qemu.check_fast_snapshot_memory_consistency(snapshot) + } + + pub fn list_devices(&self) -> Vec { + self.qemu.list_devices() + } +} diff --git a/libafl_qemu/src/emu/usermode.rs b/libafl_qemu/src/emu/usermode.rs new file mode 100644 index 0000000000..1dbfffe55e --- /dev/null +++ b/libafl_qemu/src/emu/usermode.rs @@ -0,0 +1,491 @@ +use core::{ffi::c_void, mem::MaybeUninit, ptr::copy_nonoverlapping}; +use std::{cell::OnceCell, slice::from_raw_parts, str::from_utf8_unchecked}; + +use libafl_qemu_sys::{ + exec_path, free_self_maps, guest_base, libafl_dump_core_hook, libafl_force_dfl, libafl_get_brk, + libafl_load_addr, libafl_maps_next, libafl_qemu_run, libafl_set_brk, mmap_next_start, + read_self_maps, strlen, GuestAddr, GuestUsize, MapInfo, MmapPerms, VerifyAccess, +}; +use libc::c_int; +#[cfg(feature = "python")] +use pyo3::prelude::*; + +use crate::{ + emu::{HasExecutions, State}, + sync_backdoor::SyncBackdoorError, + EmuExitHandler, Emulator, HookData, NewThreadHookId, PostSyscallHookId, PreSyscallHookId, Qemu, + QemuExitReason, QemuExitReasonError, QemuHelperTuple, SyscallHookResult, CPU, +}; + +#[derive(Debug, Clone)] +pub enum HandlerError { + EmuExitReasonError(QemuExitReasonError), + SyncBackdoorError(SyncBackdoorError), + MultipleInputDefinition, +} + +#[cfg_attr(feature = "python", pyclass(unsendable))] +pub struct GuestMaps { + orig_c_iter: *const c_void, + c_iter: *const c_void, +} + +// Consider a private new only for Emulator +impl GuestMaps { + #[must_use] + pub(crate) fn new() -> Self { + unsafe { + let maps = read_self_maps(); + Self { + orig_c_iter: maps, + c_iter: maps, + } + } + } +} + +impl Iterator for GuestMaps { + type Item = MapInfo; + + #[allow(clippy::uninit_assumed_init)] + fn next(&mut self) -> Option { + if self.c_iter.is_null() { + return None; + } + unsafe { + let mut ret = MaybeUninit::uninit(); + self.c_iter = libafl_maps_next(self.c_iter, ret.as_mut_ptr()); + if self.c_iter.is_null() { + None + } else { + Some(ret.assume_init()) + } + } + } +} + +#[cfg(feature = "python")] +#[pymethods] +impl GuestMaps { + fn __iter__(slf: PyRef) -> PyRef { + slf + } + fn __next__(mut slf: PyRefMut) -> Option { + Python::with_gil(|py| slf.next().map(|x| x.into_py(py))) + } +} + +impl Drop for GuestMaps { + fn drop(&mut self) { + unsafe { + free_self_maps(self.orig_c_iter); + } + } +} + +impl CPU { + /// Write a value to a guest address. + /// + /// # Safety + /// This will write to a translated guest address (using `g2h`). + /// It just adds `guest_base` and writes to that location, without checking the bounds. + /// This may only be safely used for valid guest addresses! + pub unsafe fn write_mem(&self, addr: GuestAddr, buf: &[u8]) { + let host_addr = Qemu::get().unwrap().g2h(addr); + copy_nonoverlapping(buf.as_ptr(), host_addr, buf.len()); + } + + /// Read a value from a guest address. + /// + /// # Safety + /// This will read from a translated guest address (using `g2h`). + /// It just adds `guest_base` and writes to that location, without checking the bounds. + /// This may only be safely used for valid guest addresses! + pub unsafe fn read_mem(&self, addr: GuestAddr, buf: &mut [u8]) { + let host_addr = Qemu::get().unwrap().g2h(addr); + copy_nonoverlapping(host_addr, buf.as_mut_ptr(), buf.len()); + } + + #[must_use] + pub fn page_size(&self) -> usize { + thread_local! { + static PAGE_SIZE: OnceCell = const { OnceCell::new() }; + } + + PAGE_SIZE.with(|s| { + *s.get_or_init(|| { + unsafe { libc::sysconf(libc::_SC_PAGE_SIZE) } + .try_into() + .expect("Invalid page size") + }) + }) + } +} + +#[allow(clippy::unused_self)] +impl Qemu { + #[must_use] + pub fn mappings(&self) -> GuestMaps { + GuestMaps::new() + } + + #[must_use] + pub fn g2h(&self, addr: GuestAddr) -> *mut T { + unsafe { (addr as usize + guest_base) as *mut T } + } + + #[must_use] + pub fn h2g(&self, addr: *const T) -> GuestAddr { + unsafe { (addr as usize - guest_base) as GuestAddr } + } + + #[must_use] + pub fn access_ok(&self, kind: VerifyAccess, addr: GuestAddr, size: usize) -> bool { + self.current_cpu() + .unwrap_or_else(|| self.cpu_from_index(0)) + .access_ok(kind, addr, size) + } + + pub fn force_dfl(&self) { + unsafe { + libafl_force_dfl = 1; + } + } + + /// This function will run the emulator until the next breakpoint, or until finish. + /// # Safety + /// + /// Should, in general, be safe to call. + /// Of course, the emulated target is not contained securely and can corrupt state or interact with the operating system. + pub unsafe fn run(&self) -> Result { + libafl_qemu_run(); + + self.post_run() + } + + #[must_use] + pub fn binary_path<'a>(&self) -> &'a str { + unsafe { from_utf8_unchecked(from_raw_parts(exec_path, strlen(exec_path))) } + } + + #[must_use] + pub fn load_addr(&self) -> GuestAddr { + unsafe { libafl_load_addr() as GuestAddr } + } + + #[must_use] + pub fn get_brk(&self) -> GuestAddr { + unsafe { libafl_get_brk() as GuestAddr } + } + + pub fn set_brk(&self, brk: GuestAddr) { + unsafe { libafl_set_brk(brk.into()) }; + } + + #[must_use] + pub fn get_mmap_start(&self) -> GuestAddr { + unsafe { mmap_next_start } + } + + pub fn set_mmap_start(&self, start: GuestAddr) { + unsafe { mmap_next_start = start }; + } + + #[allow(clippy::cast_sign_loss)] + fn mmap( + self, + addr: GuestAddr, + size: usize, + perms: MmapPerms, + flags: c_int, + ) -> Result { + let res = unsafe { + libafl_qemu_sys::target_mmap(addr, size as GuestUsize, perms.into(), flags, -1, 0) + }; + if res <= 0 { + Err(()) + } else { + Ok(res as GuestAddr) + } + } + + pub fn map_private( + &self, + addr: GuestAddr, + size: usize, + perms: MmapPerms, + ) -> Result { + self.mmap(addr, size, perms, libc::MAP_PRIVATE | libc::MAP_ANONYMOUS) + .map_err(|()| format!("Failed to map {addr}")) + .map(|addr| addr as GuestAddr) + } + + pub fn map_fixed( + &self, + addr: GuestAddr, + size: usize, + perms: MmapPerms, + ) -> Result { + self.mmap( + addr, + size, + perms, + libc::MAP_FIXED | libc::MAP_PRIVATE | libc::MAP_ANONYMOUS, + ) + .map_err(|()| format!("Failed to map {addr}")) + .map(|addr| addr as GuestAddr) + } + + pub fn mprotect(&self, addr: GuestAddr, size: usize, perms: MmapPerms) -> Result<(), String> { + let res = unsafe { + libafl_qemu_sys::target_mprotect(addr.into(), size as GuestUsize, perms.into()) + }; + if res == 0 { + Ok(()) + } else { + Err(format!("Failed to mprotect {addr}")) + } + } + + pub fn unmap(&self, addr: GuestAddr, size: usize) -> Result<(), String> { + if unsafe { libafl_qemu_sys::target_munmap(addr.into(), size as GuestUsize) } == 0 { + Ok(()) + } else { + Err(format!("Failed to unmap {addr}")) + } + } + + #[allow(clippy::type_complexity)] + pub fn add_pre_syscall_hook>( + &self, + data: T, + callback: extern "C" fn( + T, + i32, + GuestAddr, + GuestAddr, + GuestAddr, + GuestAddr, + GuestAddr, + GuestAddr, + GuestAddr, + GuestAddr, + ) -> SyscallHookResult, + ) -> PreSyscallHookId { + unsafe { + let data: u64 = data.into().0; + let callback: extern "C" fn( + u64, + i32, + GuestAddr, + GuestAddr, + GuestAddr, + GuestAddr, + GuestAddr, + GuestAddr, + GuestAddr, + GuestAddr, + ) -> libafl_qemu_sys::syshook_ret = core::mem::transmute(callback); + let num = libafl_qemu_sys::libafl_add_pre_syscall_hook(Some(callback), data); + PreSyscallHookId(num) + } + } + + #[allow(clippy::type_complexity)] + pub fn add_post_syscall_hook>( + &self, + data: T, + callback: extern "C" fn( + T, + GuestAddr, + i32, + GuestAddr, + GuestAddr, + GuestAddr, + GuestAddr, + GuestAddr, + GuestAddr, + GuestAddr, + GuestAddr, + ) -> GuestAddr, + ) -> PostSyscallHookId { + unsafe { + let data: u64 = data.into().0; + let callback: extern "C" fn( + u64, + GuestAddr, + i32, + GuestAddr, + GuestAddr, + GuestAddr, + GuestAddr, + GuestAddr, + GuestAddr, + GuestAddr, + GuestAddr, + ) -> GuestAddr = core::mem::transmute(callback); + let num = libafl_qemu_sys::libafl_add_post_syscall_hook(Some(callback), data); + PostSyscallHookId(num) + } + } + + pub fn add_new_thread_hook>( + &self, + data: T, + callback: extern "C" fn(T, tid: u32) -> bool, + ) -> NewThreadHookId { + unsafe { + let data: u64 = data.into().0; + let callback: extern "C" fn(u64, u32) -> bool = core::mem::transmute(callback); + let num = libafl_qemu_sys::libafl_add_new_thread_hook(Some(callback), data); + NewThreadHookId(num) + } + } + + #[allow(clippy::type_complexity)] + pub fn set_crash_hook(&self, callback: extern "C" fn(i32)) { + unsafe { + libafl_dump_core_hook = callback; + } + } +} + +impl Emulator +where + QT: QemuHelperTuple, + S: State + HasExecutions, + E: EmuExitHandler, +{ + /// This function gets the memory mappings from the emulator. + #[must_use] + pub fn mappings(&self) -> GuestMaps { + self.qemu.mappings() + } + + #[must_use] + pub fn g2h(&self, addr: GuestAddr) -> *mut T { + self.qemu.g2h(addr) + } + + #[must_use] + pub fn h2g(&self, addr: *const T) -> GuestAddr { + self.qemu.h2g(addr) + } + + #[must_use] + pub fn access_ok(&self, kind: VerifyAccess, addr: GuestAddr, size: usize) -> bool { + self.qemu.access_ok(kind, addr, size) + } + + pub fn force_dfl(&self) { + self.qemu.force_dfl(); + } + + #[must_use] + pub fn binary_path<'a>(&self) -> &'a str { + self.qemu.binary_path() + } + + #[must_use] + pub fn load_addr(&self) -> GuestAddr { + self.qemu.load_addr() + } + + #[must_use] + pub fn get_brk(&self) -> GuestAddr { + self.qemu.get_brk() + } + + pub fn set_brk(&self, brk: GuestAddr) { + self.qemu.set_brk(brk); + } + + #[must_use] + pub fn get_mmap_start(&self) -> GuestAddr { + self.qemu.get_mmap_start() + } + + pub fn set_mmap_start(&self, start: GuestAddr) { + self.qemu.set_mmap_start(start); + } + + pub fn map_private( + &self, + addr: GuestAddr, + size: usize, + perms: MmapPerms, + ) -> Result { + self.qemu.map_private(addr, size, perms) + } + + pub fn map_fixed( + &self, + addr: GuestAddr, + size: usize, + perms: MmapPerms, + ) -> Result { + self.qemu.map_fixed(addr, size, perms) + } + + pub fn mprotect(&self, addr: GuestAddr, size: usize, perms: MmapPerms) -> Result<(), String> { + self.qemu.mprotect(addr, size, perms) + } + + pub fn unmap(&self, addr: GuestAddr, size: usize) -> Result<(), String> { + self.qemu.unmap(addr, size) + } + + #[allow(clippy::type_complexity)] + pub fn add_pre_syscall_hook>( + &self, + data: T, + callback: extern "C" fn( + T, + i32, + GuestAddr, + GuestAddr, + GuestAddr, + GuestAddr, + GuestAddr, + GuestAddr, + GuestAddr, + GuestAddr, + ) -> SyscallHookResult, + ) -> PreSyscallHookId { + self.qemu.add_pre_syscall_hook(data, callback) + } + + #[allow(clippy::type_complexity)] + pub fn add_post_syscall_hook>( + &self, + data: T, + callback: extern "C" fn( + T, + GuestAddr, + i32, + GuestAddr, + GuestAddr, + GuestAddr, + GuestAddr, + GuestAddr, + GuestAddr, + GuestAddr, + GuestAddr, + ) -> GuestAddr, + ) -> PostSyscallHookId { + self.qemu.add_post_syscall_hook(data, callback) + } + + pub fn add_new_thread_hook>( + &self, + data: T, + callback: extern "C" fn(T, tid: u32) -> bool, + ) -> NewThreadHookId { + self.qemu.add_new_thread_hook(data, callback) + } + + #[allow(clippy::type_complexity)] + pub fn set_crash_hook(&self, callback: extern "C" fn(i32)) { + self.qemu.set_crash_hook(callback); + } +} diff --git a/libafl_qemu/src/executor/mod.rs b/libafl_qemu/src/executor/mod.rs index 7567295d35..169b422864 100644 --- a/libafl_qemu/src/executor/mod.rs +++ b/libafl_qemu/src/executor/mod.rs @@ -30,7 +30,7 @@ use libafl_bolts::os::unix_signals::{siginfo_t, ucontext_t, Signal}; #[cfg(feature = "fork")] use libafl_bolts::shmem::ShMemProvider; -use crate::{emu::Emulator, helper::QemuHelperTuple, hooks::QemuHooks}; +use crate::{helper::QemuHelperTuple, hooks::QemuHooks, Qemu}; /// A version of `QemuExecutor` with a state accessible from the harness. pub mod stateful; @@ -149,7 +149,7 @@ where unsafe { libafl::executors::inprocess::generic_inproc_crash_handler::(); } - if let Some(cpu) = hooks.emulator().current_cpu() { + if let Some(cpu) = hooks.qemu().current_cpu() { eprint!("Context:\n{}", cpu.display_context()); } }; @@ -172,8 +172,8 @@ where } #[must_use] - pub fn emulator(&self) -> &Emulator { - self.hooks.emulator() + pub fn qemu(&self) -> &Qemu { + self.hooks.qemu() } } @@ -246,8 +246,8 @@ where self.state.hooks_mut() } - pub fn emulator(&self) -> &Emulator { - self.state.emulator() + pub fn emulator(&self) -> &Qemu { + self.state.qemu() } } @@ -256,7 +256,7 @@ where S: State + HasExecutions + HasCorpus + HasSolutions, QT: QemuHelperTuple + Debug, { - fn pre_exec(&mut self, input: &E::Input, emu: &Emulator) + fn pre_exec(&mut self, input: &E::Input, qemu: Qemu) where E: Executor, EM: EventFirer + EventRestarter, @@ -267,13 +267,13 @@ where self.hooks.helpers().first_exec_all(self.hooks); self.first_exec = false; } - self.hooks.helpers_mut().pre_exec_all(emu, input); + self.hooks.helpers_mut().pre_exec_all(qemu, input); } fn post_exec( &mut self, input: &E::Input, - emu: &Emulator, + qemu: Qemu, observers: &mut OT, exit_kind: &mut ExitKind, ) where @@ -285,7 +285,7 @@ where { self.hooks .helpers_mut() - .post_exec_all(emu, input, observers, exit_kind); + .post_exec_all(qemu, input, observers, exit_kind); } } @@ -306,12 +306,12 @@ where mgr: &mut EM, input: &Self::Input, ) -> Result { - let emu = Emulator::get().unwrap(); - self.state.pre_exec::(input, &emu); + let qemu = Qemu::get().unwrap(); + self.state.pre_exec::(input, qemu); let mut exit_kind = self.inner.run_target(fuzzer, state, mgr, input)?; self.state.post_exec::( input, - &emu, + qemu, self.inner.observers_mut(), &mut exit_kind, ); @@ -449,8 +449,8 @@ where self.state.hooks } - pub fn emulator(&self) -> &Emulator { - self.state.hooks.emulator() + pub fn qemu(&self) -> &Qemu { + self.state.hooks.qemu() } } @@ -474,15 +474,15 @@ where mgr: &mut EM, input: &Self::Input, ) -> Result { - let emu = Emulator::get().unwrap(); + let qemu = *self.state.hooks.qemu(); if self.state.first_exec { self.state.hooks.helpers().first_exec_all(self.state.hooks); self.state.first_exec = false; } - self.state.hooks.helpers_mut().pre_exec_all(&emu, input); + self.state.hooks.helpers_mut().pre_exec_all(qemu, input); let mut exit_kind = self.inner.run_target(fuzzer, state, mgr, input)?; self.state.hooks.helpers_mut().post_exec_all( - &emu, + qemu, input, self.inner.observers_mut(), &mut exit_kind, diff --git a/libafl_qemu/src/executor/stateful.rs b/libafl_qemu/src/executor/stateful.rs index 512c9a8847..71dccaee8a 100644 --- a/libafl_qemu/src/executor/stateful.rs +++ b/libafl_qemu/src/executor/stateful.rs @@ -22,9 +22,7 @@ use libafl::{ use crate::executor::inproc_qemu_crash_handler; #[cfg(emulation_mode = "systemmode")] use crate::executor::{inproc_qemu_timeout_handler, BREAK_ON_TMOUT}; -use crate::{ - emu::Emulator, executor::QemuExecutorState, helper::QemuHelperTuple, hooks::QemuHooks, -}; +use crate::{executor::QemuExecutorState, helper::QemuHelperTuple, hooks::QemuHooks, Qemu}; pub struct StatefulQemuExecutor<'a, H, OT, QT, S> where @@ -134,8 +132,8 @@ where self.inner.exposed_executor_state_mut().hooks_mut() } - pub fn emulator(&self) -> &Emulator { - self.inner.exposed_executor_state().emulator() + pub fn emulator(&self) -> &Qemu { + self.inner.exposed_executor_state().qemu() } } @@ -156,16 +154,16 @@ where mgr: &mut EM, input: &Self::Input, ) -> Result { - let emu = Emulator::get().unwrap(); + let qemu = Qemu::get().unwrap(); self.inner .exposed_executor_state_mut() - .pre_exec::(input, &emu); + .pre_exec::(input, qemu); let mut exit_kind = self.inner.run_target(fuzzer, state, mgr, input)?; self.inner .exposed_executor_state .post_exec::( input, - &emu, + qemu, self.inner.inner.observers_mut(), &mut exit_kind, ); diff --git a/libafl_qemu/src/helper.rs b/libafl_qemu/src/helper.rs index ab421085da..efdee20780 100644 --- a/libafl_qemu/src/helper.rs +++ b/libafl_qemu/src/helper.rs @@ -1,14 +1,11 @@ use core::{fmt::Debug, ops::Range}; -use std::{collections::HashSet, hash}; +use std::{collections::HashSet, hash::BuildHasher}; use libafl::{executors::ExitKind, inputs::UsesInput, observers::ObserversTuple}; use libafl_bolts::tuples::{MatchFirstType, SplitBorrowExtractFirstType}; +use libafl_qemu_sys::{GuestAddr, GuestPhysAddr}; -use crate::{ - emu::{Emulator, GuestAddr}, - hooks::QemuHooks, - GuestPhysAddr, -}; +use crate::{hooks::QemuHooks, Qemu}; /// A helper for `libafl_qemu`. // TODO remove 'static when specialization will be stable @@ -30,11 +27,11 @@ where { } - fn pre_exec(&mut self, _emulator: &Emulator, _input: &S::Input) {} + fn pre_exec(&mut self, _qemu: Qemu, _input: &S::Input) {} fn post_exec( &mut self, - _emulator: &Emulator, + _qemu: Qemu, _input: &S::Input, _observers: &mut OT, _exit_kind: &mut ExitKind, @@ -58,11 +55,11 @@ where where QT: QemuHelperTuple; - fn pre_exec_all(&mut self, _emulator: &Emulator, input: &S::Input); + fn pre_exec_all(&mut self, _qemu: Qemu, input: &S::Input); fn post_exec_all( &mut self, - _emulator: &Emulator, + _qemu: Qemu, input: &S::Input, _observers: &mut OT, _exit_kind: &mut ExitKind, @@ -88,11 +85,11 @@ where { } - fn pre_exec_all(&mut self, _emulator: &Emulator, _input: &S::Input) {} + fn pre_exec_all(&mut self, _qemu: Qemu, _input: &S::Input) {} fn post_exec_all( &mut self, - _emulator: &Emulator, + _qemu: Qemu, _input: &S::Input, _observers: &mut OT, _exit_kind: &mut ExitKind, @@ -102,6 +99,21 @@ where } } +impl HasInstrumentationFilter for (Head, ()) +where + Head: QemuHelper + HasInstrumentationFilter, + S: UsesInput, + F: IsFilter, +{ + fn filter(&self) -> &F { + self.0.filter() + } + + fn filter_mut(&mut self) -> &mut F { + self.0.filter_mut() + } +} + impl QemuHelperTuple for (Head, Tail) where Head: QemuHelper, @@ -126,27 +138,27 @@ where self.1.first_exec_all(hooks); } - fn pre_exec_all(&mut self, emulator: &Emulator, input: &S::Input) { - self.0.pre_exec(emulator, input); - self.1.pre_exec_all(emulator, input); + fn pre_exec_all(&mut self, qemu: Qemu, input: &S::Input) { + self.0.pre_exec(qemu, input); + self.1.pre_exec_all(qemu, input); } fn post_exec_all( &mut self, - emulator: &Emulator, + qemu: Qemu, input: &S::Input, observers: &mut OT, exit_kind: &mut ExitKind, ) where OT: ObserversTuple, { - self.0.post_exec(emulator, input, observers, exit_kind); - self.1.post_exec_all(emulator, input, observers, exit_kind); + self.0.post_exec(qemu, input, observers, exit_kind); + self.1.post_exec_all(qemu, input, observers, exit_kind); } } -#[derive(Debug)] -pub enum QemuFilterList { +#[derive(Debug, Clone)] +pub enum QemuFilterList { AllowList(T), DenyList(T), None, @@ -154,7 +166,7 @@ pub enum QemuFilterList { impl IsFilter for QemuFilterList where - T: IsFilter, + T: IsFilter + Clone, { type FilterParameter = T::FilterParameter; @@ -169,7 +181,10 @@ where pub type QemuInstrumentationPagingFilter = QemuFilterList>; -impl IsFilter for HashSet { +impl IsFilter for HashSet +where + H: BuildHasher, +{ type FilterParameter = Option; fn allowed(&self, paging_id: Self::FilterParameter) -> bool { @@ -192,7 +207,7 @@ impl IsFilter for Vec> { } } -pub trait HasInstrumentationFilter +pub trait HasInstrumentationFilter where F: IsFilter, { @@ -200,12 +215,43 @@ where fn filter_mut(&mut self) -> &mut F; - fn update_filter(&mut self, filter: F, emu: &Emulator) { + fn update_filter(&mut self, filter: F, emu: &Qemu) { *self.filter_mut() = filter; emu.flush_jit(); } } +#[cfg(emulation_mode = "usermode")] +pub trait StdInstrumentationFilter: + HasInstrumentationFilter +{ +} + +#[cfg(emulation_mode = "systemmode")] +pub trait StdInstrumentationFilter: + HasInstrumentationFilter + + HasInstrumentationFilter +{ +} + +#[cfg(emulation_mode = "systemmode")] +impl StdInstrumentationFilter for (Head, ()) +where + Head: QemuHelper + + HasInstrumentationFilter + + HasInstrumentationFilter, + S: UsesInput, +{ +} + +#[cfg(emulation_mode = "usermode")] +impl StdInstrumentationFilter for (Head, ()) +where + Head: QemuHelper + HasInstrumentationFilter, + S: UsesInput, +{ +} + pub trait IsFilter: Debug { type FilterParameter; diff --git a/libafl_qemu/src/hexagon.rs b/libafl_qemu/src/hexagon.rs index 726c3b0a54..d931876034 100644 --- a/libafl_qemu/src/hexagon.rs +++ b/libafl_qemu/src/hexagon.rs @@ -6,7 +6,7 @@ use num_enum::{IntoPrimitive, TryFromPrimitive}; use pyo3::prelude::*; pub use strum_macros::EnumIter; -use crate::{sync_backdoor::SyncBackdoorArgs, CallingConvention}; +use crate::{sync_backdoor::BackdoorArgs, CallingConvention}; #[derive(IntoPrimitive, TryFromPrimitive, Debug, Clone, Copy, EnumIter)] #[repr(i32)] @@ -64,19 +64,19 @@ pub enum Regs { Pktcnthi = 51, } -static SYNC_BACKDOOR_ARCH_REGS: OnceLock> = OnceLock::new(); +static BACKDOOR_ARCH_REGS: OnceLock> = OnceLock::new(); -pub fn get_sync_backdoor_arch_regs() -> &'static EnumMap { - SYNC_BACKDOOR_ARCH_REGS.get_or_init(|| { +pub fn get_backdoor_arch_regs() -> &'static EnumMap { + BACKDOOR_ARCH_REGS.get_or_init(|| { enum_map! { - SyncBackdoorArgs::Ret => Regs::R0, - SyncBackdoorArgs::Cmd => Regs::R0, - SyncBackdoorArgs::Arg1 => Regs::R1, - SyncBackdoorArgs::Arg2 => Regs::R2, - SyncBackdoorArgs::Arg3 => Regs::R3, - SyncBackdoorArgs::Arg4 => Regs::R4, - SyncBackdoorArgs::Arg5 => Regs::R5, - SyncBackdoorArgs::Arg6 => Regs::R6, + BackdoorArgs::Ret => Regs::R0, + BackdoorArgs::Cmd => Regs::R0, + BackdoorArgs::Arg1 => Regs::R1, + BackdoorArgs::Arg2 => Regs::R2, + BackdoorArgs::Arg3 => Regs::R3, + BackdoorArgs::Arg4 => Regs::R4, + BackdoorArgs::Arg5 => Regs::R5, + BackdoorArgs::Arg6 => Regs::R6, } }) } diff --git a/libafl_qemu/src/hooks.rs b/libafl_qemu/src/hooks.rs index f3d63c85e3..9ed5f03d9f 100644 --- a/libafl_qemu/src/hooks.rs +++ b/libafl_qemu/src/hooks.rs @@ -1,13 +1,15 @@ //! The high-level hooks #![allow(clippy::type_complexity)] +#[cfg(emulation_mode = "usermode")] +use core::ptr::addr_of_mut; use core::{ ffi::c_void, fmt::{self, Debug, Formatter}, marker::PhantomData, mem::transmute, pin::Pin, - ptr::{self, addr_of, addr_of_mut}, + ptr::{self, addr_of}, }; use libafl::{ @@ -15,15 +17,17 @@ use libafl::{ inputs::UsesInput, state::NopState, }; +use libafl_qemu_sys::{FatPtr, GuestAddr, GuestUsize}; pub use crate::emu::SyscallHookResult; use crate::{ - emu::{Emulator, FatPtr, MemAccessInfo, SKIP_EXEC_HOOK}, + emu::{MemAccessInfo, Qemu, SKIP_EXEC_HOOK}, helper::QemuHelperTuple, - BackdoorHookId, BlockHookId, CmpHookId, EdgeHookId, GuestAddr, GuestUsize, HookId, - InstructionHookId, NewThreadHookId, PostSyscallHookId, PreSyscallHookId, ReadHookId, + BackdoorHookId, BlockHookId, CmpHookId, EdgeHookId, HookId, InstructionHookId, ReadHookId, WriteHookId, }; +#[cfg(emulation_mode = "usermode")] +use crate::{NewThreadHookId, PostSyscallHookId, PreSyscallHookId}; /* // all kinds of hooks @@ -386,7 +390,7 @@ where S: UsesInput, { helpers: QT, - emulator: Emulator, + qemu: Qemu, phantom: PhantomData, } @@ -398,7 +402,7 @@ where fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { f.debug_struct("QemuHooks") .field("helpers", &self.helpers) - .field("emulator", &self.emulator) + .field("emulator", &self.qemu) .finish() } } @@ -408,8 +412,8 @@ where QT: QemuHelperTuple>, NopState: UsesInput, { - pub fn reproducer(emulator: Emulator, helpers: QT) -> Box { - Self::new(emulator, helpers) + pub fn reproducer(qemu: Qemu, helpers: QT) -> Box { + Self::new(qemu, helpers) } pub fn repro_run(&mut self, harness: &mut H, input: &I) -> ExitKind @@ -422,12 +426,12 @@ where FIRST_EXEC = false; } } - self.helpers.pre_exec_all(&self.emulator, input); + self.helpers.pre_exec_all(self.qemu, input); let mut exit_kind = harness(input); self.helpers - .post_exec_all(&self.emulator, input, &mut (), &mut exit_kind); + .post_exec_all(self.qemu, input, &mut (), &mut exit_kind); exit_kind } @@ -438,7 +442,7 @@ where QT: QemuHelperTuple, S: UsesInput, { - pub fn new(emulator: Emulator, helpers: QT) -> Box { + pub fn new(qemu: Qemu, helpers: QT) -> Box { unsafe { assert!( !HOOKS_IS_INITIALIZED, @@ -447,9 +451,9 @@ where HOOKS_IS_INITIALIZED = true; } // re-translate blocks with hooks - emulator.flush_jit(); + qemu.flush_jit(); let slf = Box::new(Self { - emulator, + qemu, helpers, phantom: PhantomData, }); @@ -476,8 +480,8 @@ where self.helpers.match_first_type_mut::() } - pub fn emulator(&self) -> &Emulator { - &self.emulator + pub fn qemu(&self) -> &Qemu { + &self.qemu } pub fn helpers(&self) -> &QT { @@ -503,7 +507,7 @@ where Hook::Closure(c) => self.instruction_closure(addr, c, invalidate_block), Hook::Raw(r) => { let z: *const () = ptr::null::<()>(); - self.emulator.set_hook(z, addr, r, invalidate_block) + self.qemu.set_hook(z, addr, r, invalidate_block) } Hook::Empty => InstructionHookId(0), // TODO error type } @@ -516,7 +520,7 @@ where invalidate_block: bool, ) -> InstructionHookId { unsafe { - self.emulator.set_hook( + self.qemu.set_hook( transmute(hook), addr, func_generic_hook_wrapper::, @@ -534,7 +538,7 @@ where unsafe { let fat: FatPtr = transmute(hook); GENERIC_HOOKS.push(Box::pin((InstructionHookId(0), fat))); - let id = self.emulator.set_hook( + let id = self.qemu.set_hook( &mut GENERIC_HOOKS .last_mut() .unwrap() @@ -596,7 +600,7 @@ where post_gen: HookRepr::Empty, execs: [hook_to_repr!(execution_hook)], })); - let id = self.emulator.add_edge_hooks( + let id = self.qemu.add_edge_hooks( EDGE_HOOKS.last_mut().unwrap().as_mut().get_unchecked_mut(), gen, exec, @@ -655,7 +659,7 @@ where post_gen: hook_to_repr!(post_generation_hook), execs: [hook_to_repr!(execution_hook)], })); - let id = self.emulator.add_block_hooks( + let id = self.qemu.add_block_hooks( BLOCK_HOOKS.last_mut().unwrap().as_mut().get_unchecked_mut(), gen, postgen, @@ -759,7 +763,7 @@ where hook_to_repr!(execution_hook_n), ], })); - let id = self.emulator.add_read_hooks( + let id = self.qemu.add_read_hooks( READ_HOOKS.last_mut().unwrap().as_mut().get_unchecked_mut(), gen, exec1, @@ -871,7 +875,7 @@ where hook_to_repr!(execution_hook_n), ], })); - let id = self.emulator.add_write_hooks( + let id = self.qemu.add_write_hooks( WRITE_HOOKS.last_mut().unwrap().as_mut().get_unchecked_mut(), gen, exec1, @@ -957,7 +961,7 @@ where hook_to_repr!(execution_hook_8), ], })); - let id = self.emulator.add_cmp_hooks( + let id = self.qemu.add_cmp_hooks( CMP_HOOKS.last_mut().unwrap().as_mut().get_unchecked_mut(), gen, exec1, @@ -988,7 +992,7 @@ where Hook::Closure(c) => self.backdoor_closure(c), Hook::Raw(r) => { let z: *const () = ptr::null::<()>(); - self.emulator.add_backdoor_hook(z, r) + self.qemu.add_backdoor_hook(z, r) } Hook::Empty => BackdoorHookId(0), // TODO error type } @@ -999,7 +1003,7 @@ where hook: fn(&mut Self, Option<&mut S>, pc: GuestAddr), ) -> BackdoorHookId { unsafe { - self.emulator + self.qemu .add_backdoor_hook(transmute(hook), func_backdoor_hook_wrapper::) } } @@ -1011,7 +1015,7 @@ where unsafe { let fat: FatPtr = transmute(hook); BACKDOOR_HOOKS.push(Box::pin((BackdoorHookId(0), fat))); - let id = self.emulator.add_backdoor_hook( + let id = self.qemu.add_backdoor_hook( &mut BACKDOOR_HOOKS .last_mut() .unwrap() @@ -1082,7 +1086,7 @@ where Hook::Closure(c) => self.syscalls_closure(c), Hook::Raw(r) => { let z: *const () = ptr::null::<()>(); - self.emulator.add_pre_syscall_hook(z, r) + self.qemu.add_pre_syscall_hook(z, r) } Hook::Empty => PreSyscallHookId(0), // TODO error type } @@ -1107,7 +1111,7 @@ where ) -> SyscallHookResult, ) -> PreSyscallHookId { unsafe { - self.emulator + self.qemu .add_pre_syscall_hook(transmute(hook), func_pre_syscall_hook_wrapper::) } } @@ -1135,7 +1139,7 @@ where unsafe { let fat: FatPtr = transmute(hook); PRE_SYSCALL_HOOKS.push(Box::pin((PreSyscallHookId(0), fat))); - let id = self.emulator.add_pre_syscall_hook( + let id = self.qemu.add_pre_syscall_hook( &mut PRE_SYSCALL_HOOKS .last_mut() .unwrap() @@ -1209,7 +1213,7 @@ where Hook::Closure(c) => self.after_syscalls_closure(c), Hook::Raw(r) => { let z: *const () = ptr::null::<()>(); - self.emulator.add_post_syscall_hook(z, r) + self.qemu.add_post_syscall_hook(z, r) } Hook::Empty => PostSyscallHookId(0), // TODO error type } @@ -1235,7 +1239,7 @@ where ) -> GuestAddr, ) -> PostSyscallHookId { unsafe { - self.emulator + self.qemu .add_post_syscall_hook(transmute(hook), func_post_syscall_hook_wrapper::) } } @@ -1264,7 +1268,7 @@ where unsafe { let fat: FatPtr = transmute(hook); POST_SYSCALL_HOOKS.push(Box::pin((PostSyscallHookId(0), fat))); - let id = self.emulator.add_post_syscall_hook( + let id = self.qemu.add_post_syscall_hook( &mut POST_SYSCALL_HOOKS .last_mut() .unwrap() @@ -1297,7 +1301,7 @@ where Hook::Closure(c) => self.thread_creation_closure(c), Hook::Raw(r) => { let z: *const () = ptr::null::<()>(); - self.emulator.add_new_thread_hook(z, r) + self.qemu.add_new_thread_hook(z, r) } Hook::Empty => NewThreadHookId(0), // TODO error type } @@ -1309,7 +1313,7 @@ where hook: fn(&mut Self, Option<&mut S>, tid: u32) -> bool, ) -> NewThreadHookId { unsafe { - self.emulator + self.qemu .add_new_thread_hook(transmute(hook), func_new_thread_hook_wrapper::) } } @@ -1322,7 +1326,7 @@ where unsafe { let fat: FatPtr = transmute(hook); NEW_THREAD_HOOKS.push(Box::pin((NewThreadHookId(0), fat))); - let id = self.emulator.add_new_thread_hook( + let id = self.qemu.add_new_thread_hook( &mut NEW_THREAD_HOOKS .last_mut() .unwrap() @@ -1344,7 +1348,7 @@ where #[cfg(emulation_mode = "usermode")] pub fn crash_function(&self, hook: fn(&mut Self, target_signal: i32)) { unsafe { - self.emulator.set_crash_hook(crash_hook_wrapper::); + self.qemu.set_crash_hook(crash_hook_wrapper::); CRASH_HOOKS.push(HookRepr::Function(hook as *const libc::c_void)); } } @@ -1352,7 +1356,7 @@ where #[cfg(emulation_mode = "usermode")] pub fn crash_closure(&self, hook: Box) { unsafe { - self.emulator.set_crash_hook(crash_hook_wrapper::); + self.qemu.set_crash_hook(crash_hook_wrapper::); CRASH_HOOKS.push(HookRepr::Closure(transmute(hook))); } } diff --git a/libafl_qemu/src/i386.rs b/libafl_qemu/src/i386.rs index 44bbd0ca92..d70578987a 100644 --- a/libafl_qemu/src/i386.rs +++ b/libafl_qemu/src/i386.rs @@ -8,7 +8,7 @@ use pyo3::prelude::*; pub use strum_macros::EnumIter; pub use syscall_numbers::x86::*; -use crate::{sync_backdoor::SyncBackdoorArgs, CallingConvention, GuestAddr}; +use crate::{sync_backdoor::BackdoorArgs, CallingConvention, GuestAddr}; #[derive(IntoPrimitive, TryFromPrimitive, Debug, Clone, Copy, EnumIter)] #[repr(i32)] @@ -25,19 +25,19 @@ pub enum Regs { Eflags = 9, } -static SYNC_BACKDOOR_ARCH_REGS: OnceLock> = OnceLock::new(); +static BACKDOOR_ARCH_REGS: OnceLock> = OnceLock::new(); -pub fn get_sync_backdoor_arch_regs() -> &'static EnumMap { - SYNC_BACKDOOR_ARCH_REGS.get_or_init(|| { +pub fn get_backdoor_arch_regs() -> &'static EnumMap { + BACKDOOR_ARCH_REGS.get_or_init(|| { enum_map! { - SyncBackdoorArgs::Ret => Regs::Eax, - SyncBackdoorArgs::Cmd => Regs::Eax, - SyncBackdoorArgs::Arg1 => Regs::Edi, - SyncBackdoorArgs::Arg2 => Regs::Esi, - SyncBackdoorArgs::Arg3 => Regs::Edx, - SyncBackdoorArgs::Arg4 => Regs::Ebx, - SyncBackdoorArgs::Arg5 => Regs::Ecx, - SyncBackdoorArgs::Arg6 => Regs::Ebp, + BackdoorArgs::Ret => Regs::Eax, + BackdoorArgs::Cmd => Regs::Eax, + BackdoorArgs::Arg1 => Regs::Edi, + BackdoorArgs::Arg2 => Regs::Esi, + BackdoorArgs::Arg3 => Regs::Edx, + BackdoorArgs::Arg4 => Regs::Ebx, + BackdoorArgs::Arg5 => Regs::Ecx, + BackdoorArgs::Arg6 => Regs::Ebp, } }) } diff --git a/libafl_qemu/src/injections.rs b/libafl_qemu/src/injections.rs index 8de806c934..ff96aad6f0 100644 --- a/libafl_qemu/src/injections.rs +++ b/libafl_qemu/src/injections.rs @@ -15,13 +15,14 @@ use std::{ffi::CStr, fmt::Display, fs, os::raw::c_char, path::Path}; use hashbrown::HashMap; use libafl::{inputs::UsesInput, Error}; +use libafl_qemu_sys::GuestAddr; use serde::{Deserialize, Serialize}; #[cfg(not(cpu_target = "hexagon"))] use crate::SYS_execve; use crate::{ - elf::EasyElf, emu::ArchExtras, CallingConvention, Emulator, GuestAddr, Hook, QemuHelper, - QemuHelperTuple, QemuHooks, SyscallHookResult, + elf::EasyElf, emu::ArchExtras, CallingConvention, Hook, Qemu, QemuHelper, QemuHelperTuple, + QemuHooks, SyscallHookResult, }; #[cfg(cpu_target = "hexagon")] /// Hexagon syscalls are not currently supported by the `syscalls` crate, so we just paste this here for now. @@ -211,8 +212,8 @@ impl QemuInjectionHelper { id: usize, parameter: u8, ) { - let emu = hooks.emulator(); - let reg: GuestAddr = emu + let qemu = hooks.qemu(); + let reg: GuestAddr = qemu .current_cpu() .unwrap() .read_function_argument(CallingConvention::Cdecl, parameter) @@ -266,10 +267,10 @@ where where QT: QemuHelperTuple, { - let emu = hooks.emulator(); + let qemu = *hooks.qemu(); let mut libs: Vec = Vec::new(); - for region in emu.mappings() { + for region in qemu.mappings() { if let Some(path) = region.path().map(ToOwned::to_owned) { if !path.is_empty() { LibInfo::add_unique( @@ -300,7 +301,7 @@ where vec![func_pc] } else { libs.iter() - .filter_map(|lib| find_function(emu, &lib.name, name, lib.off).unwrap()) + .filter_map(|lib| find_function(qemu, &lib.name, name, lib.off).unwrap()) .map(|func_pc| { log::info!("Injections: Function {name} found at {func_pc:#x}",); func_pc @@ -393,7 +394,7 @@ where } fn find_function( - emu: &Emulator, + qemu: Qemu, file: &str, function: &str, loadaddr: GuestAddr, @@ -403,7 +404,7 @@ fn find_function( let offset = if loadaddr > 0 { loadaddr } else { - emu.load_addr() + qemu.load_addr() }; Ok(elf.resolve_symbol(function, offset)) } diff --git a/libafl_qemu/src/lib.rs b/libafl_qemu/src/lib.rs index db5bcfaccf..3c21f3ae08 100644 --- a/libafl_qemu/src/lib.rs +++ b/libafl_qemu/src/lib.rs @@ -96,7 +96,7 @@ pub use snapshot::QemuSnapshotHelper; #[cfg(all(emulation_mode = "usermode", not(cpu_target = "hexagon")))] pub mod asan; #[cfg(all(emulation_mode = "usermode", not(cpu_target = "hexagon")))] -pub use asan::{init_with_asan, QemuAsanHelper}; +pub use asan::{init_qemu_with_asan, QemuAsanHelper}; #[cfg(not(cpu_target = "hexagon"))] pub mod calls; @@ -111,6 +111,8 @@ pub use executor::QemuForkExecutor; pub mod emu; pub use emu::*; +pub mod breakpoint; +pub mod command; pub mod sync_backdoor; #[must_use] @@ -155,7 +157,7 @@ pub fn python_module(py: Python, m: &PyModule) -> PyResult<()> { m.add_class::()?; m.add_class::()?; m.add_class::()?; - m.add_class::()?; + m.add_class::()?; Ok(()) } diff --git a/libafl_qemu/src/mips.rs b/libafl_qemu/src/mips.rs index bd39e4da21..96b464d9fd 100644 --- a/libafl_qemu/src/mips.rs +++ b/libafl_qemu/src/mips.rs @@ -7,7 +7,7 @@ use pyo3::prelude::*; pub use strum_macros::EnumIter; pub use syscall_numbers::mips::*; -use crate::{sync_backdoor::SyncBackdoorArgs, CallingConvention}; +use crate::{sync_backdoor::BackdoorArgs, CallingConvention}; /// Registers for the MIPS instruction set. #[derive(IntoPrimitive, TryFromPrimitive, Debug, Clone, Copy, EnumIter)] @@ -49,19 +49,19 @@ pub enum Regs { Pc = 37, } -static SYNC_BACKDOOR_ARCH_REGS: OnceLock> = OnceLock::new(); +static BACKDOOR_ARCH_REGS: OnceLock> = OnceLock::new(); -pub fn get_sync_backdoor_arch_regs() -> &'static EnumMap { - SYNC_BACKDOOR_ARCH_REGS.get_or_init(|| { +pub fn get_backdoor_arch_regs() -> &'static EnumMap { + BACKDOOR_ARCH_REGS.get_or_init(|| { enum_map! { - SyncBackdoorArgs::Ret => Regs::V0, - SyncBackdoorArgs::Cmd => Regs::V0, - SyncBackdoorArgs::Arg1 => Regs::A0, - SyncBackdoorArgs::Arg2 => Regs::A1, - SyncBackdoorArgs::Arg3 => Regs::A2, - SyncBackdoorArgs::Arg4 => Regs::A3, - SyncBackdoorArgs::Arg5 => Regs::T0, - SyncBackdoorArgs::Arg6 => Regs::T1, + BackdoorArgs::Ret => Regs::V0, + BackdoorArgs::Cmd => Regs::V0, + BackdoorArgs::Arg1 => Regs::A0, + BackdoorArgs::Arg2 => Regs::A1, + BackdoorArgs::Arg3 => Regs::A2, + BackdoorArgs::Arg4 => Regs::A3, + BackdoorArgs::Arg5 => Regs::T0, + BackdoorArgs::Arg6 => Regs::T1, } }) } diff --git a/libafl_qemu/src/ppc.rs b/libafl_qemu/src/ppc.rs index 3f4bbcdccc..9fdd4def74 100644 --- a/libafl_qemu/src/ppc.rs +++ b/libafl_qemu/src/ppc.rs @@ -7,7 +7,7 @@ use pyo3::prelude::*; pub use strum_macros::EnumIter; pub use syscall_numbers::powerpc::*; -use crate::{sync_backdoor::SyncBackdoorArgs, CallingConvention}; +use crate::{sync_backdoor::BackdoorArgs, CallingConvention}; /// Registers for the MIPS instruction set. #[derive(IntoPrimitive, TryFromPrimitive, Debug, Clone, Copy, EnumIter)] @@ -88,19 +88,19 @@ pub enum Regs { Fpscr = 70, } -static SYNC_BACKDOOR_ARCH_REGS: OnceLock> = OnceLock::new(); +static BACKDOOR_ARCH_REGS: OnceLock> = OnceLock::new(); -pub fn get_sync_backdoor_arch_regs() -> &'static EnumMap { - SYNC_BACKDOOR_ARCH_REGS.get_or_init(|| { +pub fn get_backdoor_arch_regs() -> &'static EnumMap { + BACKDOOR_ARCH_REGS.get_or_init(|| { enum_map! { - SyncBackdoorArgs::Ret => Regs::R3, - SyncBackdoorArgs::Cmd => Regs::R0, - SyncBackdoorArgs::Arg1 => Regs::R3, - SyncBackdoorArgs::Arg2 => Regs::R4, - SyncBackdoorArgs::Arg3 => Regs::R5, - SyncBackdoorArgs::Arg4 => Regs::R6, - SyncBackdoorArgs::Arg5 => Regs::R7, - SyncBackdoorArgs::Arg6 => Regs::R8, + BackdoorArgs::Ret => Regs::R3, + BackdoorArgs::Cmd => Regs::R0, + BackdoorArgs::Arg1 => Regs::R3, + BackdoorArgs::Arg2 => Regs::R4, + BackdoorArgs::Arg3 => Regs::R5, + BackdoorArgs::Arg4 => Regs::R6, + BackdoorArgs::Arg5 => Regs::R7, + BackdoorArgs::Arg6 => Regs::R8, } }) } diff --git a/libafl_qemu/src/snapshot.rs b/libafl_qemu/src/snapshot.rs index 76460cbfeb..74c7008af2 100644 --- a/libafl_qemu/src/snapshot.rs +++ b/libafl_qemu/src/snapshot.rs @@ -5,6 +5,7 @@ use std::{ }; use libafl::{inputs::UsesInput, state::HasMetadata}; +use libafl_qemu_sys::{GuestAddr, MmapPerms}; use meminterval::{Interval, IntervalTree}; use thread_local::ThreadLocal; @@ -23,18 +24,18 @@ use crate::SYS_mmap2; use crate::SYS_newfstatat; use crate::{ asan::QemuAsanHelper, - emu::{Emulator, MmapPerms, SyscallHookResult}, + emu::SyscallHookResult, helper::{QemuHelper, QemuHelperTuple}, hooks::{Hook, QemuHooks}, - GuestAddr, SYS_fstat, SYS_fstatfs, SYS_futex, SYS_getrandom, SYS_mprotect, SYS_mremap, - SYS_munmap, SYS_pread64, SYS_read, SYS_readlinkat, SYS_statfs, + Qemu, SYS_fstat, SYS_fstatfs, SYS_futex, SYS_getrandom, SYS_mprotect, SYS_mremap, SYS_munmap, + SYS_pread64, SYS_read, SYS_readlinkat, SYS_statfs, }; -// TODO use the functions provided by Emulator +// TODO use the functions provided by Qemu pub const SNAPSHOT_PAGE_SIZE: usize = 4096; pub const SNAPSHOT_PAGE_MASK: GuestAddr = !(SNAPSHOT_PAGE_SIZE as GuestAddr - 1); -pub type StopExecutionCallback = Box; +pub type StopExecutionCallback = Box; #[derive(Debug)] pub struct SnapshotPageInfo { @@ -136,11 +137,11 @@ impl QemuSnapshotHelper { } #[allow(clippy::uninit_assumed_init)] - pub fn snapshot(&mut self, emulator: &Emulator) { - self.brk = emulator.get_brk(); - self.mmap_start = emulator.get_mmap_start(); + pub fn snapshot(&mut self, qemu: Qemu) { + self.brk = qemu.get_brk(); + self.mmap_start = qemu.get_mmap_start(); self.pages.clear(); - for map in emulator.mappings() { + for map in qemu.mappings() { let mut addr = map.start(); while addr < map.end() { let mut info = SnapshotPageInfo { @@ -149,11 +150,11 @@ impl QemuSnapshotHelper { private: map.is_priv(), data: None, }; - if map.flags().is_r() { + if map.flags().readable() { // TODO not just for R pages unsafe { info.data = Some(Box::new(core::mem::zeroed())); - emulator.read_mem(addr, &mut info.data.as_mut().unwrap()[..]); + qemu.read_mem(addr, &mut info.data.as_mut().unwrap()[..]); } } self.pages.insert(addr, info); @@ -208,7 +209,7 @@ impl QemuSnapshotHelper { } } - pub fn reset(&mut self, emulator: &Emulator) { + pub fn reset(&mut self, qemu: Qemu) { { let new_maps = self.new_maps.get_mut().unwrap(); @@ -223,8 +224,8 @@ impl QemuSnapshotHelper { .tree .query_mut(*page..(page + SNAPSHOT_PAGE_SIZE as GuestAddr)) { - if !entry.value.perms.unwrap_or(MmapPerms::None).is_w() { - drop(emulator.mprotect( + if !entry.value.perms.unwrap_or(MmapPerms::None).writable() { + drop(qemu.mprotect( entry.interval.start, (entry.interval.end - entry.interval.start) as usize, MmapPerms::ReadWrite, @@ -239,7 +240,7 @@ impl QemuSnapshotHelper { return true; // Restore later } - unsafe { emulator.write_mem(*page, &data[..]) }; + unsafe { qemu.write_mem(*page, &data[..]) }; } else { panic!("Cannot restored a dirty but unsaved page"); } @@ -249,7 +250,7 @@ impl QemuSnapshotHelper { } } - self.reset_maps(emulator); + self.reset_maps(qemu); // This one is after that we remapped potential regions mapped at snapshot time but unmapped during execution for acc in &mut self.accesses { @@ -259,9 +260,10 @@ impl QemuSnapshotHelper { .tree .query_mut(*page..(page + SNAPSHOT_PAGE_SIZE as GuestAddr)) { - if !entry.value.perms.unwrap_or(MmapPerms::None).is_w() && !entry.value.changed + if !entry.value.perms.unwrap_or(MmapPerms::None).writable() + && !entry.value.changed { - drop(emulator.mprotect( + drop(qemu.mprotect( entry.interval.start, (entry.interval.end - entry.interval.start) as usize, MmapPerms::ReadWrite, @@ -273,7 +275,7 @@ impl QemuSnapshotHelper { if let Some(info) = self.pages.get_mut(page) { // TODO avoid duplicated memcpy if let Some(data) = info.data.as_ref() { - unsafe { emulator.write_mem(*page, &data[..]) }; + unsafe { qemu.write_mem(*page, &data[..]) }; } else { panic!("Cannot restored a dirty but unsaved page"); } @@ -284,7 +286,7 @@ impl QemuSnapshotHelper { for entry in self.maps.tree.query_mut(0..GuestAddr::MAX) { if entry.value.changed { - drop(emulator.mprotect( + drop(qemu.mprotect( entry.interval.start, (entry.interval.end - entry.interval.start) as usize, entry.value.perms.unwrap(), @@ -293,8 +295,8 @@ impl QemuSnapshotHelper { } } - emulator.set_brk(self.brk); - emulator.set_mmap_start(self.mmap_start); + qemu.set_brk(self.brk); + qemu.set_mmap_start(self.mmap_start); } pub fn is_unmap_allowed(&mut self, start: GuestAddr, mut size: usize) -> bool { @@ -331,8 +333,8 @@ impl QemuSnapshotHelper { if self.mmap_limit != 0 && total_size > self.mmap_limit { let mut cb = self.stop_execution.take().unwrap(); - let emu = Emulator::get().unwrap(); - (cb)(self, &emu); + let qemu = Qemu::get().unwrap(); + (cb)(self, &qemu); self.stop_execution = Some(cb); } } @@ -425,7 +427,7 @@ impl QemuSnapshotHelper { } } - pub fn reset_maps(&mut self, emulator: &Emulator) { + pub fn reset_maps(&mut self, qemu: Qemu) { let new_maps = self.new_maps.get_mut().unwrap(); for entry in self.maps.tree.query(0..GuestAddr::MAX) { @@ -440,14 +442,14 @@ impl QemuSnapshotHelper { if found.is_empty() { //panic!("A pre-snapshot memory region was unmapped"); - drop(emulator.map_fixed( + drop(qemu.map_fixed( entry.interval.start, (entry.interval.end - entry.interval.start) as usize, entry.value.perms.unwrap(), )); } else if found.len() == 1 && found[0].0 == *entry.interval { if found[0].1 && found[0].2 != entry.value.perms { - drop(emulator.mprotect( + drop(qemu.mprotect( entry.interval.start, (entry.interval.end - entry.interval.start) as usize, entry.value.perms.unwrap(), @@ -455,7 +457,7 @@ impl QemuSnapshotHelper { } } else { // TODO check for holes - drop(emulator.mprotect( + drop(qemu.mprotect( entry.interval.start, (entry.interval.end - entry.interval.start) as usize, entry.value.perms.unwrap(), @@ -468,7 +470,7 @@ impl QemuSnapshotHelper { } for entry in new_maps.tree.query(0..GuestAddr::MAX) { - drop(emulator.unmap( + drop(qemu.unmap( entry.interval.start, (entry.interval.end - entry.interval.start) as usize, )); @@ -510,11 +512,11 @@ where hooks.after_syscalls(Hook::Function(trace_mmap_snapshot::)); } - fn pre_exec(&mut self, emulator: &Emulator, _input: &S::Input) { + fn pre_exec(&mut self, qemu: Qemu, _input: &S::Input) { if self.empty { - self.snapshot(emulator); + self.snapshot(qemu); } else { - self.reset(emulator); + self.reset(qemu); } } } diff --git a/libafl_qemu/src/sync_backdoor.rs b/libafl_qemu/src/sync_backdoor.rs index a3cfb1c9d9..a5bab0fa15 100644 --- a/libafl_qemu/src/sync_backdoor.rs +++ b/libafl_qemu/src/sync_backdoor.rs @@ -4,18 +4,27 @@ use std::{ }; use enum_map::{enum_map, Enum, EnumMap}; -use libafl::executors::ExitKind; -use num_enum::{TryFromPrimitive, TryFromPrimitiveError}; +use libafl::{ + executors::ExitKind, + state::{HasExecutions, State}, +}; +use libafl_qemu_sys::{GuestAddr, GuestPhysAddr, GuestVirtAddr}; +use num_enum::TryFromPrimitiveError; use crate::{ - get_sync_backdoor_arch_regs, Emulator, GuestAddrKind, GuestPhysAddr, GuestReg, GuestVirtAddr, - Regs, + command::{ + Command, EmulatorMemoryChunk, EndCommand, FilterCommand, InputCommand, LoadCommand, + NativeBackdoorCommand, NativeExitKind, SaveCommand, StartCommand, VersionCommand, + }, + get_backdoor_arch_regs, EmuExitHandler, Emulator, GuestReg, QemuHelperTuple, + QemuInstrumentationAddressRangeFilter, Regs, CPU, }; #[derive(Debug, Clone)] pub enum SyncBackdoorError { UnknownCommand(GuestReg), RegError(String), + VersionDifference(u64), } impl From for SyncBackdoorError { @@ -25,7 +34,7 @@ impl From for SyncBackdoorError { } #[derive(Debug, Clone, Enum)] -pub enum SyncBackdoorArgs { +pub enum BackdoorArgs { Ret, Cmd, Arg1, @@ -36,98 +45,18 @@ pub enum SyncBackdoorArgs { Arg6, } -// TODO: Move in a separate header file to have a central definition of native definitions, -// reusable in targets directly. -#[derive(Debug, Clone, TryFromPrimitive)] -#[repr(u64)] -pub enum NativeSyncBackdoorCommand { - Save = 0, // Save the VM - Load = 1, // Reload the target without ending the run? - InputVirt = 2, // The address is a virtual address using the paging currently running in the VM. - InputPhys = 3, // The address is a physical address - End = 4, // Implies reloading of the target. The first argument gives the exit status. - StartVirt = 5, // Shortcut for Save + InputVirt - StartPhys = 6, // Shortcut for Save + InputPhys -} - -#[derive(Debug, Clone, Enum, TryFromPrimitive)] -#[repr(u64)] -pub enum NativeExitKind { - Unknown = 0, // Should not be used - Ok = 1, // Normal exit - Crash = 2, // Crash reported in the VM -} - static EMU_EXIT_KIND_MAP: OnceLock>> = OnceLock::new(); -impl From> for SyncBackdoorError { - fn from(error: TryFromPrimitiveError) -> Self { +impl From> for SyncBackdoorError { + fn from(error: TryFromPrimitiveError) -> Self { SyncBackdoorError::UnknownCommand(error.number.try_into().unwrap()) } } -#[derive(Debug, Clone)] -pub struct CommandInput { - addr: GuestAddrKind, - max_input_size: GuestReg, -} - -impl CommandInput { - pub fn exec(&self, emu: &Emulator, backdoor: &SyncBackdoor, input: &[u8]) { - match self.addr { - GuestAddrKind::Physical(hwaddr) => unsafe { - #[cfg(emulation_mode = "usermode")] - { - // For now the default behaviour is to fall back to virtual addresses - emu.write_mem(hwaddr.try_into().unwrap(), input); - } - #[cfg(emulation_mode = "systemmode")] - { - emu.write_phys_mem(hwaddr, input); - } - }, - GuestAddrKind::Virtual(vaddr) => unsafe { - emu.write_mem(vaddr.try_into().unwrap(), input); - }, - }; - - backdoor.ret(emu, input.len().try_into().unwrap()).unwrap(); - } -} - -impl Display for CommandInput { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - write!(f, "{} ({:x} max nb bytes)", self.addr, self.max_input_size) - } -} - -#[derive(Debug, Clone)] -pub enum Command { - Save, - Load, - Input(CommandInput), - Start(CommandInput), - Exit(Option), -} - -impl Display for Command { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - match self { - Command::Save => write!(f, "Save VM"), - Command::Load => write!(f, "Reload VM"), - Command::Input(command_input) => write!(f, "Set fuzzing input @{command_input}"), - Command::Start(command_input) => { - write!(f, "Start fuzzing with input @{command_input}") - } - Command::Exit(exit_kind) => write!(f, "Exit of kind {exit_kind:?}"), - } - } -} - #[derive(Debug, Clone)] pub struct SyncBackdoor { command: Command, - arch_regs_map: &'static EnumMap, + arch_regs_map: &'static EnumMap, } impl SyncBackdoor { @@ -136,8 +65,13 @@ impl SyncBackdoor { &self.command } - pub fn ret(&self, emu: &Emulator, value: GuestReg) -> Result<(), SyncBackdoorError> { - Ok(emu.write_reg(self.arch_regs_map[SyncBackdoorArgs::Ret], value)?) + pub fn ret(&self, cpu: &CPU, value: GuestReg) -> Result<(), SyncBackdoorError> { + Ok(cpu.write_reg(self.arch_regs_map[BackdoorArgs::Ret], value)?) + } + + #[must_use] + pub fn ret_reg(&self) -> Regs { + self.arch_regs_map[BackdoorArgs::Ret] } } @@ -147,54 +81,63 @@ impl Display for SyncBackdoor { } } -impl TryFrom<&Emulator> for SyncBackdoor { +impl TryFrom<&Emulator> for SyncBackdoor +where + E: EmuExitHandler, + QT: QemuHelperTuple, + S: State + HasExecutions, +{ type Error = SyncBackdoorError; - fn try_from(emu: &Emulator) -> Result { - let arch_regs_map: &'static EnumMap = get_sync_backdoor_arch_regs(); - let cmd_id: GuestReg = - emu.read_reg::(arch_regs_map[SyncBackdoorArgs::Cmd])?; + #[allow(clippy::too_many_lines)] + fn try_from(emu: &Emulator) -> Result { + let arch_regs_map: &'static EnumMap = get_backdoor_arch_regs(); + let cmd_id: GuestReg = emu + .qemu() + .read_reg::(arch_regs_map[BackdoorArgs::Cmd])?; Ok(match u64::from(cmd_id).try_into()? { - NativeSyncBackdoorCommand::Save => SyncBackdoor { - command: Command::Save, + NativeBackdoorCommand::Save => SyncBackdoor { + command: Command::SaveCommand(SaveCommand), arch_regs_map, }, - NativeSyncBackdoorCommand::Load => SyncBackdoor { - command: Command::Load, + NativeBackdoorCommand::Load => SyncBackdoor { + command: Command::LoadCommand(LoadCommand), arch_regs_map, }, - NativeSyncBackdoorCommand::InputVirt => { + NativeBackdoorCommand::InputVirt => { let virt_addr: GuestVirtAddr = - emu.read_reg(arch_regs_map[SyncBackdoorArgs::Arg1])?; + emu.qemu().read_reg(arch_regs_map[BackdoorArgs::Arg1])?; let max_input_size: GuestReg = - emu.read_reg(arch_regs_map[SyncBackdoorArgs::Arg2])?; + emu.qemu().read_reg(arch_regs_map[BackdoorArgs::Arg2])?; SyncBackdoor { - command: Command::Input(CommandInput { - addr: GuestAddrKind::Virtual(virt_addr), + command: Command::InputCommand(InputCommand::new(EmulatorMemoryChunk::virt( + virt_addr, max_input_size, - }), + emu.qemu().current_cpu().unwrap().clone(), + ))), arch_regs_map, } } - NativeSyncBackdoorCommand::InputPhys => { + NativeBackdoorCommand::InputPhys => { let phys_addr: GuestPhysAddr = - emu.read_reg(arch_regs_map[SyncBackdoorArgs::Arg1])?; + emu.qemu().read_reg(arch_regs_map[BackdoorArgs::Arg1])?; let max_input_size: GuestReg = - emu.read_reg(arch_regs_map[SyncBackdoorArgs::Arg2])?; + emu.qemu().read_reg(arch_regs_map[BackdoorArgs::Arg2])?; SyncBackdoor { - command: Command::Input(CommandInput { - addr: GuestAddrKind::Physical(phys_addr), + command: Command::InputCommand(InputCommand::new(EmulatorMemoryChunk::phys( + phys_addr, max_input_size, - }), + Some(emu.qemu().current_cpu().unwrap().clone()), + ))), arch_regs_map, } } - NativeSyncBackdoorCommand::End => { + NativeBackdoorCommand::End => { let native_exit_kind: GuestReg = - emu.read_reg(arch_regs_map[SyncBackdoorArgs::Arg1])?; + emu.qemu().read_reg(arch_regs_map[BackdoorArgs::Arg1])?; let native_exit_kind: Result = u64::from(native_exit_kind).try_into(); @@ -209,35 +152,61 @@ impl TryFrom<&Emulator> for SyncBackdoor { }); SyncBackdoor { - command: Command::Exit(exit_kind), + command: Command::EndCommand(EndCommand::new(exit_kind)), arch_regs_map, } } - NativeSyncBackdoorCommand::StartPhys => { + NativeBackdoorCommand::StartPhys => { let input_phys_addr: GuestPhysAddr = - emu.read_reg(arch_regs_map[SyncBackdoorArgs::Arg1])?; + emu.qemu().read_reg(arch_regs_map[BackdoorArgs::Arg1])?; let max_input_size: GuestReg = - emu.read_reg(arch_regs_map[SyncBackdoorArgs::Arg2])?; + emu.qemu().read_reg(arch_regs_map[BackdoorArgs::Arg2])?; SyncBackdoor { - command: Command::Start(CommandInput { - addr: GuestAddrKind::Physical(input_phys_addr), + command: Command::StartCommand(StartCommand::new(EmulatorMemoryChunk::phys( + input_phys_addr, max_input_size, - }), + Some(emu.qemu().current_cpu().unwrap().clone()), + ))), arch_regs_map, } } - NativeSyncBackdoorCommand::StartVirt => { + NativeBackdoorCommand::StartVirt => { let input_virt_addr: GuestVirtAddr = - emu.read_reg(arch_regs_map[SyncBackdoorArgs::Arg1])?; + emu.qemu().read_reg(arch_regs_map[BackdoorArgs::Arg1])?; let max_input_size: GuestReg = - emu.read_reg(arch_regs_map[SyncBackdoorArgs::Arg2])?; + emu.qemu().read_reg(arch_regs_map[BackdoorArgs::Arg2])?; SyncBackdoor { - command: Command::Start(CommandInput { - addr: GuestAddrKind::Virtual(input_virt_addr), + command: Command::StartCommand(StartCommand::new(EmulatorMemoryChunk::virt( + input_virt_addr, max_input_size, - }), + emu.qemu().current_cpu().unwrap().clone(), + ))), + arch_regs_map, + } + } + NativeBackdoorCommand::Version => { + let client_version = emu.qemu().read_reg(arch_regs_map[BackdoorArgs::Arg1])?; + + SyncBackdoor { + command: Command::VersionCommand(VersionCommand::new(client_version)), + arch_regs_map, + } + } + NativeBackdoorCommand::VaddrFilterAllowRange => { + let vaddr_start: GuestAddr = + emu.qemu().read_reg(arch_regs_map[BackdoorArgs::Arg1])?; + let vaddr_end: GuestAddr = + emu.qemu().read_reg(arch_regs_map[BackdoorArgs::Arg2])?; + + SyncBackdoor { + command: Command::AddressRangeFilterCommand(FilterCommand::new( + #[allow(clippy::single_range_in_vec_init)] + QemuInstrumentationAddressRangeFilter::AllowList(vec![ + vaddr_start..vaddr_end, + ]), + )), arch_regs_map, } } diff --git a/libafl_qemu/src/x86_64.rs b/libafl_qemu/src/x86_64.rs index 3003a922f1..d6ac5aac0d 100644 --- a/libafl_qemu/src/x86_64.rs +++ b/libafl_qemu/src/x86_64.rs @@ -8,7 +8,7 @@ use pyo3::prelude::*; pub use strum_macros::EnumIter; pub use syscall_numbers::x86_64::*; -use crate::{sync_backdoor::SyncBackdoorArgs, CallingConvention}; +use crate::{sync_backdoor::BackdoorArgs, CallingConvention}; #[derive(IntoPrimitive, TryFromPrimitive, Debug, Clone, Copy, EnumIter)] #[repr(i32)] @@ -33,19 +33,19 @@ pub enum Regs { Rflags = 17, } -static SYNC_BACKDOOR_ARCH_REGS: OnceLock> = OnceLock::new(); +static BACKDOOR_ARCH_REGS: OnceLock> = OnceLock::new(); -pub fn get_sync_backdoor_arch_regs() -> &'static EnumMap { - SYNC_BACKDOOR_ARCH_REGS.get_or_init(|| { +pub fn get_backdoor_arch_regs() -> &'static EnumMap { + BACKDOOR_ARCH_REGS.get_or_init(|| { enum_map! { - SyncBackdoorArgs::Ret => Regs::Rax, - SyncBackdoorArgs::Cmd => Regs::Rax, - SyncBackdoorArgs::Arg1 => Regs::Rdi, - SyncBackdoorArgs::Arg2 => Regs::Rsi, - SyncBackdoorArgs::Arg3 => Regs::Rdx, - SyncBackdoorArgs::Arg4 => Regs::R10, - SyncBackdoorArgs::Arg5 => Regs::R8, - SyncBackdoorArgs::Arg6 => Regs::R9, + BackdoorArgs::Ret => Regs::Rax, + BackdoorArgs::Cmd => Regs::Rax, + BackdoorArgs::Arg1 => Regs::Rdi, + BackdoorArgs::Arg2 => Regs::Rsi, + BackdoorArgs::Arg3 => Regs::Rdx, + BackdoorArgs::Arg4 => Regs::R10, + BackdoorArgs::Arg5 => Regs::R8, + BackdoorArgs::Arg6 => Regs::R9, } }) } diff --git a/libafl_sugar/src/qemu.rs b/libafl_sugar/src/qemu.rs index f3330d7522..30c00f911a 100644 --- a/libafl_sugar/src/qemu.rs +++ b/libafl_sugar/src/qemu.rs @@ -34,7 +34,7 @@ use libafl_bolts::{ tuples::{tuple_list, Merge}, AsSlice, }; -pub use libafl_qemu::emu::Emulator; +pub use libafl_qemu::emu::Qemu; #[cfg(not(any(feature = "mips", feature = "hexagon")))] use libafl_qemu::QemuCmpLogHelper; use libafl_qemu::{edges, QemuEdgeCoverageHelper, QemuExecutor, QemuHooks}; @@ -118,7 +118,7 @@ where { /// Run the fuzzer #[allow(clippy::too_many_lines, clippy::similar_names)] - pub fn run(&mut self, emulator: &Emulator) { + pub fn run(&mut self, qemu: &Qemu) { let conf = match self.configuration.as_ref() { Some(name) => EventConfig::from_name(name), None => EventConfig::AlwaysUnique, @@ -214,7 +214,7 @@ where if self.use_cmplog.unwrap_or(false) { let mut hooks = QemuHooks::new( - emulator.clone(), + *qemu, #[cfg(not(any(feature = "mips", feature = "hexagon")))] tuple_list!( QemuEdgeCoverageHelper::default(), @@ -325,10 +325,8 @@ where } } } else { - let mut hooks = QemuHooks::new( - emulator.clone(), - tuple_list!(QemuEdgeCoverageHelper::default()), - ); + let mut hooks = + QemuHooks::new(*qemu, tuple_list!(QemuEdgeCoverageHelper::default())); let mut executor = QemuExecutor::new( &mut hooks, @@ -445,7 +443,7 @@ pub mod pybind { use std::path::PathBuf; use libafl_bolts::core_affinity::Cores; - use libafl_qemu::emu::pybind::Emulator; + use libafl_qemu::emu::pybind::Qemu; use pyo3::{prelude::*, types::PyBytes}; use crate::qemu; @@ -492,7 +490,7 @@ pub mod pybind { /// Run the fuzzer #[allow(clippy::needless_pass_by_value)] - pub fn run(&self, emulator: &Emulator, harness: PyObject) { + pub fn run(&self, qemu: &Qemu, harness: PyObject) { qemu::QemuBytesCoverageSugar::builder() .input_dirs(&self.input_dirs) .output_dir(self.output_dir.clone()) @@ -511,7 +509,7 @@ pub mod pybind { .tokens_file(self.tokens_file.clone()) .iterations(self.iterations) .build() - .run(&emulator.emu); + .run(&qemu.qemu); } }