diff --git a/libafl_qemu/Cargo.toml b/libafl_qemu/Cargo.toml index e93d4cd9f5..4e2a16fdd6 100644 --- a/libafl_qemu/Cargo.toml +++ b/libafl_qemu/Cargo.toml @@ -51,6 +51,7 @@ libafl_qemu_sys = { path = "./libafl_qemu_sys", version = "0.11.1" } serde = { version = "1.0", default-features = false, features = ["alloc"] } # serialization lib hashbrown = { version = "0.14", features = ["serde"] } # A faster hashmap, nostd compatible num-traits = "0.2" +num-derive = "0.4" num_enum = "0.7" goblin = "0.7" libc = "0.2" @@ -64,6 +65,7 @@ rangemap = "1.3" log = "0.4.20" addr2line = "0.21" typed-arena = "2.0" +enum-map = "2.7" pyo3 = { version = "0.18", optional = true } diff --git a/libafl_qemu/libafl_qemu_build/src/bindings.rs b/libafl_qemu/libafl_qemu_build/src/bindings.rs index 9e918cf04f..6d5347295d 100644 --- a/libafl_qemu/libafl_qemu_build/src/bindings.rs +++ b/libafl_qemu/libafl_qemu_build/src/bindings.rs @@ -76,6 +76,8 @@ const WRAPPER_HEADER: &str = r#" #include "qemu/plugin-memory.h" +#include "libafl_extras/exit.h" + "#; pub fn generate( @@ -107,7 +109,11 @@ pub fn generate( .allowlist_type("qemu_plugin_mem_rw") .allowlist_type("MemOpIdx") .allowlist_type("MemOp") - .allowlist_type("device_snapshot_kind_t") + .allowlist_type("DeviceSnapshotKind") + .allowlist_type("libafl_exit_reason") + .allowlist_type("libafl_exit_reason_kind") + .allowlist_type("libafl_exit_reason_sync_backdoor") + .allowlist_type("libafl_exit_reason_breakpoint") .allowlist_function("qemu_user_init") .allowlist_function("target_mmap") .allowlist_function("target_mprotect") @@ -123,10 +129,11 @@ pub fn generate( .allowlist_function("qemu_plugin_get_hwaddr") .allowlist_function("qemu_target_page_size") .allowlist_function("syx_snapshot_init") - .allowlist_function("syx_snapshot_create") + .allowlist_function("syx_snapshot_new") .allowlist_function("syx_snapshot_root_restore") .allowlist_function("syx_snapshot_dirty_list_add") .allowlist_function("device_list_all") + .allowlist_function("libafl_get_exit_reason") .blocklist_function("main_loop_wait") // bindgen issue #1313 .parse_callbacks(Box::new(bindgen::CargoCallbacks)); diff --git a/libafl_qemu/libafl_qemu_build/src/build.rs b/libafl_qemu/libafl_qemu_build/src/build.rs index 4a05f8b188..b02333c926 100644 --- a/libafl_qemu/libafl_qemu_build/src/build.rs +++ b/libafl_qemu/libafl_qemu_build/src/build.rs @@ -8,7 +8,7 @@ use which::which; const QEMU_URL: &str = "https://github.com/AFLplusplus/qemu-libafl-bridge"; const QEMU_DIRNAME: &str = "qemu-libafl-bridge"; -const QEMU_REVISION: &str = "b0c827246517e36b480ad501cba5ac6e2c3f26f5"; +const QEMU_REVISION: &str = "8db5524416b52c999459f1fe3373846bdcb23ac1"; fn build_dep_check(tools: &[&str]) { for tool in tools { @@ -43,9 +43,9 @@ pub fn build( cpu_target += "el"; } - let custum_qemu_dir = env::var_os("CUSTOM_QEMU_DIR").map(|x| x.to_string_lossy().to_string()); - let custum_qemu_no_build = env::var("CUSTOM_QEMU_NO_BUILD").is_ok(); - let custum_qemu_no_configure = env::var("CUSTOM_QEMU_NO_CONFIGURE").is_ok(); + let custom_qemu_dir = env::var_os("CUSTOM_QEMU_DIR").map(|x| x.to_string_lossy().to_string()); + let custom_qemu_no_build = env::var("CUSTOM_QEMU_NO_BUILD").is_ok(); + let custom_qemu_no_configure = env::var("CUSTOM_QEMU_NO_CONFIGURE").is_ok(); println!("cargo:rerun-if-env-changed=CUSTOM_QEMU_DIR"); println!("cargo:rerun-if-env-changed=CUSTOM_QEMU_NO_BUILD"); println!("cargo:rerun-if-env-changed=CUSTOM_QEMU_NO_CONFIGURE"); @@ -63,7 +63,7 @@ pub fn build( let cc_compiler = cc::Build::new().cpp(false).get_compiler(); let cpp_compiler = cc::Build::new().cpp(true).get_compiler(); - let qemu_path = if let Some(qemu_dir) = custum_qemu_dir.as_ref() { + let qemu_path = if let Some(qemu_dir) = custom_qemu_dir.as_ref() { Path::new(&qemu_dir).to_path_buf() } else { let qemu_path = target_dir.join(QEMU_DIRNAME); @@ -128,14 +128,14 @@ pub fn build( println!("cargo:rerun-if-changed={}", output_lib.to_string_lossy()); - if !output_lib.is_file() || (custum_qemu_dir.is_some() && !custum_qemu_no_build) { + if !output_lib.is_file() || (custom_qemu_dir.is_some() && !custom_qemu_no_build) { /*drop( Command::new("make") .current_dir(&qemu_path) .arg("distclean") .status(), );*/ - if is_usermode && !custum_qemu_no_configure { + if is_usermode && !custom_qemu_no_configure { let mut cmd = Command::new("./configure"); cmd.current_dir(&qemu_path) //.arg("--as-static-lib") @@ -162,7 +162,7 @@ pub fn build( cmd.arg("--enable-debug"); } cmd.status().expect("Configure failed"); - } else if !custum_qemu_no_configure { + } else if !custom_qemu_no_configure { let mut cmd = Command::new("./configure"); cmd.current_dir(&qemu_path) //.arg("--as-static-lib") @@ -290,7 +290,8 @@ pub fn build( .arg("--disable-xen") .arg("--disable-xen-pci-passthrough") .arg("--disable-xkbcommon") - .arg("--disable-zstd"); + .arg("--disable-zstd") + .arg("--disable-tests"); if cfg!(feature = "debug_assertions") { cmd.arg("--enable-debug"); } diff --git a/libafl_qemu/src/aarch64.rs b/libafl_qemu/src/aarch64.rs index 3734406a2d..877a9319e6 100644 --- a/libafl_qemu/src/aarch64.rs +++ b/libafl_qemu/src/aarch64.rs @@ -1,11 +1,14 @@ +use std::sync::OnceLock; + use capstone::arch::BuildsCapstone; +use enum_map::{enum_map, EnumMap}; use num_enum::{IntoPrimitive, TryFromPrimitive}; #[cfg(feature = "python")] use pyo3::prelude::*; pub use strum_macros::EnumIter; pub use syscall_numbers::aarch64::*; -use crate::CallingConvention; +use crate::{sync_backdoor::SyncBackdoorArgs, CallingConvention}; #[derive(IntoPrimitive, TryFromPrimitive, Debug, Clone, Copy, EnumIter)] #[repr(i32)] @@ -46,6 +49,23 @@ pub enum Regs { Pstate = 33, } +static SYNC_BACKDOOR_ARCH_REGS: OnceLock> = OnceLock::new(); + +pub fn get_sync_backdoor_arch_regs() -> &'static EnumMap { + SYNC_BACKDOOR_ARCH_REGS.get_or_init(|| { + enum_map! { + SyncBackdoorArgs::Ret => Regs::X0, + SyncBackdoorArgs::Cmd => Regs::X0, + SyncBackdoorArgs::Arg1 => Regs::X1, + SyncBackdoorArgs::Arg2 => Regs::X2, + SyncBackdoorArgs::Arg3 => Regs::X3, + SyncBackdoorArgs::Arg4 => Regs::X4, + SyncBackdoorArgs::Arg5 => Regs::X5, + SyncBackdoorArgs::Arg6 => Regs::X6, + } + }) +} + /// alias registers #[allow(non_upper_case_globals)] impl Regs { diff --git a/libafl_qemu/src/arm.rs b/libafl_qemu/src/arm.rs index 2a1d6a7f9d..5220f60e6b 100644 --- a/libafl_qemu/src/arm.rs +++ b/libafl_qemu/src/arm.rs @@ -1,11 +1,14 @@ +use std::sync::OnceLock; + use capstone::arch::BuildsCapstone; +use enum_map::{enum_map, EnumMap}; use num_enum::{IntoPrimitive, TryFromPrimitive}; #[cfg(feature = "python")] use pyo3::prelude::*; pub use strum_macros::EnumIter; pub use syscall_numbers::arm::*; -use crate::CallingConvention; +use crate::{sync_backdoor::SyncBackdoorArgs, CallingConvention}; /// Registers for the ARM instruction set. #[derive(IntoPrimitive, TryFromPrimitive, Debug, Clone, Copy, EnumIter)] @@ -30,6 +33,23 @@ pub enum Regs { R25 = 25, } +static SYNC_BACKDOOR_ARCH_REGS: OnceLock> = OnceLock::new(); + +pub fn get_sync_backdoor_arch_regs() -> &'static EnumMap { + SYNC_BACKDOOR_ARCH_REGS.get_or_init(|| { + enum_map! { + SyncBackdoorArgs::Ret => Regs::R0, + SyncBackdoorArgs::Cmd => Regs::R0, + SyncBackdoorArgs::Arg1 => Regs::R1, + SyncBackdoorArgs::Arg2 => Regs::R2, + SyncBackdoorArgs::Arg3 => Regs::R3, + SyncBackdoorArgs::Arg4 => Regs::R4, + SyncBackdoorArgs::Arg5 => Regs::R5, + SyncBackdoorArgs::Arg6 => Regs::R6, + } + }) +} + /// alias registers #[allow(non_upper_case_globals)] impl Regs { diff --git a/libafl_qemu/src/emu.rs b/libafl_qemu/src/emu.rs index bc8f16e91c..97fabe4b01 100644 --- a/libafl_qemu/src/emu.rs +++ b/libafl_qemu/src/emu.rs @@ -4,7 +4,7 @@ use core::{ convert::Into, ffi::c_void, fmt, - mem::MaybeUninit, + mem::{transmute, MaybeUninit}, ptr::{addr_of, copy_nonoverlapping, null}, }; #[cfg(emulation_mode = "usermode")] @@ -14,7 +14,11 @@ use std::{ ffi::{CStr, CString}, ptr::null_mut, }; -use std::{slice::from_raw_parts, str::from_utf8_unchecked}; +use std::{ + slice::from_raw_parts, + str::from_utf8_unchecked, + sync::{Mutex, OnceLock}, +}; #[cfg(emulation_mode = "usermode")] use libc::c_int; @@ -28,13 +32,28 @@ use crate::{GuestReg, Regs}; pub type GuestAddr = libafl_qemu_sys::target_ulong; pub type GuestUsize = libafl_qemu_sys::target_ulong; pub type GuestIsize = libafl_qemu_sys::target_long; -pub type GuestVirtAddr = libafl_qemu_sys::hwaddr; +pub type GuestVirtAddr = libafl_qemu_sys::vaddr; pub type GuestPhysAddr = libafl_qemu_sys::hwaddr; pub type GuestHwAddrInfo = libafl_qemu_sys::qemu_plugin_hwaddr; +#[derive(Debug, Clone)] +pub enum GuestAddrKind { + Physical(GuestPhysAddr), + Virtual(GuestVirtAddr), +} + +impl fmt::Display for GuestAddrKind { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + GuestAddrKind::Physical(phys_addr) => write!(f, "hwaddr 0x{:x}", phys_addr), + GuestAddrKind::Virtual(virt_addr) => write!(f, "vaddr 0x{:x}", virt_addr), + } + } +} + #[cfg(emulation_mode = "systemmode")] -pub type FastSnapshot = *mut libafl_qemu_sys::syx_snapshot_t; +pub type FastSnapshot = *mut libafl_qemu_sys::SyxSnapshot; #[cfg(emulation_mode = "systemmode")] pub enum DeviceSnapshotFilter { @@ -45,16 +64,14 @@ pub enum DeviceSnapshotFilter { #[cfg(emulation_mode = "systemmode")] impl DeviceSnapshotFilter { - fn enum_id(&self) -> libafl_qemu_sys::device_snapshot_kind_t { + fn enum_id(&self) -> libafl_qemu_sys::DeviceSnapshotKind { match self { - DeviceSnapshotFilter::All => { - libafl_qemu_sys::device_snapshot_kind_e_DEVICE_SNAPSHOT_ALL - } + DeviceSnapshotFilter::All => libafl_qemu_sys::DeviceSnapshotKind_DEVICE_SNAPSHOT_ALL, DeviceSnapshotFilter::AllowList(_) => { - libafl_qemu_sys::device_snapshot_kind_e_DEVICE_SNAPSHOT_ALLOWLIST + libafl_qemu_sys::DeviceSnapshotKind_DEVICE_SNAPSHOT_ALLOWLIST } DeviceSnapshotFilter::DenyList(_) => { - libafl_qemu_sys::device_snapshot_kind_e_DEVICE_SNAPSHOT_DENYLIST + libafl_qemu_sys::DeviceSnapshotKind_DEVICE_SNAPSHOT_DENYLIST } } } @@ -136,9 +153,13 @@ pub const SKIP_EXEC_HOOK: u64 = u64::MAX; pub use libafl_qemu_sys::{CPUArchState, CPUState}; +use crate::sync_backdoor::{SyncBackdoor, SyncBackdoorError}; + pub type CPUStatePtr = *mut libafl_qemu_sys::CPUState; pub type CPUArchStatePtr = *mut libafl_qemu_sys::CPUArchState; +pub type ExitReasonPtr = *mut libafl_qemu_sys::libafl_exit_reason; + #[derive(IntoPrimitive, TryFromPrimitive, Debug, Clone, Copy, EnumIter, PartialEq, Eq)] #[repr(i32)] pub enum MmapPerms { @@ -360,6 +381,9 @@ extern "C" { // CPUState* libafl_qemu_current_cpu(void); fn libafl_qemu_current_cpu() -> CPUStatePtr; + // struct libafl_exit_reason* libafl_get_exit_reason(void); + fn libafl_get_exit_reason() -> ExitReasonPtr; + fn libafl_qemu_cpu_index(cpu: CPUStatePtr) -> i32; fn libafl_qemu_write_reg(cpu: CPUStatePtr, reg: i32, val: *const u8) -> i32; @@ -821,7 +845,7 @@ impl CPU { } } -static mut EMULATOR_IS_INITIALIZED: bool = false; +static EMULATOR_IS_INITIALIZED: OnceLock> = OnceLock::new(); #[derive(Clone, Debug)] pub struct Emulator { @@ -835,6 +859,60 @@ pub enum EmuError { TooManyArgs(usize), } +#[derive(Debug, Clone)] +pub enum EmuExitReason { + End, // QEMU ended for some reason. + Breakpoint(GuestVirtAddr), // Breakpoint triggered. Contains the virtual address of the trigger. + SyncBackdoor(SyncBackdoor), // Synchronous backdoor: The guest triggered a backdoor and should return to LibAFL. +} + +impl fmt::Display for EmuExitReason { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + EmuExitReason::End => write!(f, "End"), + EmuExitReason::Breakpoint(vaddr) => write!(f, "Breakpoint @vaddr 0x{:x}", vaddr), + EmuExitReason::SyncBackdoor(sync_backdoor) => { + write!(f, "Sync backdoor exit: {}", sync_backdoor) + } + } + } +} + +#[derive(Debug, Clone)] +pub enum EmuExitReasonError { + UnknownKind(), + UnexpectedExit, + SyncBackdoorError(SyncBackdoorError), +} + +impl From for EmuExitReasonError { + fn from(sync_backdoor_error: SyncBackdoorError) -> Self { + EmuExitReasonError::SyncBackdoorError(sync_backdoor_error) + } +} + +impl TryFrom<&Emulator> for EmuExitReason { + type Error = EmuExitReasonError; + fn try_from(emu: &Emulator) -> Result { + let exit_reason = unsafe { libafl_get_exit_reason() }; + if exit_reason.is_null() { + Err(EmuExitReasonError::UnexpectedExit) + } else { + let exit_reason: &mut libafl_qemu_sys::libafl_exit_reason = + unsafe { transmute(exit_reason) }; + Ok(match exit_reason.kind { + libafl_qemu_sys::libafl_exit_reason_kind_BREAKPOINT => unsafe { + EmuExitReason::Breakpoint(exit_reason.data.breakpoint.addr.into()) + }, + libafl_qemu_sys::libafl_exit_reason_kind_SYNC_BACKDOOR => { + EmuExitReason::SyncBackdoor(emu.try_into()?) + } + _ => return Err(EmuExitReasonError::UnknownKind()), + }) + } + } +} + impl std::error::Error for EmuError {} impl fmt::Display for EmuError { @@ -866,11 +944,15 @@ impl From for libafl::Error { impl Emulator { #[allow(clippy::must_use_candidate, clippy::similar_names)] pub fn new(args: &[String], env: &[(String, String)]) -> Result { - unsafe { - if EMULATOR_IS_INITIALIZED { - return Err(EmuError::MultipleInstances); - } + let mut is_initialized = EMULATOR_IS_INITIALIZED + .get_or_init(|| Mutex::new(false)) + .lock() + .unwrap(); + + if *is_initialized { + return Err(EmuError::MultipleInstances); } + if args.is_empty() { return Err(EmuError::EmptyArgs); } @@ -903,7 +985,7 @@ impl Emulator { libc::atexit(qemu_cleanup_atexit); libafl_qemu_sys::syx_snapshot_init(); } - EMULATOR_IS_INITIALIZED = true; + *is_initialized = true; } Ok(Emulator { _private: () }) } @@ -1047,7 +1129,8 @@ impl Emulator { pub fn entry_break(&self, addr: GuestAddr) { self.set_breakpoint(addr); unsafe { - self.run(); + // TODO: decide what to do with sync exit here: ignore or check for bp exit? + let _ = self.run(); } self.remove_breakpoint(addr); } @@ -1079,7 +1162,7 @@ impl Emulator { /// /// 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. - pub unsafe fn run(&self) { + pub unsafe fn run(&self) -> Result { #[cfg(emulation_mode = "usermode")] libafl_qemu_run(); #[cfg(emulation_mode = "systemmode")] @@ -1087,6 +1170,7 @@ impl Emulator { vm_start(); qemu_main_loop(); } + EmuExitReason::try_from(self) } #[cfg(emulation_mode = "usermode")] @@ -1283,9 +1367,9 @@ impl Emulator { #[must_use] pub fn create_fast_snapshot(&self, track: bool) -> FastSnapshot { unsafe { - libafl_qemu_sys::syx_snapshot_create( + libafl_qemu_sys::syx_snapshot_new( track, - libafl_qemu_sys::device_snapshot_kind_e_DEVICE_SNAPSHOT_ALL, + libafl_qemu_sys::DeviceSnapshotKind_DEVICE_SNAPSHOT_ALL, null_mut(), ) } @@ -1300,7 +1384,7 @@ impl Emulator { ) -> FastSnapshot { let mut v = vec![]; unsafe { - libafl_qemu_sys::syx_snapshot_create( + libafl_qemu_sys::syx_snapshot_new( track, device_filter.enum_id(), device_filter.devices(&mut v), diff --git a/libafl_qemu/src/hexagon.rs b/libafl_qemu/src/hexagon.rs index 6a47e4fd4b..e44885e1b8 100644 --- a/libafl_qemu/src/hexagon.rs +++ b/libafl_qemu/src/hexagon.rs @@ -1,9 +1,12 @@ +use std::sync::OnceLock; + +use enum_map::{enum_map, EnumMap}; use num_enum::{IntoPrimitive, TryFromPrimitive}; #[cfg(feature = "python")] use pyo3::prelude::*; pub use strum_macros::EnumIter; -use crate::CallingConvention; +use crate::{sync_backdoor::SyncBackdoorArgs, CallingConvention}; #[derive(IntoPrimitive, TryFromPrimitive, Debug, Clone, Copy, EnumIter)] #[repr(i32)] @@ -61,6 +64,23 @@ pub enum Regs { Pktcnthi = 51, } +static SYNC_BACKDOOR_ARCH_REGS: OnceLock> = OnceLock::new(); + +pub fn get_sync_backdoor_arch_regs() -> &'static EnumMap { + SYNC_BACKDOOR_ARCH_REGS.get_or_init(|| { + enum_map! { + SyncBackdoorArgs::Ret => Regs::R0, + SyncBackdoorArgs::Cmd => Regs::R0, + SyncBackdoorArgs::Arg1 => Regs::R1, + SyncBackdoorArgs::Arg2 => Regs::R2, + SyncBackdoorArgs::Arg3 => Regs::R3, + SyncBackdoorArgs::Arg4 => Regs::R4, + SyncBackdoorArgs::Arg5 => Regs::R5, + SyncBackdoorArgs::Arg6 => Regs::R6, + } + }) +} + /// alias registers #[allow(non_upper_case_globals)] impl Regs { diff --git a/libafl_qemu/src/i386.rs b/libafl_qemu/src/i386.rs index 811dd302aa..a6c5c9bc30 100644 --- a/libafl_qemu/src/i386.rs +++ b/libafl_qemu/src/i386.rs @@ -1,13 +1,14 @@ -use std::mem::size_of; +use std::{mem::size_of, sync::OnceLock}; use capstone::arch::BuildsCapstone; +use enum_map::{enum_map, EnumMap}; use num_enum::{IntoPrimitive, TryFromPrimitive}; #[cfg(feature = "python")] use pyo3::prelude::*; pub use strum_macros::EnumIter; pub use syscall_numbers::x86::*; -use crate::{CallingConvention, GuestAddr}; +use crate::{sync_backdoor::SyncBackdoorArgs, CallingConvention, GuestAddr}; #[derive(IntoPrimitive, TryFromPrimitive, Debug, Clone, Copy, EnumIter)] #[repr(i32)] @@ -24,6 +25,23 @@ pub enum Regs { Eflags = 9, } +static SYNC_BACKDOOR_ARCH_REGS: OnceLock> = OnceLock::new(); + +pub fn get_sync_backdoor_arch_regs() -> &'static EnumMap { + SYNC_BACKDOOR_ARCH_REGS.get_or_init(|| { + enum_map! { + SyncBackdoorArgs::Ret => Regs::Eax, + SyncBackdoorArgs::Cmd => Regs::Eax, + SyncBackdoorArgs::Arg1 => Regs::Edi, + SyncBackdoorArgs::Arg2 => Regs::Esi, + SyncBackdoorArgs::Arg3 => Regs::Edx, + SyncBackdoorArgs::Arg4 => Regs::Ebx, + SyncBackdoorArgs::Arg5 => Regs::Ecx, + SyncBackdoorArgs::Arg6 => Regs::Ebp, + } + }) +} + /// alias registers #[allow(non_upper_case_globals)] impl Regs { diff --git a/libafl_qemu/src/lib.rs b/libafl_qemu/src/lib.rs index f0155783b4..123d9f2229 100644 --- a/libafl_qemu/src/lib.rs +++ b/libafl_qemu/src/lib.rs @@ -99,6 +99,8 @@ pub use executor::QemuForkExecutor; pub mod emu; pub use emu::*; +pub mod sync_backdoor; + #[must_use] pub fn filter_qemu_args() -> Vec { let mut args = vec![env::args().next().unwrap()]; diff --git a/libafl_qemu/src/mips.rs b/libafl_qemu/src/mips.rs index abf32e356d..cb4d9047fb 100644 --- a/libafl_qemu/src/mips.rs +++ b/libafl_qemu/src/mips.rs @@ -1,10 +1,13 @@ +use std::sync::OnceLock; + +use enum_map::{enum_map, EnumMap}; use num_enum::{IntoPrimitive, TryFromPrimitive}; #[cfg(feature = "python")] use pyo3::prelude::*; pub use strum_macros::EnumIter; pub use syscall_numbers::mips::*; -use crate::CallingConvention; +use crate::{sync_backdoor::SyncBackdoorArgs, CallingConvention}; /// Registers for the MIPS instruction set. #[derive(IntoPrimitive, TryFromPrimitive, Debug, Clone, Copy, EnumIter)] @@ -46,6 +49,23 @@ pub enum Regs { Pc = 37, } +static SYNC_BACKDOOR_ARCH_REGS: OnceLock> = OnceLock::new(); + +pub fn get_sync_backdoor_arch_regs() -> &'static EnumMap { + SYNC_BACKDOOR_ARCH_REGS.get_or_init(|| { + enum_map! { + SyncBackdoorArgs::Ret => Regs::V0, + SyncBackdoorArgs::Cmd => Regs::V0, + SyncBackdoorArgs::Arg1 => Regs::A0, + SyncBackdoorArgs::Arg2 => Regs::A1, + SyncBackdoorArgs::Arg3 => Regs::A2, + SyncBackdoorArgs::Arg4 => Regs::A3, + SyncBackdoorArgs::Arg5 => Regs::T0, + SyncBackdoorArgs::Arg6 => Regs::T1, + } + }) +} + /// alias registers #[allow(non_upper_case_globals)] impl Regs { diff --git a/libafl_qemu/src/ppc.rs b/libafl_qemu/src/ppc.rs index 3c71e4881c..3722277d96 100644 --- a/libafl_qemu/src/ppc.rs +++ b/libafl_qemu/src/ppc.rs @@ -1,10 +1,13 @@ +use std::sync::OnceLock; + +use enum_map::{enum_map, EnumMap}; use num_enum::{IntoPrimitive, TryFromPrimitive}; #[cfg(feature = "python")] use pyo3::prelude::*; pub use strum_macros::EnumIter; pub use syscall_numbers::powerpc::*; -use crate::CallingConvention; +use crate::{sync_backdoor::SyncBackdoorArgs, CallingConvention}; /// Registers for the MIPS instruction set. #[derive(IntoPrimitive, TryFromPrimitive, Debug, Clone, Copy, EnumIter)] @@ -85,6 +88,23 @@ pub enum Regs { Fpscr = 70, } +static SYNC_BACKDOOR_ARCH_REGS: OnceLock> = OnceLock::new(); + +pub fn get_sync_backdoor_arch_regs() -> &'static EnumMap { + SYNC_BACKDOOR_ARCH_REGS.get_or_init(|| { + enum_map! { + SyncBackdoorArgs::Ret => Regs::R3, + SyncBackdoorArgs::Cmd => Regs::R0, + SyncBackdoorArgs::Arg1 => Regs::R3, + SyncBackdoorArgs::Arg2 => Regs::R4, + SyncBackdoorArgs::Arg3 => Regs::R5, + SyncBackdoorArgs::Arg4 => Regs::R6, + SyncBackdoorArgs::Arg5 => Regs::R7, + SyncBackdoorArgs::Arg6 => Regs::R8, + } + }) +} + /// alias registers #[allow(non_upper_case_globals)] impl Regs { diff --git a/libafl_qemu/src/sync_backdoor.rs b/libafl_qemu/src/sync_backdoor.rs new file mode 100644 index 0000000000..4f4a74fb90 --- /dev/null +++ b/libafl_qemu/src/sync_backdoor.rs @@ -0,0 +1,248 @@ +use std::{ + fmt::{Display, Formatter}, + sync::OnceLock, +}; + +use enum_map::{enum_map, Enum, EnumMap}; +use libafl::executors::ExitKind; +use num_enum::{TryFromPrimitive, TryFromPrimitiveError}; + +use crate::{ + get_sync_backdoor_arch_regs, Emulator, GuestAddrKind, GuestPhysAddr, GuestReg, GuestVirtAddr, + Regs, +}; + +#[derive(Debug, Clone)] +pub enum SyncBackdoorError { + UnknownCommand(GuestReg), + RegError(String), +} + +impl From for SyncBackdoorError { + fn from(error_string: String) -> Self { + SyncBackdoorError::RegError(error_string) + } +} + +#[derive(Debug, Clone, Enum)] +pub enum SyncBackdoorArgs { + Ret, + Cmd, + Arg1, + Arg2, + Arg3, + Arg4, + Arg5, + Arg6, +} + +// TODO: Move in a separate header file to have a central definition of native definitions, +// reusable in targets directly. +#[derive(Debug, Clone, TryFromPrimitive)] +#[repr(u64)] +pub enum NativeSyncBackdoorCommand { + Save = 0, // Save the VM + Load = 1, // Reload the target without ending the run? + InputVirt = 2, // The address is a virtual address using the paging currently running in the VM. + InputPhys = 3, // The address is a physical address + End = 4, // Implies reloading of the target. The first argument gives the exit status. + StartVirt = 5, // Shortcut for Save + InputVirt + StartPhys = 6, // Shortcut for Save + InputPhys +} + +#[derive(Debug, Clone, Enum, TryFromPrimitive)] +#[repr(u64)] +pub enum NativeExitKind { + Unknown = 0, // Should not be used + Ok = 1, // Normal exit + Crash = 2, // Crash reported in the VM +} + +static EMU_EXIT_KIND_MAP: OnceLock>> = OnceLock::new(); + +impl From> for SyncBackdoorError { + fn from(error: TryFromPrimitiveError) -> Self { + SyncBackdoorError::UnknownCommand(error.number.try_into().unwrap()) + } +} + +#[derive(Debug, Clone)] +pub struct CommandInput { + addr: GuestAddrKind, + max_input_size: GuestReg, +} + +impl CommandInput { + pub fn exec(&self, emu: &Emulator, backdoor: &SyncBackdoor, input: &[u8]) { + match self.addr { + GuestAddrKind::Physical(hwaddr) => unsafe { + #[cfg(emulation_mode = "usermode")] + { + // For now the default behaviour is to fall back to virtual addresses + emu.write_mem(hwaddr.try_into().unwrap(), input) + } + #[cfg(emulation_mode = "systemmode")] + { + emu.write_phys_mem(hwaddr, input) + } + }, + GuestAddrKind::Virtual(vaddr) => unsafe { + emu.write_mem(vaddr.try_into().unwrap(), input) + }, + }; + + backdoor.ret(&emu, input.len().try_into().unwrap()).unwrap() + } +} + +impl Display for CommandInput { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "{} ({:x} max nb bytes)", self.addr, self.max_input_size) + } +} + +#[derive(Debug, Clone)] +pub enum Command { + Save, + Load, + Input(CommandInput), + Start(CommandInput), + Exit(Option), +} + +impl Display for Command { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + Command::Save => write!(f, "Save VM"), + Command::Load => write!(f, "Reload VM"), + Command::Input(command_input) => write!(f, "Set fuzzing input @{}", command_input), + Command::Start(command_input) => { + write!(f, "Start fuzzing with input @{}", command_input) + } + Command::Exit(exit_kind) => write!(f, "Exit of kind {:?}", exit_kind), + } + } +} + +#[derive(Debug, Clone)] +pub struct SyncBackdoor { + command: Command, + arch_regs_map: &'static EnumMap, +} + +impl SyncBackdoor { + pub fn command(&self) -> &Command { + &self.command + } + + pub fn ret(&self, emu: &Emulator, value: GuestReg) -> Result<(), SyncBackdoorError> { + Ok(emu.write_reg(self.arch_regs_map[SyncBackdoorArgs::Ret], value)?) + } +} + +impl Display for SyncBackdoor { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.command) + } +} + +impl TryFrom<&Emulator> for SyncBackdoor { + type Error = SyncBackdoorError; + + fn try_from(emu: &Emulator) -> Result { + let arch_regs_map: &'static EnumMap = get_sync_backdoor_arch_regs(); + let cmd_id: GuestReg = + emu.read_reg::(arch_regs_map[SyncBackdoorArgs::Cmd])?; + + Ok(match u64::from(cmd_id).try_into()? { + NativeSyncBackdoorCommand::Save => SyncBackdoor { + command: Command::Save, + arch_regs_map, + }, + NativeSyncBackdoorCommand::Load => SyncBackdoor { + command: Command::Load, + arch_regs_map, + }, + NativeSyncBackdoorCommand::InputVirt => { + let virt_addr: GuestVirtAddr = + emu.read_reg(arch_regs_map[SyncBackdoorArgs::Arg1])?; + let max_input_size: GuestReg = + emu.read_reg(arch_regs_map[SyncBackdoorArgs::Arg2])?; + + SyncBackdoor { + command: Command::Input(CommandInput { + addr: GuestAddrKind::Virtual(virt_addr), + max_input_size, + }), + arch_regs_map, + } + } + NativeSyncBackdoorCommand::InputPhys => { + let phys_addr: GuestPhysAddr = + emu.read_reg(arch_regs_map[SyncBackdoorArgs::Arg1])?; + let max_input_size: GuestReg = + emu.read_reg(arch_regs_map[SyncBackdoorArgs::Arg2])?; + + SyncBackdoor { + command: Command::Input(CommandInput { + addr: GuestAddrKind::Physical(phys_addr), + max_input_size, + }), + arch_regs_map, + } + } + NativeSyncBackdoorCommand::End => { + let native_exit_kind: GuestReg = + emu.read_reg(arch_regs_map[SyncBackdoorArgs::Arg1])?; + let native_exit_kind: Result = + u64::from(native_exit_kind).try_into(); + + let exit_kind = native_exit_kind + .ok() + .map(|k| { + EMU_EXIT_KIND_MAP.get_or_init(|| { + enum_map! { + NativeExitKind::Unknown => None, + NativeExitKind::Ok => Some(ExitKind::Ok), + NativeExitKind::Crash => Some(ExitKind::Crash) + } + })[k] + }) + .flatten(); + + SyncBackdoor { + command: Command::Exit(exit_kind), + arch_regs_map, + } + } + NativeSyncBackdoorCommand::StartPhys => { + let input_phys_addr: GuestPhysAddr = + emu.read_reg(arch_regs_map[SyncBackdoorArgs::Arg1])?; + let max_input_size: GuestReg = + emu.read_reg(arch_regs_map[SyncBackdoorArgs::Arg2])?; + + SyncBackdoor { + command: Command::Start(CommandInput { + addr: GuestAddrKind::Physical(input_phys_addr), + max_input_size, + }), + arch_regs_map, + } + } + NativeSyncBackdoorCommand::StartVirt => { + let input_virt_addr: GuestVirtAddr = + emu.read_reg(arch_regs_map[SyncBackdoorArgs::Arg1])?; + let max_input_size: GuestReg = + emu.read_reg(arch_regs_map[SyncBackdoorArgs::Arg2])?; + + SyncBackdoor { + command: Command::Start(CommandInput { + addr: GuestAddrKind::Virtual(input_virt_addr), + max_input_size, + }), + arch_regs_map, + } + } + }) + } +} diff --git a/libafl_qemu/src/x86_64.rs b/libafl_qemu/src/x86_64.rs index 576d1a66d9..1a48c1e32e 100644 --- a/libafl_qemu/src/x86_64.rs +++ b/libafl_qemu/src/x86_64.rs @@ -1,13 +1,14 @@ -use std::mem::size_of; +use std::{mem::size_of, sync::OnceLock}; use capstone::arch::BuildsCapstone; +use enum_map::{enum_map, EnumMap}; use num_enum::{IntoPrimitive, TryFromPrimitive}; #[cfg(feature = "python")] use pyo3::prelude::*; pub use strum_macros::EnumIter; pub use syscall_numbers::x86_64::*; -use crate::CallingConvention; +use crate::{sync_backdoor::SyncBackdoorArgs, CallingConvention}; #[derive(IntoPrimitive, TryFromPrimitive, Debug, Clone, Copy, EnumIter)] #[repr(i32)] @@ -32,6 +33,23 @@ pub enum Regs { Rflags = 17, } +static SYNC_BACKDOOR_ARCH_REGS: OnceLock> = OnceLock::new(); + +pub fn get_sync_backdoor_arch_regs() -> &'static EnumMap { + SYNC_BACKDOOR_ARCH_REGS.get_or_init(|| { + enum_map! { + SyncBackdoorArgs::Ret => Regs::Rax, + SyncBackdoorArgs::Cmd => Regs::Rax, + SyncBackdoorArgs::Arg1 => Regs::Rdi, + SyncBackdoorArgs::Arg2 => Regs::Rsi, + SyncBackdoorArgs::Arg3 => Regs::Rdx, + SyncBackdoorArgs::Arg4 => Regs::R10, + SyncBackdoorArgs::Arg5 => Regs::R8, + SyncBackdoorArgs::Arg6 => Regs::R9, + } + }) +} + /// alias registers #[allow(non_upper_case_globals)] impl Regs {