From 1102ea0fe7bb7b539bac5b759c06f6f7bf52acaa Mon Sep 17 00:00:00 2001 From: Romain Malmain Date: Thu, 30 May 2024 15:14:17 +0200 Subject: [PATCH] Better RW errors for QEMU (#2260) * better error for rw in qemu. * fix python * clippy * Fix error in fuzzers * Fix error in fuzzers * fix systemmode error * import --- fuzzers/qemu_coverage/src/fuzzer.rs | 4 +- fuzzers/qemu_launcher/src/client.rs | 2 +- fuzzers/qemu_launcher/src/harness.rs | 16 +- fuzzers/qemu_systemmode/src/fuzzer_classic.rs | 5 +- libafl_qemu/src/arch/aarch64.rs | 27 ++-- libafl_qemu/src/arch/arm.rs | 27 ++-- libafl_qemu/src/arch/hexagon.rs | 29 ++-- libafl_qemu/src/arch/i386.rs | 27 ++-- libafl_qemu/src/arch/mips.rs | 27 ++-- libafl_qemu/src/arch/ppc.rs | 27 ++-- libafl_qemu/src/arch/x86_64.rs | 27 ++-- libafl_qemu/src/command/mod.rs | 12 +- libafl_qemu/src/emu/mod.rs | 10 +- libafl_qemu/src/qemu/mod.rs | 138 ++++++++++++++---- 14 files changed, 231 insertions(+), 147 deletions(-) diff --git a/fuzzers/qemu_coverage/src/fuzzer.rs b/fuzzers/qemu_coverage/src/fuzzer.rs index e2edb5da56..f79a08e4b9 100644 --- a/fuzzers/qemu_coverage/src/fuzzer.rs +++ b/fuzzers/qemu_coverage/src/fuzzer.rs @@ -28,7 +28,7 @@ use libafl_bolts::{ use libafl_qemu::{ drcov::QemuDrCovHelper, elf::EasyElf, ArchExtras, CallingConvention, GuestAddr, GuestReg, MmapPerms, Qemu, QemuExecutor, QemuExitReason, QemuHooks, - QemuInstrumentationAddressRangeFilter, QemuShutdownCause, Regs, + QemuInstrumentationAddressRangeFilter, QemuRWError, QemuShutdownCause, Regs, }; use rangemap::RangeMap; @@ -155,7 +155,7 @@ pub fn fuzz() { let stack_ptr: GuestAddr = qemu.read_reg(Regs::Sp).unwrap(); - let reset = |buf: &[u8], len: GuestReg| -> Result<(), String> { + let reset = |buf: &[u8], len: GuestReg| -> Result<(), QemuRWError> { unsafe { qemu.write_mem(input_addr, buf); qemu.write_reg(Regs::Pc, test_one_input_ptr)?; diff --git a/fuzzers/qemu_launcher/src/client.rs b/fuzzers/qemu_launcher/src/client.rs index 483e4a5b4e..0259b31552 100644 --- a/fuzzers/qemu_launcher/src/client.rs +++ b/fuzzers/qemu_launcher/src/client.rs @@ -158,7 +158,7 @@ impl<'a> Client<'a> { let ret_addr: GuestAddr = qemu .read_return_address() - .map_err(|e| Error::unknown(format!("Failed to read return address: {e:}")))?; + .map_err(|e| Error::unknown(format!("Failed to read return address: {e:?}")))?; log::debug!("ret_addr = {ret_addr:#x}"); qemu.set_breakpoint(ret_addr); diff --git a/fuzzers/qemu_launcher/src/harness.rs b/fuzzers/qemu_launcher/src/harness.rs index c8272b4a41..946796be78 100644 --- a/fuzzers/qemu_launcher/src/harness.rs +++ b/fuzzers/qemu_launcher/src/harness.rs @@ -24,15 +24,15 @@ impl<'a> Harness<'a> { let pc: GuestReg = qemu .read_reg(Regs::Pc) - .map_err(|e| Error::unknown(format!("Failed to read PC: {e:}")))?; + .map_err(|e| Error::unknown(format!("Failed to read PC: {e:?}")))?; let stack_ptr: GuestAddr = qemu .read_reg(Regs::Sp) - .map_err(|e| Error::unknown(format!("Failed to read stack pointer: {e:}")))?; + .map_err(|e| Error::unknown(format!("Failed to read stack pointer: {e:?}")))?; let ret_addr: GuestAddr = qemu .read_return_address() - .map_err(|e| Error::unknown(format!("Failed to read return address: {e:}")))?; + .map_err(|e| Error::unknown(format!("Failed to read return address: {e:?}")))?; Ok(Harness { qemu, @@ -62,23 +62,23 @@ impl<'a> Harness<'a> { self.qemu .write_reg(Regs::Pc, self.pc) - .map_err(|e| Error::unknown(format!("Failed to write PC: {e:}")))?; + .map_err(|e| Error::unknown(format!("Failed to write PC: {e:?}")))?; self.qemu .write_reg(Regs::Sp, self.stack_ptr) - .map_err(|e| Error::unknown(format!("Failed to write SP: {e:}")))?; + .map_err(|e| Error::unknown(format!("Failed to write SP: {e:?}")))?; self.qemu .write_return_address(self.ret_addr) - .map_err(|e| Error::unknown(format!("Failed to write return address: {e:}")))?; + .map_err(|e| Error::unknown(format!("Failed to write return address: {e:?}")))?; self.qemu .write_function_argument(CallingConvention::Cdecl, 0, self.input_addr) - .map_err(|e| Error::unknown(format!("Failed to write argument 0: {e:}")))?; + .map_err(|e| Error::unknown(format!("Failed to write argument 0: {e:?}")))?; self.qemu .write_function_argument(CallingConvention::Cdecl, 1, len) - .map_err(|e| Error::unknown(format!("Failed to write argument 1: {e:}")))?; + .map_err(|e| Error::unknown(format!("Failed to write argument 1: {e:?}")))?; unsafe { let _ = self.qemu.run(); }; diff --git a/fuzzers/qemu_systemmode/src/fuzzer_classic.rs b/fuzzers/qemu_systemmode/src/fuzzer_classic.rs index 7e180977c5..87575d6592 100644 --- a/fuzzers/qemu_systemmode/src/fuzzer_classic.rs +++ b/fuzzers/qemu_systemmode/src/fuzzer_classic.rs @@ -32,7 +32,8 @@ use libafl_bolts::{ use libafl_qemu::{ edges::{edges_map_mut_ptr, QemuEdgeCoverageHelper, EDGES_MAP_SIZE_IN_USE, MAX_EDGES_FOUND}, elf::EasyElf, - Qemu, QemuExecutor, QemuExitError, QemuExitReason, QemuHooks, QemuShutdownCause, Regs, + Qemu, QemuExecutor, QemuExitError, QemuExitReason, QemuHooks, QemuRWError, QemuShutdownCause, + Regs, }; use libafl_qemu_sys::GuestPhysAddr; @@ -134,7 +135,7 @@ pub fn fuzz() { // If the execution stops at any point other then the designated breakpoint (e.g. a breakpoint on a panic method) we consider it a crash let mut pcs = (0..qemu.num_cpus()) .map(|i| qemu.cpu_from_index(i)) - .map(|cpu| -> Result { cpu.read_reg(Regs::Pc) }); + .map(|cpu| -> Result { cpu.read_reg(Regs::Pc) }); let ret = match pcs .find(|pc| (breakpoint..breakpoint + 5).contains(pc.as_ref().unwrap_or(&0))) { diff --git a/libafl_qemu/src/arch/aarch64.rs b/libafl_qemu/src/arch/aarch64.rs index dec882c829..9a8395f17c 100644 --- a/libafl_qemu/src/arch/aarch64.rs +++ b/libafl_qemu/src/arch/aarch64.rs @@ -8,7 +8,7 @@ use pyo3::prelude::*; pub use strum_macros::EnumIter; pub use syscall_numbers::aarch64::*; -use crate::{sync_exit::ExitArgs, CallingConvention}; +use crate::{sync_exit::ExitArgs, CallingConvention, QemuRWError, QemuRWErrorKind}; #[derive(IntoPrimitive, TryFromPrimitive, Debug, Clone, Copy, EnumIter)] #[repr(i32)] @@ -91,27 +91,25 @@ pub fn capstone() -> capstone::arch::arm64::ArchCapstoneBuilder { pub type GuestReg = u64; impl crate::ArchExtras for crate::CPU { - fn read_return_address(&self) -> Result + fn read_return_address(&self) -> Result where T: From, { self.read_reg(Regs::Lr) } - fn write_return_address(&self, val: T) -> Result<(), String> + fn write_return_address(&self, val: T) -> Result<(), QemuRWError> where T: Into, { self.write_reg(Regs::Lr, val) } - fn read_function_argument(&self, conv: CallingConvention, idx: u8) -> Result + fn read_function_argument(&self, conv: CallingConvention, idx: u8) -> Result where T: From, { - if conv != CallingConvention::Cdecl { - return Err(format!("Unsupported calling convention: {conv:#?}")); - } + QemuRWError::check_conv(QemuRWErrorKind::Read, CallingConvention::Cdecl, conv)?; let reg_id = match idx { 0 => Regs::X0, @@ -120,7 +118,12 @@ impl crate::ArchExtras for crate::CPU { 3 => Regs::X3, 4 => Regs::X4, 5 => Regs::X5, - r => return Err(format!("Unsupported argument: {r:}")), + r => { + return Err(QemuRWError::new_argument_error( + QemuRWErrorKind::Read, + i32::from(r), + )) + } }; self.read_reg(reg_id) @@ -131,19 +134,17 @@ impl crate::ArchExtras for crate::CPU { conv: CallingConvention, idx: i32, val: T, - ) -> Result<(), String> + ) -> Result<(), QemuRWError> where T: Into, { - if conv != CallingConvention::Cdecl { - return Err(format!("Unsupported calling convention: {conv:#?}")); - } + QemuRWError::check_conv(QemuRWErrorKind::Write, CallingConvention::Cdecl, conv)?; let val: GuestReg = val.into(); match idx { 0 => self.write_reg(Regs::X0, val), 1 => self.write_reg(Regs::X1, val), - _ => Err(format!("Unsupported argument: {idx:}")), + r => Err(QemuRWError::new_argument_error(QemuRWErrorKind::Write, r)), } } } diff --git a/libafl_qemu/src/arch/arm.rs b/libafl_qemu/src/arch/arm.rs index 19d799af17..3b46323040 100644 --- a/libafl_qemu/src/arch/arm.rs +++ b/libafl_qemu/src/arch/arm.rs @@ -8,7 +8,7 @@ use pyo3::prelude::*; pub use strum_macros::EnumIter; pub use syscall_numbers::arm::*; -use crate::{sync_exit::ExitArgs, CallingConvention}; +use crate::{sync_exit::ExitArgs, CallingConvention, QemuRWError, QemuRWErrorKind}; /// Registers for the ARM instruction set. #[derive(IntoPrimitive, TryFromPrimitive, Debug, Clone, Copy, EnumIter)] @@ -88,27 +88,25 @@ pub fn capstone_thumb() -> capstone::arch::arm::ArchCapstoneBuilder { pub type GuestReg = u32; impl crate::ArchExtras for crate::CPU { - fn read_return_address(&self) -> Result + fn read_return_address(&self) -> Result where T: From, { self.read_reg(Regs::Lr) } - fn write_return_address(&self, val: T) -> Result<(), String> + fn write_return_address(&self, val: T) -> Result<(), QemuRWError> where T: Into, { self.write_reg(Regs::Lr, val) } - fn read_function_argument(&self, conv: CallingConvention, idx: u8) -> Result + fn read_function_argument(&self, conv: CallingConvention, idx: u8) -> Result where T: From, { - if conv != CallingConvention::Cdecl { - return Err(format!("Unsupported calling convention: {conv:#?}")); - } + QemuRWError::check_conv(QemuRWErrorKind::Read, CallingConvention::Cdecl, conv)?; let reg_id = match idx { 0 => Regs::R0, @@ -116,7 +114,12 @@ impl crate::ArchExtras for crate::CPU { 2 => Regs::R2, 3 => Regs::R3, // 4.. would be on the stack, let's not do this for now - r => return Err(format!("Unsupported argument: {r:}")), + r => { + return Err(QemuRWError::new_argument_error( + QemuRWErrorKind::Read, + i32::from(r), + )) + } }; self.read_reg(reg_id) @@ -127,19 +130,17 @@ impl crate::ArchExtras for crate::CPU { conv: CallingConvention, idx: i32, val: T, - ) -> Result<(), String> + ) -> Result<(), QemuRWError> where T: Into, { - if conv != CallingConvention::Cdecl { - return Err(format!("Unsupported calling convention: {conv:#?}")); - } + QemuRWError::check_conv(QemuRWErrorKind::Write, CallingConvention::Cdecl, conv)?; let val: GuestReg = val.into(); match idx { 0 => self.write_reg(Regs::R0, val), 1 => self.write_reg(Regs::R1, val), - _ => Err(format!("Unsupported argument: {idx:}")), + r => Err(QemuRWError::new_argument_error(QemuRWErrorKind::Write, r)), } } } diff --git a/libafl_qemu/src/arch/hexagon.rs b/libafl_qemu/src/arch/hexagon.rs index e9215f3fd3..83ee00fee0 100644 --- a/libafl_qemu/src/arch/hexagon.rs +++ b/libafl_qemu/src/arch/hexagon.rs @@ -6,7 +6,7 @@ use num_enum::{IntoPrimitive, TryFromPrimitive}; use pyo3::prelude::*; pub use strum_macros::EnumIter; -use crate::{sync_exit::ExitArgs, CallingConvention}; +use crate::{sync_exit::ExitArgs, CallingConvention, QemuRWError, QemuRWErrorKind}; #[derive(IntoPrimitive, TryFromPrimitive, Debug, Clone, Copy, EnumIter)] #[repr(i32)] @@ -92,27 +92,25 @@ impl Regs { pub type GuestReg = u32; impl crate::ArchExtras for crate::CPU { - fn read_return_address(&self) -> Result + fn read_return_address(&self) -> Result where T: From, { self.read_reg(Regs::Lr) } - fn write_return_address(&self, val: T) -> Result<(), String> + fn write_return_address(&self, val: T) -> Result<(), QemuRWError> where T: Into, { self.write_reg(Regs::Lr, val) } - fn read_function_argument(&self, conv: CallingConvention, idx: u8) -> Result + fn read_function_argument(&self, conv: CallingConvention, idx: u8) -> Result where T: From, { - if conv != CallingConvention::Cdecl { - return Err(format!("Unsupported calling convention: {conv:#?}")); - } + QemuRWError::check_conv(QemuRWErrorKind::Read, CallingConvention::Cdecl, conv)?; // Note that 64 bit values may be passed in two registers (and may have padding), then this mapping is off. let reg_id = match idx { @@ -122,7 +120,12 @@ impl crate::ArchExtras for crate::CPU { 3 => Regs::R3, 4 => Regs::R4, 5 => Regs::R5, - r => return Err(format!("Unsupported argument: {r:}")), + r => { + return Err(QemuRWError::new_argument_error( + QemuRWErrorKind::Read, + i32::from(r), + )) + } }; self.read_reg(reg_id) @@ -132,16 +135,14 @@ impl crate::ArchExtras for crate::CPU { &self, conv: CallingConvention, idx: i32, - _val: T, - ) -> Result<(), String> + val: T, + ) -> Result<(), QemuRWError> where T: Into, { - if conv != CallingConvention::Cdecl { - return Err(format!("Unsupported calling convention: {conv:#?}")); - } + QemuRWError::check_conv(QemuRWErrorKind::Write, CallingConvention::Cdecl, conv)?; // TODO - Err(format!("Unsupported argument: {idx:}")) + Err(QemuRWError::new_argument_error(QemuRWErrorKind::Write, idx)) } } diff --git a/libafl_qemu/src/arch/i386.rs b/libafl_qemu/src/arch/i386.rs index 2df88e0b75..dbc3f576e5 100644 --- a/libafl_qemu/src/arch/i386.rs +++ b/libafl_qemu/src/arch/i386.rs @@ -8,7 +8,7 @@ use pyo3::prelude::*; pub use strum_macros::EnumIter; pub use syscall_numbers::x86::*; -use crate::{sync_exit::ExitArgs, CallingConvention, GuestAddr}; +use crate::{sync_exit::ExitArgs, CallingConvention, GuestAddr, QemuRWError, QemuRWErrorKind}; #[derive(IntoPrimitive, TryFromPrimitive, Debug, Clone, Copy, EnumIter)] #[repr(i32)] @@ -67,7 +67,7 @@ pub fn capstone() -> capstone::arch::x86::ArchCapstoneBuilder { pub type GuestReg = u32; impl crate::ArchExtras for crate::CPU { - fn read_return_address(&self) -> Result + fn read_return_address(&self) -> Result where T: From, { @@ -77,7 +77,7 @@ impl crate::ArchExtras for crate::CPU { Ok(GuestReg::from_le_bytes(ret_addr).into()) } - fn write_return_address(&self, val: T) -> Result<(), String> + fn write_return_address(&self, val: T) -> Result<(), QemuRWError> where T: Into, { @@ -88,13 +88,11 @@ impl crate::ArchExtras for crate::CPU { Ok(()) } - fn read_function_argument(&self, conv: CallingConvention, idx: u8) -> Result + fn read_function_argument(&self, conv: CallingConvention, idx: u8) -> Result where T: From, { - if conv != CallingConvention::Cdecl { - return Err(format!("Unsupported calling convention: {conv:#?}")); - } + QemuRWError::check_conv(QemuRWErrorKind::Read, CallingConvention::Cdecl, conv)?; match idx { 0..=1 => { @@ -112,7 +110,12 @@ impl crate::ArchExtras for crate::CPU { } Ok(GuestReg::from_le_bytes(val).into()) } - _ => Err(format!("Unsupported argument: {idx:}")), + r => { + return Err(QemuRWError::new_argument_error( + QemuRWErrorKind::Read, + i32::from(r), + )) + } } } @@ -121,13 +124,11 @@ impl crate::ArchExtras for crate::CPU { conv: CallingConvention, idx: i32, val: T, - ) -> Result<(), String> + ) -> Result<(), QemuRWError> where T: Into, { - if conv != CallingConvention::Cdecl { - return Err(format!("Unsupported calling convention: {conv:#?}")); - } + QemuRWError::check_conv(QemuRWErrorKind::Write, CallingConvention::Cdecl, conv)?; match idx { 0..=1 => { @@ -146,7 +147,7 @@ impl crate::ArchExtras for crate::CPU { } Ok(()) } - _ => Err(format!("Unsupported argument: {idx:}")), + r => Err(QemuRWError::new_argument_error(QemuRWErrorKind::Write, r)), } } } diff --git a/libafl_qemu/src/arch/mips.rs b/libafl_qemu/src/arch/mips.rs index 17930ea213..125443ed86 100644 --- a/libafl_qemu/src/arch/mips.rs +++ b/libafl_qemu/src/arch/mips.rs @@ -7,7 +7,7 @@ use pyo3::prelude::*; pub use strum_macros::EnumIter; pub use syscall_numbers::mips::*; -use crate::{sync_exit::ExitArgs, CallingConvention}; +use crate::{sync_exit::ExitArgs, CallingConvention, QemuRWError, QemuRWErrorKind}; /// Registers for the MIPS instruction set. #[derive(IntoPrimitive, TryFromPrimitive, Debug, Clone, Copy, EnumIter)] @@ -88,27 +88,25 @@ pub fn capstone() -> capstone::arch::mips::ArchCapstoneBuilder { pub type GuestReg = u32; impl crate::ArchExtras for crate::CPU { - fn read_return_address(&self) -> Result + fn read_return_address(&self) -> Result where T: From, { self.read_reg(Regs::Ra) } - fn write_return_address(&self, val: T) -> Result<(), String> + fn write_return_address(&self, val: T) -> Result<(), QemuRWError> where T: Into, { self.write_reg(Regs::Ra, val) } - fn read_function_argument(&self, conv: CallingConvention, idx: u8) -> Result + fn read_function_argument(&self, conv: CallingConvention, idx: u8) -> Result where T: From, { - if conv != CallingConvention::Cdecl { - return Err(format!("Unsupported calling convention: {conv:#?}")); - } + QemuRWError::check_conv(QemuRWErrorKind::Read, CallingConvention::Cdecl, conv)?; let reg_id = match idx { 0 => Regs::A0, @@ -116,7 +114,12 @@ impl crate::ArchExtras for crate::CPU { 2 => Regs::A2, 3 => Regs::A3, // 4.. would be on the stack, let's not do this for now - r => return Err(format!("Unsupported argument: {r:}")), + r => { + return Err(QemuRWError::new_argument_error( + QemuRWErrorKind::Read, + i32::from(r), + )) + } }; self.read_reg(reg_id) @@ -127,19 +130,17 @@ impl crate::ArchExtras for crate::CPU { conv: CallingConvention, idx: i32, val: T, - ) -> Result<(), String> + ) -> Result<(), QemuRWError> where T: Into, { - if conv != CallingConvention::Cdecl { - return Err(format!("Unsupported calling convention: {conv:#?}")); - } + QemuRWError::check_conv(QemuRWErrorKind::Write, CallingConvention::Cdecl, conv)?; let val: GuestReg = val.into(); match idx { 0 => self.write_reg(Regs::A0, val), 1 => self.write_reg(Regs::A1, val), - _ => Err(format!("Unsupported argument: {idx:}")), + r => Err(QemuRWError::new_argument_error(QemuRWErrorKind::Write, r)), } } } diff --git a/libafl_qemu/src/arch/ppc.rs b/libafl_qemu/src/arch/ppc.rs index 39b50848fb..351f3e3ea5 100644 --- a/libafl_qemu/src/arch/ppc.rs +++ b/libafl_qemu/src/arch/ppc.rs @@ -7,7 +7,7 @@ use pyo3::prelude::*; pub use strum_macros::EnumIter; pub use syscall_numbers::powerpc::*; -use crate::{sync_exit::ExitArgs, CallingConvention}; +use crate::{sync_exit::ExitArgs, CallingConvention, QemuRWError, QemuRWErrorKind}; /// Registers for the MIPS instruction set. #[derive(IntoPrimitive, TryFromPrimitive, Debug, Clone, Copy, EnumIter)] @@ -128,27 +128,25 @@ pub fn capstone() -> capstone::arch::ppc::ArchCapstoneBuilder { pub type GuestReg = u32; impl crate::ArchExtras for crate::CPU { - fn read_return_address(&self) -> Result + fn read_return_address(&self) -> Result where T: From, { self.read_reg(Regs::Lr) } - fn write_return_address(&self, val: T) -> Result<(), String> + fn write_return_address(&self, val: T) -> Result<(), QemuRWError> where T: Into, { self.write_reg(Regs::Lr, val) } - fn read_function_argument(&self, conv: CallingConvention, idx: u8) -> Result + fn read_function_argument(&self, conv: CallingConvention, idx: u8) -> Result where T: From, { - if conv != CallingConvention::Cdecl { - return Err(format!("Unsupported calling convention: {conv:#?}")); - } + QemuRWError::check_conv(QemuRWErrorKind::Read, CallingConvention::Cdecl, conv)?; let reg_id = match idx { 0 => Regs::R3, @@ -157,7 +155,12 @@ impl crate::ArchExtras for crate::CPU { 3 => Regs::R6, 4 => Regs::R7, 5 => Regs::R8, - r => return Err(format!("Unsupported argument: {r:}")), + r => { + return Err(QemuRWError::new_argument_error( + QemuRWErrorKind::Read, + i32::from(r), + )) + } }; self.read_reg(reg_id) @@ -168,19 +171,17 @@ impl crate::ArchExtras for crate::CPU { conv: CallingConvention, idx: i32, val: T, - ) -> Result<(), String> + ) -> Result<(), QemuRWError> where T: Into, { - if conv != CallingConvention::Cdecl { - return Err(format!("Unsupported calling convention: {conv:#?}")); - } + QemuRWError::check_conv(QemuRWErrorKind::Write, CallingConvention::Cdecl, conv)?; let val: GuestReg = val.into(); match idx { 0 => self.write_reg(Regs::R3, val), 1 => self.write_reg(Regs::R4, val), - _ => Err(format!("Unsupported argument: {idx:}")), + r => Err(QemuRWError::new_argument_error(QemuRWErrorKind::Write, r)), } } } diff --git a/libafl_qemu/src/arch/x86_64.rs b/libafl_qemu/src/arch/x86_64.rs index f32dc200e6..9e9538f083 100644 --- a/libafl_qemu/src/arch/x86_64.rs +++ b/libafl_qemu/src/arch/x86_64.rs @@ -8,7 +8,7 @@ use pyo3::prelude::*; pub use strum_macros::EnumIter; pub use syscall_numbers::x86_64::*; -use crate::{sync_exit::ExitArgs, CallingConvention}; +use crate::{sync_exit::ExitArgs, CallingConvention, QemuRWError, QemuRWErrorKind}; #[derive(IntoPrimitive, TryFromPrimitive, Debug, Clone, Copy, EnumIter)] #[repr(i32)] @@ -76,7 +76,7 @@ pub fn capstone() -> capstone::arch::x86::ArchCapstoneBuilder { pub type GuestReg = u64; impl crate::ArchExtras for crate::CPU { - fn read_return_address(&self) -> Result + fn read_return_address(&self) -> Result where T: From, { @@ -86,7 +86,7 @@ impl crate::ArchExtras for crate::CPU { Ok(GuestReg::from_le_bytes(ret_addr).into()) } - fn write_return_address(&self, val: T) -> Result<(), String> + fn write_return_address(&self, val: T) -> Result<(), QemuRWError> where T: Into, { @@ -97,13 +97,11 @@ impl crate::ArchExtras for crate::CPU { Ok(()) } - fn read_function_argument(&self, conv: CallingConvention, idx: u8) -> Result + fn read_function_argument(&self, conv: CallingConvention, idx: u8) -> Result where T: From, { - if conv != CallingConvention::Cdecl { - return Err(format!("Unsupported calling convention: {conv:#?}")); - } + QemuRWError::check_conv(QemuRWErrorKind::Read, CallingConvention::Cdecl, conv)?; let reg_id = match idx { 0 => Regs::Rdi, @@ -112,7 +110,12 @@ impl crate::ArchExtras for crate::CPU { 3 => Regs::Rcx, 4 => Regs::R8, 5 => Regs::R9, - r => return Err(format!("Unsupported argument: {r:}")), + r => { + return Err(QemuRWError::new_argument_error( + QemuRWErrorKind::Read, + i32::from(r), + )) + } }; self.read_reg(reg_id) @@ -123,19 +126,17 @@ impl crate::ArchExtras for crate::CPU { conv: CallingConvention, idx: i32, val: T, - ) -> Result<(), String> + ) -> Result<(), QemuRWError> where T: Into, { - if conv != CallingConvention::Cdecl { - return Err(format!("Unsupported calling convention: {conv:#?}")); - } + QemuRWError::check_conv(QemuRWErrorKind::Write, CallingConvention::Cdecl, conv)?; let val: GuestReg = val.into(); match idx { 0 => self.write_reg(Regs::Rdi, val), 1 => self.write_reg(Regs::Rsi, val), - _ => Err(format!("Unsupported argument: {idx:}")), + r => Err(QemuRWError::new_argument_error(QemuRWErrorKind::Write, r)), } } } diff --git a/libafl_qemu/src/command/mod.rs b/libafl_qemu/src/command/mod.rs index d112756cbe..a79f1b52b1 100644 --- a/libafl_qemu/src/command/mod.rs +++ b/libafl_qemu/src/command/mod.rs @@ -28,8 +28,8 @@ use crate::{ sync_exit::ExitArgs, Emulator, EmulatorExitHandler, EmulatorMemoryChunk, ExitHandlerError, ExitHandlerResult, GuestReg, HasInstrumentationFilter, InputLocation, IsFilter, IsSnapshotManager, Qemu, - QemuHelperTuple, QemuInstrumentationAddressRangeFilter, Regs, StdEmulatorExitHandler, - StdInstrumentationFilter, CPU, + QemuHelperTuple, QemuInstrumentationAddressRangeFilter, QemuRWError, Regs, + StdEmulatorExitHandler, StdInstrumentationFilter, CPU, }; pub mod parser; @@ -217,13 +217,13 @@ pub type AddressRangeFilterCommand = FilterCommand for CommandError { - fn from(error_string: String) -> Self { - CommandError::RegError(error_string) +impl From for CommandError { + fn from(error: QemuRWError) -> Self { + CommandError::RWError(error) } } diff --git a/libafl_qemu/src/emu/mod.rs b/libafl_qemu/src/emu/mod.rs index 6a2a06ce6b..55b8978bc3 100644 --- a/libafl_qemu/src/emu/mod.rs +++ b/libafl_qemu/src/emu/mod.rs @@ -35,7 +35,7 @@ use crate::{ sys::TCGTemp, BackdoorHookId, BlockHookId, CmpHookId, EdgeHookId, EmulatorMemoryChunk, GuestReg, HookData, HookId, InstructionHookId, MemAccessInfo, Qemu, QemuExitError, QemuExitReason, QemuHelperTuple, - QemuInitError, QemuShutdownCause, QemuSnapshotCheckResult, ReadHookId, Regs, + QemuInitError, QemuRWError, QemuShutdownCause, QemuSnapshotCheckResult, ReadHookId, Regs, StdInstrumentationFilter, WriteHookId, CPU, }; @@ -570,10 +570,10 @@ where #[deprecated( note = "This function has been moved to the `Qemu` low-level structure. Please access it through `emu.qemu()`." )] - pub fn write_reg(&self, reg: R, val: T) -> Result<(), String> + pub fn write_reg(&self, reg: R, val: T) -> Result<(), QemuRWError> where T: Num + PartialOrd + Copy + Into, - R: Into, + R: Into + Clone, { self.qemu.write_reg(reg, val) } @@ -581,10 +581,10 @@ where #[deprecated( note = "This function has been moved to the `Qemu` low-level structure. Please access it through `emu.qemu()`." )] - pub fn read_reg(&self, reg: R) -> Result + pub fn read_reg(&self, reg: R) -> Result where T: Num + PartialOrd + Copy + From, - R: Into, + R: Into + Clone, { self.qemu.read_reg(reg) } diff --git a/libafl_qemu/src/qemu/mod.rs b/libafl_qemu/src/qemu/mod.rs index f33cef46b6..1a088bec15 100644 --- a/libafl_qemu/src/qemu/mod.rs +++ b/libafl_qemu/src/qemu/mod.rs @@ -111,6 +111,61 @@ pub struct QemuSnapshotCheckResult { nb_page_inconsistencies: u64, } +#[derive(Debug, Clone)] +pub enum QemuRWErrorKind { + Read, + Write, +} + +#[derive(Debug, Clone)] +pub enum QemuRWErrorCause { + WrongCallingConvention(CallingConvention, CallingConvention), // expected, given + WrongArgument(i32), + CurrentCpuNotFound, + Reg(i32), +} + +#[derive(Debug, Clone)] +#[allow(dead_code)] +pub struct QemuRWError { + kind: QemuRWErrorKind, + cause: QemuRWErrorCause, + cpu: Option, // Only makes sense when cause != CurrentCpuNotFound +} + +impl QemuRWError { + #[must_use] + pub fn new(kind: QemuRWErrorKind, cause: QemuRWErrorCause, cpu: Option) -> Self { + Self { kind, cause, cpu } + } + + #[must_use] + pub fn current_cpu_not_found(kind: QemuRWErrorKind) -> Self { + Self::new(kind, QemuRWErrorCause::CurrentCpuNotFound, None) + } + + #[must_use] + pub fn new_argument_error(kind: QemuRWErrorKind, reg_id: i32) -> Self { + Self::new(kind, QemuRWErrorCause::WrongArgument(reg_id), None) + } + + pub fn check_conv( + kind: QemuRWErrorKind, + expected_conv: CallingConvention, + given_conv: CallingConvention, + ) -> Result<(), Self> { + if expected_conv != given_conv { + return Err(Self::new( + kind, + QemuRWErrorCause::WrongCallingConvention(expected_conv, given_conv), + None, + )); + } + + Ok(()) + } +} + /// Represents a QEMU snapshot check result for which no error was detected impl Default for QemuSnapshotCheckResult { fn default() -> Self { @@ -183,7 +238,7 @@ pub struct CPU { ptr: CPUStatePtr, } -#[derive(Debug, PartialEq)] +#[derive(Debug, Clone, PartialEq)] pub enum CallingConvention { Cdecl, } @@ -283,13 +338,13 @@ impl From for MemAccessInfo { } pub trait ArchExtras { - fn read_return_address(&self) -> Result + fn read_return_address(&self) -> Result where T: From; - fn write_return_address(&self, val: T) -> Result<(), String> + fn write_return_address(&self, val: T) -> Result<(), QemuRWError> where T: Into; - fn read_function_argument(&self, conv: CallingConvention, idx: u8) -> Result + fn read_function_argument(&self, conv: CallingConvention, idx: u8) -> Result where T: From; fn write_function_argument( @@ -297,7 +352,7 @@ pub trait ArchExtras { conv: CallingConvention, idx: i32, val: T, - ) -> Result<(), String> + ) -> Result<(), QemuRWError> where T: Into; } @@ -349,37 +404,46 @@ impl CPU { unsafe { libafl_qemu_num_regs(self.ptr) } } - pub fn write_reg(&self, reg: R, val: T) -> Result<(), String> + pub fn write_reg(&self, reg: R, val: T) -> Result<(), QemuRWError> where - R: Into, + R: Into + Clone, T: Into, { - let reg = reg.into(); + let reg_id = reg.clone().into(); #[cfg(feature = "be")] let val = GuestReg::to_be(val.into()); #[cfg(not(feature = "be"))] let val = GuestReg::to_le(val.into()); - let success = unsafe { libafl_qemu_write_reg(self.ptr, reg, addr_of!(val) as *const u8) }; + let success = + unsafe { libafl_qemu_write_reg(self.ptr, reg_id, addr_of!(val) as *const u8) }; if success == 0 { - Err(format!("Failed to write to register {reg}")) + Err(QemuRWError { + kind: QemuRWErrorKind::Write, + cause: QemuRWErrorCause::Reg(reg.into()), + cpu: Some(self.ptr), + }) } else { Ok(()) } } - pub fn read_reg(&self, reg: R) -> Result + pub fn read_reg(&self, reg: R) -> Result where - R: Into, + R: Into + Clone, T: From, { unsafe { - let reg = reg.into(); + let reg_id = reg.clone().into(); let mut val = MaybeUninit::uninit(); - let success = libafl_qemu_read_reg(self.ptr, reg, val.as_mut_ptr() as *mut u8); + let success = libafl_qemu_read_reg(self.ptr, reg_id, val.as_mut_ptr() as *mut u8); if success == 0 { - Err(format!("Failed to read register {reg}")) + Err(QemuRWError { + kind: QemuRWErrorKind::Write, + cause: QemuRWErrorCause::Reg(reg.into()), + cpu: Some(self.ptr), + }) } else { #[cfg(feature = "be")] return Ok(GuestReg::from_be(val.assume_init()).into()); @@ -692,20 +756,24 @@ impl Qemu { self.current_cpu().unwrap().num_regs() } - pub fn write_reg(&self, reg: R, val: T) -> Result<(), String> + pub fn write_reg(&self, reg: R, val: T) -> Result<(), QemuRWError> where T: Num + PartialOrd + Copy + Into, - R: Into, + R: Into + Clone, { - self.current_cpu().unwrap().write_reg(reg, val) + self.current_cpu() + .ok_or(QemuRWError::current_cpu_not_found(QemuRWErrorKind::Write))? + .write_reg(reg, val) } - pub fn read_reg(&self, reg: R) -> Result + pub fn read_reg(&self, reg: R) -> Result where T: Num + PartialOrd + Copy + From, - R: Into, + R: Into + Clone, { - self.current_cpu().unwrap().read_reg(reg) + self.current_cpu() + .ok_or(QemuRWError::current_cpu_not_found(QemuRWErrorKind::Read))? + .read_reg(reg) } pub fn set_breakpoint(&self, addr: GuestAddr) { @@ -947,30 +1015,34 @@ impl Qemu { } impl ArchExtras for Qemu { - fn read_return_address(&self) -> Result + fn read_return_address(&self) -> Result where T: From, { self.current_cpu() - .ok_or("Failed to get current CPU")? + .ok_or(QemuRWError { + kind: QemuRWErrorKind::Read, + cause: QemuRWErrorCause::CurrentCpuNotFound, + cpu: None, + })? .read_return_address::() } - fn write_return_address(&self, val: T) -> Result<(), String> + fn write_return_address(&self, val: T) -> Result<(), QemuRWError> where T: Into, { self.current_cpu() - .ok_or("Failed to get current CPU")? + .ok_or(QemuRWError::current_cpu_not_found(QemuRWErrorKind::Write))? .write_return_address::(val) } - fn read_function_argument(&self, conv: CallingConvention, idx: u8) -> Result + fn read_function_argument(&self, conv: CallingConvention, idx: u8) -> Result where T: From, { self.current_cpu() - .ok_or("Failed to get current CPU")? + .ok_or(QemuRWError::current_cpu_not_found(QemuRWErrorKind::Read))? .read_function_argument::(conv, idx) } @@ -979,12 +1051,12 @@ impl ArchExtras for Qemu { conv: CallingConvention, idx: i32, val: T, - ) -> Result<(), String> + ) -> Result<(), QemuRWError> where T: Into, { self.current_cpu() - .ok_or("Failed to get current CPU")? + .ok_or(QemuRWError::current_cpu_not_found(QemuRWErrorKind::Write))? .write_function_argument::(conv, idx, val) } } @@ -1191,11 +1263,15 @@ pub mod pybind { } fn write_reg(&self, reg: i32, val: GuestUsize) -> PyResult<()> { - self.qemu.write_reg(reg, val).map_err(PyValueError::new_err) + self.qemu + .write_reg(reg, val) + .map_err(|_| PyValueError::new_err("write register error")) } fn read_reg(&self, reg: i32) -> PyResult { - self.qemu.read_reg(reg).map_err(PyValueError::new_err) + self.qemu + .read_reg(reg) + .map_err(|_| PyValueError::new_err("read register error")) } fn set_breakpoint(&self, addr: GuestAddr) {