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
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)
test_one_input = elf.get_function_address("LLVMFuzzerTestOneInput")
@ -41,5 +41,11 @@ def harness(b):
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)

View File

@ -6,8 +6,8 @@ use libafl_bolts::tuples::{Append, Prepend, tuple_list};
#[cfg(feature = "systemmode")]
use crate::FastSnapshotManager;
use crate::{
Emulator, NopEmulatorDriver, NopSnapshotManager, QemuInitError, QemuParams, StdEmulatorDriver,
StdSnapshotManager,
Emulator, NopEmulatorDriver, NopSnapshotManager, Qemu, QemuInitError, QemuParams,
StdEmulatorDriver, StdSnapshotManager,
command::{NopCommandManager, StdCommandManager},
config::QemuConfigBuilder,
modules::{EmulatorModule, EmulatorModuleTuple},
@ -160,6 +160,31 @@ where
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>

View File

@ -85,6 +85,9 @@ where
/// Fuzz `iterations` number of times, instead of indefinitely; implies use of `fuzz_loop_for`
#[builder(default = None)]
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>
@ -111,17 +114,27 @@ where
},
)
.field("iterations", &self.iterations)
.field("enable_stdout", &self.enable_stdout)
.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>
where
H: FnMut(&[u8]),
{
/// Run the fuzzer
#[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() {
Some(name) => EventConfig::from_name(name),
None => EventConfig::AlwaysUnique,
@ -146,7 +159,7 @@ where
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
let time_observer = TimeObserver::new("time");
@ -254,11 +267,17 @@ where
ExitKind::Ok
};
let emulator = Emulator::empty()
.qemu_parameters(qemu_cli.to_owned())
.modules(modules)
.build()
.expect("Could not initialize Emulator");
let emulator = match qemu {
QemuSugarParameter::QemuCli(qemu_cli) => Emulator::empty()
.qemu_parameters(qemu_cli.to_owned())
.modules(modules)
.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(
emulator,
@ -377,11 +396,17 @@ where
ExitKind::Ok
};
let emulator = Emulator::empty()
.qemu_parameters(qemu_cli.to_owned())
.modules(modules)
.build()
.expect("Could not initialize Emulator");
let emulator = match qemu {
QemuSugarParameter::QemuCli(qemu_cli) => Emulator::empty()
.qemu_parameters(qemu_cli.to_owned())
.modules(modules)
.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(
emulator,
@ -488,8 +513,14 @@ where
.remote_broker_addr(self.remote_broker_addr);
#[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");
}
}
@ -500,8 +531,10 @@ pub mod pybind {
use std::path::PathBuf;
use libafl_bolts::core_affinity::Cores;
use libafl_qemu::pybind::Qemu;
use pyo3::{prelude::*, types::PyBytes};
use super::QemuSugarParameter;
use crate::qemu;
#[pyclass(unsendable)]
@ -515,6 +548,7 @@ pub mod pybind {
iterations: Option<u64>,
tokens_file: Option<PathBuf>,
timeout: Option<u64>,
enable_stdout: Option<bool>,
}
#[pymethods]
@ -530,7 +564,8 @@ pub mod pybind {
use_cmplog=None,
iterations=None,
tokens_file=None,
timeout=None
timeout=None,
enable_stdout=None,
))]
fn new(
input_dirs: Vec<PathBuf>,
@ -541,6 +576,7 @@ pub mod pybind {
iterations: Option<u64>,
tokens_file: Option<PathBuf>,
timeout: Option<u64>,
enable_stdout: Option<bool>,
) -> Self {
Self {
input_dirs,
@ -551,12 +587,13 @@ pub mod pybind {
iterations,
tokens_file,
timeout,
enable_stdout,
}
}
/// Run the fuzzer
#[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()
.input_dirs(&self.input_dirs)
.output_dir(self.output_dir.clone())
@ -574,8 +611,9 @@ pub mod pybind {
.timeout(self.timeout)
.tokens_file(self.tokens_file.clone())
.iterations(self.iterations)
.enable_stdout(self.enable_stdout)
.build()
.run(&qemu_cli);
.run(QemuSugarParameter::Qemu(&qemu.qemu));
}
}