From dfd3b3278e1f71fef3896a01dfd5284e36b72bd6 Mon Sep 17 00:00:00 2001 From: Romain Malmain Date: Sat, 18 May 2024 20:43:56 +0200 Subject: [PATCH] QEMU command system refactoring (#2189) * implemented generic command builder * Added builder to `Emulator`. --- .../qemu_systemmode/src/fuzzer_breakpoint.rs | 17 +- .../qemu_systemmode/src/fuzzer_sync_exit.rs | 7 +- libafl_qemu/Cargo.toml | 1 + libafl_qemu/src/breakpoint.rs | 128 ++- libafl_qemu/src/command.rs | 741 ------------------ libafl_qemu/src/command/mod.rs | 651 +++++++++++++++ libafl_qemu/src/command/parser.rs | 292 +++++++ libafl_qemu/src/emu/mod.rs | 268 ++++--- libafl_qemu/src/emu/systemmode.rs | 9 +- libafl_qemu/src/emu/usermode.rs | 6 +- libafl_qemu/src/qemu/systemmode.rs | 1 + libafl_qemu/src/sync_exit.rs | 45 +- 12 files changed, 1282 insertions(+), 884 deletions(-) delete mode 100644 libafl_qemu/src/command.rs create mode 100644 libafl_qemu/src/command/mod.rs create mode 100644 libafl_qemu/src/command/parser.rs diff --git a/fuzzers/qemu_systemmode/src/fuzzer_breakpoint.rs b/fuzzers/qemu_systemmode/src/fuzzer_breakpoint.rs index de8bab842e..e7ba9bf0dd 100644 --- a/fuzzers/qemu_systemmode/src/fuzzer_breakpoint.rs +++ b/fuzzers/qemu_systemmode/src/fuzzer_breakpoint.rs @@ -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, ); diff --git a/fuzzers/qemu_systemmode/src/fuzzer_sync_exit.rs b/fuzzers/qemu_systemmode/src/fuzzer_sync_exit.rs index b7b45e4954..b793b0e8ab 100644 --- a/fuzzers/qemu_systemmode/src/fuzzer_sync_exit.rs +++ b/fuzzers/qemu_systemmode/src/fuzzer_sync_exit.rs @@ -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 = 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 = 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); diff --git a/libafl_qemu/Cargo.toml b/libafl_qemu/Cargo.toml index d1ca17d544..45db0290b2 100644 --- a/libafl_qemu/Cargo.toml +++ b/libafl_qemu/Cargo.toml @@ -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 } diff --git a/libafl_qemu/src/breakpoint.rs b/libafl_qemu/src/breakpoint.rs index ec9ed7ff94..0beadb2b77 100644 --- a/libafl_qemu/src/breakpoint.rs +++ b/libafl_qemu/src/breakpoint.rs @@ -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 +where + CM: CommandManager, + E: EmulatorExitHandler, + QT: QemuHelperTuple, + S: State + HasExecutions, +{ + id: BreakpointId, addr: GuestAddr, - cmd: Option, + cmd: Option>>, disable_on_trigger: bool, enabled: bool, } -impl Hash for Breakpoint { +impl BreakpointId { + pub fn new() -> Self { + static mut BREAKPOINT_ID_COUNTER: OnceLock = 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 Hash for Breakpoint +where + CM: CommandManager, + E: EmulatorExitHandler, + QT: QemuHelperTuple, + S: State + HasExecutions, +{ fn hash(&self, state: &mut H) { - self.addr.hash(state); + self.id.hash(state); } } -impl PartialEq for Breakpoint { +impl PartialEq for Breakpoint +where + CM: CommandManager, + E: EmulatorExitHandler, + QT: QemuHelperTuple, + S: State + HasExecutions, +{ fn eq(&self, other: &Self) -> bool { - self.addr == other.addr + self.id == other.id } } -impl Eq for Breakpoint {} +impl Eq for Breakpoint +where + CM: CommandManager, + E: EmulatorExitHandler, + QT: QemuHelperTuple, + S: State + HasExecutions, +{ +} -impl Display for Breakpoint { +impl Display for Breakpoint +where + CM: CommandManager, + E: EmulatorExitHandler, + QT: QemuHelperTuple, + S: State + HasExecutions, +{ fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { write!(f, "Breakpoint @vaddr 0x{:x}", self.addr) } } -impl Borrow for Breakpoint { +impl Borrow for Breakpoint +where + CM: CommandManager, + E: EmulatorExitHandler, + QT: QemuHelperTuple, + S: State + HasExecutions, +{ + fn borrow(&self) -> &BreakpointId { + &self.id + } +} + +impl Borrow for Breakpoint +where + CM: CommandManager, + E: EmulatorExitHandler, + QT: QemuHelperTuple, + S: State + HasExecutions, +{ fn borrow(&self) -> &GuestAddr { &self.addr } } -impl Breakpoint { +impl Breakpoint +where + CM: CommandManager, + E: EmulatorExitHandler, + QT: QemuHelperTuple, + 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 + '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>> { if self.disable_on_trigger { self.disable(qemu); } - self.cmd.as_ref() + self.cmd.clone() } } diff --git a/libafl_qemu/src/command.rs b/libafl_qemu/src/command.rs deleted file mode 100644 index b1ccc5e43f..0000000000 --- a/libafl_qemu/src/command.rs +++ /dev/null @@ -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 -where - QT: QemuHelperTuple, - S: State + HasExecutions, - E: EmulatorExitHandler, -{ - /// 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, - qemu_executor_state: &mut QemuExecutorState, - input: &S::Input, - ret_reg: Option, - ) -> Result, ExitHandlerError>; -} - -#[cfg(emulation_mode = "systemmode")] -pub type PagingFilterCommand = FilterCommand; - -pub type AddressRangeFilterCommand = FilterCommand; - -#[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>> = OnceLock::new(); - -#[derive(Debug, Clone)] -pub enum CommandError { - UnknownCommand(GuestReg), - RegError(String), - VersionDifference(u64), -} - -impl From> for CommandError { - fn from(error: TryFromPrimitiveError) -> Self { - CommandError::UnknownCommand(error.number.try_into().unwrap()) - } -} - -impl From for CommandError { - fn from(error_string: String) -> Self { - CommandError::RegError(error_string) - } -} - -impl TryFrom for Command { - type Error = CommandError; - - #[allow(clippy::too_many_lines)] - fn try_from(qemu: Qemu) -> Result { - let arch_regs_map: &'static EnumMap = get_exit_arch_regs(); - let cmd_id: GuestReg = qemu.read_reg::(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 = - 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 IsCommand> for Command -where - SM: IsSnapshotManager, - QT: QemuHelperTuple + StdInstrumentationFilter + Debug, - S: State + HasExecutions, - S::Input: HasTargetBytes, -{ - fn usable_at_runtime(&self) -> bool { - match self { - Command::SaveCommand(cmd) => { - >>::usable_at_runtime( - cmd, - ) - } - Command::LoadCommand(cmd) => { - >>::usable_at_runtime( - cmd, - ) - } - Command::InputCommand(cmd) => { - >>::usable_at_runtime( - cmd, - ) - } - Command::StartCommand(cmd) => { - >>::usable_at_runtime( - cmd, - ) - } - Command::EndCommand(cmd) => { - >>::usable_at_runtime(cmd) - } - Command::VersionCommand(cmd) => { - >>::usable_at_runtime( - cmd, - ) - } - #[cfg(emulation_mode = "systemmode")] - Command::PagingFilterCommand(cmd) => , - >>::usable_at_runtime(cmd), - Command::AddressRangeFilterCommand(cmd) => , - >>::usable_at_runtime(cmd), - } - } - - fn run( - &self, - emu: &Emulator>, - qemu_executor_state: &mut QemuExecutorState, - input: &S::Input, - ret_reg: Option, - ) -> Result, ExitHandlerError> { - match self { - Command::SaveCommand(cmd) => , - >>::run( - cmd, emu, qemu_executor_state, input, ret_reg - ), - Command::LoadCommand(cmd) => , - >>::run( - cmd, emu, qemu_executor_state, input, ret_reg - ), - Command::InputCommand(cmd) => , - >>::run( - cmd, emu, qemu_executor_state, input, ret_reg - ), - Command::StartCommand(cmd) => , - >>::run( - cmd, emu, qemu_executor_state, input, ret_reg - ), - Command::EndCommand(cmd) => , - >>::run( - cmd, emu, qemu_executor_state, input, ret_reg - ), - Command::VersionCommand(cmd) => , - >>::run( - cmd, emu, qemu_executor_state, input, ret_reg - ), - #[cfg(emulation_mode = "systemmode")] - Command::PagingFilterCommand(cmd) => , - >>::run( - cmd, emu, qemu_executor_state, input, ret_reg - ), - Command::AddressRangeFilterCommand(cmd) => { - >>::run( - cmd, - emu, - qemu_executor_state, - input, - ret_reg, - ) - } - } - } -} - -#[derive(Debug, Clone)] -pub struct SaveCommand; - -impl IsCommand> for SaveCommand -where - SM: IsSnapshotManager, - QT: QemuHelperTuple + StdInstrumentationFilter + Debug, - S: State + HasExecutions, - S::Input: HasTargetBytes, -{ - fn usable_at_runtime(&self) -> bool { - false - } - - fn run( - &self, - emu: &Emulator>, - #[cfg(emulation_mode = "systemmode")] qemu_executor_state: &mut QemuExecutorState, - #[cfg(not(emulation_mode = "systemmode"))] _qemu_executor_state: &mut QemuExecutorState< - QT, - S, - >, - _input: &S::Input, - _ret_reg: Option, - ) -> Result, 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::::filter_mut( - qemu_helpers, - ); - - *paging_filter = QemuInstrumentationPagingFilter::AllowList(allowed_paging_ids); - } - - Ok(None) - } -} - -#[derive(Debug, Clone)] -pub struct LoadCommand; - -impl IsCommand> for LoadCommand -where - SM: IsSnapshotManager, - QT: QemuHelperTuple + StdInstrumentationFilter + Debug, - S: State + HasExecutions, - S::Input: HasTargetBytes, -{ - fn usable_at_runtime(&self) -> bool { - false - } - - fn run( - &self, - emu: &Emulator>, - _qemu_executor_state: &mut QemuExecutorState, - _input: &S::Input, - _ret_reg: Option, - ) -> Result, 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 IsCommand> for InputCommand -where - SM: IsSnapshotManager, - QT: QemuHelperTuple + StdInstrumentationFilter + Debug, - S: State + HasExecutions, - S::Input: HasTargetBytes, -{ - fn usable_at_runtime(&self) -> bool { - true - } - - fn run( - &self, - emu: &Emulator>, - _qemu_executor_state: &mut QemuExecutorState, - input: &S::Input, - ret_reg: Option, - ) -> Result, 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 IsCommand> for StartCommand -where - SM: IsSnapshotManager, - QT: QemuHelperTuple + StdInstrumentationFilter + Debug, - S: State + HasExecutions, - S::Input: HasTargetBytes, -{ - fn usable_at_runtime(&self) -> bool { - false - } - - fn run( - &self, - emu: &Emulator>, - _qemu_executor_state: &mut QemuExecutorState, - input: &S::Input, - ret_reg: Option, - ) -> Result, 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); - -impl IsCommand> for EndCommand -where - SM: IsSnapshotManager, - QT: QemuHelperTuple + StdInstrumentationFilter + Debug, - S: State + HasExecutions, - S::Input: HasTargetBytes, -{ - fn usable_at_runtime(&self) -> bool { - false - } - - fn run( - &self, - emu: &Emulator>, - _qemu_executor_state: &mut QemuExecutorState, - _input: &S::Input, - _ret_reg: Option, - ) -> Result, 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 IsCommand> for VersionCommand -where - SM: IsSnapshotManager, - QT: QemuHelperTuple + StdInstrumentationFilter + Debug, - S: State + HasExecutions, - S::Input: HasTargetBytes, -{ - fn usable_at_runtime(&self) -> bool { - true - } - - fn run( - &self, - _emu: &Emulator>, - _qemu_executor_state: &mut QemuExecutorState, - _input: &S::Input, - _ret_reg: Option, - ) -> Result, 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 -where - T: IsFilter + Debug, -{ - filter: T, -} - -#[cfg(emulation_mode = "systemmode")] -impl IsCommand> for PagingFilterCommand -where - SM: IsSnapshotManager, - QT: QemuHelperTuple + StdInstrumentationFilter + Debug, - S: State + HasExecutions, - S::Input: HasTargetBytes, -{ - fn usable_at_runtime(&self) -> bool { - true - } - - fn run( - &self, - _emu: &Emulator>, - qemu_executor_state: &mut QemuExecutorState, - _input: &S::Input, - _ret_reg: Option, - ) -> Result, ExitHandlerError> { - let qemu_helpers = qemu_executor_state.hooks_mut().helpers_mut(); - - let paging_filter = - HasInstrumentationFilter::::filter_mut( - qemu_helpers, - ); - - *paging_filter = self.filter.clone(); - - Ok(None) - } -} - -impl IsCommand> for AddressRangeFilterCommand -where - SM: IsSnapshotManager, - QT: QemuHelperTuple + StdInstrumentationFilter + 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>, - qemu_executor_state: &mut QemuExecutorState, - _input: &S::Input, - _ret_reg: Option, - ) -> Result, ExitHandlerError> { - let qemu_helpers = qemu_executor_state.hooks_mut().helpers_mut(); - - let addr_range_filter = - HasInstrumentationFilter::::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 FilterCommand -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) -> 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() - ) - } -} diff --git a/libafl_qemu/src/command/mod.rs b/libafl_qemu/src/command/mod.rs new file mode 100644 index 0000000000..e11b621d0f --- /dev/null +++ b/libafl_qemu/src/command/mod.rs @@ -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 + where + QT: QemuHelperTuple + StdInstrumentationFilter + Debug, + S: State + HasExecutions, + S::Input: HasTargetBytes, + SM: IsSnapshotManager, + { + native_command_parsers: + HashMap, QT, S>>>, + } + + impl $name + where + QT: QemuHelperTuple + StdInstrumentationFilter + 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, + QT, + S, + >, + >),*] + .into_iter(), + ); + + let mut parsers: HashMap< + GuestReg, + Box, 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 CommandManager, QT, S> for $name + where + QT: QemuHelperTuple + StdInstrumentationFilter + Debug, + S: State + HasExecutions, + S::Input: HasTargetBytes, + SM: IsSnapshotManager, + { + fn parse( + &self, + qemu: Qemu, + ) -> Result, QT, S>>, CommandError> { + let arch_regs_map: &'static EnumMap = get_exit_arch_regs(); + let cmd_id: GuestReg = qemu.read_reg::(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 Debug for $name + where + QT: QemuHelperTuple + StdInstrumentationFilter + Debug, + S: State + HasExecutions, + S::Input: HasTargetBytes, + SM: IsSnapshotManager, + { + fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> { + write!(f, stringify!($name)) + } + } + + impl Default for $name + where + QT: QemuHelperTuple + StdInstrumentationFilter + 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: Sized +where + E: EmulatorExitHandler, + QT: QemuHelperTuple, + S: State + HasExecutions, +{ + fn parse(&self, qemu: Qemu) -> Result>, 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: Debug + Display +where + CM: CommandManager, + E: EmulatorExitHandler, + QT: QemuHelperTuple, + 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, + qemu_executor_state: &mut QemuExecutorState, + input: &S::Input, + ret_reg: Option, + ) -> Result>, ExitHandlerError>; +} + +#[cfg(emulation_mode = "systemmode")] +pub type PagingFilterCommand = FilterCommand; + +pub type AddressRangeFilterCommand = FilterCommand; + +#[derive(Debug, Clone)] +pub enum CommandError { + UnknownCommand(GuestReg), + RegError(String), + VersionDifference(u64), +} + +impl From for CommandError { + fn from(error_string: String) -> Self { + CommandError::RegError(error_string) + } +} + +#[derive(Debug, Clone)] +pub struct SaveCommand; + +impl IsCommand, QT, S> for SaveCommand +where + CM: CommandManager, QT, S>, + QT: QemuHelperTuple + StdInstrumentationFilter + Debug, + S: State + HasExecutions, + S::Input: HasTargetBytes, + SM: IsSnapshotManager, +{ + fn usable_at_runtime(&self) -> bool { + false + } + + fn run( + &self, + emu: &Emulator, QT, S>, + #[cfg(emulation_mode = "systemmode")] qemu_executor_state: &mut QemuExecutorState, + #[cfg(not(emulation_mode = "systemmode"))] _qemu_executor_state: &mut QemuExecutorState< + QT, + S, + >, + _input: &S::Input, + _ret_reg: Option, + ) -> Result, 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::::filter_mut( + qemu_helpers, + ); + + *paging_filter = QemuInstrumentationPagingFilter::AllowList(allowed_paging_ids); + } + + Ok(None) + } +} + +#[derive(Debug, Clone)] +pub struct LoadCommand; + +impl IsCommand, QT, S> for LoadCommand +where + CM: CommandManager, QT, S>, + QT: QemuHelperTuple + StdInstrumentationFilter + Debug, + S: State + HasExecutions, + S::Input: HasTargetBytes, + SM: IsSnapshotManager, +{ + fn usable_at_runtime(&self) -> bool { + false + } + + fn run( + &self, + emu: &Emulator, QT, S>, + _qemu_executor_state: &mut QemuExecutorState, + _input: &S::Input, + _ret_reg: Option, + ) -> Result, 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 IsCommand, QT, S> for InputCommand +where + CM: CommandManager, QT, S>, + QT: QemuHelperTuple + StdInstrumentationFilter + Debug, + S: State + HasExecutions, + S::Input: HasTargetBytes, + SM: IsSnapshotManager, +{ + fn usable_at_runtime(&self) -> bool { + true + } + + fn run( + &self, + emu: &Emulator, QT, S>, + _qemu_executor_state: &mut QemuExecutorState, + input: &S::Input, + ret_reg: Option, + ) -> Result, 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 IsCommand, QT, S> for StartCommand +where + CM: CommandManager, QT, S>, + QT: QemuHelperTuple + StdInstrumentationFilter + Debug, + S: State + HasExecutions, + S::Input: HasTargetBytes, + SM: IsSnapshotManager, +{ + fn usable_at_runtime(&self) -> bool { + false + } + + fn run( + &self, + emu: &Emulator, QT, S>, + _qemu_executor_state: &mut QemuExecutorState, + input: &S::Input, + ret_reg: Option, + ) -> Result, 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); + +impl IsCommand, QT, S> for EndCommand +where + CM: CommandManager, QT, S>, + QT: QemuHelperTuple + StdInstrumentationFilter + Debug, + S: State + HasExecutions, + S::Input: HasTargetBytes, + SM: IsSnapshotManager, +{ + fn usable_at_runtime(&self) -> bool { + false + } + + fn run( + &self, + emu: &Emulator, QT, S>, + _qemu_executor_state: &mut QemuExecutorState, + _input: &S::Input, + _ret_reg: Option, + ) -> Result, 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 IsCommand, QT, S> for VersionCommand +where + CM: CommandManager, QT, S>, + QT: QemuHelperTuple + StdInstrumentationFilter + Debug, + S: State + HasExecutions, + S::Input: HasTargetBytes, + SM: IsSnapshotManager, +{ + fn usable_at_runtime(&self) -> bool { + true + } + + fn run( + &self, + _emu: &Emulator, QT, S>, + _qemu_executor_state: &mut QemuExecutorState, + _input: &S::Input, + _ret_reg: Option, + ) -> Result, 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 +where + T: IsFilter + Debug, +{ + filter: T, +} + +#[cfg(emulation_mode = "systemmode")] +impl IsCommand, QT, S> for PagingFilterCommand +where + CM: CommandManager, QT, S>, + QT: QemuHelperTuple + StdInstrumentationFilter + Debug, + S: State + HasExecutions, + S::Input: HasTargetBytes, + SM: IsSnapshotManager, +{ + fn usable_at_runtime(&self) -> bool { + true + } + + fn run( + &self, + _emu: &Emulator, QT, S>, + qemu_executor_state: &mut QemuExecutorState, + _input: &S::Input, + _ret_reg: Option, + ) -> Result, QT, S>>, ExitHandlerError> + { + let qemu_helpers = qemu_executor_state.hooks_mut().helpers_mut(); + + let paging_filter = + HasInstrumentationFilter::::filter_mut( + qemu_helpers, + ); + + *paging_filter = self.filter.clone(); + + Ok(None) + } +} + +impl IsCommand, QT, S> for AddressRangeFilterCommand +where + CM: CommandManager, QT, S>, + QT: QemuHelperTuple + StdInstrumentationFilter + 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, QT, S>, + qemu_executor_state: &mut QemuExecutorState, + _input: &S::Input, + _ret_reg: Option, + ) -> Result, QT, S>>, ExitHandlerError> + { + let qemu_helpers = qemu_executor_state.hooks_mut().helpers_mut(); + + let addr_range_filter = + HasInstrumentationFilter::::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 FilterCommand +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) -> Self { + Self(exit_kind) + } +} + +impl InputCommand { + #[must_use] + pub fn new(location: EmulatorMemoryChunk, cpu: CPU) -> Self { + Self { location, cpu } + } +} diff --git a/libafl_qemu/src/command/parser.rs b/libafl_qemu/src/command/parser.rs new file mode 100644 index 0000000000..1c850efd94 --- /dev/null +++ b/libafl_qemu/src/command/parser.rs @@ -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>> = OnceLock::new(); + +pub trait NativeCommandParser +where + CM: CommandManager, + E: EmulatorExitHandler, + QT: QemuHelperTuple, + S: State + HasExecutions, +{ + fn command_id(&self) -> GuestReg; + + fn parse( + &self, + qemu: Qemu, + arch_regs_map: &'static EnumMap, + ) -> Result>, CommandError>; +} + +pub struct InputPhysCommandParser; +impl NativeCommandParser, QT, S> + for InputPhysCommandParser +where + CM: CommandManager, QT, S>, + QT: QemuHelperTuple + StdInstrumentationFilter + 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, + ) -> Result, 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 NativeCommandParser, QT, S> + for crate::command::parser::InputVirtCommandParser +where + CM: CommandManager, QT, S>, + QT: QemuHelperTuple + StdInstrumentationFilter + 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, + ) -> Result, 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 NativeCommandParser, QT, S> + for StartPhysCommandParser +where + CM: CommandManager, QT, S>, + QT: QemuHelperTuple + StdInstrumentationFilter + 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, + ) -> Result, 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 NativeCommandParser, QT, S> + for StartVirtCommandParser +where + CM: CommandManager, QT, S>, + QT: QemuHelperTuple + StdInstrumentationFilter + 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, + ) -> Result, 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 NativeCommandParser, QT, S> for SaveCommandParser +where + CM: CommandManager, QT, S>, + QT: QemuHelperTuple + StdInstrumentationFilter + 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, + ) -> Result, QT, S>>, CommandError> { + Ok(Rc::new(SaveCommand)) + } +} + +pub struct LoadCommandParser; +impl NativeCommandParser, QT, S> for LoadCommandParser +where + CM: CommandManager, QT, S>, + QT: QemuHelperTuple + StdInstrumentationFilter + 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, + ) -> Result, QT, S>>, CommandError> { + Ok(Rc::new(LoadCommand)) + } +} + +pub struct EndCommandParser; +impl NativeCommandParser, QT, S> for EndCommandParser +where + CM: CommandManager, QT, S>, + QT: QemuHelperTuple + StdInstrumentationFilter + 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, + ) -> Result, QT, S>>, CommandError> { + let native_exit_kind: GuestReg = qemu.read_reg(arch_regs_map[ExitArgs::Arg1])?; + let native_exit_kind: Result = 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 NativeCommandParser, QT, S> + for VersionCommandParser +where + CM: CommandManager, QT, S>, + QT: QemuHelperTuple + StdInstrumentationFilter + 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, + ) -> Result, 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 NativeCommandParser, QT, S> + for VaddrFilterAllowRangeCommandParser +where + CM: CommandManager, QT, S>, + QT: QemuHelperTuple + StdInstrumentationFilter + 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, + ) -> Result, 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]), + ))) + } +} diff --git a/libafl_qemu/src/emu/mod.rs b/libafl_qemu/src/emu/mod.rs index 82a027a0ea..1a03a8ef7e 100644 --- a/libafl_qemu/src/emu/mod.rs +++ b/libafl_qemu/src/emu/mod.rs @@ -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 = Rc>; +type BreakpointMutRef = Rc>>; + #[derive(Clone, Copy)] pub enum GuestAddrKind { Physical(GuestPhysAddr), @@ -52,10 +60,16 @@ pub enum GuestAddrKind { } #[derive(Debug, Clone)] -pub enum EmulatorExitResult { +pub enum EmulatorExitResult +where + CM: CommandManager, + E: EmulatorExitHandler, + QT: QemuHelperTuple, + 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>>), // Breakpoint triggered. Contains the address of the trigger. + SyncExit(Rc>>), // 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 +where + CM: CommandManager, + E: EmulatorExitHandler, + QT: QemuHelperTuple, + S: State + HasExecutions, +{ + 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. 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 for ExitHandlerResult { +impl TryFrom> for ExitKind +where + CM: CommandManager + Debug, + E: EmulatorExitHandler, + QT: QemuHelperTuple + Debug, + S: State + HasExecutions + Debug, +{ type Error = String; - fn try_into(self) -> Result { - match self { + fn try_from(value: ExitHandlerResult) -> Result { + match value { ExitHandlerResult::ReturnToHarness(unhandled_qemu_exit) => { Err(format!("Unhandled QEMU exit: {:?}", &unhandled_qemu_exit)) } @@ -157,18 +183,18 @@ where QT: QemuHelperTuple, S: State + HasExecutions, { - fn qemu_pre_run( - emu: &Emulator, + fn qemu_pre_run>( + emu: &Emulator, qemu_executor_state: &mut QemuExecutorState, input: &S::Input, ); - fn qemu_post_run( - emu: &Emulator, - exit_reason: Result, + fn qemu_post_run>( + emu: &Emulator, + exit_reason: Result, EmulatorExitError>, qemu_executor_state: &mut QemuExecutorState, input: &S::Input, - ) -> Result, ExitHandlerError>; + ) -> Result>, ExitHandlerError>; } /// Special kind of Exit handler with no data embedded. @@ -182,14 +208,19 @@ where QT: QemuHelperTuple, S: State + HasExecutions, { - fn qemu_pre_run(_: &Emulator, _: &mut QemuExecutorState, _: &S::Input) {} - - fn qemu_post_run( - _: &Emulator, - exit_reason: Result, + fn qemu_pre_run>( + _: &Emulator, _: &mut QemuExecutorState, _: &S::Input, - ) -> Result, ExitHandlerError> { + ) { + } + + fn qemu_post_run>( + _: &Emulator, + exit_reason: Result, EmulatorExitError>, + _: &mut QemuExecutorState, + _: &S::Input, + ) -> Result>, 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 where SM: IsSnapshotManager + Clone, { snapshot_manager: RefCell, + #[builder(default)] snapshot_id: OnceCell, + #[builder(default)] input_location: OnceCell, } @@ -260,19 +293,19 @@ where } // TODO: replace handlers with generics to permit compile-time customization of handlers -impl EmulatorExitHandler for StdEmulatorExitHandler +impl EmulatorExitHandler for StdEmulatorExitHandler where - SM: IsSnapshotManager, QT: QemuHelperTuple + StdInstrumentationFilter + Debug, S: State + HasExecutions, S::Input: HasTargetBytes, + SM: IsSnapshotManager, { - fn qemu_pre_run( - emu: &Emulator, + fn qemu_pre_run>( + emu: &Emulator, qemu_executor_state: &mut QemuExecutorState, 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, - exit_reason: Result, + fn qemu_post_run>( + emu: &Emulator, + exit_reason: Result, EmulatorExitError>, qemu_executor_state: &mut QemuExecutorState, input: &S::Input, - ) -> Result, ExitHandlerError> { + ) -> Result>, ExitHandlerError> { let exit_handler = emu.exit_handler().borrow_mut(); let qemu = emu.qemu(); @@ -308,23 +341,26 @@ where }, }; - let (command, ret_reg): (Option, Option) = 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>, Option) = + 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 for ExitHandlerError { } } -impl Display for EmulatorExitResult { +impl Display for EmulatorExitResult +where + CM: CommandManager, + E: EmulatorExitHandler, + QT: QemuHelperTuple, + 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 for EmulatorExitError { } } -#[derive(Debug, Clone)] -pub struct EmulatorState +#[derive(Clone, Debug, TypedBuilder)] +pub struct Emulator where + CM: CommandManager, + E: EmulatorExitHandler, QT: QemuHelperTuple, S: State + HasExecutions, - E: EmulatorExitHandler, { + command_manager: CM, exit_handler: RefCell, - breakpoints: RefCell>, + #[builder(default)] + breakpoints_by_addr: RefCell>>, + #[builder(default)] + breakpoints_by_id: RefCell>>, + qemu: Qemu, _phantom: PhantomData<(QT, S)>, } -#[derive(Clone, Debug)] -pub struct Emulator -where - QT: QemuHelperTuple, - S: State + HasExecutions, - E: EmulatorExitHandler, -{ - state: EmulatorState, - qemu: Qemu, -} - #[allow(clippy::unused_self)] -impl Emulator +impl Emulator where + CM: CommandManager, + E: EmulatorExitHandler, QT: QemuHelperTuple, S: State + HasExecutions, - E: EmulatorExitHandler, { #[allow(clippy::must_use_candidate, clippy::similar_names)] pub fn new( args: &[String], env: &[(String, String)], exit_handler: E, + command_manager: CM, ) -> Result { 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 { - 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 { 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 { - &self.state - } - - #[must_use] - pub fn state_mut(&mut self) -> &mut EmulatorState { - &mut self.state - } - #[must_use] pub fn exit_handler(&self) -> &RefCell { - &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, 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 { + unsafe fn run_qemu(&self) -> Result, 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, - ) -> Result { + ) -> Result, ExitHandlerError> { loop { // Insert input if the location is already known E::qemu_pre_run(self, qemu_executor_state, input); diff --git a/libafl_qemu/src/emu/systemmode.rs b/libafl_qemu/src/emu/systemmode.rs index acbc711658..d6873566af 100644 --- a/libafl_qemu/src/emu/systemmode.rs +++ b/libafl_qemu/src/emu/systemmode.rs @@ -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 Emulator +impl Emulator where + CM: CommandManager, + E: EmulatorExitHandler, QT: QemuHelperTuple, S: State + HasExecutions, - E: EmulatorExitHandler, { /// Write a value to a phsical guest address, including ROM areas. pub unsafe fn write_phys_mem(&self, paddr: GuestPhysAddr, buf: &[u8]) { diff --git a/libafl_qemu/src/emu/usermode.rs b/libafl_qemu/src/emu/usermode.rs index dff342547a..5295a12948 100644 --- a/libafl_qemu/src/emu/usermode.rs +++ b/libafl_qemu/src/emu/usermode.rs @@ -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 Emulator +impl Emulator where + CM: CommandManager, + E: EmulatorExitHandler, QT: QemuHelperTuple, S: State + HasExecutions, - E: EmulatorExitHandler, { /// This function gets the memory mappings from the emulator. #[must_use] diff --git a/libafl_qemu/src/qemu/systemmode.rs b/libafl_qemu/src/qemu/systemmode.rs index 10d5695214..2693bf5909 100644 --- a/libafl_qemu/src/qemu/systemmode.rs +++ b/libafl_qemu/src/qemu/systemmode.rs @@ -302,6 +302,7 @@ impl EmulatorMemoryChunk { } } + #[allow(clippy::map_flatten)] pub fn host_iter(&self, qemu: Qemu) -> Box> { Box::new( self.phys_iter(qemu) diff --git a/libafl_qemu/src/sync_exit.rs b/libafl_qemu/src/sync_exit.rs index 691282424e..3d18fedd0b 100644 --- a/libafl_qemu/src/sync_exit.rs +++ b/libafl_qemu/src/sync_exit.rs @@ -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 +where + CM: CommandManager, + E: EmulatorExitHandler, + QT: QemuHelperTuple, + S: State + HasExecutions, +{ + command: Rc>, } -impl SyncExit { +impl SyncExit +where + CM: CommandManager, + E: EmulatorExitHandler, + QT: QemuHelperTuple, + S: State + HasExecutions, +{ #[must_use] - pub fn new(command: Command) -> Self { + pub fn new(command: Rc>) -> Self { Self { command } } #[must_use] - pub fn command(&self) -> &Command { - &self.command + pub fn command(&self) -> Rc> { + self.command.clone() } pub fn ret(&self, cpu: &CPU, value: GuestReg) { @@ -43,7 +62,13 @@ impl SyncExit { } } -impl Display for SyncExit { +impl Display for SyncExit +where + CM: CommandManager, + E: EmulatorExitHandler, + QT: QemuHelperTuple, + S: State + HasExecutions, +{ fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { write!(f, "{}", self.command) }