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_ # 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 folder contains an example fuzzer that will fuzz binary-only targets, cross-architecture, on Linux.
This automatically spawns n child processes, and binds them to a free core. 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: The following architectures are supported:
* arm * arm
* aarch64 * aarch64
* i386 * i386
@ -11,10 +15,10 @@ The following architectures are supported:
* mips * mips
* ppc * ppc
Note that the injection feature `-j` is currently only supported on x86_64 For usermode, this fuzzer supports injection fuzzing with `-j`.
and aarch64.
## Prerequisites ## Prerequisites
```bash ```bash
sudo apt install \ sudo apt install \
gcc-arm-linux-gnueabi \ gcc-arm-linux-gnueabi \
@ -32,7 +36,8 @@ sudo apt install \
## Run ## Run
Defaults to `x86_64` architecture Defaults to `x86_64` architecture. Change the architecture by
```bash ```bash
cargo make run cargo make run
``` ```

View File

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

View File

@ -4,7 +4,9 @@ use libafl::{
Error, Error,
}; };
use libafl_bolts::AsSlice; 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 { pub struct Harness {
qemu: Qemu, qemu: Qemu,
@ -17,7 +19,40 @@ pub struct Harness {
pub const MAX_INPUT_SIZE: usize = 1_048_576; // 1MB pub const MAX_INPUT_SIZE: usize = 1_048_576; // 1MB
impl Harness { 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 let input_addr = qemu
.map_private(0, MAX_INPUT_SIZE, MmapPerms::ReadWrite) .map_private(0, MAX_INPUT_SIZE, MmapPerms::ReadWrite)
.map_err(|e| Error::unknown(format!("Failed to map input buffer: {e:}")))?; .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 { pub fn run(&self, input: &BytesInput) -> ExitKind {
self.reset(input).unwrap(); self.reset(input).unwrap();
ExitKind::Ok ExitKind::Ok

View File

@ -61,6 +61,9 @@ pub type ClientMgr<M> =
#[derive(TypedBuilder)] #[derive(TypedBuilder)]
pub struct Instance<'a, M: Monitor> { pub struct Instance<'a, M: Monitor> {
options: &'a FuzzerOptions, 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, qemu: Qemu,
mgr: ClientMgr<M>, mgr: ClientMgr<M>,
core_id: CoreId, core_id: CoreId,
@ -186,7 +189,12 @@ impl<'a, M: Monitor> Instance<'a, M> {
state.add_metadata(tokens); 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<_, _, _, _, _>, let mut harness = |_emulator: &mut Emulator<_, _, _, _, _>,
_state: &mut _, _state: &mut _,
input: &BytesInput| harness.run(input); input: &BytesInput| harness.run(input);