Improved calling convention support for x86, x86_64, arm and aarch64 for libafl-qemu (#3013)

* Improved calling convention support for x86, x86_64, arm and aarch64

* fix

* fix write_function_arguments

* fix argument_error

* follow clippy advice

* last change?

* fix guestaddr issue

* add code block for cargo-fmt

* default to default convention

* fix mistake

* add pub

* fix

* changes after review

* last change?

---------

Co-authored-by: celian <cglenaz>
This commit is contained in:
Celian G. 2025-02-21 18:04:45 +01:00 committed by GitHub
parent 7c83be2408
commit 40c0a8c57c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
15 changed files with 369 additions and 187 deletions

View File

@ -30,9 +30,8 @@ use libafl_bolts::{
#[cfg(feature = "fork")]
use libafl_qemu::QemuForkExecutor;
use libafl_qemu::{
elf::EasyElf, modules::edges::StdEdgeCoverageChildModule, ArchExtras, CallingConvention,
Emulator, GuestAddr, GuestReg, MmapPerms, QemuExitError, QemuExitReason, QemuShutdownCause,
Regs,
elf::EasyElf, modules::edges::StdEdgeCoverageChildModule, ArchExtras, Emulator, GuestAddr,
GuestReg, MmapPerms, QemuExitError, QemuExitReason, QemuShutdownCause, Regs,
};
#[cfg(feature = "snapshot")]
use libafl_qemu::{modules::SnapshotModule, QemuExecutor};
@ -232,10 +231,8 @@ pub fn fuzz() -> Result<(), Error> {
qemu.write_reg(Regs::Pc, test_one_input_ptr).unwrap();
qemu.write_reg(Regs::Sp, stack_ptr).unwrap();
qemu.write_return_address(ret_addr).unwrap();
qemu.write_function_argument(CallingConvention::Cdecl, 0, input_addr)
.unwrap();
qemu.write_function_argument(CallingConvention::Cdecl, 1, len)
.unwrap();
qemu.write_function_argument(0, input_addr).unwrap();
qemu.write_function_argument(1, len).unwrap();
match qemu.run() {
Ok(QemuExitReason::Breakpoint(_)) => {}
@ -268,10 +265,8 @@ pub fn fuzz() -> Result<(), Error> {
qemu.write_reg(Regs::Pc, test_one_input_ptr).unwrap();
qemu.write_reg(Regs::Sp, stack_ptr).unwrap();
qemu.write_return_address(ret_addr).unwrap();
qemu.write_function_argument(CallingConvention::Cdecl, 0, input_addr)
.unwrap();
qemu.write_function_argument(CallingConvention::Cdecl, 1, len)
.unwrap();
qemu.write_function_argument(0, input_addr).unwrap();
qemu.write_function_argument(1, len).unwrap();
match qemu.run() {
Ok(QemuExitReason::Breakpoint(_)) => {}

View File

@ -29,8 +29,8 @@ use libafl_bolts::{
use libafl_qemu::{
elf::EasyElf,
modules::{drcov::DrCovModule, SnapshotModule},
ArchExtras, CallingConvention, Emulator, GuestAddr, GuestReg, MmapPerms, Qemu, QemuExecutor,
QemuExitReason, QemuMappingsViewer, QemuRWError, QemuShutdownCause, Regs,
ArchExtras, Emulator, GuestAddr, GuestReg, MmapPerms, Qemu, QemuExecutor, QemuExitReason,
QemuMappingsViewer, QemuRWError, QemuShutdownCause, Regs,
};
#[derive(Default)]
@ -179,8 +179,8 @@ pub fn fuzz() {
qemu.write_reg(Regs::Pc, test_one_input_ptr)?;
qemu.write_reg(Regs::Sp, stack_ptr)?;
qemu.write_return_address(ret_addr)?;
qemu.write_function_argument(CallingConvention::Cdecl, 0, input_addr)?;
qemu.write_function_argument(CallingConvention::Cdecl, 1, len)?;
qemu.write_function_argument(0, input_addr)?;
qemu.write_function_argument(1, len)?;
match qemu.run() {
Ok(QemuExitReason::Breakpoint(_)) => {}

View File

@ -4,9 +4,7 @@ use libafl::{
Error,
};
use libafl_bolts::AsSlice;
use libafl_qemu::{
elf::EasyElf, ArchExtras, CallingConvention, GuestAddr, GuestReg, MmapPerms, Qemu, Regs,
};
use libafl_qemu::{elf::EasyElf, ArchExtras, GuestAddr, GuestReg, MmapPerms, Qemu, Regs};
pub struct Harness {
qemu: Qemu,
@ -118,11 +116,11 @@ impl Harness {
.map_err(|e| Error::unknown(format!("Failed to write return address: {e:?}")))?;
self.qemu
.write_function_argument(CallingConvention::Cdecl, 0, self.input_addr)
.write_function_argument(0, self.input_addr)
.map_err(|e| Error::unknown(format!("Failed to write argument 0: {e:?}")))?;
self.qemu
.write_function_argument(CallingConvention::Cdecl, 1, len)
.write_function_argument(1, len)
.map_err(|e| Error::unknown(format!("Failed to write argument 1: {e:?}")))?;
unsafe {
let _ = self.qemu.run();

View File

@ -8,7 +8,12 @@ use pyo3::prelude::*;
pub use strum_macros::EnumIter;
pub use syscall_numbers::aarch64::*;
use crate::{sync_exit::ExitArgs, CallingConvention, QemuRWError, QemuRWErrorKind};
use crate::{sync_exit::ExitArgs, CallingConvention, GuestAddr, QemuRWError, QemuRWErrorKind};
#[expect(non_upper_case_globals)]
impl CallingConvention {
pub const Default: CallingConvention = CallingConvention::Aapcs64;
}
#[derive(IntoPrimitive, TryFromPrimitive, Debug, Clone, Copy, EnumIter)]
#[repr(i32)]
@ -94,47 +99,83 @@ impl crate::ArchExtras for crate::CPU {
self.write_reg(Regs::Lr, val)
}
fn read_function_argument(
fn read_function_argument_with_cc(
&self,
conv: CallingConvention,
idx: u8,
conv: CallingConvention,
) -> Result<GuestReg, QemuRWError> {
QemuRWError::check_conv(QemuRWErrorKind::Read, CallingConvention::Cdecl, conv)?;
QemuRWError::check_conv(QemuRWErrorKind::Read, CallingConvention::Aapcs64, conv)?;
let reg_id = match idx {
0 => Regs::X0,
1 => Regs::X1,
2 => Regs::X2,
3 => Regs::X3,
4 => Regs::X4,
5 => Regs::X5,
r => {
return Err(QemuRWError::new_argument_error(
QemuRWErrorKind::Read,
i32::from(r),
))
match idx {
0 => self.read_reg(Regs::X0),
1 => self.read_reg(Regs::X1),
2 => self.read_reg(Regs::X2),
3 => self.read_reg(Regs::X3),
4 => self.read_reg(Regs::X4),
5 => self.read_reg(Regs::X5),
6 => self.read_reg(Regs::X6),
7 => self.read_reg(Regs::X7),
_ => {
const SIZE: usize = size_of::<GuestReg>();
let stack_ptr: GuestAddr = self.read_reg(Regs::Rsp)?;
/*
* Stack is full and descending. SP points to return address, arguments
* are in reverse order above that. 8th argument is at SP + 8.
*/
let offset = (SIZE as GuestAddr) * (GuestAddr::from(idx) - 7);
let mut buf = [0; SIZE];
self.read_mem(stack_ptr + offset, &mut buf)?;
#[cfg(feature = "be")]
{
Ok(GuestReg::from_le_bytes(buf).into())
}
#[cfg(not(feature = "be"))]
Ok(GuestReg::from_le_bytes(buf).into())
}
};
self.read_reg(reg_id)
}
}
fn write_function_argument<T>(
fn write_function_argument_with_cc<T>(
&self,
conv: CallingConvention,
idx: i32,
idx: u8,
val: T,
conv: CallingConvention,
) -> Result<(), QemuRWError>
where
T: Into<GuestReg>,
{
QemuRWError::check_conv(QemuRWErrorKind::Write, CallingConvention::Cdecl, conv)?;
QemuRWError::check_conv(QemuRWErrorKind::Write, CallingConvention::Aapcs64, conv)?;
let val: GuestReg = val.into();
match idx {
0 => self.write_reg(Regs::X0, val),
1 => self.write_reg(Regs::X1, val),
r => Err(QemuRWError::new_argument_error(QemuRWErrorKind::Write, r)),
2 => self.write_reg(Regs::X2, val),
3 => self.write_reg(Regs::X3, val),
4 => self.write_reg(Regs::X4, val),
5 => self.write_reg(Regs::X5, val),
6 => self.write_reg(Regs::X6, val),
7 => self.write_reg(Regs::X7, val),
_ => {
let val: GuestReg = val.into();
let stack_ptr: GuestAddr = self.read_reg(Regs::Sp)?;
/*
* Stack is full and descending. SP points to return address, arguments
* are in reverse order above that. 8th argument is at SP + 8.
*/
let size: GuestAddr = size_of::<GuestReg>() as GuestAddr;
let offset = size * (GuestAddr::from(idx) - 7);
#[cfg(feature = "be")]
let arg = val.to_be_bytes();
#[cfg(not(feature = "be"))]
let arg = val.to_le_bytes();
self.write_mem(stack_ptr + offset, &arg)?;
Ok(())
}
}
}
}

View File

@ -8,7 +8,12 @@ use pyo3::prelude::*;
pub use strum_macros::EnumIter;
pub use syscall_numbers::arm::*;
use crate::{sync_exit::ExitArgs, CallingConvention, QemuRWError, QemuRWErrorKind};
use crate::{sync_exit::ExitArgs, CallingConvention, GuestAddr, QemuRWError, QemuRWErrorKind};
#[expect(non_upper_case_globals)]
impl CallingConvention {
pub const Default: CallingConvention = CallingConvention::Aapcs;
}
/// Registers for the ARM instruction set.
#[derive(IntoPrimitive, TryFromPrimitive, Debug, Clone, Copy, EnumIter)]
@ -91,46 +96,77 @@ impl crate::ArchExtras for crate::CPU {
self.write_reg(Regs::Lr, val)
}
fn read_function_argument(
fn read_function_argument_with_cc(
&self,
conv: CallingConvention,
idx: u8,
conv: CallingConvention,
) -> Result<GuestReg, QemuRWError> {
QemuRWError::check_conv(QemuRWErrorKind::Read, CallingConvention::Cdecl, conv)?;
QemuRWError::check_conv(QemuRWErrorKind::Read, CallingConvention::Aapcs, conv)?;
let reg_id = match idx {
0 => Regs::R0,
1 => Regs::R1,
2 => Regs::R2,
3 => Regs::R3,
// 4.. would be on the stack, let's not do this for now
r => {
return Err(QemuRWError::new_argument_error(
QemuRWErrorKind::Read,
i32::from(r),
))
match idx {
0 => self.read_reg(Regs::R0),
1 => self.read_reg(Regs::R1),
2 => self.read_reg(Regs::R2),
3 => self.read_reg(Regs::R3),
_ => {
const SIZE: usize = size_of::<GuestReg>();
let stack_ptr: GuestAddr = self.read_reg(Regs::Sp)?;
/*
* Stack is full and descending. SP points to return address, arguments
* are in reverse order above that. 4th argument is at SP + 8.
*/
let offset = (SIZE as GuestAddr) * (GuestAddr::from(idx) - 3);
let mut buf = [0; SIZE];
self.read_mem(stack_ptr + offset, &mut buf)?;
#[cfg(feature = "be")]
{
Ok(GuestReg::from_le_bytes(buf).into())
}
#[cfg(not(feature = "be"))]
Ok(GuestReg::from_le_bytes(buf).into())
}
};
self.read_reg(reg_id)
}
}
fn write_function_argument<T>(
fn write_function_argument_with_cc<T>(
&self,
conv: CallingConvention,
idx: i32,
idx: u8,
val: T,
conv: CallingConvention,
) -> Result<(), QemuRWError>
where
T: Into<GuestReg>,
{
QemuRWError::check_conv(QemuRWErrorKind::Write, CallingConvention::Cdecl, conv)?;
QemuRWError::check_conv(QemuRWErrorKind::Write, CallingConvention::Aapcs, conv)?;
let val: GuestReg = val.into();
match idx {
0 => self.write_reg(Regs::R0, val),
1 => self.write_reg(Regs::R1, val),
r => Err(QemuRWError::new_argument_error(QemuRWErrorKind::Write, r)),
2 => self.write_reg(Regs::R2, val),
3 => self.write_reg(Regs::R3, val),
_ => {
let val: GuestReg = val.into();
let stack_ptr: GuestAddr = self.read_reg(Regs::Sp)?;
/*
* Stack is full and descending. SP points to return address, arguments
* are in reverse order above that. 4th argument is at SP + 4.
*/
let size: GuestAddr = size_of::<GuestReg>() as GuestAddr;
let offset = size * (GuestAddr::from(idx) - 3);
#[cfg(feature = "be")]
let arg = val.to_be_bytes();
#[cfg(not(feature = "be"))]
let arg = val.to_le_bytes();
self.write_mem(stack_ptr + offset, &arg)?;
Ok(())
}
}
}
}

View File

@ -8,6 +8,11 @@ pub use strum_macros::EnumIter;
use crate::{sync_exit::ExitArgs, CallingConvention, QemuRWError, QemuRWErrorKind};
#[expect(non_upper_case_globals)]
impl CallingConvention {
pub const Default: CallingConvention = CallingConvention::Hexagon;
}
#[derive(IntoPrimitive, TryFromPrimitive, Debug, Clone, Copy, EnumIter)]
#[repr(i32)]
pub enum Regs {
@ -103,12 +108,12 @@ impl crate::ArchExtras for crate::CPU {
self.write_reg(Regs::Lr, val)
}
fn read_function_argument(
fn read_function_argument_with_cc(
&self,
conv: CallingConvention,
idx: u8,
conv: CallingConvention,
) -> Result<GuestReg, QemuRWError> {
QemuRWError::check_conv(QemuRWErrorKind::Read, CallingConvention::Cdecl, conv)?;
QemuRWError::check_conv(QemuRWErrorKind::Read, CallingConvention::Hexagon, 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 {
@ -118,29 +123,33 @@ impl crate::ArchExtras for crate::CPU {
3 => Regs::R3,
4 => Regs::R4,
5 => Regs::R5,
r => {
return Err(QemuRWError::new_argument_error(
QemuRWErrorKind::Read,
i32::from(r),
))
}
r => return Err(QemuRWError::new_argument_error(QemuRWErrorKind::Read, r)),
};
self.read_reg(reg_id)
}
fn write_function_argument<T>(
fn write_function_argument_with_cc<T>(
&self,
idx: u8,
val: T,
conv: CallingConvention,
idx: i32,
_val: T,
) -> Result<(), QemuRWError>
where
T: Into<GuestReg>,
{
QemuRWError::check_conv(QemuRWErrorKind::Write, CallingConvention::Cdecl, conv)?;
QemuRWError::check_conv(QemuRWErrorKind::Write, CallingConvention::Hexagon, conv)?;
// TODO
Err(QemuRWError::new_argument_error(QemuRWErrorKind::Write, idx))
// Note that 64 bit values may be passed in two registers (and may have padding), then this mapping is off.
let val: GuestReg = val.into();
match idx {
0 => self.write_reg(Regs::R0, val),
1 => self.write_reg(Regs::R1, val),
2 => self.write_reg(Regs::R2, val),
3 => self.write_reg(Regs::R3, val),
4 => self.write_reg(Regs::R4, val),
5 => self.write_reg(Regs::R5, val),
r => Err(QemuRWError::new_argument_error(QemuRWErrorKind::Write, r)),
}
}
}

View File

@ -10,6 +10,11 @@ pub use syscall_numbers::x86::*;
use crate::{sync_exit::ExitArgs, CallingConvention, GuestAddr, QemuRWError, QemuRWErrorKind};
#[expect(non_upper_case_globals)]
impl CallingConvention {
pub const Default: CallingConvention = CallingConvention::Cdecl;
}
#[derive(IntoPrimitive, TryFromPrimitive, Debug, Clone, Copy, EnumIter)]
#[repr(i32)]
pub enum Regs {
@ -77,43 +82,37 @@ impl crate::ArchExtras for crate::CPU {
Ok(())
}
fn read_function_argument(
fn read_function_argument_with_cc(
&self,
conv: CallingConvention,
idx: u8,
conv: CallingConvention,
) -> Result<GuestReg, QemuRWError> {
QemuRWError::check_conv(QemuRWErrorKind::Read, CallingConvention::Cdecl, conv)?;
match idx {
0..=1 => {
_ => {
const SIZE: usize = size_of::<GuestReg>();
let stack_ptr: GuestAddr = self.read_reg(Regs::Sp)?;
/*
* Stack is full and descending. SP points to return address, arguments
* are in reverse order above that.
*/
let size: GuestAddr = size_of::<GuestReg>() as GuestAddr;
let offset = size * (idx as GuestAddr + 1);
let mut val = [0u8; size_of::<GuestReg>()];
let offset = (SIZE as GuestAddr) * (GuestAddr::from(idx) + 1);
let mut val = [0u8; SIZE];
unsafe {
self.read_mem(stack_ptr + offset, &mut val);
}
Ok(GuestReg::from_le_bytes(val).into())
}
r => {
return Err(QemuRWError::new_argument_error(
QemuRWErrorKind::Read,
i32::from(r),
))
}
}
}
fn write_function_argument<T>(
fn write_function_argument_with_cc<T>(
&self,
conv: CallingConvention,
idx: i32,
idx: u8,
val: T,
conv: CallingConvention,
) -> Result<(), QemuRWError>
where
T: Into<GuestReg>,
@ -121,7 +120,7 @@ impl crate::ArchExtras for crate::CPU {
QemuRWError::check_conv(QemuRWErrorKind::Write, CallingConvention::Cdecl, conv)?;
match idx {
0..=1 => {
_ => {
let val: GuestReg = val.into();
let stack_ptr: GuestAddr = self.read_reg(Regs::Sp)?;
/*
@ -129,7 +128,7 @@ impl crate::ArchExtras for crate::CPU {
* are in reverse order above that.
*/
let size: GuestAddr = size_of::<GuestReg>() as GuestAddr;
let offset = size * (idx as GuestAddr + 1);
let offset = size * (GuestAddr::from(idx) + 1);
let arg = val.to_le_bytes();
unsafe {

View File

@ -9,6 +9,11 @@ pub use syscall_numbers::mips::*;
use crate::{sync_exit::ExitArgs, CallingConvention, QemuRWError, QemuRWErrorKind};
#[expect(non_upper_case_globals)]
impl CallingConvention {
pub const Default: CallingConvention = CallingConvention::MipsO32;
}
/// Registers for the MIPS instruction set.
#[derive(IntoPrimitive, TryFromPrimitive, Debug, Clone, Copy, EnumIter)]
#[repr(i32)]
@ -91,12 +96,12 @@ impl crate::ArchExtras for crate::CPU {
self.write_reg(Regs::Ra, val)
}
fn read_function_argument(
fn read_function_argument_with_cc(
&self,
conv: CallingConvention,
idx: u8,
conv: CallingConvention,
) -> Result<GuestReg, QemuRWError> {
QemuRWError::check_conv(QemuRWErrorKind::Read, CallingConvention::Cdecl, conv)?;
QemuRWError::check_conv(QemuRWErrorKind::Read, CallingConvention::MipsO32, conv)?;
let reg_id = match idx {
0 => Regs::A0,
@ -104,32 +109,30 @@ 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(QemuRWError::new_argument_error(
QemuRWErrorKind::Read,
i32::from(r),
))
}
r => return Err(QemuRWError::new_argument_error(QemuRWErrorKind::Read, r)),
};
self.read_reg(reg_id)
}
fn write_function_argument<T>(
fn write_function_argument_with_cc<T>(
&self,
conv: CallingConvention,
idx: i32,
idx: u8,
val: T,
conv: CallingConvention,
) -> Result<(), QemuRWError>
where
T: Into<GuestReg>,
{
QemuRWError::check_conv(QemuRWErrorKind::Write, CallingConvention::Cdecl, conv)?;
QemuRWError::check_conv(QemuRWErrorKind::Write, CallingConvention::MipsO32, conv)?;
let val: GuestReg = val.into();
match idx {
0 => self.write_reg(Regs::A0, val),
1 => self.write_reg(Regs::A1, val),
2 => self.write_reg(Regs::A2, val),
3 => self.write_reg(Regs::A3, val),
// 4.. would be on the stack, let's not do this for now
r => Err(QemuRWError::new_argument_error(QemuRWErrorKind::Write, r)),
}
}

View File

@ -9,6 +9,11 @@ pub use syscall_numbers::powerpc::*;
use crate::{sync_exit::ExitArgs, CallingConvention, QemuRWError, QemuRWErrorKind};
#[expect(non_upper_case_globals)]
impl CallingConvention {
pub const Default: CallingConvention = CallingConvention::Ppc32;
}
/// Registers for the MIPS instruction set.
#[derive(IntoPrimitive, TryFromPrimitive, Debug, Clone, Copy, EnumIter)]
#[repr(i32)]
@ -131,12 +136,12 @@ impl crate::ArchExtras for crate::CPU {
self.write_reg(Regs::Lr, val)
}
fn read_function_argument(
fn read_function_argument_with_cc(
&self,
conv: CallingConvention,
idx: u8,
conv: CallingConvention,
) -> Result<GuestReg, QemuRWError> {
QemuRWError::check_conv(QemuRWErrorKind::Read, CallingConvention::Cdecl, conv)?;
QemuRWError::check_conv(QemuRWErrorKind::Read, CallingConvention::Ppc32, conv)?;
let reg_id = match idx {
0 => Regs::R3,
@ -145,27 +150,22 @@ impl crate::ArchExtras for crate::CPU {
3 => Regs::R6,
4 => Regs::R7,
5 => Regs::R8,
r => {
return Err(QemuRWError::new_argument_error(
QemuRWErrorKind::Read,
i32::from(r),
))
}
r => return Err(QemuRWError::new_argument_error(QemuRWErrorKind::Read, r)),
};
self.read_reg(reg_id)
}
fn write_function_argument<T>(
fn write_function_argument_with_cc<T>(
&self,
conv: CallingConvention,
idx: i32,
idx: u8,
val: T,
conv: CallingConvention,
) -> Result<(), QemuRWError>
where
T: Into<GuestReg>,
{
QemuRWError::check_conv(QemuRWErrorKind::Write, CallingConvention::Cdecl, conv)?;
QemuRWError::check_conv(QemuRWErrorKind::Write, CallingConvention::Ppc32, conv)?;
let val: GuestReg = val.into();
match idx {

View File

@ -22,6 +22,11 @@ pub const SYS_riscv_hwprobe: c_long = SYS_arch_specific_syscall + 14;
use crate::{sync_exit::ExitArgs, CallingConvention, QemuRWError, QemuRWErrorKind};
#[expect(non_upper_case_globals)]
impl CallingConvention {
pub const Default: CallingConvention = CallingConvention::RiscVilp32;
}
#[derive(IntoPrimitive, TryFromPrimitive, Debug, Clone, Copy, EnumIter)]
#[repr(i32)]
pub enum Regs {
@ -106,12 +111,12 @@ impl crate::ArchExtras for crate::CPU {
self.write_reg(Regs::Ra, val)
}
fn read_function_argument(
fn read_function_argument_with_cc(
&self,
conv: CallingConvention,
idx: u8,
conv: CallingConvention,
) -> Result<GuestReg, QemuRWError> {
QemuRWError::check_conv(QemuRWErrorKind::Read, CallingConvention::Cdecl, conv)?;
QemuRWError::check_conv(QemuRWErrorKind::Read, CallingConvention::RiscVilp32, conv)?;
// Note that 64 bit values may be passed in two registers (and are even-odd eg. A0, A2 and A3 where A1 is empty), then this mapping is off.
// Note: This does not consider the floating point registers.
@ -125,27 +130,22 @@ impl crate::ArchExtras for crate::CPU {
5 => Regs::A5, // argument value
6 => Regs::A6, // argument value
7 => Regs::A7, // argument value
r => {
return Err(QemuRWError::new_argument_error(
QemuRWErrorKind::Read,
i32::from(r),
))
}
r => return Err(QemuRWError::new_argument_error(QemuRWErrorKind::Read, r)),
};
self.read_reg(reg_id)
}
fn write_function_argument<T>(
fn write_function_argument_with_cc<T>(
&self,
conv: CallingConvention,
idx: i32,
idx: u8,
val: T,
conv: CallingConvention,
) -> Result<(), QemuRWError>
where
T: Into<GuestReg>,
{
QemuRWError::check_conv(QemuRWErrorKind::Write, CallingConvention::Cdecl, conv)?;
QemuRWError::check_conv(QemuRWErrorKind::Write, CallingConvention::RiscVilp32, conv)?;
let val: GuestReg = val.into();
match idx {

View File

@ -6,7 +6,12 @@ use num_enum::{IntoPrimitive, TryFromPrimitive};
pub use strum_macros::EnumIter;
pub use syscall_numbers::x86_64::*;
use crate::{sync_exit::ExitArgs, CallingConvention, QemuRWError, QemuRWErrorKind};
use crate::{sync_exit::ExitArgs, CallingConvention, GuestAddr, QemuRWError, QemuRWErrorKind};
#[expect(non_upper_case_globals)]
impl CallingConvention {
pub const Default: CallingConvention = CallingConvention::SystemV;
}
#[derive(IntoPrimitive, TryFromPrimitive, Debug, Clone, Copy, EnumIter)]
#[repr(i32)]
@ -84,47 +89,68 @@ impl crate::ArchExtras for crate::CPU {
Ok(())
}
fn read_function_argument(
fn read_function_argument_with_cc(
&self,
conv: CallingConvention,
idx: u8,
conv: CallingConvention,
) -> Result<GuestReg, QemuRWError> {
QemuRWError::check_conv(QemuRWErrorKind::Read, CallingConvention::Cdecl, conv)?;
QemuRWError::check_conv(QemuRWErrorKind::Read, CallingConvention::SystemV, conv)?;
let reg_id = match idx {
0 => Regs::Rdi,
1 => Regs::Rsi,
2 => Regs::Rdx,
3 => Regs::Rcx,
4 => Regs::R8,
5 => Regs::R9,
r => {
return Err(QemuRWError::new_argument_error(
QemuRWErrorKind::Read,
i32::from(r),
))
match idx {
0 => self.read_reg(Regs::Rdi),
1 => self.read_reg(Regs::Rsi),
2 => self.read_reg(Regs::Rdx),
3 => self.read_reg(Regs::Rcx),
4 => self.read_reg(Regs::R8),
5 => self.read_reg(Regs::R9),
_ => {
const SIZE: usize = size_of::<GuestReg>();
let stack_ptr: GuestAddr = self.read_reg(Regs::Sp)?;
/*
* Stack is full and descending. SP points to return address, arguments
* are in reverse order above that. 6th argument is at SP + 8.
*/
let offset = (SIZE as GuestAddr) * (GuestAddr::from(idx) - 5);
let mut buf = [0; SIZE];
self.read_mem(stack_ptr + offset, &mut buf)?;
Ok(GuestAddr::from_le_bytes(buf))
}
};
self.read_reg(reg_id)
}
}
fn write_function_argument<T>(
fn write_function_argument_with_cc<T>(
&self,
conv: CallingConvention,
idx: i32,
idx: u8,
val: T,
conv: CallingConvention,
) -> Result<(), QemuRWError>
where
T: Into<GuestReg>,
{
QemuRWError::check_conv(QemuRWErrorKind::Write, CallingConvention::Cdecl, conv)?;
QemuRWError::check_conv(QemuRWErrorKind::Write, CallingConvention::SystemV, conv)?;
let val: GuestReg = val.into();
match idx {
0 => self.write_reg(Regs::Rdi, val),
1 => self.write_reg(Regs::Rsi, val),
r => Err(QemuRWError::new_argument_error(QemuRWErrorKind::Write, r)),
2 => self.write_reg(Regs::Rdx, val),
3 => self.write_reg(Regs::Rcx, val),
4 => self.write_reg(Regs::R8, val),
5 => self.write_reg(Regs::R9, val),
_ => {
let val: GuestReg = val.into();
let stack_ptr: GuestAddr = self.read_reg(Regs::Rsp)?;
/*
* Stack is full and descending. SP points to return address, arguments
* are in reverse order above that. 6th argument is at SP + 8.
*/
let size: GuestAddr = size_of::<GuestReg>() as GuestAddr;
let offset = size * (GuestAddr::from(idx) - 5);
let arg = val.to_le_bytes();
self.write_mem(stack_ptr + offset, &arg)
}
}
}
}

View File

@ -12,10 +12,10 @@ pub use libafl_targets::{
};
use serde::{Deserialize, Serialize};
#[cfg(feature = "usermode")]
use crate::capstone;
#[cfg(feature = "systemmode")]
use crate::modules::utils::filters::{NopPageFilter, NOP_PAGE_FILTER};
#[cfg(feature = "usermode")]
use crate::{capstone, qemu::ArchExtras, CallingConvention};
use crate::{
emu::EmulatorModules,
modules::{
@ -301,12 +301,8 @@ impl CmpLogRoutinesModule {
let qemu = Qemu::get().unwrap();
let a0: GuestAddr = qemu
.read_function_argument(CallingConvention::Cdecl, 0)
.unwrap_or(0);
let a1: GuestAddr = qemu
.read_function_argument(CallingConvention::Cdecl, 1)
.unwrap_or(0);
let a0: GuestAddr = qemu.read_function_argument(0).unwrap_or(0);
let a1: GuestAddr = qemu.read_function_argument(1).unwrap_or(0);
if a0 == 0 || a1 == 0 {
return;

View File

@ -228,7 +228,7 @@ impl InjectionModule {
let reg: GuestAddr = qemu
.current_cpu()
.unwrap()
.read_function_argument(CallingConvention::Cdecl, parameter)
.read_function_argument_with_cc(parameter, CallingConvention::Default)
.unwrap_or_default();
let module = emulator_modules.get_mut::<Self>().unwrap();

View File

@ -36,7 +36,7 @@ pub enum QemuRWErrorKind {
#[derive(Clone, Debug)]
pub enum QemuRWErrorCause {
WrongCallingConvention(CallingConvention, CallingConvention), // expected, given
WrongArgument(i32),
WrongArgument(u8),
CurrentCpuNotFound,
Reg(i32),
WrongMemoryLocation(GuestAddr, usize), // addr, size
@ -121,8 +121,8 @@ impl QemuRWError {
}
#[must_use]
pub fn new_argument_error(kind: QemuRWErrorKind, reg_id: i32) -> Self {
Self::new(kind, QemuRWErrorCause::WrongArgument(reg_id), None)
pub fn new_argument_error(kind: QemuRWErrorKind, arg_id: u8) -> Self {
Self::new(kind, QemuRWErrorCause::WrongArgument(arg_id), None)
}
pub fn check_conv(

View File

@ -74,16 +74,17 @@ pub trait ArchExtras {
fn write_return_address<T>(&self, val: T) -> Result<(), QemuRWError>
where
T: Into<GuestReg>;
fn read_function_argument(
fn read_function_argument_with_cc(
&self,
conv: CallingConvention,
idx: u8,
) -> Result<GuestReg, QemuRWError>;
fn write_function_argument<T>(
&self,
conv: CallingConvention,
idx: i32,
) -> Result<GuestReg, QemuRWError>;
fn write_function_argument_with_cc<T>(
&self,
idx: u8,
val: T,
conv: CallingConvention,
) -> Result<(), QemuRWError>
where
T: Into<GuestReg>;
@ -155,7 +156,14 @@ pub struct CPU {
#[derive(Debug, Clone, PartialEq)]
pub enum CallingConvention {
SystemV,
Cdecl,
Aapcs64,
Aapcs,
Hexagon,
MipsO32,
Ppc32,
RiscVilp32,
}
#[derive(Debug)]
@ -937,6 +945,65 @@ impl Qemu {
pub fn is_running(&self) -> bool {
unsafe { QEMU_IS_RUNNING }
}
/// Write the function arguments by following default calling convention.
/// Assume that every arguments has integer/pointer type, otherwise the value
/// may be stored at wrong place because of different rules for complex types.
/// Note that the stack pointer register must point the top of the stack at the start
/// of the called function, in case the argument is written in the stack.
/// Support downward-growing stack only.
/// If you need to specify a calling convention, use [`Self::write_function_arguments_with_cc`].
pub fn write_function_arguments<T>(&mut self, val: &[T]) -> Result<(), QemuRWError>
where
T: Into<GuestReg> + Copy,
{
self.write_function_arguments_with_cc(val, &CallingConvention::Default)
}
/// Write the function arguments by following calling convention `conv`.
/// Assume that every arguments has integer/pointer type, otherwise the value
/// may be stored at wrong place because of different rules for complex types.
/// Note that the stack pointer register must point the top of the stack at the start
/// of the called function, in case the argument is written in the stack.
/// Support downward-growing stack only.
pub fn write_function_arguments_with_cc<T>(
&mut self,
val: &[T],
conv: &CallingConvention,
) -> Result<(), QemuRWError>
where
T: Into<GuestReg> + Copy,
{
for (idx, elem) in val.iter().enumerate() {
self.write_function_argument_with_cc(idx as u8, elem.to_owned(), conv.clone())?;
}
Ok(())
}
/// Read the function `idx` argument by following default calling convention.
/// Assume that this argument and every prior arguments has integer/pointer type, otherwise
/// it may return a wrong value because of different rules for complex types.
/// Note that the stack pointer register must point the top of the stack at the start
/// of the called function, in case the value is in the stack.
/// Support downward-growing stack only.
/// If you need to specify a calling convention, use [`Self::read_function_argument_with_cc`].
pub fn read_function_argument(&self, idx: u8) -> Result<GuestReg, QemuRWError> {
self.read_function_argument_with_cc(idx, CallingConvention::Default)
}
/// Write the function `val` into `idx` argument by following default calling convention.
/// Assume that `val` and every prior arguments has integer/pointer type, otherwise the value
/// may be stored at wrong place because of different rules for complex types.
/// Note that the stack pointer register must point the top of the stack at the start
/// of the called function, in case the argument is written in the stack.
/// Support downward-growing stack only.
/// If you need to specify a calling convention, use [`Self::write_function_argument_with_cc`].
pub fn write_function_argument<T>(&self, idx: u8, val: T) -> Result<(), QemuRWError>
where
T: Into<GuestReg>,
{
self.write_function_argument_with_cc(idx, val, CallingConvention::Default)
}
}
impl ArchExtras for Qemu {
@ -955,28 +1022,40 @@ impl ArchExtras for Qemu {
.write_return_address::<T>(val)
}
fn read_function_argument(
/// Read the function `idx` argument by following calling convention `conv`.
/// Assume that this argument and every prior arguments has integer/pointer type, otherwise
/// it may return a wrong value because of different rules for complex types.
/// Note that the stack pointer register must point the top of the stack at the start
/// of the called function, in case the value is in the stack.
/// Support downward-growing stack only.
fn read_function_argument_with_cc(
&self,
conv: CallingConvention,
idx: u8,
conv: CallingConvention,
) -> Result<GuestReg, QemuRWError> {
self.current_cpu()
.ok_or(QemuRWError::current_cpu_not_found(QemuRWErrorKind::Read))?
.read_function_argument(conv, idx)
.read_function_argument_with_cc(idx, conv)
}
fn write_function_argument<T>(
/// Write the function `val` into `idx` argument by following calling convention `conv`.
/// Assume that `val` and every prior arguments has integer/pointer type, otherwise the value
/// may be stored at wrong place because of different rules for complex types.
/// Note that the stack pointer register must point the top of the stack at the start
/// of the called function, in case the argument is written in the stack.
/// Support downward-growing stack only.
fn write_function_argument_with_cc<T>(
&self,
conv: CallingConvention,
idx: i32,
idx: u8,
val: T,
conv: CallingConvention,
) -> Result<(), QemuRWError>
where
T: Into<GuestReg>,
{
self.current_cpu()
.ok_or(QemuRWError::current_cpu_not_found(QemuRWErrorKind::Write))?
.write_function_argument::<T>(conv, idx, val)
.write_function_argument_with_cc::<T>(idx, val, conv)
}
}