Move all target-specific code to harness.rs (#2605)

This commit is contained in:
Dominik Maier 2024-10-11 12:41:42 +02:00 committed by GitHub
parent 830941ce3a
commit 2bfed2d488
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 68 additions and 30 deletions

View File

@ -1,9 +1,13 @@
# qemu_launcher_
This folder contains an example fuzzer for libpng, using LLMP for fast multi-process fuzzing and crash detection. It has been tested on Linux.
This automatically spawns n child processes, and binds them to a free core.
This folder contains an example fuzzer that will fuzz binary-only targets, cross-architecture, on Linux.
It's using LLMP for fast multi-process fuzzing and crash detection.
This automatically spawns `n` child processes, and binds them to a free core.
To adapt the fuzzer to your custom target, change [`harness.rs`](./src/harness.rs).
The following architectures are supported:
* arm
* aarch64
* i386
@ -11,10 +15,10 @@ The following architectures are supported:
* mips
* ppc
Note that the injection feature `-j` is currently only supported on x86_64
and aarch64.
For usermode, this fuzzer supports injection fuzzing with `-j`.
## Prerequisites
```bash
sudo apt install \
gcc-arm-linux-gnueabi \
@ -32,7 +36,8 @@ sudo apt install \
## Run
Defaults to `x86_64` architecture
Defaults to `x86_64` architecture. Change the architecture by
```bash
cargo make run
```

View File

@ -11,16 +11,16 @@ use libafl_bolts::{core_affinity::CoreId, rands::StdRand, tuples::tuple_list};
#[cfg(feature = "injections")]
use libafl_qemu::modules::injections::InjectionModule;
use libafl_qemu::{
elf::EasyElf,
modules::{
asan::{init_qemu_with_asan, AsanModule},
asan_guest::{init_qemu_with_asan_guest, AsanGuestModule},
cmplog::CmpLogModule,
},
ArchExtras, GuestAddr, Qemu,
Qemu,
};
use crate::{
harness::Harness,
instance::{ClientMgr, Instance},
options::FuzzerOptions,
};
@ -55,16 +55,7 @@ impl<'a> Client<'a> {
.collect::<Vec<(String, String)>>()
}
fn start_pc(qemu: Qemu) -> Result<GuestAddr, Error> {
let mut elf_buffer = Vec::new();
let elf = EasyElf::from_file(qemu.binary_path(), &mut elf_buffer)?;
let start_pc = elf
.resolve_symbol("LLVMFuzzerTestOneInput", qemu.load_addr())
.ok_or_else(|| Error::empty_optional("Symbol LLVMFuzzerTestOneInput not found"))?;
Ok(start_pc)
}
#[allow(clippy::too_many_lines)]
pub fn run<M: Monitor>(
&self,
state: Option<ClientState>,
@ -72,9 +63,11 @@ impl<'a> Client<'a> {
core_id: CoreId,
) -> Result<(), Error> {
let mut args = self.args()?;
Harness::edit_args(&mut args);
log::debug!("ARGS: {:#?}", args);
let mut env = self.env();
Harness::edit_env(&mut env);
log::debug!("ENV: {:#?}", env);
let is_asan = self.options.is_asan_core(core_id);
@ -96,9 +89,6 @@ impl<'a> Client<'a> {
}
};
let start_pc = Self::start_pc(qemu)?;
log::debug!("start_pc @ {start_pc:#x}");
#[cfg(not(feature = "injections"))]
let injection_module = None;
@ -118,13 +108,7 @@ impl<'a> Client<'a> {
}
});
qemu.entry_break(start_pc);
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}");
qemu.set_breakpoint(ret_addr);
let harness = Harness::init(qemu).expect("Error setting up harness.");
let is_cmplog = self.options.is_cmplog_core(core_id);
@ -136,6 +120,7 @@ impl<'a> Client<'a> {
let instance_builder = Instance::builder()
.options(self.options)
.qemu(qemu)
.harness(harness)
.mgr(mgr)
.core_id(core_id)
.extra_tokens(extra_tokens);

View File

@ -4,7 +4,9 @@ use libafl::{
Error,
};
use libafl_bolts::AsSlice;
use libafl_qemu::{ArchExtras, CallingConvention, GuestAddr, GuestReg, MmapPerms, Qemu, Regs};
use libafl_qemu::{
elf::EasyElf, ArchExtras, CallingConvention, GuestAddr, GuestReg, MmapPerms, Qemu, Regs,
};
pub struct Harness {
qemu: Qemu,
@ -17,7 +19,40 @@ pub struct Harness {
pub const MAX_INPUT_SIZE: usize = 1_048_576; // 1MB
impl Harness {
pub fn new(qemu: Qemu) -> Result<Harness, Error> {
/// Change environment
#[inline]
#[allow(clippy::ptr_arg)]
pub fn edit_env(_env: &mut Vec<(String, String)>) {}
/// Change arguments
#[inline]
#[allow(clippy::ptr_arg)]
pub fn edit_args(_args: &mut Vec<String>) {}
/// Helper function to find the function we want to fuzz.
fn start_pc(qemu: Qemu) -> Result<GuestAddr, Error> {
let mut elf_buffer = Vec::new();
let elf = EasyElf::from_file(qemu.binary_path(), &mut elf_buffer)?;
let start_pc = elf
.resolve_symbol("LLVMFuzzerTestOneInput", qemu.load_addr())
.ok_or_else(|| Error::empty_optional("Symbol LLVMFuzzerTestOneInput not found"))?;
Ok(start_pc)
}
/// Initialize the emulator, run to the entrypoint (or jump there) and return the [`Harness`] struct
pub fn init(qemu: Qemu) -> Result<Harness, Error> {
let start_pc = Self::start_pc(qemu)?;
log::debug!("start_pc @ {start_pc:#x}");
qemu.entry_break(start_pc);
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}");
qemu.set_breakpoint(ret_addr);
let input_addr = qemu
.map_private(0, MAX_INPUT_SIZE, MmapPerms::ReadWrite)
.map_err(|e| Error::unknown(format!("Failed to map input buffer: {e:}")))?;
@ -43,6 +78,11 @@ impl Harness {
})
}
/// If we need to do extra work after forking, we can do that here.
#[inline]
#[allow(clippy::unused_self)]
pub fn post_fork(&self) {}
pub fn run(&self, input: &BytesInput) -> ExitKind {
self.reset(input).unwrap();
ExitKind::Ok

View File

@ -61,6 +61,9 @@ pub type ClientMgr<M> =
#[derive(TypedBuilder)]
pub struct Instance<'a, M: Monitor> {
options: &'a FuzzerOptions,
/// The harness. We create it before forking, then `take()` it inside the client.
#[builder(setter(strip_option))]
harness: Option<Harness>,
qemu: Qemu,
mgr: ClientMgr<M>,
core_id: CoreId,
@ -186,7 +189,12 @@ impl<'a, M: Monitor> Instance<'a, M> {
state.add_metadata(tokens);
let harness = Harness::new(self.qemu)?;
let harness = self
.harness
.take()
.expect("The harness can never be None here!");
harness.post_fork();
let mut harness = |_emulator: &mut Emulator<_, _, _, _, _>,
_state: &mut _,
input: &BytesInput| harness.run(input);