diff --git a/fuzzers/binary_only/qemu_cmin/src/fuzzer.rs b/fuzzers/binary_only/qemu_cmin/src/fuzzer.rs index 9cd8b328ce..fd8301692f 100644 --- a/fuzzers/binary_only/qemu_cmin/src/fuzzer.rs +++ b/fuzzers/binary_only/qemu_cmin/src/fuzzer.rs @@ -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(_)) => {} diff --git a/fuzzers/binary_only/qemu_coverage/src/fuzzer.rs b/fuzzers/binary_only/qemu_coverage/src/fuzzer.rs index a84bcad6ec..6879b7d36c 100644 --- a/fuzzers/binary_only/qemu_coverage/src/fuzzer.rs +++ b/fuzzers/binary_only/qemu_coverage/src/fuzzer.rs @@ -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(_)) => {} diff --git a/fuzzers/binary_only/qemu_launcher/src/harness.rs b/fuzzers/binary_only/qemu_launcher/src/harness.rs index 7cd32825e7..3fe66e6b81 100644 --- a/fuzzers/binary_only/qemu_launcher/src/harness.rs +++ b/fuzzers/binary_only/qemu_launcher/src/harness.rs @@ -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(); diff --git a/libafl_qemu/src/arch/aarch64.rs b/libafl_qemu/src/arch/aarch64.rs index aad7624b14..b3cb844882 100644 --- a/libafl_qemu/src/arch/aarch64.rs +++ b/libafl_qemu/src/arch/aarch64.rs @@ -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 { - 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::(); + 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( + fn write_function_argument_with_cc( &self, - conv: CallingConvention, - idx: i32, + idx: u8, val: T, + conv: CallingConvention, ) -> Result<(), QemuRWError> where T: Into, { - 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::() 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(()) + } } } } diff --git a/libafl_qemu/src/arch/arm.rs b/libafl_qemu/src/arch/arm.rs index ad7918f412..313235b560 100644 --- a/libafl_qemu/src/arch/arm.rs +++ b/libafl_qemu/src/arch/arm.rs @@ -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 { - 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::(); + 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( + fn write_function_argument_with_cc( &self, - conv: CallingConvention, - idx: i32, + idx: u8, val: T, + conv: CallingConvention, ) -> Result<(), QemuRWError> where T: Into, { - 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::() 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(()) + } } } } diff --git a/libafl_qemu/src/arch/hexagon.rs b/libafl_qemu/src/arch/hexagon.rs index 69d35beea1..7ef78be30e 100644 --- a/libafl_qemu/src/arch/hexagon.rs +++ b/libafl_qemu/src/arch/hexagon.rs @@ -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 { - 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( + fn write_function_argument_with_cc( &self, + idx: u8, + val: T, conv: CallingConvention, - idx: i32, - _val: T, ) -> Result<(), QemuRWError> where T: Into, { - 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)), + } } } diff --git a/libafl_qemu/src/arch/i386.rs b/libafl_qemu/src/arch/i386.rs index 239010bf9d..51af79884b 100644 --- a/libafl_qemu/src/arch/i386.rs +++ b/libafl_qemu/src/arch/i386.rs @@ -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 { QemuRWError::check_conv(QemuRWErrorKind::Read, CallingConvention::Cdecl, conv)?; match idx { - 0..=1 => { + _ => { + const SIZE: usize = size_of::(); 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::() as GuestAddr; - let offset = size * (idx as GuestAddr + 1); - let mut val = [0u8; size_of::()]; + 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( + fn write_function_argument_with_cc( &self, - conv: CallingConvention, - idx: i32, + idx: u8, val: T, + conv: CallingConvention, ) -> Result<(), QemuRWError> where T: Into, @@ -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::() as GuestAddr; - let offset = size * (idx as GuestAddr + 1); + let offset = size * (GuestAddr::from(idx) + 1); let arg = val.to_le_bytes(); unsafe { diff --git a/libafl_qemu/src/arch/mips.rs b/libafl_qemu/src/arch/mips.rs index e9adfc714c..bcce7942d0 100644 --- a/libafl_qemu/src/arch/mips.rs +++ b/libafl_qemu/src/arch/mips.rs @@ -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 { - 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( + fn write_function_argument_with_cc( &self, - conv: CallingConvention, - idx: i32, + idx: u8, val: T, + conv: CallingConvention, ) -> Result<(), QemuRWError> where T: Into, { - 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)), } } diff --git a/libafl_qemu/src/arch/ppc.rs b/libafl_qemu/src/arch/ppc.rs index 373d0e15c8..a42f022680 100644 --- a/libafl_qemu/src/arch/ppc.rs +++ b/libafl_qemu/src/arch/ppc.rs @@ -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 { - 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( + fn write_function_argument_with_cc( &self, - conv: CallingConvention, - idx: i32, + idx: u8, val: T, + conv: CallingConvention, ) -> Result<(), QemuRWError> where T: Into, { - QemuRWError::check_conv(QemuRWErrorKind::Write, CallingConvention::Cdecl, conv)?; + QemuRWError::check_conv(QemuRWErrorKind::Write, CallingConvention::Ppc32, conv)?; let val: GuestReg = val.into(); match idx { diff --git a/libafl_qemu/src/arch/riscv.rs b/libafl_qemu/src/arch/riscv.rs index 8dc4bac0a2..79aef7c720 100644 --- a/libafl_qemu/src/arch/riscv.rs +++ b/libafl_qemu/src/arch/riscv.rs @@ -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 { - 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( + fn write_function_argument_with_cc( &self, - conv: CallingConvention, - idx: i32, + idx: u8, val: T, + conv: CallingConvention, ) -> Result<(), QemuRWError> where T: Into, { - QemuRWError::check_conv(QemuRWErrorKind::Write, CallingConvention::Cdecl, conv)?; + QemuRWError::check_conv(QemuRWErrorKind::Write, CallingConvention::RiscVilp32, conv)?; let val: GuestReg = val.into(); match idx { diff --git a/libafl_qemu/src/arch/x86_64.rs b/libafl_qemu/src/arch/x86_64.rs index 87badd3702..19dc5bf952 100644 --- a/libafl_qemu/src/arch/x86_64.rs +++ b/libafl_qemu/src/arch/x86_64.rs @@ -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 { - 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::(); + 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( + fn write_function_argument_with_cc( &self, - conv: CallingConvention, - idx: i32, + idx: u8, val: T, + conv: CallingConvention, ) -> Result<(), QemuRWError> where T: Into, { - 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::() as GuestAddr; + let offset = size * (GuestAddr::from(idx) - 5); + let arg = val.to_le_bytes(); + self.write_mem(stack_ptr + offset, &arg) + } } } } diff --git a/libafl_qemu/src/modules/cmplog.rs b/libafl_qemu/src/modules/cmplog.rs index 725a3708c1..09bd83c626 100644 --- a/libafl_qemu/src/modules/cmplog.rs +++ b/libafl_qemu/src/modules/cmplog.rs @@ -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; diff --git a/libafl_qemu/src/modules/usermode/injections.rs b/libafl_qemu/src/modules/usermode/injections.rs index d469f8d33a..783cdc6efa 100644 --- a/libafl_qemu/src/modules/usermode/injections.rs +++ b/libafl_qemu/src/modules/usermode/injections.rs @@ -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::().unwrap(); diff --git a/libafl_qemu/src/qemu/error.rs b/libafl_qemu/src/qemu/error.rs index 304e50e7d6..b2ef2e8472 100644 --- a/libafl_qemu/src/qemu/error.rs +++ b/libafl_qemu/src/qemu/error.rs @@ -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( diff --git a/libafl_qemu/src/qemu/mod.rs b/libafl_qemu/src/qemu/mod.rs index db0ef7d233..0340844218 100644 --- a/libafl_qemu/src/qemu/mod.rs +++ b/libafl_qemu/src/qemu/mod.rs @@ -74,16 +74,17 @@ pub trait ArchExtras { fn write_return_address(&self, val: T) -> Result<(), QemuRWError> where T: Into; - fn read_function_argument( + fn read_function_argument_with_cc( &self, - conv: CallingConvention, idx: u8, - ) -> Result; - fn write_function_argument( - &self, conv: CallingConvention, - idx: i32, + ) -> Result; + + fn write_function_argument_with_cc( + &self, + idx: u8, val: T, + conv: CallingConvention, ) -> Result<(), QemuRWError> where T: Into; @@ -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(&mut self, val: &[T]) -> Result<(), QemuRWError> + where + T: Into + 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( + &mut self, + val: &[T], + conv: &CallingConvention, + ) -> Result<(), QemuRWError> + where + T: Into + 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 { + 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(&self, idx: u8, val: T) -> Result<(), QemuRWError> + where + T: Into, + { + 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::(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 { 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( + /// 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( &self, - conv: CallingConvention, - idx: i32, + idx: u8, val: T, + conv: CallingConvention, ) -> Result<(), QemuRWError> where T: Into, { self.current_cpu() .ok_or(QemuRWError::current_cpu_not_found(QemuRWErrorKind::Write))? - .write_function_argument::(conv, idx, val) + .write_function_argument_with_cc::(idx, val, conv) } }