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
This commit is contained in:
Romain Malmain 2024-05-30 15:14:17 +02:00 committed by GitHub
parent e912216a37
commit 1102ea0fe7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 231 additions and 147 deletions

View File

@ -28,7 +28,7 @@ use libafl_bolts::{
use libafl_qemu::{ use libafl_qemu::{
drcov::QemuDrCovHelper, elf::EasyElf, ArchExtras, CallingConvention, GuestAddr, GuestReg, drcov::QemuDrCovHelper, elf::EasyElf, ArchExtras, CallingConvention, GuestAddr, GuestReg,
MmapPerms, Qemu, QemuExecutor, QemuExitReason, QemuHooks, MmapPerms, Qemu, QemuExecutor, QemuExitReason, QemuHooks,
QemuInstrumentationAddressRangeFilter, QemuShutdownCause, Regs, QemuInstrumentationAddressRangeFilter, QemuRWError, QemuShutdownCause, Regs,
}; };
use rangemap::RangeMap; use rangemap::RangeMap;
@ -155,7 +155,7 @@ pub fn fuzz() {
let stack_ptr: GuestAddr = qemu.read_reg(Regs::Sp).unwrap(); 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 { unsafe {
qemu.write_mem(input_addr, buf); qemu.write_mem(input_addr, buf);
qemu.write_reg(Regs::Pc, test_one_input_ptr)?; qemu.write_reg(Regs::Pc, test_one_input_ptr)?;

View File

@ -158,7 +158,7 @@ impl<'a> Client<'a> {
let ret_addr: GuestAddr = qemu let ret_addr: GuestAddr = qemu
.read_return_address() .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}"); log::debug!("ret_addr = {ret_addr:#x}");
qemu.set_breakpoint(ret_addr); qemu.set_breakpoint(ret_addr);

View File

@ -24,15 +24,15 @@ impl<'a> Harness<'a> {
let pc: GuestReg = qemu let pc: GuestReg = qemu
.read_reg(Regs::Pc) .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 let stack_ptr: GuestAddr = qemu
.read_reg(Regs::Sp) .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 let ret_addr: GuestAddr = qemu
.read_return_address() .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 { Ok(Harness {
qemu, qemu,
@ -62,23 +62,23 @@ impl<'a> Harness<'a> {
self.qemu self.qemu
.write_reg(Regs::Pc, self.pc) .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 self.qemu
.write_reg(Regs::Sp, self.stack_ptr) .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 self.qemu
.write_return_address(self.ret_addr) .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 self.qemu
.write_function_argument(CallingConvention::Cdecl, 0, self.input_addr) .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 self.qemu
.write_function_argument(CallingConvention::Cdecl, 1, len) .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 { unsafe {
let _ = self.qemu.run(); let _ = self.qemu.run();
}; };

View File

@ -32,7 +32,8 @@ use libafl_bolts::{
use libafl_qemu::{ use libafl_qemu::{
edges::{edges_map_mut_ptr, QemuEdgeCoverageHelper, EDGES_MAP_SIZE_IN_USE, MAX_EDGES_FOUND}, edges::{edges_map_mut_ptr, QemuEdgeCoverageHelper, EDGES_MAP_SIZE_IN_USE, MAX_EDGES_FOUND},
elf::EasyElf, elf::EasyElf,
Qemu, QemuExecutor, QemuExitError, QemuExitReason, QemuHooks, QemuShutdownCause, Regs, Qemu, QemuExecutor, QemuExitError, QemuExitReason, QemuHooks, QemuRWError, QemuShutdownCause,
Regs,
}; };
use libafl_qemu_sys::GuestPhysAddr; 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 // 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()) let mut pcs = (0..qemu.num_cpus())
.map(|i| qemu.cpu_from_index(i)) .map(|i| qemu.cpu_from_index(i))
.map(|cpu| -> Result<u32, String> { cpu.read_reg(Regs::Pc) }); .map(|cpu| -> Result<u32, QemuRWError> { cpu.read_reg(Regs::Pc) });
let ret = match pcs let ret = match pcs
.find(|pc| (breakpoint..breakpoint + 5).contains(pc.as_ref().unwrap_or(&0))) .find(|pc| (breakpoint..breakpoint + 5).contains(pc.as_ref().unwrap_or(&0)))
{ {

View File

@ -8,7 +8,7 @@ use pyo3::prelude::*;
pub use strum_macros::EnumIter; pub use strum_macros::EnumIter;
pub use syscall_numbers::aarch64::*; 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)] #[derive(IntoPrimitive, TryFromPrimitive, Debug, Clone, Copy, EnumIter)]
#[repr(i32)] #[repr(i32)]
@ -91,27 +91,25 @@ pub fn capstone() -> capstone::arch::arm64::ArchCapstoneBuilder {
pub type GuestReg = u64; pub type GuestReg = u64;
impl crate::ArchExtras for crate::CPU { impl crate::ArchExtras for crate::CPU {
fn read_return_address<T>(&self) -> Result<T, String> fn read_return_address<T>(&self) -> Result<T, QemuRWError>
where where
T: From<GuestReg>, T: From<GuestReg>,
{ {
self.read_reg(Regs::Lr) self.read_reg(Regs::Lr)
} }
fn write_return_address<T>(&self, val: T) -> Result<(), String> fn write_return_address<T>(&self, val: T) -> Result<(), QemuRWError>
where where
T: Into<GuestReg>, T: Into<GuestReg>,
{ {
self.write_reg(Regs::Lr, val) self.write_reg(Regs::Lr, val)
} }
fn read_function_argument<T>(&self, conv: CallingConvention, idx: u8) -> Result<T, String> fn read_function_argument<T>(&self, conv: CallingConvention, idx: u8) -> Result<T, QemuRWError>
where where
T: From<GuestReg>, T: From<GuestReg>,
{ {
if conv != CallingConvention::Cdecl { QemuRWError::check_conv(QemuRWErrorKind::Read, CallingConvention::Cdecl, conv)?;
return Err(format!("Unsupported calling convention: {conv:#?}"));
}
let reg_id = match idx { let reg_id = match idx {
0 => Regs::X0, 0 => Regs::X0,
@ -120,7 +118,12 @@ impl crate::ArchExtras for crate::CPU {
3 => Regs::X3, 3 => Regs::X3,
4 => Regs::X4, 4 => Regs::X4,
5 => Regs::X5, 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) self.read_reg(reg_id)
@ -131,19 +134,17 @@ impl crate::ArchExtras for crate::CPU {
conv: CallingConvention, conv: CallingConvention,
idx: i32, idx: i32,
val: T, val: T,
) -> Result<(), String> ) -> Result<(), QemuRWError>
where where
T: Into<GuestReg>, T: Into<GuestReg>,
{ {
if conv != CallingConvention::Cdecl { QemuRWError::check_conv(QemuRWErrorKind::Write, CallingConvention::Cdecl, conv)?;
return Err(format!("Unsupported calling convention: {conv:#?}"));
}
let val: GuestReg = val.into(); let val: GuestReg = val.into();
match idx { match idx {
0 => self.write_reg(Regs::X0, val), 0 => self.write_reg(Regs::X0, val),
1 => self.write_reg(Regs::X1, val), 1 => self.write_reg(Regs::X1, val),
_ => Err(format!("Unsupported argument: {idx:}")), r => Err(QemuRWError::new_argument_error(QemuRWErrorKind::Write, r)),
} }
} }
} }

View File

@ -8,7 +8,7 @@ use pyo3::prelude::*;
pub use strum_macros::EnumIter; pub use strum_macros::EnumIter;
pub use syscall_numbers::arm::*; 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. /// Registers for the ARM instruction set.
#[derive(IntoPrimitive, TryFromPrimitive, Debug, Clone, Copy, EnumIter)] #[derive(IntoPrimitive, TryFromPrimitive, Debug, Clone, Copy, EnumIter)]
@ -88,27 +88,25 @@ pub fn capstone_thumb() -> capstone::arch::arm::ArchCapstoneBuilder {
pub type GuestReg = u32; pub type GuestReg = u32;
impl crate::ArchExtras for crate::CPU { impl crate::ArchExtras for crate::CPU {
fn read_return_address<T>(&self) -> Result<T, String> fn read_return_address<T>(&self) -> Result<T, QemuRWError>
where where
T: From<GuestReg>, T: From<GuestReg>,
{ {
self.read_reg(Regs::Lr) self.read_reg(Regs::Lr)
} }
fn write_return_address<T>(&self, val: T) -> Result<(), String> fn write_return_address<T>(&self, val: T) -> Result<(), QemuRWError>
where where
T: Into<GuestReg>, T: Into<GuestReg>,
{ {
self.write_reg(Regs::Lr, val) self.write_reg(Regs::Lr, val)
} }
fn read_function_argument<T>(&self, conv: CallingConvention, idx: u8) -> Result<T, String> fn read_function_argument<T>(&self, conv: CallingConvention, idx: u8) -> Result<T, QemuRWError>
where where
T: From<GuestReg>, T: From<GuestReg>,
{ {
if conv != CallingConvention::Cdecl { QemuRWError::check_conv(QemuRWErrorKind::Read, CallingConvention::Cdecl, conv)?;
return Err(format!("Unsupported calling convention: {conv:#?}"));
}
let reg_id = match idx { let reg_id = match idx {
0 => Regs::R0, 0 => Regs::R0,
@ -116,7 +114,12 @@ impl crate::ArchExtras for crate::CPU {
2 => Regs::R2, 2 => Regs::R2,
3 => Regs::R3, 3 => Regs::R3,
// 4.. would be on the stack, let's not do this for now // 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) self.read_reg(reg_id)
@ -127,19 +130,17 @@ impl crate::ArchExtras for crate::CPU {
conv: CallingConvention, conv: CallingConvention,
idx: i32, idx: i32,
val: T, val: T,
) -> Result<(), String> ) -> Result<(), QemuRWError>
where where
T: Into<GuestReg>, T: Into<GuestReg>,
{ {
if conv != CallingConvention::Cdecl { QemuRWError::check_conv(QemuRWErrorKind::Write, CallingConvention::Cdecl, conv)?;
return Err(format!("Unsupported calling convention: {conv:#?}"));
}
let val: GuestReg = val.into(); let val: GuestReg = val.into();
match idx { match idx {
0 => self.write_reg(Regs::R0, val), 0 => self.write_reg(Regs::R0, val),
1 => self.write_reg(Regs::R1, val), 1 => self.write_reg(Regs::R1, val),
_ => Err(format!("Unsupported argument: {idx:}")), r => Err(QemuRWError::new_argument_error(QemuRWErrorKind::Write, r)),
} }
} }
} }

View File

@ -6,7 +6,7 @@ use num_enum::{IntoPrimitive, TryFromPrimitive};
use pyo3::prelude::*; use pyo3::prelude::*;
pub use strum_macros::EnumIter; 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)] #[derive(IntoPrimitive, TryFromPrimitive, Debug, Clone, Copy, EnumIter)]
#[repr(i32)] #[repr(i32)]
@ -92,27 +92,25 @@ impl Regs {
pub type GuestReg = u32; pub type GuestReg = u32;
impl crate::ArchExtras for crate::CPU { impl crate::ArchExtras for crate::CPU {
fn read_return_address<T>(&self) -> Result<T, String> fn read_return_address<T>(&self) -> Result<T, QemuRWError>
where where
T: From<GuestReg>, T: From<GuestReg>,
{ {
self.read_reg(Regs::Lr) self.read_reg(Regs::Lr)
} }
fn write_return_address<T>(&self, val: T) -> Result<(), String> fn write_return_address<T>(&self, val: T) -> Result<(), QemuRWError>
where where
T: Into<GuestReg>, T: Into<GuestReg>,
{ {
self.write_reg(Regs::Lr, val) self.write_reg(Regs::Lr, val)
} }
fn read_function_argument<T>(&self, conv: CallingConvention, idx: u8) -> Result<T, String> fn read_function_argument<T>(&self, conv: CallingConvention, idx: u8) -> Result<T, QemuRWError>
where where
T: From<GuestReg>, T: From<GuestReg>,
{ {
if conv != CallingConvention::Cdecl { QemuRWError::check_conv(QemuRWErrorKind::Read, CallingConvention::Cdecl, conv)?;
return Err(format!("Unsupported calling convention: {conv:#?}"));
}
// Note that 64 bit values may be passed in two registers (and may have padding), then this mapping is off. // 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 { let reg_id = match idx {
@ -122,7 +120,12 @@ impl crate::ArchExtras for crate::CPU {
3 => Regs::R3, 3 => Regs::R3,
4 => Regs::R4, 4 => Regs::R4,
5 => Regs::R5, 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) self.read_reg(reg_id)
@ -132,16 +135,14 @@ impl crate::ArchExtras for crate::CPU {
&self, &self,
conv: CallingConvention, conv: CallingConvention,
idx: i32, idx: i32,
_val: T, val: T,
) -> Result<(), String> ) -> Result<(), QemuRWError>
where where
T: Into<GuestReg>, T: Into<GuestReg>,
{ {
if conv != CallingConvention::Cdecl { QemuRWError::check_conv(QemuRWErrorKind::Write, CallingConvention::Cdecl, conv)?;
return Err(format!("Unsupported calling convention: {conv:#?}"));
}
// TODO // TODO
Err(format!("Unsupported argument: {idx:}")) Err(QemuRWError::new_argument_error(QemuRWErrorKind::Write, idx))
} }
} }

View File

@ -8,7 +8,7 @@ use pyo3::prelude::*;
pub use strum_macros::EnumIter; pub use strum_macros::EnumIter;
pub use syscall_numbers::x86::*; 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)] #[derive(IntoPrimitive, TryFromPrimitive, Debug, Clone, Copy, EnumIter)]
#[repr(i32)] #[repr(i32)]
@ -67,7 +67,7 @@ pub fn capstone() -> capstone::arch::x86::ArchCapstoneBuilder {
pub type GuestReg = u32; pub type GuestReg = u32;
impl crate::ArchExtras for crate::CPU { impl crate::ArchExtras for crate::CPU {
fn read_return_address<T>(&self) -> Result<T, String> fn read_return_address<T>(&self) -> Result<T, QemuRWError>
where where
T: From<GuestReg>, T: From<GuestReg>,
{ {
@ -77,7 +77,7 @@ impl crate::ArchExtras for crate::CPU {
Ok(GuestReg::from_le_bytes(ret_addr).into()) Ok(GuestReg::from_le_bytes(ret_addr).into())
} }
fn write_return_address<T>(&self, val: T) -> Result<(), String> fn write_return_address<T>(&self, val: T) -> Result<(), QemuRWError>
where where
T: Into<GuestReg>, T: Into<GuestReg>,
{ {
@ -88,13 +88,11 @@ impl crate::ArchExtras for crate::CPU {
Ok(()) Ok(())
} }
fn read_function_argument<T>(&self, conv: CallingConvention, idx: u8) -> Result<T, String> fn read_function_argument<T>(&self, conv: CallingConvention, idx: u8) -> Result<T, QemuRWError>
where where
T: From<GuestReg>, T: From<GuestReg>,
{ {
if conv != CallingConvention::Cdecl { QemuRWError::check_conv(QemuRWErrorKind::Read, CallingConvention::Cdecl, conv)?;
return Err(format!("Unsupported calling convention: {conv:#?}"));
}
match idx { match idx {
0..=1 => { 0..=1 => {
@ -112,7 +110,12 @@ impl crate::ArchExtras for crate::CPU {
} }
Ok(GuestReg::from_le_bytes(val).into()) 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, conv: CallingConvention,
idx: i32, idx: i32,
val: T, val: T,
) -> Result<(), String> ) -> Result<(), QemuRWError>
where where
T: Into<GuestReg>, T: Into<GuestReg>,
{ {
if conv != CallingConvention::Cdecl { QemuRWError::check_conv(QemuRWErrorKind::Write, CallingConvention::Cdecl, conv)?;
return Err(format!("Unsupported calling convention: {conv:#?}"));
}
match idx { match idx {
0..=1 => { 0..=1 => {
@ -146,7 +147,7 @@ impl crate::ArchExtras for crate::CPU {
} }
Ok(()) Ok(())
} }
_ => Err(format!("Unsupported argument: {idx:}")), r => Err(QemuRWError::new_argument_error(QemuRWErrorKind::Write, r)),
} }
} }
} }

View File

@ -7,7 +7,7 @@ use pyo3::prelude::*;
pub use strum_macros::EnumIter; pub use strum_macros::EnumIter;
pub use syscall_numbers::mips::*; 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. /// Registers for the MIPS instruction set.
#[derive(IntoPrimitive, TryFromPrimitive, Debug, Clone, Copy, EnumIter)] #[derive(IntoPrimitive, TryFromPrimitive, Debug, Clone, Copy, EnumIter)]
@ -88,27 +88,25 @@ pub fn capstone() -> capstone::arch::mips::ArchCapstoneBuilder {
pub type GuestReg = u32; pub type GuestReg = u32;
impl crate::ArchExtras for crate::CPU { impl crate::ArchExtras for crate::CPU {
fn read_return_address<T>(&self) -> Result<T, String> fn read_return_address<T>(&self) -> Result<T, QemuRWError>
where where
T: From<GuestReg>, T: From<GuestReg>,
{ {
self.read_reg(Regs::Ra) self.read_reg(Regs::Ra)
} }
fn write_return_address<T>(&self, val: T) -> Result<(), String> fn write_return_address<T>(&self, val: T) -> Result<(), QemuRWError>
where where
T: Into<GuestReg>, T: Into<GuestReg>,
{ {
self.write_reg(Regs::Ra, val) self.write_reg(Regs::Ra, val)
} }
fn read_function_argument<T>(&self, conv: CallingConvention, idx: u8) -> Result<T, String> fn read_function_argument<T>(&self, conv: CallingConvention, idx: u8) -> Result<T, QemuRWError>
where where
T: From<GuestReg>, T: From<GuestReg>,
{ {
if conv != CallingConvention::Cdecl { QemuRWError::check_conv(QemuRWErrorKind::Read, CallingConvention::Cdecl, conv)?;
return Err(format!("Unsupported calling convention: {conv:#?}"));
}
let reg_id = match idx { let reg_id = match idx {
0 => Regs::A0, 0 => Regs::A0,
@ -116,7 +114,12 @@ impl crate::ArchExtras for crate::CPU {
2 => Regs::A2, 2 => Regs::A2,
3 => Regs::A3, 3 => Regs::A3,
// 4.. would be on the stack, let's not do this for now // 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) self.read_reg(reg_id)
@ -127,19 +130,17 @@ impl crate::ArchExtras for crate::CPU {
conv: CallingConvention, conv: CallingConvention,
idx: i32, idx: i32,
val: T, val: T,
) -> Result<(), String> ) -> Result<(), QemuRWError>
where where
T: Into<GuestReg>, T: Into<GuestReg>,
{ {
if conv != CallingConvention::Cdecl { QemuRWError::check_conv(QemuRWErrorKind::Write, CallingConvention::Cdecl, conv)?;
return Err(format!("Unsupported calling convention: {conv:#?}"));
}
let val: GuestReg = val.into(); let val: GuestReg = val.into();
match idx { match idx {
0 => self.write_reg(Regs::A0, val), 0 => self.write_reg(Regs::A0, val),
1 => self.write_reg(Regs::A1, val), 1 => self.write_reg(Regs::A1, val),
_ => Err(format!("Unsupported argument: {idx:}")), r => Err(QemuRWError::new_argument_error(QemuRWErrorKind::Write, r)),
} }
} }
} }

View File

@ -7,7 +7,7 @@ use pyo3::prelude::*;
pub use strum_macros::EnumIter; pub use strum_macros::EnumIter;
pub use syscall_numbers::powerpc::*; 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. /// Registers for the MIPS instruction set.
#[derive(IntoPrimitive, TryFromPrimitive, Debug, Clone, Copy, EnumIter)] #[derive(IntoPrimitive, TryFromPrimitive, Debug, Clone, Copy, EnumIter)]
@ -128,27 +128,25 @@ pub fn capstone() -> capstone::arch::ppc::ArchCapstoneBuilder {
pub type GuestReg = u32; pub type GuestReg = u32;
impl crate::ArchExtras for crate::CPU { impl crate::ArchExtras for crate::CPU {
fn read_return_address<T>(&self) -> Result<T, String> fn read_return_address<T>(&self) -> Result<T, QemuRWError>
where where
T: From<GuestReg>, T: From<GuestReg>,
{ {
self.read_reg(Regs::Lr) self.read_reg(Regs::Lr)
} }
fn write_return_address<T>(&self, val: T) -> Result<(), String> fn write_return_address<T>(&self, val: T) -> Result<(), QemuRWError>
where where
T: Into<GuestReg>, T: Into<GuestReg>,
{ {
self.write_reg(Regs::Lr, val) self.write_reg(Regs::Lr, val)
} }
fn read_function_argument<T>(&self, conv: CallingConvention, idx: u8) -> Result<T, String> fn read_function_argument<T>(&self, conv: CallingConvention, idx: u8) -> Result<T, QemuRWError>
where where
T: From<GuestReg>, T: From<GuestReg>,
{ {
if conv != CallingConvention::Cdecl { QemuRWError::check_conv(QemuRWErrorKind::Read, CallingConvention::Cdecl, conv)?;
return Err(format!("Unsupported calling convention: {conv:#?}"));
}
let reg_id = match idx { let reg_id = match idx {
0 => Regs::R3, 0 => Regs::R3,
@ -157,7 +155,12 @@ impl crate::ArchExtras for crate::CPU {
3 => Regs::R6, 3 => Regs::R6,
4 => Regs::R7, 4 => Regs::R7,
5 => Regs::R8, 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) self.read_reg(reg_id)
@ -168,19 +171,17 @@ impl crate::ArchExtras for crate::CPU {
conv: CallingConvention, conv: CallingConvention,
idx: i32, idx: i32,
val: T, val: T,
) -> Result<(), String> ) -> Result<(), QemuRWError>
where where
T: Into<GuestReg>, T: Into<GuestReg>,
{ {
if conv != CallingConvention::Cdecl { QemuRWError::check_conv(QemuRWErrorKind::Write, CallingConvention::Cdecl, conv)?;
return Err(format!("Unsupported calling convention: {conv:#?}"));
}
let val: GuestReg = val.into(); let val: GuestReg = val.into();
match idx { match idx {
0 => self.write_reg(Regs::R3, val), 0 => self.write_reg(Regs::R3, val),
1 => self.write_reg(Regs::R4, val), 1 => self.write_reg(Regs::R4, val),
_ => Err(format!("Unsupported argument: {idx:}")), r => Err(QemuRWError::new_argument_error(QemuRWErrorKind::Write, r)),
} }
} }
} }

View File

@ -8,7 +8,7 @@ use pyo3::prelude::*;
pub use strum_macros::EnumIter; pub use strum_macros::EnumIter;
pub use syscall_numbers::x86_64::*; 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)] #[derive(IntoPrimitive, TryFromPrimitive, Debug, Clone, Copy, EnumIter)]
#[repr(i32)] #[repr(i32)]
@ -76,7 +76,7 @@ pub fn capstone() -> capstone::arch::x86::ArchCapstoneBuilder {
pub type GuestReg = u64; pub type GuestReg = u64;
impl crate::ArchExtras for crate::CPU { impl crate::ArchExtras for crate::CPU {
fn read_return_address<T>(&self) -> Result<T, String> fn read_return_address<T>(&self) -> Result<T, QemuRWError>
where where
T: From<GuestReg>, T: From<GuestReg>,
{ {
@ -86,7 +86,7 @@ impl crate::ArchExtras for crate::CPU {
Ok(GuestReg::from_le_bytes(ret_addr).into()) Ok(GuestReg::from_le_bytes(ret_addr).into())
} }
fn write_return_address<T>(&self, val: T) -> Result<(), String> fn write_return_address<T>(&self, val: T) -> Result<(), QemuRWError>
where where
T: Into<GuestReg>, T: Into<GuestReg>,
{ {
@ -97,13 +97,11 @@ impl crate::ArchExtras for crate::CPU {
Ok(()) Ok(())
} }
fn read_function_argument<T>(&self, conv: CallingConvention, idx: u8) -> Result<T, String> fn read_function_argument<T>(&self, conv: CallingConvention, idx: u8) -> Result<T, QemuRWError>
where where
T: From<GuestReg>, T: From<GuestReg>,
{ {
if conv != CallingConvention::Cdecl { QemuRWError::check_conv(QemuRWErrorKind::Read, CallingConvention::Cdecl, conv)?;
return Err(format!("Unsupported calling convention: {conv:#?}"));
}
let reg_id = match idx { let reg_id = match idx {
0 => Regs::Rdi, 0 => Regs::Rdi,
@ -112,7 +110,12 @@ impl crate::ArchExtras for crate::CPU {
3 => Regs::Rcx, 3 => Regs::Rcx,
4 => Regs::R8, 4 => Regs::R8,
5 => Regs::R9, 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) self.read_reg(reg_id)
@ -123,19 +126,17 @@ impl crate::ArchExtras for crate::CPU {
conv: CallingConvention, conv: CallingConvention,
idx: i32, idx: i32,
val: T, val: T,
) -> Result<(), String> ) -> Result<(), QemuRWError>
where where
T: Into<GuestReg>, T: Into<GuestReg>,
{ {
if conv != CallingConvention::Cdecl { QemuRWError::check_conv(QemuRWErrorKind::Write, CallingConvention::Cdecl, conv)?;
return Err(format!("Unsupported calling convention: {conv:#?}"));
}
let val: GuestReg = val.into(); let val: GuestReg = val.into();
match idx { match idx {
0 => self.write_reg(Regs::Rdi, val), 0 => self.write_reg(Regs::Rdi, val),
1 => self.write_reg(Regs::Rsi, val), 1 => self.write_reg(Regs::Rsi, val),
_ => Err(format!("Unsupported argument: {idx:}")), r => Err(QemuRWError::new_argument_error(QemuRWErrorKind::Write, r)),
} }
} }
} }

View File

@ -28,8 +28,8 @@ use crate::{
sync_exit::ExitArgs, sync_exit::ExitArgs,
Emulator, EmulatorExitHandler, EmulatorMemoryChunk, ExitHandlerError, ExitHandlerResult, Emulator, EmulatorExitHandler, EmulatorMemoryChunk, ExitHandlerError, ExitHandlerResult,
GuestReg, HasInstrumentationFilter, InputLocation, IsFilter, IsSnapshotManager, Qemu, GuestReg, HasInstrumentationFilter, InputLocation, IsFilter, IsSnapshotManager, Qemu,
QemuHelperTuple, QemuInstrumentationAddressRangeFilter, Regs, StdEmulatorExitHandler, QemuHelperTuple, QemuInstrumentationAddressRangeFilter, QemuRWError, Regs,
StdInstrumentationFilter, CPU, StdEmulatorExitHandler, StdInstrumentationFilter, CPU,
}; };
pub mod parser; pub mod parser;
@ -217,13 +217,13 @@ pub type AddressRangeFilterCommand = FilterCommand<QemuInstrumentationAddressRan
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub enum CommandError { pub enum CommandError {
UnknownCommand(GuestReg), UnknownCommand(GuestReg),
RegError(String), RWError(QemuRWError),
VersionDifference(u64), VersionDifference(u64),
} }
impl From<String> for CommandError { impl From<QemuRWError> for CommandError {
fn from(error_string: String) -> Self { fn from(error: QemuRWError) -> Self {
CommandError::RegError(error_string) CommandError::RWError(error)
} }
} }

View File

@ -35,7 +35,7 @@ use crate::{
sys::TCGTemp, sys::TCGTemp,
BackdoorHookId, BlockHookId, CmpHookId, EdgeHookId, EmulatorMemoryChunk, GuestReg, HookData, BackdoorHookId, BlockHookId, CmpHookId, EdgeHookId, EmulatorMemoryChunk, GuestReg, HookData,
HookId, InstructionHookId, MemAccessInfo, Qemu, QemuExitError, QemuExitReason, QemuHelperTuple, HookId, InstructionHookId, MemAccessInfo, Qemu, QemuExitError, QemuExitReason, QemuHelperTuple,
QemuInitError, QemuShutdownCause, QemuSnapshotCheckResult, ReadHookId, Regs, QemuInitError, QemuRWError, QemuShutdownCause, QemuSnapshotCheckResult, ReadHookId, Regs,
StdInstrumentationFilter, WriteHookId, CPU, StdInstrumentationFilter, WriteHookId, CPU,
}; };
@ -570,10 +570,10 @@ where
#[deprecated( #[deprecated(
note = "This function has been moved to the `Qemu` low-level structure. Please access it through `emu.qemu()`." note = "This function has been moved to the `Qemu` low-level structure. Please access it through `emu.qemu()`."
)] )]
pub fn write_reg<R, T>(&self, reg: R, val: T) -> Result<(), String> pub fn write_reg<R, T>(&self, reg: R, val: T) -> Result<(), QemuRWError>
where where
T: Num + PartialOrd + Copy + Into<GuestReg>, T: Num + PartialOrd + Copy + Into<GuestReg>,
R: Into<i32>, R: Into<i32> + Clone,
{ {
self.qemu.write_reg(reg, val) self.qemu.write_reg(reg, val)
} }
@ -581,10 +581,10 @@ where
#[deprecated( #[deprecated(
note = "This function has been moved to the `Qemu` low-level structure. Please access it through `emu.qemu()`." note = "This function has been moved to the `Qemu` low-level structure. Please access it through `emu.qemu()`."
)] )]
pub fn read_reg<R, T>(&self, reg: R) -> Result<T, String> pub fn read_reg<R, T>(&self, reg: R) -> Result<T, QemuRWError>
where where
T: Num + PartialOrd + Copy + From<GuestReg>, T: Num + PartialOrd + Copy + From<GuestReg>,
R: Into<i32>, R: Into<i32> + Clone,
{ {
self.qemu.read_reg(reg) self.qemu.read_reg(reg)
} }

View File

@ -111,6 +111,61 @@ pub struct QemuSnapshotCheckResult {
nb_page_inconsistencies: u64, 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<CPUStatePtr>, // Only makes sense when cause != CurrentCpuNotFound
}
impl QemuRWError {
#[must_use]
pub fn new(kind: QemuRWErrorKind, cause: QemuRWErrorCause, cpu: Option<CPUStatePtr>) -> 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 /// Represents a QEMU snapshot check result for which no error was detected
impl Default for QemuSnapshotCheckResult { impl Default for QemuSnapshotCheckResult {
fn default() -> Self { fn default() -> Self {
@ -183,7 +238,7 @@ pub struct CPU {
ptr: CPUStatePtr, ptr: CPUStatePtr,
} }
#[derive(Debug, PartialEq)] #[derive(Debug, Clone, PartialEq)]
pub enum CallingConvention { pub enum CallingConvention {
Cdecl, Cdecl,
} }
@ -283,13 +338,13 @@ impl From<libafl_qemu_sys::MemOpIdx> for MemAccessInfo {
} }
pub trait ArchExtras { pub trait ArchExtras {
fn read_return_address<T>(&self) -> Result<T, String> fn read_return_address<T>(&self) -> Result<T, QemuRWError>
where where
T: From<GuestReg>; T: From<GuestReg>;
fn write_return_address<T>(&self, val: T) -> Result<(), String> fn write_return_address<T>(&self, val: T) -> Result<(), QemuRWError>
where where
T: Into<GuestReg>; T: Into<GuestReg>;
fn read_function_argument<T>(&self, conv: CallingConvention, idx: u8) -> Result<T, String> fn read_function_argument<T>(&self, conv: CallingConvention, idx: u8) -> Result<T, QemuRWError>
where where
T: From<GuestReg>; T: From<GuestReg>;
fn write_function_argument<T>( fn write_function_argument<T>(
@ -297,7 +352,7 @@ pub trait ArchExtras {
conv: CallingConvention, conv: CallingConvention,
idx: i32, idx: i32,
val: T, val: T,
) -> Result<(), String> ) -> Result<(), QemuRWError>
where where
T: Into<GuestReg>; T: Into<GuestReg>;
} }
@ -349,37 +404,46 @@ impl CPU {
unsafe { libafl_qemu_num_regs(self.ptr) } unsafe { libafl_qemu_num_regs(self.ptr) }
} }
pub fn write_reg<R, T>(&self, reg: R, val: T) -> Result<(), String> pub fn write_reg<R, T>(&self, reg: R, val: T) -> Result<(), QemuRWError>
where where
R: Into<i32>, R: Into<i32> + Clone,
T: Into<GuestReg>, T: Into<GuestReg>,
{ {
let reg = reg.into(); let reg_id = reg.clone().into();
#[cfg(feature = "be")] #[cfg(feature = "be")]
let val = GuestReg::to_be(val.into()); let val = GuestReg::to_be(val.into());
#[cfg(not(feature = "be"))] #[cfg(not(feature = "be"))]
let val = GuestReg::to_le(val.into()); 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 { 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 { } else {
Ok(()) Ok(())
} }
} }
pub fn read_reg<R, T>(&self, reg: R) -> Result<T, String> pub fn read_reg<R, T>(&self, reg: R) -> Result<T, QemuRWError>
where where
R: Into<i32>, R: Into<i32> + Clone,
T: From<GuestReg>, T: From<GuestReg>,
{ {
unsafe { unsafe {
let reg = reg.into(); let reg_id = reg.clone().into();
let mut val = MaybeUninit::uninit(); 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 { 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 { } else {
#[cfg(feature = "be")] #[cfg(feature = "be")]
return Ok(GuestReg::from_be(val.assume_init()).into()); return Ok(GuestReg::from_be(val.assume_init()).into());
@ -692,20 +756,24 @@ impl Qemu {
self.current_cpu().unwrap().num_regs() self.current_cpu().unwrap().num_regs()
} }
pub fn write_reg<R, T>(&self, reg: R, val: T) -> Result<(), String> pub fn write_reg<R, T>(&self, reg: R, val: T) -> Result<(), QemuRWError>
where where
T: Num + PartialOrd + Copy + Into<GuestReg>, T: Num + PartialOrd + Copy + Into<GuestReg>,
R: Into<i32>, R: Into<i32> + 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<R, T>(&self, reg: R) -> Result<T, String> pub fn read_reg<R, T>(&self, reg: R) -> Result<T, QemuRWError>
where where
T: Num + PartialOrd + Copy + From<GuestReg>, T: Num + PartialOrd + Copy + From<GuestReg>,
R: Into<i32>, R: Into<i32> + 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) { pub fn set_breakpoint(&self, addr: GuestAddr) {
@ -947,30 +1015,34 @@ impl Qemu {
} }
impl ArchExtras for Qemu { impl ArchExtras for Qemu {
fn read_return_address<T>(&self) -> Result<T, String> fn read_return_address<T>(&self) -> Result<T, QemuRWError>
where where
T: From<GuestReg>, T: From<GuestReg>,
{ {
self.current_cpu() self.current_cpu()
.ok_or("Failed to get current CPU")? .ok_or(QemuRWError {
kind: QemuRWErrorKind::Read,
cause: QemuRWErrorCause::CurrentCpuNotFound,
cpu: None,
})?
.read_return_address::<T>() .read_return_address::<T>()
} }
fn write_return_address<T>(&self, val: T) -> Result<(), String> fn write_return_address<T>(&self, val: T) -> Result<(), QemuRWError>
where where
T: Into<GuestReg>, T: Into<GuestReg>,
{ {
self.current_cpu() self.current_cpu()
.ok_or("Failed to get current CPU")? .ok_or(QemuRWError::current_cpu_not_found(QemuRWErrorKind::Write))?
.write_return_address::<T>(val) .write_return_address::<T>(val)
} }
fn read_function_argument<T>(&self, conv: CallingConvention, idx: u8) -> Result<T, String> fn read_function_argument<T>(&self, conv: CallingConvention, idx: u8) -> Result<T, QemuRWError>
where where
T: From<GuestReg>, T: From<GuestReg>,
{ {
self.current_cpu() self.current_cpu()
.ok_or("Failed to get current CPU")? .ok_or(QemuRWError::current_cpu_not_found(QemuRWErrorKind::Read))?
.read_function_argument::<T>(conv, idx) .read_function_argument::<T>(conv, idx)
} }
@ -979,12 +1051,12 @@ impl ArchExtras for Qemu {
conv: CallingConvention, conv: CallingConvention,
idx: i32, idx: i32,
val: T, val: T,
) -> Result<(), String> ) -> Result<(), QemuRWError>
where where
T: Into<GuestReg>, T: Into<GuestReg>,
{ {
self.current_cpu() self.current_cpu()
.ok_or("Failed to get current CPU")? .ok_or(QemuRWError::current_cpu_not_found(QemuRWErrorKind::Write))?
.write_function_argument::<T>(conv, idx, val) .write_function_argument::<T>(conv, idx, val)
} }
} }
@ -1191,11 +1263,15 @@ pub mod pybind {
} }
fn write_reg(&self, reg: i32, val: GuestUsize) -> PyResult<()> { 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<GuestUsize> { fn read_reg(&self, reg: i32) -> PyResult<GuestUsize> {
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) { fn set_breakpoint(&self, addr: GuestAddr) {