QEMU command system refactoring (#2189)
* implemented generic command builder * Added builder to `Emulator`.
This commit is contained in:
parent
3a087301ac
commit
dfd3b3278e
@ -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,
|
||||
);
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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 }
|
||||
|
||||
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
@ -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()
|
||||
)
|
||||
}
|
||||
}
|
651
libafl_qemu/src/command/mod.rs
Normal file
651
libafl_qemu/src/command/mod.rs
Normal 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 }
|
||||
}
|
||||
}
|
292
libafl_qemu/src/command/parser.rs
Normal file
292
libafl_qemu/src/command/parser.rs
Normal 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]),
|
||||
)))
|
||||
}
|
||||
}
|
@ -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);
|
||||
|
@ -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]) {
|
||||
|
@ -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]
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user