QEMU command system refactoring (#2189)

* implemented generic command builder

* Added builder to `Emulator`.
This commit is contained in:
Romain Malmain 2024-05-18 20:43:56 +02:00 committed by GitHub
parent 3a087301ac
commit dfd3b3278e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 1282 additions and 884 deletions

View File

@ -29,7 +29,7 @@ use libafl_bolts::{
};
use libafl_qemu::{
breakpoint::Breakpoint,
command::{Command, EndCommand, StartCommand},
command::{EndCommand, StartCommand, StdCommandManager},
edges::{edges_map_mut_ptr, QemuEdgeCoverageHelper, EDGES_MAP_SIZE_IN_USE, MAX_EDGES_FOUND},
elf::EasyElf,
emu::Emulator,
@ -95,28 +95,27 @@ pub fn fuzz() {
// Choose Exit Handler
let emu_exit_handler = StdEmulatorExitHandler::new(emu_snapshot_manager);
// Choose Command Manager
let cmd_manager = StdCommandManager::new();
// Create emulator
let emu = Emulator::new(&args, &env, emu_exit_handler).unwrap();
let emu = Emulator::new(&args, &env, emu_exit_handler, cmd_manager).unwrap();
// Set breakpoints of interest with corresponding commands.
emu.add_breakpoint(
Breakpoint::with_command(
main_addr,
Command::StartCommand(StartCommand::new(EmulatorMemoryChunk::phys(
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,
),
Breakpoint::with_command(breakpoint, EndCommand::new(Some(ExitKind::Ok)), false),
true,
);

View File

@ -27,6 +27,7 @@ use libafl_bolts::{
tuples::tuple_list,
};
use libafl_qemu::{
command::StdCommandManager,
edges::{edges_map_mut_ptr, QemuEdgeCoverageHelper, EDGES_MAP_SIZE_IN_USE, MAX_EDGES_FOUND},
emu::Emulator,
executor::{stateful::StatefulQemuExecutor, QemuExecutorState},
@ -52,11 +53,15 @@ pub fn fuzz() {
// 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: StdEmulatorExitHandler<FastSnapshotManager> =
StdEmulatorExitHandler::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 cmd_manager = StdCommandManager::new();
let emu = Emulator::new(&args, &env, emu_exit_handler, cmd_manager).unwrap(); // Create the emulator
let devices = emu.list_devices();
println!("Devices = {:?}", devices);

View File

@ -91,6 +91,7 @@ serde_yaml = { version = "0.9", optional = true } # For parsing the injections y
toml = { version = "0.8.13", optional = true } # For parsing the injections toml file
pyo3 = { version = "0.18", optional = true , features = ["multiple-pymethods"]}
bytes-utils = "0.1"
typed-builder = "0.18"
# Document all features of this crate (for `cargo doc`)
document-features = { version = "0.2", optional = true }

View File

@ -2,52 +2,138 @@ use std::{
borrow::Borrow,
fmt::{Display, Formatter},
hash::{Hash, Hasher},
rc::Rc,
sync::{
atomic::{AtomicU64, Ordering},
OnceLock,
},
};
use libafl::state::{HasExecutions, State};
use libafl_qemu_sys::GuestAddr;
use crate::{command::Command, Qemu};
use crate::{
command::{CommandManager, IsCommand},
EmulatorExitHandler, Qemu, QemuHelperTuple,
};
#[repr(transparent)]
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub struct BreakpointId(u64);
// TODO: distinguish breakpoints with IDs instead of addresses to avoid collisions.
#[derive(Debug, Clone)]
pub struct Breakpoint {
#[derive(Debug)]
pub struct Breakpoint<CM, E, QT, S>
where
CM: CommandManager<E, QT, S>,
E: EmulatorExitHandler<QT, S>,
QT: QemuHelperTuple<S>,
S: State + HasExecutions,
{
id: BreakpointId,
addr: GuestAddr,
cmd: Option<Command>,
cmd: Option<Rc<dyn IsCommand<CM, E, QT, S>>>,
disable_on_trigger: bool,
enabled: bool,
}
impl Hash for Breakpoint {
impl BreakpointId {
pub fn new() -> Self {
static mut BREAKPOINT_ID_COUNTER: OnceLock<AtomicU64> = OnceLock::new();
let counter = unsafe { BREAKPOINT_ID_COUNTER.get_or_init(|| AtomicU64::new(0)) };
BreakpointId(counter.fetch_add(1, Ordering::SeqCst))
}
}
impl Default for BreakpointId {
fn default() -> Self {
Self::new()
}
}
impl<CM, E, QT, S> Hash for Breakpoint<CM, E, QT, S>
where
CM: CommandManager<E, QT, S>,
E: EmulatorExitHandler<QT, S>,
QT: QemuHelperTuple<S>,
S: State + HasExecutions,
{
fn hash<H: Hasher>(&self, state: &mut H) {
self.addr.hash(state);
self.id.hash(state);
}
}
impl PartialEq for Breakpoint {
impl<CM, E, QT, S> PartialEq for Breakpoint<CM, E, QT, S>
where
CM: CommandManager<E, QT, S>,
E: EmulatorExitHandler<QT, S>,
QT: QemuHelperTuple<S>,
S: State + HasExecutions,
{
fn eq(&self, other: &Self) -> bool {
self.addr == other.addr
self.id == other.id
}
}
impl Eq for Breakpoint {}
impl<CM, E, QT, S> Eq for Breakpoint<CM, E, QT, S>
where
CM: CommandManager<E, QT, S>,
E: EmulatorExitHandler<QT, S>,
QT: QemuHelperTuple<S>,
S: State + HasExecutions,
{
}
impl Display for Breakpoint {
impl<CM, E, QT, S> Display for Breakpoint<CM, E, QT, S>
where
CM: CommandManager<E, QT, S>,
E: EmulatorExitHandler<QT, S>,
QT: QemuHelperTuple<S>,
S: State + HasExecutions,
{
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "Breakpoint @vaddr 0x{:x}", self.addr)
}
}
impl Borrow<GuestAddr> for Breakpoint {
impl<CM, E, QT, S> Borrow<BreakpointId> for Breakpoint<CM, E, QT, S>
where
CM: CommandManager<E, QT, S>,
E: EmulatorExitHandler<QT, S>,
QT: QemuHelperTuple<S>,
S: State + HasExecutions,
{
fn borrow(&self) -> &BreakpointId {
&self.id
}
}
impl<CM, E, QT, S> Borrow<GuestAddr> for Breakpoint<CM, E, QT, S>
where
CM: CommandManager<E, QT, S>,
E: EmulatorExitHandler<QT, S>,
QT: QemuHelperTuple<S>,
S: State + HasExecutions,
{
fn borrow(&self) -> &GuestAddr {
&self.addr
}
}
impl Breakpoint {
impl<CM, E, QT, S> Breakpoint<CM, E, QT, S>
where
CM: CommandManager<E, QT, S>,
E: EmulatorExitHandler<QT, S>,
QT: QemuHelperTuple<S>,
S: State + HasExecutions,
{
// Emu will return with the breakpoint as exit reason.
#[must_use]
pub fn without_command(addr: GuestAddr, disable_on_trigger: bool) -> Self {
Self {
id: BreakpointId::new(),
addr,
cmd: None,
disable_on_trigger,
@ -57,15 +143,25 @@ impl Breakpoint {
// 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 {
pub fn with_command<C: IsCommand<CM, E, QT, S> + 'static>(
addr: GuestAddr,
cmd: C,
disable_on_trigger: bool,
) -> Self {
Self {
id: BreakpointId::new(),
addr,
cmd: Some(cmd),
cmd: Some(Rc::new(cmd)),
disable_on_trigger,
enabled: false,
}
}
#[must_use]
pub fn id(&self) -> BreakpointId {
self.id
}
#[must_use]
pub fn addr(&self) -> GuestAddr {
self.addr
@ -85,11 +181,11 @@ impl Breakpoint {
}
}
pub fn trigger(&mut self, qemu: &Qemu) -> Option<&Command> {
pub fn trigger(&mut self, qemu: &Qemu) -> Option<Rc<dyn IsCommand<CM, E, QT, S>>> {
if self.disable_on_trigger {
self.disable(qemu);
}
self.cmd.as_ref()
self.cmd.clone()
}
}

View File

@ -1,741 +0,0 @@
#[cfg(emulation_mode = "systemmode")]
use std::collections::HashSet;
use std::{
fmt::{Debug, Display, Formatter},
sync::OnceLock,
};
use enum_map::{enum_map, Enum, EnumMap};
use libafl::{
executors::ExitKind,
inputs::HasTargetBytes,
state::{HasExecutions, State},
};
use libafl_bolts::AsSlice;
use libafl_qemu_sys::{GuestAddr, GuestPhysAddr, GuestVirtAddr};
use num_enum::{TryFromPrimitive, TryFromPrimitiveError};
#[cfg(emulation_mode = "systemmode")]
use crate::QemuInstrumentationPagingFilter;
use crate::{
executor::QemuExecutorState, get_exit_arch_regs, sync_exit::ExitArgs, Emulator,
EmulatorExitHandler, EmulatorMemoryChunk, ExitHandlerError, ExitHandlerResult, GuestReg,
HasInstrumentationFilter, InputLocation, IsFilter, IsSnapshotManager, Qemu, QemuHelperTuple,
QemuInstrumentationAddressRangeFilter, Regs, StdEmulatorExitHandler, StdInstrumentationFilter,
CPU,
};
pub const VERSION: u64 = bindings::LIBAFL_QEMU_HDR_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"), "/libafl_qemu_bindings.rs"));
}
#[derive(Debug, Clone, TryFromPrimitive)]
#[repr(u64)]
pub enum NativeCommand {
StartVirt = bindings::LibaflQemuCommand_LIBAFL_QEMU_COMMAND_START_VIRT.0 as u64, // Shortcut for Save + InputVirt
StartPhys = bindings::LibaflQemuCommand_LIBAFL_QEMU_COMMAND_START_PHYS.0 as u64, // Shortcut for Save + InputPhys
InputVirt = bindings::LibaflQemuCommand_LIBAFL_QEMU_COMMAND_INPUT_VIRT.0 as u64, // The address is a virtual address using the paging currently running in the VM.
InputPhys = bindings::LibaflQemuCommand_LIBAFL_QEMU_COMMAND_INPUT_PHYS.0 as u64, // The address is a physical address
End = bindings::LibaflQemuCommand_LIBAFL_QEMU_COMMAND_END.0 as u64, // Implies reloading of the target. The first argument gives the exit status.
Save = bindings::LibaflQemuCommand_LIBAFL_QEMU_COMMAND_SAVE.0 as u64, // Save the VM
Load = bindings::LibaflQemuCommand_LIBAFL_QEMU_COMMAND_LOAD.0 as u64, // Reload the target without ending the run?
Version = bindings::LibaflQemuCommand_LIBAFL_QEMU_COMMAND_VERSION.0 as u64, // Version of the bindings used in the target
VaddrFilterAllowRange =
bindings::LibaflQemuCommand_LIBAFL_QEMU_COMMAND_VADDR_FILTER_ALLOW.0 as u64, // Allow given address range
}
#[derive(Debug, Clone, Enum, TryFromPrimitive)]
#[repr(u64)]
pub enum NativeExitKind {
Unknown = bindings::LibaflQemuEndStatus_LIBAFL_QEMU_END_UNKNOWN.0 as u64, // Should not be used
Ok = bindings::LibaflQemuEndStatus_LIBAFL_QEMU_END_OK.0 as u64, // Normal exit
Crash = bindings::LibaflQemuEndStatus_LIBAFL_QEMU_END_CRASH.0 as u64, // Crash reported in the VM
}
pub trait IsCommand<QT, S, E>
where
QT: QemuHelperTuple<S>,
S: State + HasExecutions,
E: EmulatorExitHandler<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: &S::Input,
ret_reg: Option<Regs>,
) -> Result<Option<ExitHandlerResult>, ExitHandlerError>;
}
#[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),
}
pub static EMU_EXIT_KIND_MAP: OnceLock<EnumMap<NativeExitKind, Option<ExitKind>>> = OnceLock::new();
#[derive(Debug, Clone)]
pub enum CommandError {
UnknownCommand(GuestReg),
RegError(String),
VersionDifference(u64),
}
impl From<TryFromPrimitiveError<NativeCommand>> for CommandError {
fn from(error: TryFromPrimitiveError<NativeCommand>) -> Self {
CommandError::UnknownCommand(error.number.try_into().unwrap())
}
}
impl From<String> for CommandError {
fn from(error_string: String) -> Self {
CommandError::RegError(error_string)
}
}
impl TryFrom<Qemu> for Command {
type Error = CommandError;
#[allow(clippy::too_many_lines)]
fn try_from(qemu: Qemu) -> Result<Self, Self::Error> {
let arch_regs_map: &'static EnumMap<ExitArgs, Regs> = get_exit_arch_regs();
let cmd_id: GuestReg = qemu.read_reg::<Regs, GuestReg>(arch_regs_map[ExitArgs::Cmd])?;
Ok(match u64::from(cmd_id).try_into()? {
NativeCommand::Save => Command::SaveCommand(SaveCommand),
NativeCommand::Load => Command::LoadCommand(LoadCommand),
NativeCommand::InputVirt => {
let virt_addr: GuestVirtAddr = qemu.read_reg(arch_regs_map[ExitArgs::Arg1])?;
let max_input_size: GuestReg = qemu.read_reg(arch_regs_map[ExitArgs::Arg2])?;
Command::InputCommand(InputCommand::new(
EmulatorMemoryChunk::virt(
virt_addr,
max_input_size,
qemu.current_cpu().unwrap(),
),
qemu.current_cpu().unwrap(),
))
}
NativeCommand::InputPhys => {
let phys_addr: GuestPhysAddr = qemu.read_reg(arch_regs_map[ExitArgs::Arg1])?;
let max_input_size: GuestReg = qemu.read_reg(arch_regs_map[ExitArgs::Arg2])?;
Command::InputCommand(InputCommand::new(
EmulatorMemoryChunk::phys(
phys_addr,
max_input_size,
Some(qemu.current_cpu().unwrap()),
),
qemu.current_cpu().unwrap(),
))
}
NativeCommand::End => {
let native_exit_kind: GuestReg = qemu.read_reg(arch_regs_map[ExitArgs::Arg1])?;
let native_exit_kind: Result<NativeExitKind, _> =
u64::from(native_exit_kind).try_into();
let exit_kind = native_exit_kind.ok().and_then(|k| {
EMU_EXIT_KIND_MAP.get_or_init(|| {
enum_map! {
NativeExitKind::Unknown => None,
NativeExitKind::Ok => Some(ExitKind::Ok),
NativeExitKind::Crash => Some(ExitKind::Crash)
}
})[k]
});
Command::EndCommand(EndCommand::new(exit_kind))
}
NativeCommand::StartPhys => {
let input_phys_addr: GuestPhysAddr =
qemu.read_reg(arch_regs_map[ExitArgs::Arg1])?;
let max_input_size: GuestReg = qemu.read_reg(arch_regs_map[ExitArgs::Arg2])?;
Command::StartCommand(StartCommand::new(EmulatorMemoryChunk::phys(
input_phys_addr,
max_input_size,
Some(qemu.current_cpu().unwrap()),
)))
}
NativeCommand::StartVirt => {
let input_virt_addr: GuestVirtAddr =
qemu.read_reg(arch_regs_map[ExitArgs::Arg1])?;
let max_input_size: GuestReg = qemu.read_reg(arch_regs_map[ExitArgs::Arg2])?;
Command::StartCommand(StartCommand::new(EmulatorMemoryChunk::virt(
input_virt_addr,
max_input_size,
qemu.current_cpu().unwrap(),
)))
}
NativeCommand::Version => {
let client_version = qemu.read_reg(arch_regs_map[ExitArgs::Arg1])?;
Command::VersionCommand(VersionCommand::new(client_version))
}
NativeCommand::VaddrFilterAllowRange => {
let vaddr_start: GuestAddr = qemu.read_reg(arch_regs_map[ExitArgs::Arg1])?;
let vaddr_end: GuestAddr = qemu.read_reg(arch_regs_map[ExitArgs::Arg2])?;
Command::AddressRangeFilterCommand(FilterCommand::new(
#[allow(clippy::single_range_in_vec_init)]
QemuInstrumentationAddressRangeFilter::AllowList(vec![vaddr_start..vaddr_end]),
))
}
})
}
}
// TODO: Replace with enum_dispatch implementation
impl<SM, QT, S> IsCommand<QT, S, StdEmulatorExitHandler<SM>> for Command
where
SM: IsSnapshotManager,
QT: QemuHelperTuple<S> + StdInstrumentationFilter<S> + Debug,
S: State + HasExecutions,
S::Input: HasTargetBytes,
{
fn usable_at_runtime(&self) -> bool {
match self {
Command::SaveCommand(cmd) => {
<SaveCommand as IsCommand<QT, S, StdEmulatorExitHandler<SM>>>::usable_at_runtime(
cmd,
)
}
Command::LoadCommand(cmd) => {
<LoadCommand as IsCommand<QT, S, StdEmulatorExitHandler<SM>>>::usable_at_runtime(
cmd,
)
}
Command::InputCommand(cmd) => {
<InputCommand as IsCommand<QT, S, StdEmulatorExitHandler<SM>>>::usable_at_runtime(
cmd,
)
}
Command::StartCommand(cmd) => {
<StartCommand as IsCommand<QT, S, StdEmulatorExitHandler<SM>>>::usable_at_runtime(
cmd,
)
}
Command::EndCommand(cmd) => {
<EndCommand as IsCommand<QT, S, StdEmulatorExitHandler<SM>>>::usable_at_runtime(cmd)
}
Command::VersionCommand(cmd) => {
<VersionCommand as IsCommand<QT, S, StdEmulatorExitHandler<SM>>>::usable_at_runtime(
cmd,
)
}
#[cfg(emulation_mode = "systemmode")]
Command::PagingFilterCommand(cmd) => <PagingFilterCommand as IsCommand<
QT,
S,
StdEmulatorExitHandler<SM>,
>>::usable_at_runtime(cmd),
Command::AddressRangeFilterCommand(cmd) => <AddressRangeFilterCommand as IsCommand<
QT,
S,
StdEmulatorExitHandler<SM>,
>>::usable_at_runtime(cmd),
}
}
fn run(
&self,
emu: &Emulator<QT, S, StdEmulatorExitHandler<SM>>,
qemu_executor_state: &mut QemuExecutorState<QT, S>,
input: &S::Input,
ret_reg: Option<Regs>,
) -> Result<Option<ExitHandlerResult>, ExitHandlerError> {
match self {
Command::SaveCommand(cmd) => <SaveCommand as IsCommand<
QT,
S,
StdEmulatorExitHandler<SM>,
>>::run(
cmd, emu, qemu_executor_state, input, ret_reg
),
Command::LoadCommand(cmd) => <LoadCommand as IsCommand<
QT,
S,
StdEmulatorExitHandler<SM>,
>>::run(
cmd, emu, qemu_executor_state, input, ret_reg
),
Command::InputCommand(cmd) => <InputCommand as IsCommand<
QT,
S,
StdEmulatorExitHandler<SM>,
>>::run(
cmd, emu, qemu_executor_state, input, ret_reg
),
Command::StartCommand(cmd) => <StartCommand as IsCommand<
QT,
S,
StdEmulatorExitHandler<SM>,
>>::run(
cmd, emu, qemu_executor_state, input, ret_reg
),
Command::EndCommand(cmd) => <EndCommand as IsCommand<
QT,
S,
StdEmulatorExitHandler<SM>,
>>::run(
cmd, emu, qemu_executor_state, input, ret_reg
),
Command::VersionCommand(cmd) => <VersionCommand as IsCommand<
QT,
S,
StdEmulatorExitHandler<SM>,
>>::run(
cmd, emu, qemu_executor_state, input, ret_reg
),
#[cfg(emulation_mode = "systemmode")]
Command::PagingFilterCommand(cmd) => <PagingFilterCommand as IsCommand<
QT,
S,
StdEmulatorExitHandler<SM>,
>>::run(
cmd, emu, qemu_executor_state, input, ret_reg
),
Command::AddressRangeFilterCommand(cmd) => {
<AddressRangeFilterCommand as IsCommand<QT, S, StdEmulatorExitHandler<SM>>>::run(
cmd,
emu,
qemu_executor_state,
input,
ret_reg,
)
}
}
}
}
#[derive(Debug, Clone)]
pub struct SaveCommand;
impl<SM, QT, S> IsCommand<QT, S, StdEmulatorExitHandler<SM>> for SaveCommand
where
SM: IsSnapshotManager,
QT: QemuHelperTuple<S> + StdInstrumentationFilter<S> + Debug,
S: State + HasExecutions,
S::Input: HasTargetBytes,
{
fn usable_at_runtime(&self) -> bool {
false
}
fn run(
&self,
emu: &Emulator<QT, S, StdEmulatorExitHandler<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: &S::Input,
_ret_reg: Option<Regs>,
) -> Result<Option<ExitHandlerResult>, ExitHandlerError> {
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(|_| ExitHandlerError::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(None)
}
}
#[derive(Debug, Clone)]
pub struct LoadCommand;
impl<SM, QT, S> IsCommand<QT, S, StdEmulatorExitHandler<SM>> for LoadCommand
where
SM: IsSnapshotManager,
QT: QemuHelperTuple<S> + StdInstrumentationFilter<S> + Debug,
S: State + HasExecutions,
S::Input: HasTargetBytes,
{
fn usable_at_runtime(&self) -> bool {
false
}
fn run(
&self,
emu: &Emulator<QT, S, StdEmulatorExitHandler<SM>>,
_qemu_executor_state: &mut QemuExecutorState<QT, S>,
_input: &S::Input,
_ret_reg: Option<Regs>,
) -> Result<Option<ExitHandlerResult>, ExitHandlerError> {
let qemu = emu.qemu();
let emu_exit_handler = emu.exit_handler().borrow_mut();
let snapshot_id = emu_exit_handler
.snapshot_id()
.ok_or(ExitHandlerError::SnapshotNotFound)?;
emu_exit_handler
.snapshot_manager_borrow_mut()
.restore(&snapshot_id, qemu)?;
Ok(None)
}
}
#[derive(Debug, Clone)]
pub struct InputCommand {
location: EmulatorMemoryChunk,
cpu: CPU,
}
impl<SM, QT, S> IsCommand<QT, S, StdEmulatorExitHandler<SM>> for InputCommand
where
SM: IsSnapshotManager,
QT: QemuHelperTuple<S> + StdInstrumentationFilter<S> + Debug,
S: State + HasExecutions,
S::Input: HasTargetBytes,
{
fn usable_at_runtime(&self) -> bool {
true
}
fn run(
&self,
emu: &Emulator<QT, S, StdEmulatorExitHandler<SM>>,
_qemu_executor_state: &mut QemuExecutorState<QT, S>,
input: &S::Input,
ret_reg: Option<Regs>,
) -> Result<Option<ExitHandlerResult>, ExitHandlerError> {
let qemu = emu.qemu();
let ret_value = self.location.write(qemu, input.target_bytes().as_slice());
if let Some(reg) = ret_reg {
self.cpu.write_reg(reg, ret_value).unwrap();
}
Ok(None)
}
}
#[derive(Debug, Clone)]
pub struct StartCommand {
input_location: EmulatorMemoryChunk,
}
impl<SM, QT, S> IsCommand<QT, S, StdEmulatorExitHandler<SM>> for StartCommand
where
SM: IsSnapshotManager,
QT: QemuHelperTuple<S> + StdInstrumentationFilter<S> + Debug,
S: State + HasExecutions,
S::Input: HasTargetBytes,
{
fn usable_at_runtime(&self) -> bool {
false
}
fn run(
&self,
emu: &Emulator<QT, S, StdEmulatorExitHandler<SM>>,
_qemu_executor_state: &mut QemuExecutorState<QT, S>,
input: &S::Input,
ret_reg: Option<Regs>,
) -> Result<Option<ExitHandlerResult>, ExitHandlerError> {
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(|_| ExitHandlerError::MultipleSnapshotDefinition)?;
emu_exit_handler
.set_input_location(InputLocation::new(
self.input_location.clone(),
qemu.current_cpu().unwrap(),
ret_reg,
))
.unwrap();
let ret_value = self
.input_location
.write(qemu, input.target_bytes().as_slice());
if let Some(reg) = ret_reg {
qemu.write_reg(reg, ret_value).unwrap();
}
Ok(None)
}
}
#[derive(Debug, Clone)]
pub struct EndCommand(Option<ExitKind>);
impl<SM, QT, S> IsCommand<QT, S, StdEmulatorExitHandler<SM>> for EndCommand
where
SM: IsSnapshotManager,
QT: QemuHelperTuple<S> + StdInstrumentationFilter<S> + Debug,
S: State + HasExecutions,
S::Input: HasTargetBytes,
{
fn usable_at_runtime(&self) -> bool {
false
}
fn run(
&self,
emu: &Emulator<QT, S, StdEmulatorExitHandler<SM>>,
_qemu_executor_state: &mut QemuExecutorState<QT, S>,
_input: &S::Input,
_ret_reg: Option<Regs>,
) -> Result<Option<ExitHandlerResult>, ExitHandlerError> {
let emu_exit_handler = emu.exit_handler().borrow_mut();
let snapshot_id = emu_exit_handler
.snapshot_id()
.ok_or(ExitHandlerError::SnapshotNotFound)?;
emu_exit_handler
.snapshot_manager_borrow_mut()
.restore(&snapshot_id, emu.qemu())?;
Ok(Some(ExitHandlerResult::EndOfRun(self.0.unwrap())))
}
}
#[derive(Debug, Clone)]
pub struct VersionCommand(u64);
impl<SM, QT, S> IsCommand<QT, S, StdEmulatorExitHandler<SM>> for VersionCommand
where
SM: IsSnapshotManager,
QT: QemuHelperTuple<S> + StdInstrumentationFilter<S> + Debug,
S: State + HasExecutions,
S::Input: HasTargetBytes,
{
fn usable_at_runtime(&self) -> bool {
true
}
fn run(
&self,
_emu: &Emulator<QT, S, StdEmulatorExitHandler<SM>>,
_qemu_executor_state: &mut QemuExecutorState<QT, S>,
_input: &S::Input,
_ret_reg: Option<Regs>,
) -> Result<Option<ExitHandlerResult>, ExitHandlerError> {
let guest_version = self.0;
if VERSION == guest_version {
Ok(None)
} else {
Err(ExitHandlerError::CommandError(
CommandError::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, StdEmulatorExitHandler<SM>> for PagingFilterCommand
where
SM: IsSnapshotManager,
QT: QemuHelperTuple<S> + StdInstrumentationFilter<S> + Debug,
S: State + HasExecutions,
S::Input: HasTargetBytes,
{
fn usable_at_runtime(&self) -> bool {
true
}
fn run(
&self,
_emu: &Emulator<QT, S, StdEmulatorExitHandler<SM>>,
qemu_executor_state: &mut QemuExecutorState<QT, S>,
_input: &S::Input,
_ret_reg: Option<Regs>,
) -> Result<Option<ExitHandlerResult>, ExitHandlerError> {
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(None)
}
}
impl<SM, QT, S> IsCommand<QT, S, StdEmulatorExitHandler<SM>> for AddressRangeFilterCommand
where
SM: IsSnapshotManager,
QT: QemuHelperTuple<S> + StdInstrumentationFilter<S> + Debug,
S: State + HasExecutions,
S::Input: HasTargetBytes,
{
fn usable_at_runtime(&self) -> bool {
true
}
#[allow(clippy::type_complexity)] // TODO: refactor with correct type.
fn run(
&self,
_emu: &Emulator<QT, S, StdEmulatorExitHandler<SM>>,
qemu_executor_state: &mut QemuExecutorState<QT, S>,
_input: &S::Input,
_ret_reg: Option<Regs>,
) -> Result<Option<ExitHandlerResult>, ExitHandlerError> {
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(None)
}
}
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, cpu: CPU) -> Self {
Self { location, cpu }
}
}
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

@ -0,0 +1,651 @@
#[cfg(emulation_mode = "systemmode")]
use std::collections::HashSet;
use std::{
collections::HashMap,
fmt::{Debug, Display, Error, Formatter},
rc::Rc,
};
use enum_map::{Enum, EnumMap};
use libafl::{
executors::ExitKind,
inputs::HasTargetBytes,
state::{HasExecutions, State},
};
use libafl_bolts::AsSlice;
use num_enum::TryFromPrimitive;
#[cfg(emulation_mode = "systemmode")]
use crate::QemuInstrumentationPagingFilter;
use crate::{
command::parser::{
EndCommandParser, InputPhysCommandParser, InputVirtCommandParser, LoadCommandParser,
NativeCommandParser, SaveCommandParser, StartPhysCommandParser, StartVirtCommandParser,
VaddrFilterAllowRangeCommandParser, VersionCommandParser,
},
executor::QemuExecutorState,
get_exit_arch_regs,
sync_exit::ExitArgs,
Emulator, EmulatorExitHandler, EmulatorMemoryChunk, ExitHandlerError, ExitHandlerResult,
GuestReg, HasInstrumentationFilter, InputLocation, IsFilter, IsSnapshotManager, Qemu,
QemuHelperTuple, QemuInstrumentationAddressRangeFilter, Regs, StdEmulatorExitHandler,
StdInstrumentationFilter, CPU,
};
pub mod parser;
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"), "/libafl_qemu_bindings.rs"));
}
pub const VERSION: u64 = bindings::LIBAFL_QEMU_HDR_VERSION_NUMBER as u64;
macro_rules! define_std_command_manager {
($name:ident, [$($native_command_parser:ident),+]) => {
pub struct $name<QT, S, SM>
where
QT: QemuHelperTuple<S> + StdInstrumentationFilter<S> + Debug,
S: State + HasExecutions,
S::Input: HasTargetBytes,
SM: IsSnapshotManager,
{
native_command_parsers:
HashMap<GuestReg, Box<dyn NativeCommandParser<Self, StdEmulatorExitHandler<SM>, QT, S>>>,
}
impl<QT, S, SM> $name<QT, S, SM>
where
QT: QemuHelperTuple<S> + StdInstrumentationFilter<S> + Debug,
S: State + HasExecutions,
S::Input: HasTargetBytes,
SM: IsSnapshotManager,
{
#[must_use]
pub fn new() -> Self {
let native_parsers = Box::new(
vec![$(Box::new($native_command_parser)
as Box<
dyn NativeCommandParser<
Self,
StdEmulatorExitHandler<SM>,
QT,
S,
>,
>),*]
.into_iter(),
);
let mut parsers: HashMap<
GuestReg,
Box<dyn NativeCommandParser<Self, StdEmulatorExitHandler<SM>, QT, S>>,
> = HashMap::new();
for parser in native_parsers {
assert!(parsers
.insert(parser.command_id(), parser)
.is_none(), "Trying to use native commands with the same ID");
}
Self {
native_command_parsers: parsers,
}
}
}
impl<QT, S, SM> CommandManager<StdEmulatorExitHandler<SM>, QT, S> for $name<QT, S, SM>
where
QT: QemuHelperTuple<S> + StdInstrumentationFilter<S> + Debug,
S: State + HasExecutions,
S::Input: HasTargetBytes,
SM: IsSnapshotManager,
{
fn parse(
&self,
qemu: Qemu,
) -> Result<Rc<dyn IsCommand<Self, StdEmulatorExitHandler<SM>, QT, S>>, CommandError> {
let arch_regs_map: &'static EnumMap<ExitArgs, Regs> = get_exit_arch_regs();
let cmd_id: GuestReg = qemu.read_reg::<Regs, GuestReg>(arch_regs_map[ExitArgs::Cmd])?;
let cmd_parser = self
.native_command_parsers
.get(&cmd_id)
.ok_or(CommandError::UnknownCommand(cmd_id))?;
let cmd = cmd_parser.parse(qemu, arch_regs_map)?;
Ok(cmd)
}
}
impl<QT, S, SM> Debug for $name<QT, S, SM>
where
QT: QemuHelperTuple<S> + StdInstrumentationFilter<S> + Debug,
S: State + HasExecutions,
S::Input: HasTargetBytes,
SM: IsSnapshotManager,
{
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> {
write!(f, stringify!($name))
}
}
impl<QT, S, SM> Default for $name<QT, S, SM>
where
QT: QemuHelperTuple<S> + StdInstrumentationFilter<S> + Debug,
S: State + HasExecutions,
S::Input: HasTargetBytes,
SM: IsSnapshotManager,
{
fn default() -> Self {
Self::new()
}
}
};
}
define_std_command_manager!(
StdCommandManager,
[
StartPhysCommandParser,
StartVirtCommandParser,
InputPhysCommandParser,
InputVirtCommandParser,
SaveCommandParser,
LoadCommandParser,
EndCommandParser,
VersionCommandParser,
VaddrFilterAllowRangeCommandParser
]
);
pub trait CommandManager<E, QT, S>: Sized
where
E: EmulatorExitHandler<QT, S>,
QT: QemuHelperTuple<S>,
S: State + HasExecutions,
{
fn parse(&self, qemu: Qemu) -> Result<Rc<dyn IsCommand<Self, E, QT, S>>, CommandError>;
}
#[derive(Debug, Clone, Enum, TryFromPrimitive)]
#[repr(u64)]
pub enum NativeExitKind {
Unknown = bindings::LibaflQemuEndStatus_LIBAFL_QEMU_END_UNKNOWN.0 as u64, // Should not be used
Ok = bindings::LibaflQemuEndStatus_LIBAFL_QEMU_END_OK.0 as u64, // Normal exit
Crash = bindings::LibaflQemuEndStatus_LIBAFL_QEMU_END_CRASH.0 as u64, // Crash reported in the VM
}
pub trait IsCommand<CM, E, QT, S>: Debug + Display
where
CM: CommandManager<E, QT, S>,
E: EmulatorExitHandler<QT, S>,
QT: QemuHelperTuple<S>,
S: State + HasExecutions,
{
/// 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<CM, E, QT, S>,
qemu_executor_state: &mut QemuExecutorState<QT, S>,
input: &S::Input,
ret_reg: Option<Regs>,
) -> Result<Option<ExitHandlerResult<CM, E, QT, S>>, ExitHandlerError>;
}
#[cfg(emulation_mode = "systemmode")]
pub type PagingFilterCommand = FilterCommand<QemuInstrumentationPagingFilter>;
pub type AddressRangeFilterCommand = FilterCommand<QemuInstrumentationAddressRangeFilter>;
#[derive(Debug, Clone)]
pub enum CommandError {
UnknownCommand(GuestReg),
RegError(String),
VersionDifference(u64),
}
impl From<String> for CommandError {
fn from(error_string: String) -> Self {
CommandError::RegError(error_string)
}
}
#[derive(Debug, Clone)]
pub struct SaveCommand;
impl<CM, QT, S, SM> IsCommand<CM, StdEmulatorExitHandler<SM>, QT, S> for SaveCommand
where
CM: CommandManager<StdEmulatorExitHandler<SM>, QT, S>,
QT: QemuHelperTuple<S> + StdInstrumentationFilter<S> + Debug,
S: State + HasExecutions,
S::Input: HasTargetBytes,
SM: IsSnapshotManager,
{
fn usable_at_runtime(&self) -> bool {
false
}
fn run(
&self,
emu: &Emulator<CM, StdEmulatorExitHandler<SM>, QT, S>,
#[cfg(emulation_mode = "systemmode")] qemu_executor_state: &mut QemuExecutorState<QT, S>,
#[cfg(not(emulation_mode = "systemmode"))] _qemu_executor_state: &mut QemuExecutorState<
QT,
S,
>,
_input: &S::Input,
_ret_reg: Option<Regs>,
) -> Result<Option<ExitHandlerResult<CM, StdEmulatorExitHandler<SM>, QT, S>>, ExitHandlerError>
{
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(|_| ExitHandlerError::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(None)
}
}
#[derive(Debug, Clone)]
pub struct LoadCommand;
impl<CM, QT, S, SM> IsCommand<CM, StdEmulatorExitHandler<SM>, QT, S> for LoadCommand
where
CM: CommandManager<StdEmulatorExitHandler<SM>, QT, S>,
QT: QemuHelperTuple<S> + StdInstrumentationFilter<S> + Debug,
S: State + HasExecutions,
S::Input: HasTargetBytes,
SM: IsSnapshotManager,
{
fn usable_at_runtime(&self) -> bool {
false
}
fn run(
&self,
emu: &Emulator<CM, StdEmulatorExitHandler<SM>, QT, S>,
_qemu_executor_state: &mut QemuExecutorState<QT, S>,
_input: &S::Input,
_ret_reg: Option<Regs>,
) -> Result<Option<ExitHandlerResult<CM, StdEmulatorExitHandler<SM>, QT, S>>, ExitHandlerError>
{
let qemu = emu.qemu();
let emu_exit_handler = emu.exit_handler().borrow_mut();
let snapshot_id = emu_exit_handler
.snapshot_id()
.ok_or(ExitHandlerError::SnapshotNotFound)?;
emu_exit_handler
.snapshot_manager_borrow_mut()
.restore(&snapshot_id, qemu)?;
Ok(None)
}
}
#[derive(Debug, Clone)]
pub struct InputCommand {
location: EmulatorMemoryChunk,
cpu: CPU,
}
impl<CM, QT, S, SM> IsCommand<CM, StdEmulatorExitHandler<SM>, QT, S> for InputCommand
where
CM: CommandManager<StdEmulatorExitHandler<SM>, QT, S>,
QT: QemuHelperTuple<S> + StdInstrumentationFilter<S> + Debug,
S: State + HasExecutions,
S::Input: HasTargetBytes,
SM: IsSnapshotManager,
{
fn usable_at_runtime(&self) -> bool {
true
}
fn run(
&self,
emu: &Emulator<CM, StdEmulatorExitHandler<SM>, QT, S>,
_qemu_executor_state: &mut QemuExecutorState<QT, S>,
input: &S::Input,
ret_reg: Option<Regs>,
) -> Result<Option<ExitHandlerResult<CM, StdEmulatorExitHandler<SM>, QT, S>>, ExitHandlerError>
{
let qemu = emu.qemu();
let ret_value = self.location.write(qemu, input.target_bytes().as_slice());
if let Some(reg) = ret_reg {
self.cpu.write_reg(reg, ret_value).unwrap();
}
Ok(None)
}
}
#[derive(Debug, Clone)]
pub struct StartCommand {
input_location: EmulatorMemoryChunk,
}
impl<CM, QT, S, SM> IsCommand<CM, StdEmulatorExitHandler<SM>, QT, S> for StartCommand
where
CM: CommandManager<StdEmulatorExitHandler<SM>, QT, S>,
QT: QemuHelperTuple<S> + StdInstrumentationFilter<S> + Debug,
S: State + HasExecutions,
S::Input: HasTargetBytes,
SM: IsSnapshotManager,
{
fn usable_at_runtime(&self) -> bool {
false
}
fn run(
&self,
emu: &Emulator<CM, StdEmulatorExitHandler<SM>, QT, S>,
_qemu_executor_state: &mut QemuExecutorState<QT, S>,
input: &S::Input,
ret_reg: Option<Regs>,
) -> Result<Option<ExitHandlerResult<CM, StdEmulatorExitHandler<SM>, QT, S>>, ExitHandlerError>
{
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(|_| ExitHandlerError::MultipleSnapshotDefinition)?;
emu_exit_handler
.set_input_location(InputLocation::new(
self.input_location.clone(),
qemu.current_cpu().unwrap(),
ret_reg,
))
.unwrap();
let ret_value = self
.input_location
.write(qemu, input.target_bytes().as_slice());
if let Some(reg) = ret_reg {
qemu.write_reg(reg, ret_value).unwrap();
}
Ok(None)
}
}
#[derive(Debug, Clone)]
pub struct EndCommand(Option<ExitKind>);
impl<CM, QT, S, SM> IsCommand<CM, StdEmulatorExitHandler<SM>, QT, S> for EndCommand
where
CM: CommandManager<StdEmulatorExitHandler<SM>, QT, S>,
QT: QemuHelperTuple<S> + StdInstrumentationFilter<S> + Debug,
S: State + HasExecutions,
S::Input: HasTargetBytes,
SM: IsSnapshotManager,
{
fn usable_at_runtime(&self) -> bool {
false
}
fn run(
&self,
emu: &Emulator<CM, StdEmulatorExitHandler<SM>, QT, S>,
_qemu_executor_state: &mut QemuExecutorState<QT, S>,
_input: &S::Input,
_ret_reg: Option<Regs>,
) -> Result<Option<ExitHandlerResult<CM, StdEmulatorExitHandler<SM>, QT, S>>, ExitHandlerError>
{
let emu_exit_handler = emu.exit_handler().borrow_mut();
let snapshot_id = emu_exit_handler
.snapshot_id()
.ok_or(ExitHandlerError::SnapshotNotFound)?;
emu_exit_handler
.snapshot_manager_borrow_mut()
.restore(&snapshot_id, emu.qemu())?;
Ok(Some(ExitHandlerResult::EndOfRun(self.0.unwrap())))
}
}
#[derive(Debug, Clone)]
pub struct VersionCommand(u64);
impl<CM, QT, S, SM> IsCommand<CM, StdEmulatorExitHandler<SM>, QT, S> for VersionCommand
where
CM: CommandManager<StdEmulatorExitHandler<SM>, QT, S>,
QT: QemuHelperTuple<S> + StdInstrumentationFilter<S> + Debug,
S: State + HasExecutions,
S::Input: HasTargetBytes,
SM: IsSnapshotManager,
{
fn usable_at_runtime(&self) -> bool {
true
}
fn run(
&self,
_emu: &Emulator<CM, StdEmulatorExitHandler<SM>, QT, S>,
_qemu_executor_state: &mut QemuExecutorState<QT, S>,
_input: &S::Input,
_ret_reg: Option<Regs>,
) -> Result<Option<ExitHandlerResult<CM, StdEmulatorExitHandler<SM>, QT, S>>, ExitHandlerError>
{
let guest_version = self.0;
if VERSION == guest_version {
Ok(None)
} else {
Err(ExitHandlerError::CommandError(
CommandError::VersionDifference(guest_version),
))
}
}
}
#[derive(Debug, Clone)]
pub struct FilterCommand<T>
where
T: IsFilter + Debug,
{
filter: T,
}
#[cfg(emulation_mode = "systemmode")]
impl<CM, QT, S, SM> IsCommand<CM, StdEmulatorExitHandler<SM>, QT, S> for PagingFilterCommand
where
CM: CommandManager<StdEmulatorExitHandler<SM>, QT, S>,
QT: QemuHelperTuple<S> + StdInstrumentationFilter<S> + Debug,
S: State + HasExecutions,
S::Input: HasTargetBytes,
SM: IsSnapshotManager,
{
fn usable_at_runtime(&self) -> bool {
true
}
fn run(
&self,
_emu: &Emulator<CM, StdEmulatorExitHandler<SM>, QT, S>,
qemu_executor_state: &mut QemuExecutorState<QT, S>,
_input: &S::Input,
_ret_reg: Option<Regs>,
) -> Result<Option<ExitHandlerResult<CM, StdEmulatorExitHandler<SM>, QT, S>>, ExitHandlerError>
{
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(None)
}
}
impl<CM, QT, S, SM> IsCommand<CM, StdEmulatorExitHandler<SM>, QT, S> for AddressRangeFilterCommand
where
CM: CommandManager<StdEmulatorExitHandler<SM>, QT, S>,
QT: QemuHelperTuple<S> + StdInstrumentationFilter<S> + Debug,
S: State + HasExecutions,
S::Input: HasTargetBytes,
SM: IsSnapshotManager,
{
fn usable_at_runtime(&self) -> bool {
true
}
#[allow(clippy::type_complexity)] // TODO: refactor with correct type.
fn run(
&self,
_emu: &Emulator<CM, StdEmulatorExitHandler<SM>, QT, S>,
qemu_executor_state: &mut QemuExecutorState<QT, S>,
_input: &S::Input,
_ret_reg: Option<Regs>,
) -> Result<Option<ExitHandlerResult<CM, StdEmulatorExitHandler<SM>, QT, S>>, ExitHandlerError>
{
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(None)
}
}
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 }
}
}
impl Display for SaveCommand {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "Save VM")
}
}
impl Display for LoadCommand {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "Reload VM")
}
}
impl Display for InputCommand {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "Set fuzzing input @{}", self.location.addr())
}
}
impl Display for StartCommand {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(
f,
"Start fuzzing with input @{}",
self.input_location.addr()
)
}
}
impl Display for EndCommand {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "Exit of kind {:?}", self.0)
}
}
impl Display for VersionCommand {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "Client version: {}", self.0)
}
}
impl Display for AddressRangeFilterCommand {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "Addr range filter: {:?}", self.filter,)
}
}
#[cfg(emulation_mode = "systemmode")]
impl Display for PagingFilterCommand {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "Addr range filter: {:?}", self.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, cpu: CPU) -> Self {
Self { location, cpu }
}
}

View File

@ -0,0 +1,292 @@
use std::{fmt::Debug, rc::Rc, sync::OnceLock};
use enum_map::{enum_map, EnumMap};
use libafl::{
executors::ExitKind,
inputs::HasTargetBytes,
state::{HasExecutions, State},
};
use libafl_qemu_sys::{GuestAddr, GuestPhysAddr, GuestVirtAddr};
use crate::{
command::{
bindings, CommandError, CommandManager, EndCommand, FilterCommand, InputCommand, IsCommand,
LoadCommand, NativeExitKind, SaveCommand, StartCommand, VersionCommand,
},
sync_exit::ExitArgs,
EmulatorExitHandler, EmulatorMemoryChunk, GuestReg, IsSnapshotManager, Qemu, QemuHelperTuple,
QemuInstrumentationAddressRangeFilter, Regs, StdEmulatorExitHandler, StdInstrumentationFilter,
};
pub static EMU_EXIT_KIND_MAP: OnceLock<EnumMap<NativeExitKind, Option<ExitKind>>> = OnceLock::new();
pub trait NativeCommandParser<CM, E, QT, S>
where
CM: CommandManager<E, QT, S>,
E: EmulatorExitHandler<QT, S>,
QT: QemuHelperTuple<S>,
S: State + HasExecutions,
{
fn command_id(&self) -> GuestReg;
fn parse(
&self,
qemu: Qemu,
arch_regs_map: &'static EnumMap<ExitArgs, Regs>,
) -> Result<Rc<dyn IsCommand<CM, E, QT, S>>, CommandError>;
}
pub struct InputPhysCommandParser;
impl<CM, QT, S, SM> NativeCommandParser<CM, StdEmulatorExitHandler<SM>, QT, S>
for InputPhysCommandParser
where
CM: CommandManager<StdEmulatorExitHandler<SM>, QT, S>,
QT: QemuHelperTuple<S> + StdInstrumentationFilter<S> + Debug,
S: State + HasExecutions,
S::Input: HasTargetBytes,
SM: IsSnapshotManager,
{
fn command_id(&self) -> GuestReg {
GuestReg::from(bindings::LibaflQemuCommand_LIBAFL_QEMU_COMMAND_INPUT_PHYS.0)
}
fn parse(
&self,
qemu: Qemu,
arch_regs_map: &'static EnumMap<ExitArgs, Regs>,
) -> Result<Rc<dyn IsCommand<CM, StdEmulatorExitHandler<SM>, QT, S>>, CommandError> {
let input_phys_addr: GuestPhysAddr = qemu.read_reg(arch_regs_map[ExitArgs::Arg1])?;
let max_input_size: GuestReg = qemu.read_reg(arch_regs_map[ExitArgs::Arg2])?;
Ok(Rc::new(InputCommand::new(
EmulatorMemoryChunk::phys(
input_phys_addr,
max_input_size,
Some(qemu.current_cpu().unwrap()),
),
qemu.current_cpu().unwrap(),
)))
}
}
pub struct InputVirtCommandParser;
impl<CM, QT, S, SM> NativeCommandParser<CM, StdEmulatorExitHandler<SM>, QT, S>
for crate::command::parser::InputVirtCommandParser
where
CM: CommandManager<StdEmulatorExitHandler<SM>, QT, S>,
QT: QemuHelperTuple<S> + StdInstrumentationFilter<S> + Debug,
S: State + HasExecutions,
S::Input: HasTargetBytes,
SM: IsSnapshotManager,
{
fn command_id(&self) -> GuestReg {
GuestReg::from(bindings::LibaflQemuCommand_LIBAFL_QEMU_COMMAND_INPUT_VIRT.0)
}
fn parse(
&self,
qemu: Qemu,
arch_regs_map: &'static EnumMap<ExitArgs, Regs>,
) -> Result<Rc<dyn IsCommand<CM, StdEmulatorExitHandler<SM>, QT, S>>, CommandError> {
let input_virt_addr: GuestVirtAddr = qemu.read_reg(arch_regs_map[ExitArgs::Arg1])?;
let max_input_size: GuestReg = qemu.read_reg(arch_regs_map[ExitArgs::Arg2])?;
Ok(Rc::new(InputCommand::new(
EmulatorMemoryChunk::virt(input_virt_addr, max_input_size, qemu.current_cpu().unwrap()),
qemu.current_cpu().unwrap(),
)))
}
}
pub struct StartPhysCommandParser;
impl<CM, QT, S, SM> NativeCommandParser<CM, StdEmulatorExitHandler<SM>, QT, S>
for StartPhysCommandParser
where
CM: CommandManager<StdEmulatorExitHandler<SM>, QT, S>,
QT: QemuHelperTuple<S> + StdInstrumentationFilter<S> + Debug,
S: State + HasExecutions,
S::Input: HasTargetBytes,
SM: IsSnapshotManager,
{
fn command_id(&self) -> GuestReg {
GuestReg::from(bindings::LibaflQemuCommand_LIBAFL_QEMU_COMMAND_START_PHYS.0)
}
fn parse(
&self,
qemu: Qemu,
arch_regs_map: &'static EnumMap<ExitArgs, Regs>,
) -> Result<Rc<dyn IsCommand<CM, StdEmulatorExitHandler<SM>, QT, S>>, CommandError> {
let input_phys_addr: GuestPhysAddr = qemu.read_reg(arch_regs_map[ExitArgs::Arg1])?;
let max_input_size: GuestReg = qemu.read_reg(arch_regs_map[ExitArgs::Arg2])?;
Ok(Rc::new(StartCommand::new(EmulatorMemoryChunk::phys(
input_phys_addr,
max_input_size,
Some(qemu.current_cpu().unwrap()),
))))
}
}
pub struct StartVirtCommandParser;
impl<CM, QT, S, SM> NativeCommandParser<CM, StdEmulatorExitHandler<SM>, QT, S>
for StartVirtCommandParser
where
CM: CommandManager<StdEmulatorExitHandler<SM>, QT, S>,
QT: QemuHelperTuple<S> + StdInstrumentationFilter<S> + Debug,
S: State + HasExecutions,
S::Input: HasTargetBytes,
SM: IsSnapshotManager,
{
fn command_id(&self) -> GuestReg {
GuestReg::from(bindings::LibaflQemuCommand_LIBAFL_QEMU_COMMAND_START_VIRT.0)
}
fn parse(
&self,
qemu: Qemu,
arch_regs_map: &'static EnumMap<ExitArgs, Regs>,
) -> Result<Rc<dyn IsCommand<CM, StdEmulatorExitHandler<SM>, QT, S>>, CommandError> {
let input_virt_addr: GuestVirtAddr = qemu.read_reg(arch_regs_map[ExitArgs::Arg1])?;
let max_input_size: GuestReg = qemu.read_reg(arch_regs_map[ExitArgs::Arg2])?;
Ok(Rc::new(StartCommand::new(EmulatorMemoryChunk::virt(
input_virt_addr,
max_input_size,
qemu.current_cpu().unwrap(),
))))
}
}
pub struct SaveCommandParser;
impl<CM, QT, S, SM> NativeCommandParser<CM, StdEmulatorExitHandler<SM>, QT, S> for SaveCommandParser
where
CM: CommandManager<StdEmulatorExitHandler<SM>, QT, S>,
QT: QemuHelperTuple<S> + StdInstrumentationFilter<S> + Debug,
S: State + HasExecutions,
S::Input: HasTargetBytes,
SM: IsSnapshotManager,
{
fn command_id(&self) -> GuestReg {
GuestReg::from(bindings::LibaflQemuCommand_LIBAFL_QEMU_COMMAND_SAVE.0)
}
fn parse(
&self,
_qemu: Qemu,
_arch_regs_map: &'static EnumMap<ExitArgs, Regs>,
) -> Result<Rc<dyn IsCommand<CM, StdEmulatorExitHandler<SM>, QT, S>>, CommandError> {
Ok(Rc::new(SaveCommand))
}
}
pub struct LoadCommandParser;
impl<CM, QT, S, SM> NativeCommandParser<CM, StdEmulatorExitHandler<SM>, QT, S> for LoadCommandParser
where
CM: CommandManager<StdEmulatorExitHandler<SM>, QT, S>,
QT: QemuHelperTuple<S> + StdInstrumentationFilter<S> + Debug,
S: State + HasExecutions,
S::Input: HasTargetBytes,
SM: IsSnapshotManager,
{
fn command_id(&self) -> GuestReg {
GuestReg::from(bindings::LibaflQemuCommand_LIBAFL_QEMU_COMMAND_LOAD.0)
}
fn parse(
&self,
_qemu: Qemu,
_arch_regs_map: &'static EnumMap<ExitArgs, Regs>,
) -> Result<Rc<dyn IsCommand<CM, StdEmulatorExitHandler<SM>, QT, S>>, CommandError> {
Ok(Rc::new(LoadCommand))
}
}
pub struct EndCommandParser;
impl<CM, QT, S, SM> NativeCommandParser<CM, StdEmulatorExitHandler<SM>, QT, S> for EndCommandParser
where
CM: CommandManager<StdEmulatorExitHandler<SM>, QT, S>,
QT: QemuHelperTuple<S> + StdInstrumentationFilter<S> + Debug,
S: State + HasExecutions,
S::Input: HasTargetBytes,
SM: IsSnapshotManager,
{
fn command_id(&self) -> GuestReg {
GuestReg::from(bindings::LibaflQemuCommand_LIBAFL_QEMU_COMMAND_END.0)
}
fn parse(
&self,
qemu: Qemu,
arch_regs_map: &'static EnumMap<ExitArgs, Regs>,
) -> Result<Rc<dyn IsCommand<CM, StdEmulatorExitHandler<SM>, QT, S>>, CommandError> {
let native_exit_kind: GuestReg = qemu.read_reg(arch_regs_map[ExitArgs::Arg1])?;
let native_exit_kind: Result<NativeExitKind, _> = u64::from(native_exit_kind).try_into();
let exit_kind = native_exit_kind.ok().and_then(|k| {
EMU_EXIT_KIND_MAP.get_or_init(|| {
enum_map! {
NativeExitKind::Unknown => None,
NativeExitKind::Ok => Some(ExitKind::Ok),
NativeExitKind::Crash => Some(ExitKind::Crash)
}
})[k]
});
Ok(Rc::new(EndCommand::new(exit_kind)))
}
}
pub struct VersionCommandParser;
impl<CM, QT, S, SM> NativeCommandParser<CM, StdEmulatorExitHandler<SM>, QT, S>
for VersionCommandParser
where
CM: CommandManager<StdEmulatorExitHandler<SM>, QT, S>,
QT: QemuHelperTuple<S> + StdInstrumentationFilter<S> + Debug,
S: State + HasExecutions,
S::Input: HasTargetBytes,
SM: IsSnapshotManager,
{
fn command_id(&self) -> GuestReg {
GuestReg::from(bindings::LibaflQemuCommand_LIBAFL_QEMU_COMMAND_VERSION.0)
}
fn parse(
&self,
qemu: Qemu,
arch_regs_map: &'static EnumMap<ExitArgs, Regs>,
) -> Result<Rc<dyn IsCommand<CM, StdEmulatorExitHandler<SM>, QT, S>>, CommandError> {
let client_version = qemu.read_reg(arch_regs_map[ExitArgs::Arg1])?;
Ok(Rc::new(VersionCommand::new(client_version)))
}
}
pub struct VaddrFilterAllowRangeCommandParser;
impl<CM, QT, S, SM> NativeCommandParser<CM, StdEmulatorExitHandler<SM>, QT, S>
for VaddrFilterAllowRangeCommandParser
where
CM: CommandManager<StdEmulatorExitHandler<SM>, QT, S>,
QT: QemuHelperTuple<S> + StdInstrumentationFilter<S> + Debug,
S: State + HasExecutions,
S::Input: HasTargetBytes,
SM: IsSnapshotManager,
{
fn command_id(&self) -> GuestReg {
GuestReg::from(bindings::LibaflQemuCommand_LIBAFL_QEMU_COMMAND_VADDR_FILTER_ALLOW.0)
}
fn parse(
&self,
qemu: Qemu,
arch_regs_map: &'static EnumMap<ExitArgs, Regs>,
) -> Result<Rc<dyn IsCommand<CM, StdEmulatorExitHandler<SM>, QT, S>>, CommandError> {
let vaddr_start: GuestAddr = qemu.read_reg(arch_regs_map[ExitArgs::Arg1])?;
let vaddr_end: GuestAddr = qemu.read_reg(arch_regs_map[ExitArgs::Arg2])?;
Ok(Rc::new(FilterCommand::new(
#[allow(clippy::single_range_in_vec_init)]
QemuInstrumentationAddressRangeFilter::AllowList(vec![vaddr_start..vaddr_end]),
)))
}
}

View File

@ -8,8 +8,10 @@ use core::{
};
use std::{
cell::{OnceCell, Ref, RefCell, RefMut},
collections::HashSet,
collections::HashMap,
hash::Hash,
ops::Add,
rc::Rc,
};
use libafl::{
@ -23,10 +25,11 @@ pub use libafl_qemu_sys::{GuestAddr, GuestPhysAddr, GuestVirtAddr};
#[cfg(emulation_mode = "usermode")]
pub use libafl_qemu_sys::{MapInfo, MmapPerms, MmapPermsIter};
use num_traits::Num;
use typed_builder::TypedBuilder;
use crate::{
breakpoint::Breakpoint,
command::{Command, CommandError, InputCommand, IsCommand},
command::{CommandError, InputCommand, IsCommand},
executor::QemuExecutorState,
sync_exit::SyncExit,
sys::TCGTemp,
@ -45,6 +48,11 @@ mod systemmode;
#[cfg(emulation_mode = "systemmode")]
pub use systemmode::*;
use crate::{breakpoint::BreakpointId, command::CommandManager};
type CommandRef<CM, E, QT, S> = Rc<dyn IsCommand<CM, E, QT, S>>;
type BreakpointMutRef<CM, E, QT, S> = Rc<RefCell<Breakpoint<CM, E, QT, S>>>;
#[derive(Clone, Copy)]
pub enum GuestAddrKind {
Physical(GuestPhysAddr),
@ -52,10 +60,16 @@ pub enum GuestAddrKind {
}
#[derive(Debug, Clone)]
pub enum EmulatorExitResult {
pub enum EmulatorExitResult<CM, E, QT, S>
where
CM: CommandManager<E, QT, S>,
E: EmulatorExitHandler<QT, S>,
QT: QemuHelperTuple<S>,
S: State + HasExecutions,
{
QemuExit(QemuShutdownCause), // QEMU ended for some reason.
Breakpoint(Breakpoint), // Breakpoint triggered. Contains the address of the trigger.
SyncExit(SyncExit), // Synchronous backdoor: The guest triggered a backdoor and should return to LibAFL.
Breakpoint(Rc<RefCell<Breakpoint<CM, E, QT, S>>>), // Breakpoint triggered. Contains the address of the trigger.
SyncExit(Rc<RefCell<SyncExit<CM, E, QT, S>>>), // Synchronous backdoor: The guest triggered a backdoor and should return to LibAFL.
}
#[derive(Debug, Clone)]
@ -67,8 +81,14 @@ pub enum EmulatorExitError {
}
#[derive(Debug, Clone)]
pub enum ExitHandlerResult {
ReturnToHarness(EmulatorExitResult), // Return to the harness immediately. Can happen at any point of the run when the handler is not supposed to handle a request.
pub enum ExitHandlerResult<CM, E, QT, S>
where
CM: CommandManager<E, QT, S>,
E: EmulatorExitHandler<QT, S>,
QT: QemuHelperTuple<S>,
S: State + HasExecutions,
{
ReturnToHarness(EmulatorExitResult<CM, E, QT, S>), // Return to the harness immediately. Can happen at any point of the run when the handler is not supposed to handle a request.
EndOfRun(ExitKind), // The run is over and the emulator is ready for the next iteration.
}
@ -89,11 +109,17 @@ pub enum SnapshotManagerError {
MemoryInconsistencies(u64),
}
impl TryInto<ExitKind> for ExitHandlerResult {
impl<CM, E, QT, S> TryFrom<ExitHandlerResult<CM, E, QT, S>> for ExitKind
where
CM: CommandManager<E, QT, S> + Debug,
E: EmulatorExitHandler<QT, S>,
QT: QemuHelperTuple<S> + Debug,
S: State + HasExecutions + Debug,
{
type Error = String;
fn try_into(self) -> Result<ExitKind, Self::Error> {
match self {
fn try_from(value: ExitHandlerResult<CM, E, QT, S>) -> Result<Self, Self::Error> {
match value {
ExitHandlerResult::ReturnToHarness(unhandled_qemu_exit) => {
Err(format!("Unhandled QEMU exit: {:?}", &unhandled_qemu_exit))
}
@ -157,18 +183,18 @@ where
QT: QemuHelperTuple<S>,
S: State + HasExecutions,
{
fn qemu_pre_run(
emu: &Emulator<QT, S, Self>,
fn qemu_pre_run<CM: CommandManager<Self, QT, S>>(
emu: &Emulator<CM, Self, QT, S>,
qemu_executor_state: &mut QemuExecutorState<QT, S>,
input: &S::Input,
);
fn qemu_post_run(
emu: &Emulator<QT, S, Self>,
exit_reason: Result<EmulatorExitResult, EmulatorExitError>,
fn qemu_post_run<CM: CommandManager<Self, QT, S>>(
emu: &Emulator<CM, Self, QT, S>,
exit_reason: Result<EmulatorExitResult<CM, Self, QT, S>, EmulatorExitError>,
qemu_executor_state: &mut QemuExecutorState<QT, S>,
input: &S::Input,
) -> Result<Option<ExitHandlerResult>, ExitHandlerError>;
) -> Result<Option<ExitHandlerResult<CM, Self, QT, S>>, ExitHandlerError>;
}
/// Special kind of Exit handler with no data embedded.
@ -182,14 +208,19 @@ where
QT: QemuHelperTuple<S>,
S: State + HasExecutions,
{
fn qemu_pre_run(_: &Emulator<QT, S, Self>, _: &mut QemuExecutorState<QT, S>, _: &S::Input) {}
fn qemu_post_run(
_: &Emulator<QT, S, Self>,
exit_reason: Result<EmulatorExitResult, EmulatorExitError>,
fn qemu_pre_run<CM: CommandManager<Self, QT, S>>(
_: &Emulator<CM, Self, QT, S>,
_: &mut QemuExecutorState<QT, S>,
_: &S::Input,
) -> Result<Option<ExitHandlerResult>, ExitHandlerError> {
) {
}
fn qemu_post_run<CM: CommandManager<Self, QT, S>>(
_: &Emulator<CM, Self, QT, S>,
exit_reason: Result<EmulatorExitResult<CM, Self, QT, S>, EmulatorExitError>,
_: &mut QemuExecutorState<QT, S>,
_: &S::Input,
) -> Result<Option<ExitHandlerResult<CM, Self, QT, S>>, ExitHandlerError> {
match exit_reason {
Ok(reason) => Ok(Some(ExitHandlerResult::ReturnToHarness(reason))),
Err(error) => Err(error)?,
@ -216,13 +247,15 @@ impl InputLocation {
}
/// Synchronous Exit handler maintaining only one snapshot.
#[derive(Debug, Clone)]
#[derive(Debug, Clone, TypedBuilder)]
pub struct StdEmulatorExitHandler<SM>
where
SM: IsSnapshotManager + Clone,
{
snapshot_manager: RefCell<SM>,
#[builder(default)]
snapshot_id: OnceCell<SnapshotId>,
#[builder(default)]
input_location: OnceCell<InputLocation>,
}
@ -260,19 +293,19 @@ where
}
// TODO: replace handlers with generics to permit compile-time customization of handlers
impl<SM, QT, S> EmulatorExitHandler<QT, S> for StdEmulatorExitHandler<SM>
impl<QT, S, SM> EmulatorExitHandler<QT, S> for StdEmulatorExitHandler<SM>
where
SM: IsSnapshotManager,
QT: QemuHelperTuple<S> + StdInstrumentationFilter<S> + Debug,
S: State + HasExecutions,
S::Input: HasTargetBytes,
SM: IsSnapshotManager,
{
fn qemu_pre_run(
emu: &Emulator<QT, S, Self>,
fn qemu_pre_run<CM: CommandManager<Self, QT, S>>(
emu: &Emulator<CM, Self, QT, S>,
qemu_executor_state: &mut QemuExecutorState<QT, S>,
input: &S::Input,
) {
let exit_handler = emu.state().exit_handler.borrow();
let exit_handler = emu.exit_handler.borrow();
if let Some(input_location) = exit_handler.input_location.get() {
let input_command =
@ -283,12 +316,12 @@ where
}
}
fn qemu_post_run(
emu: &Emulator<QT, S, Self>,
exit_reason: Result<EmulatorExitResult, EmulatorExitError>,
fn qemu_post_run<CM: CommandManager<Self, QT, S>>(
emu: &Emulator<CM, Self, QT, S>,
exit_reason: Result<EmulatorExitResult<CM, Self, QT, S>, EmulatorExitError>,
qemu_executor_state: &mut QemuExecutorState<QT, S>,
input: &S::Input,
) -> Result<Option<ExitHandlerResult>, ExitHandlerError> {
) -> Result<Option<ExitHandlerResult<CM, Self, QT, S>>, ExitHandlerError> {
let exit_handler = emu.exit_handler().borrow_mut();
let qemu = emu.qemu();
@ -308,23 +341,26 @@ where
},
};
let (command, ret_reg): (Option<Command>, Option<Regs>) = match &mut exit_reason {
EmulatorExitResult::QemuExit(shutdown_cause) => match shutdown_cause {
QemuShutdownCause::HostSignal(signal) => {
signal.handle();
return Err(ExitHandlerError::UnhandledSignal(*signal));
#[allow(clippy::type_complexity)]
let (command, ret_reg): (Option<CommandRef<CM, Self, QT, S>>, Option<Regs>) =
match &mut exit_reason {
EmulatorExitResult::QemuExit(shutdown_cause) => match shutdown_cause {
QemuShutdownCause::HostSignal(signal) => {
signal.handle();
return Err(ExitHandlerError::UnhandledSignal(*signal));
}
QemuShutdownCause::GuestPanic => {
return Ok(Some(ExitHandlerResult::EndOfRun(ExitKind::Crash)))
}
_ => panic!("Unhandled QEMU shutdown cause: {shutdown_cause:?}."),
},
EmulatorExitResult::Breakpoint(bp) => (bp.borrow_mut().trigger(qemu), None),
EmulatorExitResult::SyncExit(sync_backdoor) => {
let sync_backdoor = sync_backdoor.borrow();
let command = sync_backdoor.command();
(Some(command), Some(sync_backdoor.ret_reg()))
}
QemuShutdownCause::GuestPanic => {
return Ok(Some(ExitHandlerResult::EndOfRun(ExitKind::Crash)))
}
_ => panic!("Unhandled QEMU shutdown cause: {shutdown_cause:?}."),
},
EmulatorExitResult::Breakpoint(bp) => (bp.trigger(qemu).cloned(), None),
EmulatorExitResult::SyncExit(sync_backdoor) => {
let command = sync_backdoor.command().clone();
(Some(command), Some(sync_backdoor.ret_reg()))
}
};
};
// manually drop ref cell here to avoid keeping it alive in cmd.
drop(exit_handler);
@ -349,13 +385,19 @@ impl From<CommandError> for ExitHandlerError {
}
}
impl Display for EmulatorExitResult {
impl<CM, E, QT, S> Display for EmulatorExitResult<CM, E, QT, S>
where
CM: CommandManager<E, QT, S>,
E: EmulatorExitHandler<QT, S>,
QT: QemuHelperTuple<S>,
S: State + HasExecutions,
{
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
match self {
EmulatorExitResult::QemuExit(shutdown_cause) => write!(f, "End: {shutdown_cause:?}"),
EmulatorExitResult::Breakpoint(bp) => write!(f, "{bp}"),
EmulatorExitResult::Breakpoint(bp) => write!(f, "{}", bp.borrow()),
EmulatorExitResult::SyncExit(sync_exit) => {
write!(f, "Sync exit: {sync_exit}")
write!(f, "Sync exit: {}", sync_exit.borrow())
}
}
}
@ -367,56 +409,55 @@ impl From<CommandError> for EmulatorExitError {
}
}
#[derive(Debug, Clone)]
pub struct EmulatorState<QT, S, E>
#[derive(Clone, Debug, TypedBuilder)]
pub struct Emulator<CM, E, QT, S>
where
CM: CommandManager<E, QT, S>,
E: EmulatorExitHandler<QT, S>,
QT: QemuHelperTuple<S>,
S: State + HasExecutions,
E: EmulatorExitHandler<QT, S>,
{
command_manager: CM,
exit_handler: RefCell<E>,
breakpoints: RefCell<HashSet<Breakpoint>>,
#[builder(default)]
breakpoints_by_addr: RefCell<HashMap<GuestAddr, BreakpointMutRef<CM, E, QT, S>>>,
#[builder(default)]
breakpoints_by_id: RefCell<HashMap<BreakpointId, BreakpointMutRef<CM, E, QT, S>>>,
qemu: Qemu,
_phantom: PhantomData<(QT, S)>,
}
#[derive(Clone, Debug)]
pub struct Emulator<QT, S, E>
where
QT: QemuHelperTuple<S>,
S: State + HasExecutions,
E: EmulatorExitHandler<QT, S>,
{
state: EmulatorState<QT, S, E>,
qemu: Qemu,
}
#[allow(clippy::unused_self)]
impl<QT, S, E> Emulator<QT, S, E>
impl<CM, E, QT, S> Emulator<CM, E, QT, S>
where
CM: CommandManager<E, QT, S>,
E: EmulatorExitHandler<QT, S>,
QT: QemuHelperTuple<S>,
S: State + HasExecutions,
E: EmulatorExitHandler<QT, S>,
{
#[allow(clippy::must_use_candidate, clippy::similar_names)]
pub fn new(
args: &[String],
env: &[(String, String)],
exit_handler: E,
command_manager: CM,
) -> Result<Self, QemuInitError> {
let qemu = Qemu::init(args, env)?;
Self::new_with_qemu(qemu, exit_handler)
Self::new_with_qemu(qemu, exit_handler, command_manager)
}
pub fn new_with_qemu(qemu: Qemu, exit_handler: E) -> Result<Self, QemuInitError> {
let emu_state = EmulatorState {
exit_handler: RefCell::new(exit_handler),
breakpoints: RefCell::new(HashSet::new()),
_phantom: PhantomData,
};
pub fn new_with_qemu(
qemu: Qemu,
exit_handler: E,
command_manager: CM,
) -> Result<Self, QemuInitError> {
Ok(Emulator {
state: emu_state,
command_manager,
exit_handler: RefCell::new(exit_handler),
breakpoints_by_addr: RefCell::new(HashMap::new()),
breakpoints_by_id: RefCell::new(HashMap::new()),
_phantom: PhantomData,
qemu,
})
}
@ -426,19 +467,9 @@ where
&self.qemu
}
#[must_use]
pub fn state(&self) -> &EmulatorState<QT, S, E> {
&self.state
}
#[must_use]
pub fn state_mut(&mut self) -> &mut EmulatorState<QT, S, E> {
&mut self.state
}
#[must_use]
pub fn exit_handler(&self) -> &RefCell<E> {
&self.state().exit_handler
&self.exit_handler
}
#[must_use]
@ -525,18 +556,54 @@ where
self.qemu.read_reg(reg)
}
pub fn add_breakpoint(&self, mut bp: Breakpoint, enable: bool) {
pub fn add_breakpoint(&self, mut bp: Breakpoint<CM, E, QT, S>, enable: bool) -> BreakpointId {
if enable {
bp.enable(&self.qemu);
}
self.state().breakpoints.borrow_mut().insert(bp);
let bp_id = bp.id();
let bp_addr = bp.addr();
let bp_ref = Rc::new(RefCell::new(bp));
assert!(
self.breakpoints_by_addr
.borrow_mut()
.insert(bp_addr, bp_ref.clone())
.is_none(),
"Adding multiple breakpoints at the same address"
);
assert!(
self.breakpoints_by_id
.borrow_mut()
.insert(bp_id, bp_ref)
.is_none(),
"Adding the same breakpoint multiple times"
);
bp_id
}
pub fn remove_breakpoint(&self, bp: &mut Breakpoint) {
bp.disable(&self.qemu);
pub fn remove_breakpoint(&self, bp_id: BreakpointId) {
let bp_addr = {
let mut bp_map = self.breakpoints_by_id.borrow_mut();
let mut bp = bp_map
.get_mut(&bp_id)
.expect("Did not find the breakpoint")
.borrow_mut();
bp.disable(&self.qemu);
bp.addr()
};
self.state().breakpoints.borrow_mut().remove(bp);
self.breakpoints_by_id
.borrow_mut()
.remove(&bp_id)
.expect("Could not remove bp");
self.breakpoints_by_addr
.borrow_mut()
.remove(&bp_addr)
.expect("Could not remove bp");
}
#[deprecated(
@ -551,7 +618,7 @@ where
///
/// 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.
unsafe fn run_qemu(&self) -> Result<EmulatorExitResult, EmulatorExitError> {
unsafe fn run_qemu(&self) -> Result<EmulatorExitResult<CM, E, QT, S>, EmulatorExitError> {
match self.qemu.run() {
Ok(qemu_exit_reason) => Ok(match qemu_exit_reason {
QemuExitReason::End(qemu_shutdown_cause) => {
@ -559,17 +626,16 @@ where
}
QemuExitReason::Breakpoint(bp_addr) => {
let bp = self
.state()
.breakpoints
.breakpoints_by_addr
.borrow()
.get(&bp_addr)
.ok_or(EmulatorExitError::BreakpointNotFound(bp_addr))?
.clone();
EmulatorExitResult::Breakpoint(bp)
}
QemuExitReason::SyncExit => {
EmulatorExitResult::SyncExit(SyncExit::new(self.qemu.try_into()?))
EmulatorExitResult::Breakpoint(bp.clone())
}
QemuExitReason::SyncExit => EmulatorExitResult::SyncExit(Rc::new(RefCell::new(
SyncExit::new(self.command_manager.parse(self.qemu)?),
))),
}),
Err(qemu_exit_reason_error) => Err(match qemu_exit_reason_error {
QemuExitError::UnexpectedExit => EmulatorExitError::UnexpectedExit,
@ -590,7 +656,7 @@ where
&self,
input: &S::Input,
qemu_executor_state: &mut QemuExecutorState<QT, S>,
) -> Result<ExitHandlerResult, ExitHandlerError> {
) -> Result<ExitHandlerResult<CM, E, QT, S>, ExitHandlerError> {
loop {
// Insert input if the location is already known
E::qemu_pre_run(self, qemu_executor_state, input);

View File

@ -8,8 +8,8 @@ use libafl::state::{HasExecutions, State};
use libafl_qemu_sys::GuestPhysAddr;
use crate::{
emu::IsSnapshotManager, DeviceSnapshotFilter, Emulator, EmulatorExitHandler, Qemu,
QemuHelperTuple, SnapshotId, SnapshotManagerError,
command::CommandManager, emu::IsSnapshotManager, DeviceSnapshotFilter, Emulator,
EmulatorExitHandler, Qemu, QemuHelperTuple, SnapshotId, SnapshotManagerError,
};
impl SnapshotId {
@ -151,11 +151,12 @@ impl IsSnapshotManager for FastSnapshotManager {
}
}
impl<QT, S, E> Emulator<QT, S, E>
impl<CM, E, QT, S> Emulator<CM, E, QT, S>
where
CM: CommandManager<E, QT, S>,
E: EmulatorExitHandler<QT, S>,
QT: QemuHelperTuple<S>,
S: State + HasExecutions,
E: EmulatorExitHandler<QT, S>,
{
/// Write a value to a phsical guest address, including ROM areas.
pub unsafe fn write_phys_mem(&self, paddr: GuestPhysAddr, buf: &[u8]) {

View File

@ -1,16 +1,18 @@
use libafl_qemu_sys::{GuestAddr, MmapPerms, VerifyAccess};
use crate::{
command::CommandManager,
emu::{HasExecutions, State},
Emulator, EmulatorExitHandler, GuestMaps, HookData, NewThreadHookId, PostSyscallHookId,
PreSyscallHookId, QemuHelperTuple, SyscallHookResult,
};
impl<QT, S, E> Emulator<QT, S, E>
impl<CM, E, QT, S> Emulator<CM, E, QT, S>
where
CM: CommandManager<E, QT, S>,
E: EmulatorExitHandler<QT, S>,
QT: QemuHelperTuple<S>,
S: State + HasExecutions,
E: EmulatorExitHandler<QT, S>,
{
/// This function gets the memory mappings from the emulator.
#[must_use]

View File

@ -302,6 +302,7 @@ impl EmulatorMemoryChunk {
}
}
#[allow(clippy::map_flatten)]
pub fn host_iter(&self, qemu: Qemu) -> Box<dyn Iterator<Item = &[u8]>> {
Box::new(
self.phys_iter(qemu)

View File

@ -1,8 +1,15 @@
use std::fmt::{Display, Formatter};
use std::{
fmt::{Display, Formatter},
rc::Rc,
};
use enum_map::Enum;
use libafl::state::{HasExecutions, State};
use crate::{command::Command, get_exit_arch_regs, GuestReg, Regs, CPU};
use crate::{
command::{CommandManager, IsCommand},
get_exit_arch_regs, EmulatorExitHandler, GuestReg, QemuHelperTuple, Regs, CPU,
};
#[derive(Debug, Clone, Enum)]
pub enum ExitArgs {
@ -16,20 +23,32 @@ pub enum ExitArgs {
Arg6,
}
#[derive(Debug, Clone)]
pub struct SyncExit {
command: Command,
#[derive(Debug)]
pub struct SyncExit<CM, E, QT, S>
where
CM: CommandManager<E, QT, S>,
E: EmulatorExitHandler<QT, S>,
QT: QemuHelperTuple<S>,
S: State + HasExecutions,
{
command: Rc<dyn IsCommand<CM, E, QT, S>>,
}
impl SyncExit {
impl<CM, E, QT, S> SyncExit<CM, E, QT, S>
where
CM: CommandManager<E, QT, S>,
E: EmulatorExitHandler<QT, S>,
QT: QemuHelperTuple<S>,
S: State + HasExecutions,
{
#[must_use]
pub fn new(command: Command) -> Self {
pub fn new(command: Rc<dyn IsCommand<CM, E, QT, S>>) -> Self {
Self { command }
}
#[must_use]
pub fn command(&self) -> &Command {
&self.command
pub fn command(&self) -> Rc<dyn IsCommand<CM, E, QT, S>> {
self.command.clone()
}
pub fn ret(&self, cpu: &CPU, value: GuestReg) {
@ -43,7 +62,13 @@ impl SyncExit {
}
}
impl Display for SyncExit {
impl<CM, E, QT, S> Display for SyncExit<CM, E, QT, S>
where
CM: CommandManager<E, QT, S>,
E: EmulatorExitHandler<QT, S>,
QT: QemuHelperTuple<S>,
S: State + HasExecutions,
{
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.command)
}