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 <tokazerkje@outlook.com>
Co-authored-by: Dominik Maier <domenukk@gmail.com>
Co-authored-by: lazymio <mio@lazym.io>
Co-authored-by: Bet4 <0xbet4@gmail.com>
Co-authored-by: mkravchik <mkravchik@hotmail.com>
This commit is contained in:
Romain Malmain 2024-03-22 18:03:29 +01:00 committed by GitHub
parent 50843b19d1
commit 44c841ffb1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
69 changed files with 5211 additions and 1683 deletions

View File

@ -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<String> = 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(),

View File

@ -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<String> = 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(),

View File

@ -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,

View File

@ -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,

View File

@ -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::<Vec<(String, String)>>()
}
fn start_pc(emu: &Emulator) -> Result<GuestAddr, Error> {
fn start_pc(qemu: &Qemu) -> Result<GuestAddr, Error> {
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<QemuInstrumentationAddressRangeFilter, Error> {
fn coverage_filter(&self, qemu: &Qemu) -> Result<QemuInstrumentationAddressRangeFilter, Error> {
/* 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);

View File

@ -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<Harness, Error> {
let input_addr = emu
pub fn new(qemu: &Qemu) -> Result<Harness, Error> {
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(())
}

View File

@ -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<M> =
#[derive(TypedBuilder)]
pub struct Instance<'a, M: Monitor> {
options: &'a FuzzerOptions,
emu: &'a Emulator,
qemu: &'a Qemu,
mgr: ClientMgr<M>,
core_id: CoreId,
extra_tokens: Option<Vec<String>>,
@ -72,7 +72,7 @@ impl<'a, M: Monitor> Instance<'a, M> {
where
QT: QemuHelperTuple<ClientState> + 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

View File

@ -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 = "*"

View File

@ -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<String> = 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<u32, String> { 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(

View File

@ -0,0 +1 @@
*.qcow2

View File

@ -0,0 +1,21 @@
[package]
name = "qemu_systemmode_breakpoints"
version = "0.11.1"
authors = ["Andrea Fioraldi <andreafioraldi@gmail.com>", "Dominik Maier <domenukk@gmail.com>", "Romain Malmain <romain.malmain@pm.me>"]
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 = "*"

View File

@ -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.

View File

@ -0,0 +1 @@
<1F>y¯»Jv<>ֵָ l<>ֵp†אZGײ…rֶsYֿסֻ—CMRױ÷}קS7;Iֹo¿7ָ6זlע1<D7A2>˜1R¯¬ף׳ָ<>p¬ד¯>G÷&_¬<5F>˜|1½¾;ג¥ינװסr<D7A1>ח<EFBFBD>o4ג¼ֶ¯ֽU<D6BD>ׁE<`ש÷²÷ּLע"<22>ף6Vֶ·k<1D>4/³"<>צtכקֺf7ײF¯°ְ<C2B0><D6B0>d]<5D>®%ץ|8•#וּNUƒnש8<>%¼4ַo־<6F>ֻ—

Binary file not shown.

View File

@ -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

View File

@ -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);
}

View File

@ -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")
}

View File

@ -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);
}

View File

@ -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::<usize>(&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<String> = 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:?}"),
}
}

View File

@ -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!");
}

View File

@ -0,0 +1 @@
*.qcow2

View File

@ -0,0 +1,21 @@
[package]
name = "qemu_systemmode_sync_backdoor"
version = "0.11.1"
authors = ["Andrea Fioraldi <andreafioraldi@gmail.com>", "Dominik Maier <domenukk@gmail.com>", "Romain Malmain <romain.malmain@pm.me>"]
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 = "*"

View File

@ -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.

View File

@ -0,0 +1 @@
<1F>y¯»Jv<>ֵָ l<>ֵp†אZGײ…rֶsYֿסֻ—CMRױ÷}קS7;Iֹo¿7ָ6זlע1<D7A2>˜1R¯¬ף׳ָ<>p¬ד¯>G÷&_¬<5F>˜|1½¾;ג¥ינװסr<D7A1>ח<EFBFBD>o4ג¼ֶ¯ֽU<D6BD>ׁE<`ש÷²÷ּLע"<22>ף6Vֶ·k<1D>4/³"<>צtכקֺf7ײF¯°ְ<C2B0><D6B0>d]<5D>®%ץ|8•#וּNUƒnש8<>%¼4ַo־<6F>ֻ—

Binary file not shown.

View File

@ -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

View File

@ -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);
}

View File

@ -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")
}

View File

@ -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);
}

View File

@ -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::<usize>(&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<String> = 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<FastSnapshotManager> =
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:?}"),
}
}

View File

@ -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!");
}

View File

@ -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");
}

View File

@ -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...
//! }
//!

View File

@ -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"

View File

@ -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();

View File

@ -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")

View File

@ -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 }

View File

@ -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<PyObject> for MmapPerms {
fn into_py(self, py: Python) -> PyObject {
let n: i32 = self.into();
n.into_py(py)
}
}

View File

@ -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;
}

View File

@ -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,
}

View File

@ -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 <stdint.h>
#include <intsafe.h>
typedef UINT64 libafl_word;
#define LIBAFL_CALLING_CONVENTION __fastcall
#else
#ifdef __x86_64__
#include <stdint.h>
typedef uint64_t libafl_word;
#define LIBAFL_CALLING_CONVENTION __attribute__(())
#endif
#ifdef __arm__
#include <stdint.h>
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

View File

@ -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

View File

@ -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<EnumMap<SyncBackdoorArgs, Regs>> = OnceLock::new();
static BACKDOOR_ARCH_REGS: OnceLock<EnumMap<BackdoorArgs, Regs>> = OnceLock::new();
pub fn get_sync_backdoor_arch_regs() -> &'static EnumMap<SyncBackdoorArgs, Regs> {
SYNC_BACKDOOR_ARCH_REGS.get_or_init(|| {
pub fn get_backdoor_arch_regs() -> &'static EnumMap<BackdoorArgs, Regs> {
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,
}
})
}

View File

@ -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<EnumMap<SyncBackdoorArgs, Regs>> = OnceLock::new();
static BACKDOOR_ARCH_REGS: OnceLock<EnumMap<BackdoorArgs, Regs>> = OnceLock::new();
pub fn get_sync_backdoor_arch_regs() -> &'static EnumMap<SyncBackdoorArgs, Regs> {
SYNC_BACKDOOR_ARCH_REGS.get_or_init(|| {
pub fn get_backdoor_arch_regs() -> &'static EnumMap<BackdoorArgs, Regs> {
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,
}
})
}

View File

@ -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<dyn FnMut(&AsanGiovese, &Emulator, GuestAddr, AsanError)>;
pub type AsanErrorCallback = Box<dyn FnMut(&AsanGiovese, Qemu, GuestAddr, AsanError)>;
#[derive(Debug, Clone)]
pub struct AllocTreeItem {
@ -209,7 +210,7 @@ impl AsanGiovese {
}
#[must_use]
fn new(emu: &Emulator) -> Pin<Box<Self>> {
fn new(emu: Qemu) -> Pin<Box<Self>> {
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<String>,
env: &mut [(String, String)],
) -> Result<(Emulator, Pin<Box<AsanGiovese>>), EmuError> {
) -> Result<(Qemu, Pin<Box<AsanGiovese>>), 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) {
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, 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, 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, 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, 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, 1));
.report_or_crash(qemu, pc, AsanError::Read(addr, size));
}
}
pub fn read_2(&mut self, emulator: &Emulator, pc: GuestAddr, addr: GuestAddr) {
if self.enabled() && AsanGiovese::is_invalid_access_2(emulator, addr) {
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, 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, 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, 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, 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, 2));
.report_or_crash(qemu, pc, AsanError::Write(addr, size));
}
}
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_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_n(&mut self, emulator: &Emulator, pc: GuestAddr, addr: GuestAddr, size: usize) {
if self.enabled() && AsanGiovese::is_invalid_access(emulator, addr, size) {
self.rt
.report_or_crash(emulator, 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_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_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_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_n(&mut self, emulator: &Emulator, pc: GuestAddr, addr: GuestAddr, size: usize) {
if self.enabled() && AsanGiovese::is_invalid_access(emulator, addr, size) {
self.rt
.report_or_crash(emulator, 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<QemuInstrumentationAddressRangeFilter> for QemuAsanHelper {
impl<S: UsesInput> HasInstrumentationFilter<QemuInstrumentationAddressRangeFilter, S>
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<OT>(
&mut self,
emulator: &Emulator,
qemu: Qemu,
_input: &S::Input,
_observers: &mut OT,
exit_kind: &mut ExitKind,
) where
OT: ObserversTuple<S>,
{
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<S>,
{
let emu = hooks.emulator().clone();
let qemu = *hooks.qemu();
let h = hooks.match_helper_mut::<QemuAsanHelper>().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<QT, S>(
@ -1047,9 +1036,9 @@ pub fn trace_read1_asan<QT, S>(
S: UsesInput,
QT: QemuHelperTuple<S>,
{
let emulator = hooks.emulator().clone();
let qemu = *hooks.qemu();
let h = hooks.match_helper_mut::<QemuAsanHelper>().unwrap();
h.read_1(&emulator, id as GuestAddr, addr);
h.read_1(qemu, id as GuestAddr, addr);
}
pub fn trace_read2_asan<QT, S>(
@ -1061,9 +1050,9 @@ pub fn trace_read2_asan<QT, S>(
S: UsesInput,
QT: QemuHelperTuple<S>,
{
let emulator = hooks.emulator().clone();
let qemu = *hooks.qemu();
let h = hooks.match_helper_mut::<QemuAsanHelper>().unwrap();
h.read_2(&emulator, id as GuestAddr, addr);
h.read_2(qemu, id as GuestAddr, addr);
}
pub fn trace_read4_asan<QT, S>(
@ -1075,9 +1064,9 @@ pub fn trace_read4_asan<QT, S>(
S: UsesInput,
QT: QemuHelperTuple<S>,
{
let emulator = hooks.emulator().clone();
let qemu = *hooks.qemu();
let h = hooks.match_helper_mut::<QemuAsanHelper>().unwrap();
h.read_4(&emulator, id as GuestAddr, addr);
h.read_4(qemu, id as GuestAddr, addr);
}
pub fn trace_read8_asan<QT, S>(
@ -1089,9 +1078,9 @@ pub fn trace_read8_asan<QT, S>(
S: UsesInput,
QT: QemuHelperTuple<S>,
{
let emulator = hooks.emulator().clone();
let qemu = *hooks.qemu();
let h = hooks.match_helper_mut::<QemuAsanHelper>().unwrap();
h.read_8(&emulator, id as GuestAddr, addr);
h.read_8(qemu, id as GuestAddr, addr);
}
pub fn trace_read_n_asan<QT, S>(
@ -1104,9 +1093,9 @@ pub fn trace_read_n_asan<QT, S>(
S: UsesInput,
QT: QemuHelperTuple<S>,
{
let emulator = hooks.emulator().clone();
let qemu = *hooks.qemu();
let h = hooks.match_helper_mut::<QemuAsanHelper>().unwrap();
h.read_n(&emulator, id as GuestAddr, addr, size);
h.read_n(qemu, id as GuestAddr, addr, size);
}
pub fn trace_write1_asan<QT, S>(
@ -1118,9 +1107,9 @@ pub fn trace_write1_asan<QT, S>(
S: UsesInput,
QT: QemuHelperTuple<S>,
{
let emulator = hooks.emulator().clone();
let qemu = *hooks.qemu();
let h = hooks.match_helper_mut::<QemuAsanHelper>().unwrap();
h.write_1(&emulator, id as GuestAddr, addr);
h.write_1(qemu, id as GuestAddr, addr);
}
pub fn trace_write2_asan<QT, S>(
@ -1132,9 +1121,9 @@ pub fn trace_write2_asan<QT, S>(
S: UsesInput,
QT: QemuHelperTuple<S>,
{
let emulator = hooks.emulator().clone();
let qemu = *hooks.qemu();
let h = hooks.match_helper_mut::<QemuAsanHelper>().unwrap();
h.write_2(&emulator, id as GuestAddr, addr);
h.write_2(qemu, id as GuestAddr, addr);
}
pub fn trace_write4_asan<QT, S>(
@ -1146,9 +1135,9 @@ pub fn trace_write4_asan<QT, S>(
S: UsesInput,
QT: QemuHelperTuple<S>,
{
let emulator = hooks.emulator().clone();
let qemu = *hooks.qemu();
let h = hooks.match_helper_mut::<QemuAsanHelper>().unwrap();
h.write_4(&emulator, id as GuestAddr, addr);
h.write_4(qemu, id as GuestAddr, addr);
}
pub fn trace_write8_asan<QT, S>(
@ -1160,9 +1149,9 @@ pub fn trace_write8_asan<QT, S>(
S: UsesInput,
QT: QemuHelperTuple<S>,
{
let emulator = hooks.emulator().clone();
let qemu = *hooks.qemu();
let h = hooks.match_helper_mut::<QemuAsanHelper>().unwrap();
h.write_8(&emulator, id as GuestAddr, addr);
h.write_8(qemu, id as GuestAddr, addr);
}
pub fn trace_write_n_asan<QT, S>(
@ -1175,9 +1164,9 @@ pub fn trace_write_n_asan<QT, S>(
S: UsesInput,
QT: QemuHelperTuple<S>,
{
let emulator = hooks.emulator().clone();
let qemu = *hooks.qemu();
let h = hooks.match_helper_mut::<QemuAsanHelper>().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<QT, S>(
@ -1208,9 +1197,9 @@ pub fn trace_write1_asan_snapshot<QT, S>(
QT: QemuHelperTuple<S>,
{
if id != 0 {
let emulator = hooks.emulator().clone();
let qemu = *hooks.qemu();
let h = hooks.match_helper_mut::<QemuAsanHelper>().unwrap();
h.write_1(&emulator, id as GuestAddr, addr);
h.write_1(qemu, id as GuestAddr, addr);
}
let h = hooks.match_helper_mut::<QemuSnapshotHelper>().unwrap();
h.access(addr, 1);
@ -1226,9 +1215,9 @@ pub fn trace_write2_asan_snapshot<QT, S>(
QT: QemuHelperTuple<S>,
{
if id != 0 {
let emulator = hooks.emulator().clone();
let qemu = *hooks.qemu();
let h = hooks.match_helper_mut::<QemuAsanHelper>().unwrap();
h.write_2(&emulator, id as GuestAddr, addr);
h.write_2(qemu, id as GuestAddr, addr);
}
let h = hooks.match_helper_mut::<QemuSnapshotHelper>().unwrap();
h.access(addr, 2);
@ -1244,9 +1233,9 @@ pub fn trace_write4_asan_snapshot<QT, S>(
QT: QemuHelperTuple<S>,
{
if id != 0 {
let emulator = hooks.emulator().clone();
let qemu = *hooks.qemu();
let h = hooks.match_helper_mut::<QemuAsanHelper>().unwrap();
h.write_4(&emulator, id as GuestAddr, addr);
h.write_4(qemu, id as GuestAddr, addr);
}
let h = hooks.match_helper_mut::<QemuSnapshotHelper>().unwrap();
h.access(addr, 4);
@ -1262,9 +1251,9 @@ pub fn trace_write8_asan_snapshot<QT, S>(
QT: QemuHelperTuple<S>,
{
if id != 0 {
let emulator = hooks.emulator().clone();
let qemu = *hooks.qemu();
let h = hooks.match_helper_mut::<QemuAsanHelper>().unwrap();
h.write_8(&emulator, id as GuestAddr, addr);
h.write_8(qemu, id as GuestAddr, addr);
}
let h = hooks.match_helper_mut::<QemuSnapshotHelper>().unwrap();
h.access(addr, 8);
@ -1281,9 +1270,9 @@ pub fn trace_write_n_asan_snapshot<QT, S>(
QT: QemuHelperTuple<S>,
{
if id != 0 {
let emulator = hooks.emulator().clone();
let qemu = *hooks.qemu();
let h = hooks.match_helper_mut::<QemuAsanHelper>().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::<QemuSnapshotHelper>().unwrap();
h.access(addr, size);
@ -1308,16 +1297,16 @@ where
QT: QemuHelperTuple<S>,
{
if sys_num == QASAN_FAKESYS_NR {
let emulator = hooks.emulator().clone();
let qemu = *hooks.qemu();
let h = hooks.match_helper_mut::<QemuAsanHelper>().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()
);
}

View File

@ -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<Command>,
disable_on_trigger: bool,
enabled: bool,
}
impl Hash for Breakpoint {
fn hash<H: Hasher>(&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<GuestAddr> 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()
}
}

View File

@ -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<S>;
// Frowarded from the `QemuCallTracerHelper`
fn pre_exec<I>(&mut self, _emulator: &Emulator, _input: &I)
fn pre_exec<I>(&mut self, _qemu: Qemu, _input: &I)
where
I: Input,
{
@ -50,7 +51,7 @@ pub trait CallTraceCollector: 'static + Debug {
fn post_exec<OT, S>(
&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<S>;
fn pre_exec_all<I>(&mut self, _emulator: &Emulator, input: &I)
fn pre_exec_all<I>(&mut self, _qemu: Qemu, input: &I)
where
I: Input;
fn post_exec_all<OT, S>(
&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<I>(&mut self, _emulator: &Emulator, _input: &I)
fn pre_exec_all<I>(&mut self, _qemu: Qemu, _input: &I)
where
I: Input,
{
@ -130,7 +131,7 @@ impl CallTraceCollectorTuple for () {
fn post_exec_all<OT, S>(
&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<I>(&mut self, emulator: &Emulator, input: &I)
fn pre_exec_all<I>(&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<OT, S>(
&mut self,
emulator: &Emulator,
qemu: Qemu,
input: &S::Input,
observers: &mut OT,
exit_kind: &mut ExitKind,
@ -208,8 +209,8 @@ where
OT: ObserversTuple<S>,
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<S>,
{
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::<Self>() {
#[allow(unused_mut)]
@ -383,9 +384,11 @@ where
}
}
impl<T> HasInstrumentationFilter<QemuInstrumentationAddressRangeFilter> for QemuCallTracerHelper<T>
impl<T, S> HasInstrumentationFilter<QemuInstrumentationAddressRangeFilter, S>
for QemuCallTracerHelper<T>
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<OT>(
&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<I>(&mut self, _emulator: &Emulator, _input: &I)
fn pre_exec<I>(&mut self, _qemu: Qemu, _input: &I)
where
I: Input,
{
@ -507,7 +507,7 @@ impl CallTraceCollector for OnCrashBacktraceCollector {
fn post_exec<OT, S>(
&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<I>(&mut self, _emulator: &Emulator, _input: &I)
fn pre_exec<I>(&mut self, _qemu: Qemu, _input: &I)
where
I: Input,
{

View File

@ -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<QemuInstrumentationAddressRangeFilter> for QemuCmpLogHelper {
impl<S: UsesInput> HasInstrumentationFilter<QemuInstrumentationAddressRangeFilter, S>
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::<Self>() {
#[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<QemuInstrumentationAddressRangeFilter> for QemuCmpLogRoutinesHelper {
impl<S: UsesInput> HasInstrumentationFilter<QemuInstrumentationAddressRangeFilter, S>
for QemuCmpLogRoutinesHelper
{
fn filter(&self) -> &QemuInstrumentationAddressRangeFilter {
&self.filter
}

660
libafl_qemu/src/command.rs Normal file
View File

@ -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<QT, S, E>
where
QT: QemuHelperTuple<S>,
S: State + HasExecutions,
E: EmuExitHandler<QT, S>,
{
/// 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<QT, S, E>,
qemu_executor_state: &mut QemuExecutorState<QT, S>,
input: &BytesInput,
ret_reg: Option<Regs>,
) -> Result<InnerHandlerResult, HandlerError>;
}
#[cfg(emulation_mode = "systemmode")]
pub type PagingFilterCommand = FilterCommand<QemuInstrumentationPagingFilter>;
pub type AddressRangeFilterCommand = FilterCommand<QemuInstrumentationAddressRangeFilter>;
#[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<SM, QT, S> IsCommand<QT, S, StdEmuExitHandler<SM>> for Command
where
SM: IsSnapshotManager,
QT: QemuHelperTuple<S> + StdInstrumentationFilter<S> + Debug,
S: State + HasExecutions,
{
fn usable_at_runtime(&self) -> bool {
match self {
Command::SaveCommand(cmd) => {
<SaveCommand as IsCommand<QT, S, StdEmuExitHandler<SM>>>::usable_at_runtime(cmd)
}
Command::LoadCommand(cmd) => {
<LoadCommand as IsCommand<QT, S, StdEmuExitHandler<SM>>>::usable_at_runtime(cmd)
}
Command::InputCommand(cmd) => {
<InputCommand as IsCommand<QT, S, StdEmuExitHandler<SM>>>::usable_at_runtime(cmd)
}
Command::StartCommand(cmd) => {
<StartCommand as IsCommand<QT, S, StdEmuExitHandler<SM>>>::usable_at_runtime(cmd)
}
Command::EndCommand(cmd) => {
<EndCommand as IsCommand<QT, S, StdEmuExitHandler<SM>>>::usable_at_runtime(cmd)
}
Command::VersionCommand(cmd) => {
<VersionCommand as IsCommand<QT, S, StdEmuExitHandler<SM>>>::usable_at_runtime(cmd)
}
#[cfg(emulation_mode = "systemmode")]
Command::PagingFilterCommand(cmd) => {
<PagingFilterCommand as IsCommand<QT, S, StdEmuExitHandler<SM>>>::usable_at_runtime(
cmd,
)
}
Command::AddressRangeFilterCommand(cmd) => <AddressRangeFilterCommand as IsCommand<
QT,
S,
StdEmuExitHandler<SM>,
>>::usable_at_runtime(cmd),
}
}
fn run(
&self,
emu: &Emulator<QT, S, StdEmuExitHandler<SM>>,
qemu_executor_state: &mut QemuExecutorState<QT, S>,
input: &BytesInput,
ret_reg: Option<Regs>,
) -> Result<InnerHandlerResult, HandlerError> {
match self {
Command::SaveCommand(cmd) => {
<SaveCommand as IsCommand<QT, S, StdEmuExitHandler<SM>>>::run(
cmd,
emu,
qemu_executor_state,
input,
ret_reg,
)
}
Command::LoadCommand(cmd) => {
<LoadCommand as IsCommand<QT, S, StdEmuExitHandler<SM>>>::run(
cmd,
emu,
qemu_executor_state,
input,
ret_reg,
)
}
Command::InputCommand(cmd) => <InputCommand as IsCommand<
QT,
S,
StdEmuExitHandler<SM>,
>>::run(
cmd, emu, qemu_executor_state, input, ret_reg
),
Command::StartCommand(cmd) => <StartCommand as IsCommand<
QT,
S,
StdEmuExitHandler<SM>,
>>::run(
cmd, emu, qemu_executor_state, input, ret_reg
),
Command::EndCommand(cmd) => {
<EndCommand as IsCommand<QT, S, StdEmuExitHandler<SM>>>::run(
cmd,
emu,
qemu_executor_state,
input,
ret_reg,
)
}
Command::VersionCommand(cmd) => <VersionCommand as IsCommand<
QT,
S,
StdEmuExitHandler<SM>,
>>::run(
cmd, emu, qemu_executor_state, input, ret_reg
),
#[cfg(emulation_mode = "systemmode")]
Command::PagingFilterCommand(cmd) => <PagingFilterCommand as IsCommand<
QT,
S,
StdEmuExitHandler<SM>,
>>::run(
cmd, emu, qemu_executor_state, input, ret_reg
),
Command::AddressRangeFilterCommand(cmd) => {
<AddressRangeFilterCommand as IsCommand<QT, S, StdEmuExitHandler<SM>>>::run(
cmd,
emu,
qemu_executor_state,
input,
ret_reg,
)
}
}
}
}
#[derive(Debug, Clone)]
pub struct EmulatorMemoryChunk {
addr: GuestAddrKind,
size: GuestReg,
cpu: Option<CPU>,
}
#[derive(Debug, Clone)]
pub struct SaveCommand;
impl<SM, QT, S> IsCommand<QT, S, StdEmuExitHandler<SM>> for SaveCommand
where
SM: IsSnapshotManager,
QT: QemuHelperTuple<S> + StdInstrumentationFilter<S> + Debug,
S: State + HasExecutions,
{
fn usable_at_runtime(&self) -> bool {
false
}
fn run(
&self,
emu: &Emulator<QT, S, StdEmuExitHandler<SM>>,
#[cfg(emulation_mode = "systemmode")] qemu_executor_state: &mut QemuExecutorState<QT, S>,
#[cfg(not(emulation_mode = "systemmode"))] _qemu_executor_state: &mut QemuExecutorState<
QT,
S,
>,
_input: &BytesInput,
_ret_reg: Option<Regs>,
) -> Result<InnerHandlerResult, HandlerError> {
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::<QemuInstrumentationPagingFilter, S>::filter_mut(
qemu_helpers,
);
*paging_filter = QemuInstrumentationPagingFilter::AllowList(allowed_paging_ids);
}
Ok(InnerHandlerResult::Continue)
}
}
#[derive(Debug, Clone)]
pub struct LoadCommand;
impl<SM, QT, S> IsCommand<QT, S, StdEmuExitHandler<SM>> for LoadCommand
where
SM: IsSnapshotManager,
QT: QemuHelperTuple<S> + StdInstrumentationFilter<S> + Debug,
S: State + HasExecutions,
{
fn usable_at_runtime(&self) -> bool {
false
}
fn run(
&self,
emu: &Emulator<QT, S, StdEmuExitHandler<SM>>,
_qemu_executor_state: &mut QemuExecutorState<QT, S>,
_input: &BytesInput,
_ret_reg: Option<Regs>,
) -> Result<InnerHandlerResult, HandlerError> {
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<SM, QT, S> IsCommand<QT, S, StdEmuExitHandler<SM>> for InputCommand
where
SM: IsSnapshotManager,
QT: QemuHelperTuple<S> + StdInstrumentationFilter<S> + Debug,
S: State + HasExecutions,
{
fn usable_at_runtime(&self) -> bool {
true
}
fn run(
&self,
emu: &Emulator<QT, S, StdEmuExitHandler<SM>>,
_qemu_executor_state: &mut QemuExecutorState<QT, S>,
input: &BytesInput,
ret_reg: Option<Regs>,
) -> Result<InnerHandlerResult, HandlerError> {
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<SM, QT, S> IsCommand<QT, S, StdEmuExitHandler<SM>> for StartCommand
where
SM: IsSnapshotManager,
QT: QemuHelperTuple<S> + StdInstrumentationFilter<S> + Debug,
S: State + HasExecutions,
{
fn usable_at_runtime(&self) -> bool {
false
}
fn run(
&self,
emu: &Emulator<QT, S, StdEmuExitHandler<SM>>,
_qemu_executor_state: &mut QemuExecutorState<QT, S>,
input: &BytesInput,
ret_reg: Option<Regs>,
) -> Result<InnerHandlerResult, HandlerError> {
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<ExitKind>);
impl<SM, QT, S> IsCommand<QT, S, StdEmuExitHandler<SM>> for EndCommand
where
SM: IsSnapshotManager,
QT: QemuHelperTuple<S> + StdInstrumentationFilter<S> + Debug,
S: State + HasExecutions,
{
fn usable_at_runtime(&self) -> bool {
false
}
fn run(
&self,
emu: &Emulator<QT, S, StdEmuExitHandler<SM>>,
_qemu_executor_state: &mut QemuExecutorState<QT, S>,
_input: &BytesInput,
_ret_reg: Option<Regs>,
) -> Result<InnerHandlerResult, HandlerError> {
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<SM, QT, S> IsCommand<QT, S, StdEmuExitHandler<SM>> for VersionCommand
where
SM: IsSnapshotManager,
QT: QemuHelperTuple<S> + StdInstrumentationFilter<S> + Debug,
S: State + HasExecutions,
{
fn usable_at_runtime(&self) -> bool {
true
}
fn run(
&self,
_emu: &Emulator<QT, S, StdEmuExitHandler<SM>>,
_qemu_executor_state: &mut QemuExecutorState<QT, S>,
_input: &BytesInput,
_ret_reg: Option<Regs>,
) -> Result<InnerHandlerResult, HandlerError> {
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<T>
where
T: IsFilter + Debug,
{
filter: T,
}
#[cfg(emulation_mode = "systemmode")]
impl<SM, QT, S> IsCommand<QT, S, StdEmuExitHandler<SM>> for PagingFilterCommand
where
SM: IsSnapshotManager,
QT: QemuHelperTuple<S> + StdInstrumentationFilter<S> + Debug,
S: State + HasExecutions,
{
fn usable_at_runtime(&self) -> bool {
true
}
fn run(
&self,
_emu: &Emulator<QT, S, StdEmuExitHandler<SM>>,
qemu_executor_state: &mut QemuExecutorState<QT, S>,
_input: &BytesInput,
_ret_reg: Option<Regs>,
) -> Result<InnerHandlerResult, HandlerError> {
let qemu_helpers = qemu_executor_state.hooks_mut().helpers_mut();
let paging_filter =
HasInstrumentationFilter::<QemuInstrumentationPagingFilter, S>::filter_mut(
qemu_helpers,
);
*paging_filter = self.filter.clone();
Ok(InnerHandlerResult::Continue)
}
}
impl<SM, QT, S> IsCommand<QT, S, StdEmuExitHandler<SM>> for AddressRangeFilterCommand
where
SM: IsSnapshotManager,
QT: QemuHelperTuple<S> + StdInstrumentationFilter<S> + 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<QT, S, StdEmuExitHandler<SM>>,
qemu_executor_state: &mut QemuExecutorState<QT, S>,
_input: &BytesInput,
_ret_reg: Option<Regs>,
) -> Result<InnerHandlerResult, HandlerError> {
let qemu_helpers = qemu_executor_state.hooks_mut().helpers_mut();
let addr_range_filter =
HasInstrumentationFilter::<QemuInstrumentationAddressRangeFilter, S>::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<T> FilterCommand<T>
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<ExitKind>) -> 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<CPU>) -> 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
)
}
}

View File

@ -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<Option<Vec<u64>>> = Mutex::new(None);
@ -78,7 +78,11 @@ impl QemuDrCovHelper {
}
}
impl HasInstrumentationFilter<QemuInstrumentationAddressRangeFilter> for QemuDrCovHelper {
impl<S: UsesInput> HasInstrumentationFilter<QemuInstrumentationAddressRangeFilter, S>
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<OT>(
&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<QT, S>(
pc: GuestAddr,
) -> Option<u64>
where
S: HasMetadata,
S: UsesInput,
S: UsesInput + HasMetadata,
QT: QemuHelperTuple<S>,
{
let drcov_helper = hooks
@ -255,8 +258,7 @@ pub fn gen_block_lengths<QT, S>(
pc: GuestAddr,
block_length: GuestUsize,
) where
S: HasMetadata,
S: UsesInput,
S: UsesInput + HasMetadata,
QT: QemuHelperTuple<S>,
{
let drcov_helper = hooks
@ -276,9 +278,8 @@ pub fn gen_block_lengths<QT, S>(
pub fn exec_trace_block<QT, S>(hooks: &mut QemuHooks<QT, S>, _state: Option<&mut S>, id: u64)
where
S: HasMetadata,
S: UsesInput,
QT: QemuHelperTuple<S>,
S: UsesInput + HasMetadata,
{
if hooks
.helpers()

View File

@ -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<QemuInstrumentationAddressRangeFilter> for QemuEdgeCoverageHelper {
impl<S: UsesInput> HasInstrumentationFilter<QemuInstrumentationAddressRangeFilter, S>
for QemuEdgeCoverageHelper
{
fn filter(&self) -> &QemuInstrumentationAddressRangeFilter {
&self.address_filter
}
@ -141,7 +145,9 @@ impl HasInstrumentationFilter<QemuInstrumentationAddressRangeFilter> for QemuEdg
}
#[cfg(emulation_mode = "systemmode")]
impl HasInstrumentationFilter<QemuInstrumentationPagingFilter> for QemuEdgeCoverageHelper {
impl<S: UsesInput> HasInstrumentationFilter<QemuInstrumentationPagingFilter, S>
for QemuEdgeCoverageHelper
{
fn filter(&self) -> &QemuInstrumentationPagingFilter {
&self.paging_filter
}
@ -277,7 +283,7 @@ impl Default for QemuEdgeCoverageChildHelper {
}
}
impl HasInstrumentationFilter<QemuInstrumentationAddressRangeFilter>
impl<S: UsesInput> HasInstrumentationFilter<QemuInstrumentationAddressRangeFilter, S>
for QemuEdgeCoverageChildHelper
{
fn filter(&self) -> &QemuInstrumentationAddressRangeFilter {
@ -290,7 +296,9 @@ impl HasInstrumentationFilter<QemuInstrumentationAddressRangeFilter>
}
#[cfg(emulation_mode = "systemmode")]
impl HasInstrumentationFilter<QemuInstrumentationPagingFilter> for QemuEdgeCoverageChildHelper {
impl<S: UsesInput> HasInstrumentationFilter<QemuInstrumentationPagingFilter, S>
for QemuEdgeCoverageChildHelper
{
fn filter(&self) -> &QemuInstrumentationPagingFilter {
&self.paging_filter
}
@ -302,8 +310,7 @@ impl HasInstrumentationFilter<QemuInstrumentationPagingFilter> for QemuEdgeCover
impl<S> QemuHelper<S> 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<QemuInstrumentationAddressRangeFilter>
impl<S: UsesInput> HasInstrumentationFilter<QemuInstrumentationAddressRangeFilter, S>
for QemuEdgeCoverageClassicHelper
{
fn filter(&self) -> &QemuInstrumentationAddressRangeFilter {
@ -426,7 +445,9 @@ impl HasInstrumentationFilter<QemuInstrumentationAddressRangeFilter>
}
#[cfg(emulation_mode = "systemmode")]
impl HasInstrumentationFilter<QemuInstrumentationPagingFilter> for QemuEdgeCoverageClassicHelper {
impl<S: UsesInput> HasInstrumentationFilter<QemuInstrumentationPagingFilter, S>
for QemuEdgeCoverageClassicHelper
{
fn filter(&self) -> &QemuInstrumentationPagingFilter {
&self.paging_filter
}
@ -436,10 +457,10 @@ impl HasInstrumentationFilter<QemuInstrumentationPagingFilter> for QemuEdgeCover
}
}
#[allow(clippy::collapsible_else_if)]
impl<S> QemuHelper<S> for QemuEdgeCoverageClassicHelper
where
S: UsesInput,
S: HasMetadata,
S: UsesInput + HasMetadata,
{
const HOOKS_DO_SIDE_EFFECTS: bool = false;
@ -448,11 +469,40 @@ where
QT: QemuHelperTuple<S>,
{
if self.use_hitcounts {
if self.use_jit {
let hook_id = hooks.blocks(
Hook::Function(gen_hashed_block_ids::<QT, S>),
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::<QT, S>),
Hook::Empty,
Hook::Raw(trace_block_transition_hitcount),
);
}
} else {
if self.use_jit {
let hook_id = hooks.blocks(
Hook::Function(gen_hashed_block_ids::<QT, S>),
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::<QT, S>),
@ -461,6 +511,7 @@ where
);
}
}
}
}
thread_local!(static PREV_LOC : UnsafeCell<u64> = const { UnsafeCell::new(0) });
@ -472,8 +523,7 @@ pub fn gen_unique_edge_ids<QT, S>(
dest: GuestAddr,
) -> Option<u64>
where
S: HasMetadata,
S: UsesInput,
S: UsesInput + HasMetadata,
QT: QemuHelperTuple<S>,
{
if let Some(h) = hooks.helpers().match_first_type::<QemuEdgeCoverageHelper>() {
@ -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) {

View File

@ -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>,

File diff suppressed because it is too large Load Diff

View File

@ -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<SnapshotId, FastSnapshotPtr>,
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<String>),
DenyList(Vec<String>),
}
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<GuestPhysAddr> {
unsafe {
let page = libafl_page_from_addr(vaddr);
let mut attrs = MaybeUninit::<libafl_qemu_sys::MemTxAttrs>::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<GuestPhysAddr> {
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<GuestPhysAddr> {
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<QemuExitReason, QemuExitReasonError> {
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<String> {
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<QT, S, E> Emulator<QT, S, E>
where
QT: QemuHelperTuple<S>,
S: State + HasExecutions,
E: EmuExitHandler<QT, S>,
{
/// 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<String> {
self.qemu.list_devices()
}
}

View File

@ -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<Self::Item> {
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<Self>) -> PyRef<Self> {
slf
}
fn __next__(mut slf: PyRefMut<Self>) -> Option<PyObject> {
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<usize> = 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<T>(&self, addr: GuestAddr) -> *mut T {
unsafe { (addr as usize + guest_base) as *mut T }
}
#[must_use]
pub fn h2g<T>(&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<QemuExitReason, QemuExitReasonError> {
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<GuestAddr, ()> {
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<GuestAddr, String> {
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<GuestAddr, String> {
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<T: Into<HookData>>(
&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<T: Into<HookData>>(
&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<T: Into<HookData>>(
&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<QT, S, E> Emulator<QT, S, E>
where
QT: QemuHelperTuple<S>,
S: State + HasExecutions,
E: EmuExitHandler<QT, S>,
{
/// This function gets the memory mappings from the emulator.
#[must_use]
pub fn mappings(&self) -> GuestMaps {
self.qemu.mappings()
}
#[must_use]
pub fn g2h<T>(&self, addr: GuestAddr) -> *mut T {
self.qemu.g2h(addr)
}
#[must_use]
pub fn h2g<T>(&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<GuestAddr, String> {
self.qemu.map_private(addr, size, perms)
}
pub fn map_fixed(
&self,
addr: GuestAddr,
size: usize,
perms: MmapPerms,
) -> Result<GuestAddr, String> {
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<T: Into<HookData>>(
&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<T: Into<HookData>>(
&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<T: Into<HookData>>(
&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);
}
}

View File

@ -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::<E, EM, OF, Z>();
}
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<S> + Debug,
{
fn pre_exec<E, EM, OF, Z>(&mut self, input: &E::Input, emu: &Emulator)
fn pre_exec<E, EM, OF, Z>(&mut self, input: &E::Input, qemu: Qemu)
where
E: Executor<EM, Z, State = S>,
EM: EventFirer<State = S> + EventRestarter<State = S>,
@ -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<E, EM, OT, OF, Z>(
&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<ExitKind, Error> {
let emu = Emulator::get().unwrap();
self.state.pre_exec::<Self, EM, OF, Z>(input, &emu);
let qemu = Qemu::get().unwrap();
self.state.pre_exec::<Self, EM, OF, Z>(input, qemu);
let mut exit_kind = self.inner.run_target(fuzzer, state, mgr, input)?;
self.state.post_exec::<Self, EM, OT, OF, Z>(
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<ExitKind, Error> {
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,

View File

@ -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<ExitKind, Error> {
let emu = Emulator::get().unwrap();
let qemu = Qemu::get().unwrap();
self.inner
.exposed_executor_state_mut()
.pre_exec::<Self, EM, OF, Z>(input, &emu);
.pre_exec::<Self, EM, OF, Z>(input, qemu);
let mut exit_kind = self.inner.run_target(fuzzer, state, mgr, input)?;
self.inner
.exposed_executor_state
.post_exec::<Self, EM, OT, OF, Z>(
input,
&emu,
qemu,
self.inner.inner.observers_mut(),
&mut exit_kind,
);

View File

@ -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<OT>(
&mut self,
_emulator: &Emulator,
_qemu: Qemu,
_input: &S::Input,
_observers: &mut OT,
_exit_kind: &mut ExitKind,
@ -58,11 +55,11 @@ where
where
QT: QemuHelperTuple<S>;
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<OT>(
&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<OT>(
&mut self,
_emulator: &Emulator,
_qemu: Qemu,
_input: &S::Input,
_observers: &mut OT,
_exit_kind: &mut ExitKind,
@ -102,6 +99,21 @@ where
}
}
impl<Head, F, S> HasInstrumentationFilter<F, S> for (Head, ())
where
Head: QemuHelper<S> + HasInstrumentationFilter<F, S>,
S: UsesInput,
F: IsFilter,
{
fn filter(&self) -> &F {
self.0.filter()
}
fn filter_mut(&mut self) -> &mut F {
self.0.filter_mut()
}
}
impl<Head, Tail, S> QemuHelperTuple<S> for (Head, Tail)
where
Head: QemuHelper<S>,
@ -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<OT>(
&mut self,
emulator: &Emulator,
qemu: Qemu,
input: &S::Input,
observers: &mut OT,
exit_kind: &mut ExitKind,
) where
OT: ObserversTuple<S>,
{
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<T: IsFilter + Debug> {
#[derive(Debug, Clone)]
pub enum QemuFilterList<T: IsFilter + Debug + Clone> {
AllowList(T),
DenyList(T),
None,
@ -154,7 +166,7 @@ pub enum QemuFilterList<T: IsFilter + Debug> {
impl<T> IsFilter for QemuFilterList<T>
where
T: IsFilter,
T: IsFilter + Clone,
{
type FilterParameter = T::FilterParameter;
@ -169,7 +181,10 @@ where
pub type QemuInstrumentationPagingFilter = QemuFilterList<HashSet<GuestPhysAddr>>;
impl<H: hash::BuildHasher> IsFilter for HashSet<GuestPhysAddr, H> {
impl<H> IsFilter for HashSet<GuestPhysAddr, H>
where
H: BuildHasher,
{
type FilterParameter = Option<GuestPhysAddr>;
fn allowed(&self, paging_id: Self::FilterParameter) -> bool {
@ -192,7 +207,7 @@ impl IsFilter for Vec<Range<GuestAddr>> {
}
}
pub trait HasInstrumentationFilter<F>
pub trait HasInstrumentationFilter<F, S>
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<S: UsesInput>:
HasInstrumentationFilter<QemuInstrumentationAddressRangeFilter, S>
{
}
#[cfg(emulation_mode = "systemmode")]
pub trait StdInstrumentationFilter<S: UsesInput>:
HasInstrumentationFilter<QemuInstrumentationAddressRangeFilter, S>
+ HasInstrumentationFilter<QemuInstrumentationPagingFilter, S>
{
}
#[cfg(emulation_mode = "systemmode")]
impl<Head, S> StdInstrumentationFilter<S> for (Head, ())
where
Head: QemuHelper<S>
+ HasInstrumentationFilter<QemuInstrumentationAddressRangeFilter, S>
+ HasInstrumentationFilter<QemuInstrumentationPagingFilter, S>,
S: UsesInput,
{
}
#[cfg(emulation_mode = "usermode")]
impl<Head, S> StdInstrumentationFilter<S> for (Head, ())
where
Head: QemuHelper<S> + HasInstrumentationFilter<QemuInstrumentationAddressRangeFilter, S>,
S: UsesInput,
{
}
pub trait IsFilter: Debug {
type FilterParameter;

View File

@ -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<EnumMap<SyncBackdoorArgs, Regs>> = OnceLock::new();
static BACKDOOR_ARCH_REGS: OnceLock<EnumMap<BackdoorArgs, Regs>> = OnceLock::new();
pub fn get_sync_backdoor_arch_regs() -> &'static EnumMap<SyncBackdoorArgs, Regs> {
SYNC_BACKDOOR_ARCH_REGS.get_or_init(|| {
pub fn get_backdoor_arch_regs() -> &'static EnumMap<BackdoorArgs, Regs> {
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,
}
})
}

View File

@ -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<S>,
}
@ -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<I>>,
NopState<I>: UsesInput<Input = I>,
{
pub fn reproducer(emulator: Emulator, helpers: QT) -> Box<Self> {
Self::new(emulator, helpers)
pub fn reproducer(qemu: Qemu, helpers: QT) -> Box<Self> {
Self::new(qemu, helpers)
}
pub fn repro_run<H>(&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>,
S: UsesInput,
{
pub fn new(emulator: Emulator, helpers: QT) -> Box<Self> {
pub fn new(qemu: Qemu, helpers: QT) -> Box<Self> {
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::<T>()
}
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::<QT, S>,
@ -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::<QT, S>)
}
}
@ -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::<QT, S>)
}
}
@ -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::<QT, S>)
}
}
@ -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::<QT, S>)
}
}
@ -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::<QT, S>);
self.qemu.set_crash_hook(crash_hook_wrapper::<QT, S>);
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<dyn FnMut(&mut Self, i32)>) {
unsafe {
self.emulator.set_crash_hook(crash_hook_wrapper::<QT, S>);
self.qemu.set_crash_hook(crash_hook_wrapper::<QT, S>);
CRASH_HOOKS.push(HookRepr::Closure(transmute(hook)));
}
}

View File

@ -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<EnumMap<SyncBackdoorArgs, Regs>> = OnceLock::new();
static BACKDOOR_ARCH_REGS: OnceLock<EnumMap<BackdoorArgs, Regs>> = OnceLock::new();
pub fn get_sync_backdoor_arch_regs() -> &'static EnumMap<SyncBackdoorArgs, Regs> {
SYNC_BACKDOOR_ARCH_REGS.get_or_init(|| {
pub fn get_backdoor_arch_regs() -> &'static EnumMap<BackdoorArgs, Regs> {
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,
}
})
}

View File

@ -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<S>,
{
let emu = hooks.emulator();
let qemu = *hooks.qemu();
let mut libs: Vec<LibInfo> = 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))
}

View File

@ -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::<emu::MapInfo>()?;
m.add_class::<emu::GuestMaps>()?;
m.add_class::<emu::SyscallHookResult>()?;
m.add_class::<emu::pybind::Emulator>()?;
m.add_class::<emu::pybind::Qemu>()?;
Ok(())
}

View File

@ -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<EnumMap<SyncBackdoorArgs, Regs>> = OnceLock::new();
static BACKDOOR_ARCH_REGS: OnceLock<EnumMap<BackdoorArgs, Regs>> = OnceLock::new();
pub fn get_sync_backdoor_arch_regs() -> &'static EnumMap<SyncBackdoorArgs, Regs> {
SYNC_BACKDOOR_ARCH_REGS.get_or_init(|| {
pub fn get_backdoor_arch_regs() -> &'static EnumMap<BackdoorArgs, Regs> {
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,
}
})
}

View File

@ -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<EnumMap<SyncBackdoorArgs, Regs>> = OnceLock::new();
static BACKDOOR_ARCH_REGS: OnceLock<EnumMap<BackdoorArgs, Regs>> = OnceLock::new();
pub fn get_sync_backdoor_arch_regs() -> &'static EnumMap<SyncBackdoorArgs, Regs> {
SYNC_BACKDOOR_ARCH_REGS.get_or_init(|| {
pub fn get_backdoor_arch_regs() -> &'static EnumMap<BackdoorArgs, Regs> {
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,
}
})
}

View File

@ -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<dyn FnMut(&mut QemuSnapshotHelper, &Emulator)>;
pub type StopExecutionCallback = Box<dyn FnMut(&mut QemuSnapshotHelper, &Qemu)>;
#[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::<QT, S>));
}
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);
}
}
}

View File

@ -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<String> for SyncBackdoorError {
@ -25,7 +34,7 @@ impl From<String> 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<EnumMap<NativeExitKind, Option<ExitKind>>> = OnceLock::new();
impl From<TryFromPrimitiveError<NativeSyncBackdoorCommand>> for SyncBackdoorError {
fn from(error: TryFromPrimitiveError<NativeSyncBackdoorCommand>) -> Self {
impl From<TryFromPrimitiveError<NativeBackdoorCommand>> for SyncBackdoorError {
fn from(error: TryFromPrimitiveError<NativeBackdoorCommand>) -> 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<ExitKind>),
}
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<SyncBackdoorArgs, Regs>,
arch_regs_map: &'static EnumMap<BackdoorArgs, Regs>,
}
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<QT, S, E> TryFrom<&Emulator<QT, S, E>> for SyncBackdoor
where
E: EmuExitHandler<QT, S>,
QT: QemuHelperTuple<S>,
S: State + HasExecutions,
{
type Error = SyncBackdoorError;
fn try_from(emu: &Emulator) -> Result<Self, Self::Error> {
let arch_regs_map: &'static EnumMap<SyncBackdoorArgs, Regs> = get_sync_backdoor_arch_regs();
let cmd_id: GuestReg =
emu.read_reg::<Regs, GuestReg>(arch_regs_map[SyncBackdoorArgs::Cmd])?;
#[allow(clippy::too_many_lines)]
fn try_from(emu: &Emulator<QT, S, E>) -> Result<Self, Self::Error> {
let arch_regs_map: &'static EnumMap<BackdoorArgs, Regs> = get_backdoor_arch_regs();
let cmd_id: GuestReg = emu
.qemu()
.read_reg::<Regs, GuestReg>(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<NativeExitKind, _> =
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,
}
}

View File

@ -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<EnumMap<SyncBackdoorArgs, Regs>> = OnceLock::new();
static BACKDOOR_ARCH_REGS: OnceLock<EnumMap<BackdoorArgs, Regs>> = OnceLock::new();
pub fn get_sync_backdoor_arch_regs() -> &'static EnumMap<SyncBackdoorArgs, Regs> {
SYNC_BACKDOOR_ARCH_REGS.get_or_init(|| {
pub fn get_backdoor_arch_regs() -> &'static EnumMap<BackdoorArgs, Regs> {
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,
}
})
}

View File

@ -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);
}
}