Allow passing Qemu object to QemuBytesCoverageSugar (#3261)

* attempt to return Qemu object as a parameter to QemuBytesCoverageSugar

* apply clippy suggestions from precommit.sh

* python qemu sugar: add option to enable stdout

---------

Co-authored-by: Dongjia "toka" Zhang <tokazerkje@outlook.com>
This commit is contained in:
jma 2025-05-23 10:22:00 +02:00 committed by GitHub
parent 213651a95c
commit 871548c366
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 89 additions and 20 deletions

View File

@ -6,7 +6,7 @@ import lief
MAX_SIZE = 0x100 MAX_SIZE = 0x100
BINARY_PATH = "./a.out" BINARY_PATH = "./a.out"
emu = qemu.Qemu(["qemu-x86_64", BINARY_PATH], []) emu = qemu.Qemu(["qemu-x86_64", BINARY_PATH])
elf = lief.parse(BINARY_PATH) elf = lief.parse(BINARY_PATH)
test_one_input = elf.get_function_address("LLVMFuzzerTestOneInput") test_one_input = elf.get_function_address("LLVMFuzzerTestOneInput")
@ -41,5 +41,11 @@ def harness(b):
emu.run() emu.run()
fuzz = sugar.QemuBytesCoverageSugar(["./in"], "./out", 3456, [0, 1, 2, 3]) # Create a fuzzer using the launcher
# with 4 instances bounds to cores 0-3
# LLMP uses port 3456 to synchronize
# stdout from the target is NOT redirected to /dev/null
fuzz = sugar.QemuBytesCoverageSugar(
["./in"], "./out", 3456, [0, 1, 2, 3], enable_stdout=True
)
fuzz.run(emu, harness) fuzz.run(emu, harness)

View File

@ -6,8 +6,8 @@ use libafl_bolts::tuples::{Append, Prepend, tuple_list};
#[cfg(feature = "systemmode")] #[cfg(feature = "systemmode")]
use crate::FastSnapshotManager; use crate::FastSnapshotManager;
use crate::{ use crate::{
Emulator, NopEmulatorDriver, NopSnapshotManager, QemuInitError, QemuParams, StdEmulatorDriver, Emulator, NopEmulatorDriver, NopSnapshotManager, Qemu, QemuInitError, QemuParams,
StdSnapshotManager, StdEmulatorDriver, StdSnapshotManager,
command::{NopCommandManager, StdCommandManager}, command::{NopCommandManager, StdCommandManager},
config::QemuConfigBuilder, config::QemuConfigBuilder,
modules::{EmulatorModule, EmulatorModuleTuple}, modules::{EmulatorModule, EmulatorModuleTuple},
@ -160,6 +160,31 @@ where
self.command_manager, self.command_manager,
) )
} }
#[allow(clippy::type_complexity)]
pub fn build_with_qemu(
self,
qemu: Qemu,
) -> Result<Emulator<C, CM, ED, ET, I, S, SM>, QemuInitError>
where
ET: EmulatorModuleTuple<I, S>,
{
// The logic from Emulator::new needs to be duplicated here because of type mismatch on modules
// between Emulator::new and Emulator::new_wit_qemu
let emulator_hooks =
unsafe { super::EmulatorHooks::new(crate::QemuHooks::get_unchecked()) };
let emulator_modules = unsafe { super::EmulatorModules::new(emulator_hooks, self.modules) };
unsafe {
Ok(Emulator::new_with_qemu(
qemu,
emulator_modules,
self.driver,
self.snapshot_manager,
self.command_manager,
))
}
}
} }
impl<C, CM, ED, ET, QP, I, S, SM> EmulatorBuilder<C, CM, ED, ET, QP, I, S, SM> impl<C, CM, ED, ET, QP, I, S, SM> EmulatorBuilder<C, CM, ED, ET, QP, I, S, SM>

View File

@ -85,6 +85,9 @@ where
/// Fuzz `iterations` number of times, instead of indefinitely; implies use of `fuzz_loop_for` /// Fuzz `iterations` number of times, instead of indefinitely; implies use of `fuzz_loop_for`
#[builder(default = None)] #[builder(default = None)]
iterations: Option<u64>, iterations: Option<u64>,
/// Disable redirection of stdout to /dev/null on unix build targets
#[builder(default = None)]
enable_stdout: Option<bool>,
} }
impl<H> Debug for QemuBytesCoverageSugar<'_, H> impl<H> Debug for QemuBytesCoverageSugar<'_, H>
@ -111,17 +114,27 @@ where
}, },
) )
.field("iterations", &self.iterations) .field("iterations", &self.iterations)
.field("enable_stdout", &self.enable_stdout)
.finish() .finish()
} }
} }
/// Enum to allow passing either qemu cli parameters or a running qemu instance
#[derive(Debug, Copy, Clone)]
pub enum QemuSugarParameter<'a> {
/// Argument list to pass to initialize Qemu
QemuCli(&'a [String]),
/// Already existing Qemu instance
Qemu(&'a Qemu),
}
impl<H> QemuBytesCoverageSugar<'_, H> impl<H> QemuBytesCoverageSugar<'_, H>
where where
H: FnMut(&[u8]), H: FnMut(&[u8]),
{ {
/// Run the fuzzer /// Run the fuzzer
#[expect(clippy::too_many_lines)] #[expect(clippy::too_many_lines)]
pub fn run(&mut self, qemu_cli: &[String]) { pub fn run(&mut self, qemu: QemuSugarParameter) {
let conf = match self.configuration.as_ref() { let conf = match self.configuration.as_ref() {
Some(name) => EventConfig::from_name(name), Some(name) => EventConfig::from_name(name),
None => EventConfig::AlwaysUnique, None => EventConfig::AlwaysUnique,
@ -146,7 +159,7 @@ where
let shmem_provider = StdShMemProvider::new().expect("Failed to init shared memory"); let shmem_provider = StdShMemProvider::new().expect("Failed to init shared memory");
let monitor = MultiMonitor::new(|s| log::info!("{s}")); let monitor = MultiMonitor::new(|s| println!("{s}"));
// Create an observation channel to keep track of the execution time // Create an observation channel to keep track of the execution time
let time_observer = TimeObserver::new("time"); let time_observer = TimeObserver::new("time");
@ -254,11 +267,17 @@ where
ExitKind::Ok ExitKind::Ok
}; };
let emulator = Emulator::empty() let emulator = match qemu {
.qemu_parameters(qemu_cli.to_owned()) QemuSugarParameter::QemuCli(qemu_cli) => Emulator::empty()
.modules(modules) .qemu_parameters(qemu_cli.to_owned())
.build() .modules(modules)
.expect("Could not initialize Emulator"); .build()
.expect("Could not initialize Emulator"),
QemuSugarParameter::Qemu(qemu) => Emulator::empty()
.modules(modules)
.build_with_qemu(*qemu)
.expect("Could not initialize Emulator"),
};
let executor = QemuExecutor::new( let executor = QemuExecutor::new(
emulator, emulator,
@ -377,11 +396,17 @@ where
ExitKind::Ok ExitKind::Ok
}; };
let emulator = Emulator::empty() let emulator = match qemu {
.qemu_parameters(qemu_cli.to_owned()) QemuSugarParameter::QemuCli(qemu_cli) => Emulator::empty()
.modules(modules) .qemu_parameters(qemu_cli.to_owned())
.build() .modules(modules)
.expect("Could not initialize Emulator"); .build()
.expect("Could not initialize Emulator"),
QemuSugarParameter::Qemu(qemu) => Emulator::empty()
.modules(modules)
.build_with_qemu(*qemu)
.expect("Could not initialize Emulator"),
};
let mut executor = QemuExecutor::new( let mut executor = QemuExecutor::new(
emulator, emulator,
@ -488,8 +513,14 @@ where
.remote_broker_addr(self.remote_broker_addr); .remote_broker_addr(self.remote_broker_addr);
#[cfg(unix)] #[cfg(unix)]
let launcher = launcher.stdout_file(Some("/dev/null")); if self.enable_stdout.unwrap_or(false) {
launcher.build().launch().expect("Launcher failed");
} else {
let launcher = launcher.stdout_file(Some("/dev/null"));
launcher.build().launch().expect("Launcher failed");
}
#[cfg(not(unix))]
launcher.build().launch().expect("Launcher failed"); launcher.build().launch().expect("Launcher failed");
} }
} }
@ -500,8 +531,10 @@ pub mod pybind {
use std::path::PathBuf; use std::path::PathBuf;
use libafl_bolts::core_affinity::Cores; use libafl_bolts::core_affinity::Cores;
use libafl_qemu::pybind::Qemu;
use pyo3::{prelude::*, types::PyBytes}; use pyo3::{prelude::*, types::PyBytes};
use super::QemuSugarParameter;
use crate::qemu; use crate::qemu;
#[pyclass(unsendable)] #[pyclass(unsendable)]
@ -515,6 +548,7 @@ pub mod pybind {
iterations: Option<u64>, iterations: Option<u64>,
tokens_file: Option<PathBuf>, tokens_file: Option<PathBuf>,
timeout: Option<u64>, timeout: Option<u64>,
enable_stdout: Option<bool>,
} }
#[pymethods] #[pymethods]
@ -530,7 +564,8 @@ pub mod pybind {
use_cmplog=None, use_cmplog=None,
iterations=None, iterations=None,
tokens_file=None, tokens_file=None,
timeout=None timeout=None,
enable_stdout=None,
))] ))]
fn new( fn new(
input_dirs: Vec<PathBuf>, input_dirs: Vec<PathBuf>,
@ -541,6 +576,7 @@ pub mod pybind {
iterations: Option<u64>, iterations: Option<u64>,
tokens_file: Option<PathBuf>, tokens_file: Option<PathBuf>,
timeout: Option<u64>, timeout: Option<u64>,
enable_stdout: Option<bool>,
) -> Self { ) -> Self {
Self { Self {
input_dirs, input_dirs,
@ -551,12 +587,13 @@ pub mod pybind {
iterations, iterations,
tokens_file, tokens_file,
timeout, timeout,
enable_stdout,
} }
} }
/// Run the fuzzer /// Run the fuzzer
#[expect(clippy::needless_pass_by_value)] #[expect(clippy::needless_pass_by_value)]
pub fn run(&self, qemu_cli: Vec<String>, harness: PyObject) { pub fn run(&self, qemu: &Qemu, harness: PyObject) {
qemu::QemuBytesCoverageSugar::builder() qemu::QemuBytesCoverageSugar::builder()
.input_dirs(&self.input_dirs) .input_dirs(&self.input_dirs)
.output_dir(self.output_dir.clone()) .output_dir(self.output_dir.clone())
@ -574,8 +611,9 @@ pub mod pybind {
.timeout(self.timeout) .timeout(self.timeout)
.tokens_file(self.tokens_file.clone()) .tokens_file(self.tokens_file.clone())
.iterations(self.iterations) .iterations(self.iterations)
.enable_stdout(self.enable_stdout)
.build() .build()
.run(&qemu_cli); .run(QemuSugarParameter::Qemu(&qemu.qemu));
} }
} }