Arch independent helpers in libafl_qemu (#1355)

* Add more features to libafl_qemu to remove some of the heavy lifting from the fuzzers

* Refactor qemu_coverage

* Minor tweaks to fix other fuzzers

* Autofix

* Add CallingConvention to write_function_argument

* Replay reverted clippy fixes

---------

Co-authored-by: Your Name <you@example.com>
This commit is contained in:
WorksButNotTested 2023-07-11 09:56:40 +01:00 committed by GitHub
parent 109755208e
commit 2002bbca35
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
26 changed files with 471 additions and 336 deletions

View File

@ -51,7 +51,7 @@ use libafl_qemu::{
emu::Emulator,
filter_qemu_args,
hooks::QemuHooks,
MmapPerms, QemuForkExecutor, Regs,
GuestReg, MmapPerms, QemuForkExecutor, Regs,
};
#[cfg(unix)]
use nix::{self, unistd::dup};
@ -318,7 +318,7 @@ fn fuzz(
emu.write_mem(input_addr, buf);
emu.write_reg(Regs::Rdi, input_addr).unwrap();
emu.write_reg(Regs::Rsi, len).unwrap();
emu.write_reg(Regs::Rsi, len as GuestReg).unwrap();
emu.write_reg(Regs::Rip, test_one_input_ptr).unwrap();
emu.write_reg(Regs::Rsp, stack_ptr).unwrap();

View File

@ -54,6 +54,7 @@ use libafl_qemu::{
emu::Emulator,
filter_qemu_args,
hooks::QemuHooks,
GuestReg,
//snapshot::QemuSnapshotHelper,
MmapPerms,
QemuExecutor,
@ -330,7 +331,7 @@ fn fuzz(
emu.write_mem(input_addr, buf);
emu.write_reg(Regs::Rdi, input_addr).unwrap();
emu.write_reg(Regs::Rsi, len).unwrap();
emu.write_reg(Regs::Rsi, len as GuestReg).unwrap();
emu.write_reg(Regs::Rip, test_one_input_ptr).unwrap();
emu.write_reg(Regs::Rsp, stack_ptr).unwrap();

View File

@ -13,12 +13,11 @@ debug = true
[features]
default = ["std"]
std = []
be = []
64bit = []
be = ["libafl_qemu/be"]
arm = ["libafl_qemu/arm"]
x86_64 = ["libafl_qemu/x86_64", "64bit"]
x86_64 = ["libafl_qemu/x86_64"]
i386 = ["libafl_qemu/i386"]
aarch64 = ["libafl_qemu/aarch64", "64bit"]
aarch64 = ["libafl_qemu/aarch64"]
mips = ["libafl_qemu/mips"]
ppc = ["libafl_qemu/ppc", "be"]

View File

@ -28,17 +28,11 @@ use libafl::{
Error,
};
use libafl_qemu::{
drcov::QemuDrCovHelper, elf::EasyElf, emu::Emulator, MmapPerms, QemuExecutor, QemuHooks,
QemuInstrumentationFilter, Regs,
drcov::QemuDrCovHelper, elf::EasyElf, emu::Emulator, ArchExtras, CallingConvention, GuestAddr,
GuestReg, MmapPerms, QemuExecutor, QemuHooks, QemuInstrumentationFilter, Regs,
};
use rangemap::RangeMap;
#[cfg(feature = "64bit")]
type GuestReg = u64;
#[cfg(not(feature = "64bit"))]
type GuestReg = u32;
#[derive(Default)]
pub struct Version;
@ -141,60 +135,10 @@ pub fn fuzz() {
);
}
let read_reg = |emu: &Emulator, reg: Regs| -> GuestReg {
let val: GuestReg = emu.read_reg(reg).unwrap();
#[cfg(feature = "be")]
return GuestReg::from_be(val);
#[cfg(not(feature = "be"))]
return GuestReg::from_le(val);
};
let write_reg = |emu: &Emulator, reg: Regs, val: GuestReg| {
#[cfg(feature = "be")]
let val = GuestReg::to_be(val);
#[cfg(not(feature = "be"))]
let val = GuestReg::to_le(val);
emu.write_reg(reg, val).unwrap();
};
println!("Break at {:#x}", read_reg(&emu, Regs::Pc));
#[cfg(feature = "arm")]
let ret_addr: u32 = read_reg(&emu, Regs::Lr);
#[cfg(feature = "aarch64")]
let ret_addr: u64 = read_reg(&emu, Regs::Lr);
#[cfg(feature = "x86_64")]
let stack_ptr: u64 = read_reg(&emu, Regs::Rsp);
#[cfg(feature = "x86_64")]
let ret_addr: u64 = {
let mut ret_addr = [0; 8];
unsafe { emu.read_mem(stack_ptr, &mut ret_addr) };
u64::from_le_bytes(ret_addr)
};
#[cfg(feature = "i386")]
let stack_ptr: u32 = read_reg(&emu, Regs::Esp);
#[cfg(feature = "i386")]
let ret_addr: u32 = {
let mut ret_addr = [0; 4];
unsafe { emu.read_mem(stack_ptr, &mut ret_addr) };
u32::from_le_bytes(ret_addr)
};
#[cfg(feature = "mips")]
let ret_addr: u32 = read_reg(&emu, Regs::Ra);
#[cfg(feature = "ppc")]
let ret_addr: u32 = read_reg(&emu, Regs::Lr);
let pc: GuestReg = emu.read_reg(Regs::Pc).unwrap();
println!("Break at {pc:#x}");
let ret_addr: GuestAddr = emu.read_return_address().unwrap();
println!("Return address = {ret_addr:#x}");
emu.remove_breakpoint(test_one_input_ptr);
@ -203,6 +147,21 @@ pub fn fuzz() {
let input_addr = emu.map_private(0, 4096, MmapPerms::ReadWrite).unwrap();
println!("Placing input at {input_addr:#x}");
let stack_ptr: GuestAddr = emu.read_reg(Regs::Sp).unwrap();
let reset = |buf: &[u8], len: GuestReg| -> Result<(), String> {
unsafe {
emu.write_mem(input_addr, buf);
emu.write_reg(Regs::Pc, test_one_input_ptr)?;
emu.write_reg(Regs::Sp, stack_ptr)?;
emu.write_return_address(ret_addr)?;
emu.write_function_argument(CallingConvention::Cdecl, 0, input_addr)?;
emu.write_function_argument(CallingConvention::Cdecl, 1, len)?;
emu.run();
Ok(())
}
};
let mut harness = |input: &BytesInput| {
let target = input.target_bytes();
let buf = target
@ -211,65 +170,7 @@ pub fn fuzz() {
.next()
.expect("Failed to get chunk");
let len = buf.len() as GuestReg;
unsafe {
emu.write_mem(input_addr, buf);
#[cfg(feature = "arm")]
{
write_reg(&emu, Regs::R0, input_addr);
write_reg(&emu, Regs::R1, len);
write_reg(&emu, Regs::Pc, test_one_input_ptr);
write_reg(&emu, Regs::Lr, ret_addr);
}
#[cfg(feature = "aarch64")]
{
write_reg(&emu, Regs::X0, input_addr);
write_reg(&emu, Regs::X1, len);
write_reg(&emu, Regs::Pc, test_one_input_ptr);
write_reg(&emu, Regs::Lr, ret_addr);
}
#[cfg(feature = "x86_64")]
{
write_reg(&emu, Regs::Rdi, input_addr);
write_reg(&emu, Regs::Rsi, len);
write_reg(&emu, Regs::Rip, test_one_input_ptr);
write_reg(&emu, Regs::Rsp, stack_ptr);
}
#[cfg(feature = "i386")]
{
let input_addr_bytes = input_addr.to_le_bytes();
emu.write_mem(stack_ptr + (size_of::<u32>() as u32), &input_addr_bytes);
let len_bytes = len.to_le_bytes();
emu.write_mem(stack_ptr + ((2 * size_of::<u32>()) as u32), &len_bytes);
write_reg(&emu, Regs::Eip, test_one_input_ptr);
write_reg(&emu, Regs::Esp, stack_ptr);
}
#[cfg(feature = "mips")]
{
write_reg(&emu, Regs::A0, input_addr);
write_reg(&emu, Regs::A1, len);
write_reg(&emu, Regs::Pc, test_one_input_ptr);
write_reg(&emu, Regs::Ra, ret_addr);
}
#[cfg(feature = "ppc")]
{
write_reg(&emu, Regs::R3, input_addr);
write_reg(&emu, Regs::R4, len);
write_reg(&emu, Regs::Pc, test_one_input_ptr);
write_reg(&emu, Regs::Lr, ret_addr);
}
emu.run();
}
reset(buf, len).unwrap();
ExitKind::Ok
};

View File

@ -7,12 +7,11 @@ edition = "2021"
[features]
default = ["std"]
std = []
be = []
64bit = []
be = ["libafl_qemu/be"]
arm = ["libafl_qemu/arm"]
x86_64 = ["libafl_qemu/x86_64", "64bit"]
x86_64 = ["libafl_qemu/x86_64"]
i386 = ["libafl_qemu/i386"]
aarch64 = ["libafl_qemu/aarch64", "64bit"]
aarch64 = ["libafl_qemu/aarch64"]
mips = ["libafl_qemu/mips"]
ppc = ["libafl_qemu/ppc", "be"]

View File

@ -1,7 +1,5 @@
//! A libfuzzer-like fuzzer using qemu for binary-only coverage
//!
#[cfg(feature = "i386")]
use core::mem::size_of;
use core::{ptr::addr_of_mut, time::Duration};
use std::{env, path::PathBuf, process};
@ -36,16 +34,11 @@ use libafl_qemu::{
edges::{edges_map_mut_slice, QemuEdgeCoverageHelper, MAX_EDGES_NUM},
elf::EasyElf,
emu::Emulator,
MmapPerms, QemuExecutor, QemuHooks, QemuInstrumentationFilter, Regs,
ArchExtras, CallingConvention, GuestAddr, GuestReg, MmapPerms, QemuExecutor, QemuHooks,
QemuInstrumentationFilter, Regs,
};
use rangemap::RangeMap;
#[cfg(feature = "64bit")]
type GuestReg = u64;
#[cfg(not(feature = "64bit"))]
type GuestReg = u32;
#[derive(Default)]
pub struct Version;
@ -148,60 +141,10 @@ pub fn fuzz() {
);
}
let read_reg = |emu: &Emulator, reg: Regs| -> GuestReg {
let val: GuestReg = emu.read_reg(reg).unwrap();
#[cfg(feature = "be")]
return GuestReg::from_be(val);
#[cfg(not(feature = "be"))]
return GuestReg::from_le(val);
};
let write_reg = |emu: &Emulator, reg: Regs, val: GuestReg| {
#[cfg(feature = "be")]
let val = GuestReg::to_be(val);
#[cfg(not(feature = "be"))]
let val = GuestReg::to_le(val);
emu.write_reg(reg, val).unwrap();
};
println!("Break at {:#x}", read_reg(&emu, Regs::Pc));
#[cfg(feature = "arm")]
let ret_addr: u32 = read_reg(&emu, Regs::Lr);
#[cfg(feature = "aarch64")]
let ret_addr: u64 = read_reg(&emu, Regs::Lr);
#[cfg(feature = "x86_64")]
let stack_ptr: u64 = read_reg(&emu, Regs::Rsp);
#[cfg(feature = "x86_64")]
let ret_addr: u64 = {
let mut ret_addr = [0; 8];
unsafe { emu.read_mem(stack_ptr, &mut ret_addr) };
u64::from_le_bytes(ret_addr)
};
#[cfg(feature = "i386")]
let stack_ptr: u32 = read_reg(&emu, Regs::Esp);
#[cfg(feature = "i386")]
let ret_addr: u32 = {
let mut ret_addr = [0; 4];
unsafe { emu.read_mem(stack_ptr, &mut ret_addr) };
u32::from_le_bytes(ret_addr)
};
#[cfg(feature = "mips")]
let ret_addr: u32 = read_reg(&emu, Regs::Ra);
#[cfg(feature = "ppc")]
let ret_addr: u32 = read_reg(&emu, Regs::Lr);
let pc: GuestReg = emu.read_reg(Regs::Pc).unwrap();
println!("Break at {pc:#x}");
let ret_addr: GuestAddr = emu.read_return_address().unwrap();
println!("Return address = {ret_addr:#x}");
emu.remove_breakpoint(test_one_input_ptr);
@ -210,6 +153,21 @@ pub fn fuzz() {
let input_addr = emu.map_private(0, 4096, MmapPerms::ReadWrite).unwrap();
println!("Placing input at {input_addr:#x}");
let stack_ptr: GuestAddr = emu.read_reg(Regs::Sp).unwrap();
let reset = |buf: &[u8], len: GuestReg| -> Result<(), String> {
unsafe {
emu.write_mem(input_addr, buf);
emu.write_reg(Regs::Pc, test_one_input_ptr)?;
emu.write_reg(Regs::Sp, stack_ptr)?;
emu.write_return_address(ret_addr)?;
emu.write_function_argument(CallingConvention::Cdecl, 0, input_addr)?;
emu.write_function_argument(CallingConvention::Cdecl, 1, len)?;
emu.run();
Ok(())
}
};
let mut harness = |input: &BytesInput| {
let target = input.target_bytes();
let buf = target
@ -218,65 +176,7 @@ pub fn fuzz() {
.next()
.expect("Failed to get chunk");
let len = buf.len() as GuestReg;
unsafe {
emu.write_mem(input_addr, buf);
#[cfg(feature = "arm")]
{
write_reg(&emu, Regs::R0, input_addr);
write_reg(&emu, Regs::R1, len);
write_reg(&emu, Regs::Pc, test_one_input_ptr);
write_reg(&emu, Regs::Lr, ret_addr);
}
#[cfg(feature = "aarch64")]
{
write_reg(&emu, Regs::X0, input_addr);
write_reg(&emu, Regs::X1, len);
write_reg(&emu, Regs::Pc, test_one_input_ptr);
write_reg(&emu, Regs::Lr, ret_addr);
}
#[cfg(feature = "x86_64")]
{
write_reg(&emu, Regs::Rdi, input_addr);
write_reg(&emu, Regs::Rsi, len);
write_reg(&emu, Regs::Rip, test_one_input_ptr);
write_reg(&emu, Regs::Rsp, stack_ptr);
}
#[cfg(feature = "i386")]
{
let input_addr_bytes = input_addr.to_le_bytes();
emu.write_mem(stack_ptr + (size_of::<u32>() as u32), &input_addr_bytes);
let len_bytes = len.to_le_bytes();
emu.write_mem(stack_ptr + ((2 * size_of::<u32>()) as u32), &len_bytes);
write_reg(&emu, Regs::Eip, test_one_input_ptr);
write_reg(&emu, Regs::Esp, stack_ptr);
}
#[cfg(feature = "mips")]
{
write_reg(&emu, Regs::A0, input_addr);
write_reg(&emu, Regs::A1, len);
write_reg(&emu, Regs::Pc, test_one_input_ptr);
write_reg(&emu, Regs::Ra, ret_addr);
}
#[cfg(feature = "ppc")]
{
write_reg(&emu, Regs::R3, input_addr);
write_reg(&emu, Regs::R4, len);
write_reg(&emu, Regs::Pc, test_one_input_ptr);
write_reg(&emu, Regs::Lr, ret_addr);
}
emu.run();
}
reset(buf, len).unwrap();
ExitKind::Ok
};

View File

@ -1867,13 +1867,13 @@ mod tests {
#[test]
fn test_read_tokens() {
let _res = fs::remove_file("test.tkns");
let data = r###"
let data = r#"
# comment
token1@123="AAA"
token1="A\x41A"
"A\AA"
token2="B"
"###;
"#;
fs::write("test.tkns", data).expect("Unable to write test.tkns");
let tokens = Tokens::from_file("test.tkns").unwrap();
log::info!("Token file entries: {:?}", tokens.tokens());

View File

@ -385,14 +385,13 @@ impl Allocator {
metadatas.sort_by(|a, b| a.address.cmp(&b.address));
let mut offset_to_closest = i64::max_value();
let mut closest = None;
let ptr: i64 = ptr.try_into().unwrap();
for metadata in metadatas {
let address: i64 = metadata.address.try_into().unwrap();
let new_offset = if hint_base == metadata.address {
(ptr as i64 - metadata.address as i64).abs()
(ptr - address).abs()
} else {
std::cmp::min(
offset_to_closest,
(ptr as i64 - metadata.address as i64).abs(),
)
std::cmp::min(offset_to_closest, (ptr - address).abs())
};
if new_offset < offset_to_closest {
offset_to_closest = new_offset;

View File

@ -251,14 +251,13 @@ impl AsanErrors {
cs.set_skipdata(true).expect("failed to set skipdata");
let start_pc = error.pc - 4 * 5;
for insn in cs
for insn in &*cs
.disasm_count(
unsafe { std::slice::from_raw_parts(start_pc as *mut u8, 4 * 11) },
start_pc as u64,
11,
)
.expect("failed to disassemble instructions")
.iter()
{
if insn.address() as usize == error.pc {
output
@ -276,7 +275,9 @@ impl AsanErrors {
#[allow(clippy::non_ascii_literal)]
writeln!(output, "{:━^100}", " ALLOCATION INFO ").unwrap();
let offset: i64 = fault_address as i64 - (error.metadata.address + 0x1000) as i64;
let fault_address: i64 = fault_address.try_into().unwrap();
let metadata_address: i64 = error.metadata.address.try_into().unwrap();
let offset: i64 = fault_address - (metadata_address + 0x1000);
let direction = if offset > 0 { "right" } else { "left" };
writeln!(
output,
@ -505,14 +506,13 @@ impl AsanErrors {
cs.set_skipdata(true).expect("failed to set skipdata");
let start_pc = pc;
for insn in cs
for insn in &*cs
.disasm_count(
unsafe { std::slice::from_raw_parts(start_pc as *mut u8, 4 * 11) },
start_pc as u64,
11,
)
.expect("failed to disassemble instructions")
.iter()
{
if insn.address() as usize == pc {
output

View File

@ -150,7 +150,7 @@ impl CoverageRuntime {
; mov QWORD [rsp-0x98], rbx
// Load the previous_pc
; mov rax, QWORD prev_loc_ptr as *mut u64 as _
; mov rax, QWORD prev_loc_ptr as _
; mov rax, QWORD [rax]
// Calculate the edge id
@ -158,7 +158,7 @@ impl CoverageRuntime {
; xor rax, rbx
// Load the map byte address
; mov rbx, QWORD map_addr_ptr as *mut [u8; MAP_SIZE] as _
; mov rbx, QWORD map_addr_ptr as _
; add rax, rbx
// Update the map byte
@ -168,7 +168,7 @@ impl CoverageRuntime {
; mov BYTE [rax],bl
// Update the previous_pc value
; mov rax, QWORD prev_loc_ptr as *mut u64 as _
; mov rax, QWORD prev_loc_ptr as _
; mov ebx, WORD (h64 >> 1) as i32
; mov QWORD [rax], rbx

View File

@ -4,6 +4,8 @@ use pyo3::prelude::*;
pub use strum_macros::EnumIter;
pub use syscall_numbers::aarch64::*;
use crate::CallingConvention;
#[derive(IntoPrimitive, TryFromPrimitive, Debug, Clone, Copy, EnumIter)]
#[repr(i32)]
pub enum Regs {
@ -62,3 +64,42 @@ impl IntoPy<PyObject> for Regs {
pub fn capstone() -> capstone::arch::arm64::ArchCapstoneBuilder {
capstone::Capstone::new().arm64()
}
pub type GuestReg = u64;
impl crate::ArchExtras for crate::CPU {
fn read_return_address<T>(&self) -> Result<T, String>
where
T: From<GuestReg>,
{
self.read_reg(Regs::Lr)
}
fn write_return_address<T>(&self, val: T) -> Result<(), String>
where
T: Into<GuestReg>,
{
self.write_reg(Regs::Lr, val)
}
fn write_function_argument<T>(
&self,
conv: CallingConvention,
idx: i32,
val: T,
) -> Result<(), String>
where
T: Into<GuestReg>,
{
if conv != CallingConvention::Cdecl {
return Err(format!("Unsupported calling convention: {conv:#?}"));
}
let val: GuestReg = val.into();
match idx {
0 => self.write_reg(Regs::X0, val),
1 => self.write_reg(Regs::X1, val),
_ => Err(format!("Unsupported argument: {idx:}")),
}
}
}

View File

@ -5,6 +5,8 @@ use pyo3::prelude::*;
pub use strum_macros::EnumIter;
pub use syscall_numbers::arm::*;
use crate::CallingConvention;
/// Registers for the ARM instruction set.
#[derive(IntoPrimitive, TryFromPrimitive, Debug, Clone, Copy, EnumIter)]
#[repr(i32)]
@ -62,3 +64,42 @@ pub fn capstone_thumb() -> capstone::arch::arm::ArchCapstoneBuilder {
.arm()
.mode(capstone::arch::arm::ArchMode::Thumb)
}
pub type GuestReg = u32;
impl crate::ArchExtras for crate::CPU {
fn read_return_address<T>(&self) -> Result<T, String>
where
T: From<GuestReg>,
{
self.read_reg(Regs::Lr)
}
fn write_return_address<T>(&self, val: T) -> Result<(), String>
where
T: Into<GuestReg>,
{
self.write_reg(Regs::Lr, val)
}
fn write_function_argument<T>(
&self,
conv: CallingConvention,
idx: i32,
val: T,
) -> Result<(), String>
where
T: Into<GuestReg>,
{
if conv != CallingConvention::Cdecl {
return Err(format!("Unsupported calling convention: {conv:#?}"));
}
let val: GuestReg = val.into();
match idx {
0 => self.write_reg(Regs::R0, val),
1 => self.write_reg(Regs::R1, val),
_ => Err(format!("Unsupported argument: {idx:}")),
}
}
}

View File

@ -395,7 +395,7 @@ impl AsanGiovese {
if self.snapshot_shadow {
let set = self.dirty_shadow.lock().unwrap();
for &page in set.iter() {
for &page in &*set {
let data = Self::get_shadow_page(emu, page).to_vec();
self.saved_shadow.insert(page, data);
}
@ -425,7 +425,7 @@ impl AsanGiovese {
if self.snapshot_shadow {
let mut set = self.dirty_shadow.lock().unwrap();
for &page in set.iter() {
for &page in &*set {
let original = self.saved_shadow.get(&page);
if let Some(data) = original {
let cur = Self::get_shadow_page(emu, page);
@ -472,7 +472,7 @@ pub fn init_with_asan(
|e: &str| "LD_PRELOAD=".to_string() + &asan_lib + " " + &e["LD_PRELOAD=".len()..];
let mut added = false;
for (k, v) in env.iter_mut() {
for (k, v) in &mut *env {
if k == "QEMU_SET_ENV" {
let mut new_v = vec![];
for e in v.split(',') {

View File

@ -10,10 +10,10 @@ use libafl::{
use crate::{
capstone,
emu::Emulator,
emu::{ArchExtras, Emulator},
helper::{QemuHelper, QemuHelperTuple, QemuInstrumentationFilter},
hooks::QemuHooks,
GuestAddr, Regs,
GuestAddr,
};
pub trait CallTraceCollector: 'static + Debug {
@ -242,51 +242,7 @@ where
S: UsesInput,
QT: QemuHelperTuple<S>,
{
#[cfg(cpu_target = "x86_64")]
let ret_addr = {
let emu = hooks.emulator();
let stack_ptr: GuestAddr = emu.read_reg(Regs::Rsp).unwrap();
let mut ret_addr = [0; 8];
unsafe { emu.read_mem(stack_ptr, &mut ret_addr) };
GuestAddr::from_le_bytes(ret_addr)
};
#[cfg(cpu_target = "i386")]
let ret_addr = {
let emu = hooks.emulator();
let stack_ptr: GuestAddr = emu.read_reg(Regs::Esp).unwrap();
let mut ret_addr = [0; 4];
unsafe { emu.read_mem(stack_ptr, &mut ret_addr) };
GuestAddr::from_le_bytes(ret_addr)
};
#[cfg(any(cpu_target = "arm", cpu_target = "aarch64"))]
let ret_addr = {
let emu = hooks.emulator();
let ret_addr: GuestAddr = emu.read_reg(Regs::Lr).unwrap();
ret_addr
};
#[cfg(cpu_target = "mips")]
let ret_addr = {
let emu = hooks.emulator();
let ret_addr: GuestAddr = emu.read_reg(Regs::Ra).unwrap();
ret_addr
};
#[cfg(cpu_target = "ppc")]
let ret_addr = {
let emu = hooks.emulator();
let ret_addr: GuestAddr = emu.read_reg(Regs::Lr).unwrap();
ret_addr
};
#[cfg(cpu_target = "hexagon")]
let ret_addr = {
let emu = hooks.emulator();
let ret_addr: GuestAddr = emu.read_reg(Regs::Lr).unwrap();
ret_addr
};
let ret_addr: GuestAddr = hooks.emulator().read_return_address().unwrap();
// log::info!("RET @ 0x{:#x}", ret_addr);

View File

@ -102,8 +102,8 @@ where
if self.full_trace {
if DRCOV_IDS.lock().unwrap().as_ref().unwrap().len() > self.drcov_len {
let mut drcov_vec = Vec::<DrCovBasicBlock>::new();
for id in DRCOV_IDS.lock().unwrap().as_ref().unwrap().iter() {
'pcs_full: for (pc, idm) in DRCOV_MAP.lock().unwrap().as_ref().unwrap().iter() {
for id in DRCOV_IDS.lock().unwrap().as_ref().unwrap() {
'pcs_full: for (pc, idm) in DRCOV_MAP.lock().unwrap().as_ref().unwrap() {
let mut module_found = false;
for module in self.module_mapping.iter() {
let (range, (_, _)) = module;
@ -141,7 +141,7 @@ where
} else {
if DRCOV_MAP.lock().unwrap().as_ref().unwrap().len() > self.drcov_len {
let mut drcov_vec = Vec::<DrCovBasicBlock>::new();
'pcs: for (pc, _) in DRCOV_MAP.lock().unwrap().as_ref().unwrap().iter() {
'pcs: for (pc, _) in DRCOV_MAP.lock().unwrap().as_ref().unwrap() {
let mut module_found = false;
for module in self.module_mapping.iter() {
let (range, (_, _)) = module;

View File

@ -41,7 +41,7 @@ impl<'a> EasyElf<'a> {
#[must_use]
pub fn resolve_symbol(&self, name: &str, load_addr: GuestAddr) -> Option<GuestAddr> {
for sym in self.elf.syms.iter() {
for sym in &self.elf.syms {
if let Some(sym_name) = self.elf.strtab.get_at(sym.st_name) {
if sym_name == name {
return if sym.st_value == 0 {

View File

@ -17,6 +17,8 @@ use num_enum::{IntoPrimitive, TryFromPrimitive};
use num_traits::Num;
use strum_macros::EnumIter;
use crate::GuestReg;
pub type GuestAddr = libafl_qemu_sys::target_ulong;
pub type GuestUsize = libafl_qemu_sys::target_ulong;
pub type GuestIsize = libafl_qemu_sys::target_long;
@ -485,6 +487,28 @@ pub struct CPU {
ptr: CPUStatePtr,
}
#[derive(Debug, PartialEq)]
pub enum CallingConvention {
Cdecl,
}
pub trait ArchExtras {
fn read_return_address<T>(&self) -> Result<T, String>
where
T: From<GuestReg>;
fn write_return_address<T>(&self, val: T) -> Result<(), String>
where
T: Into<GuestReg>;
fn write_function_argument<T>(
&self,
conv: CallingConvention,
idx: i32,
val: T,
) -> Result<(), String>
where
T: Into<GuestReg>;
}
#[allow(clippy::unused_self)]
impl CPU {
#[must_use]
@ -617,8 +641,15 @@ impl CPU {
pub fn write_reg<R, T>(&self, reg: R, val: T) -> Result<(), String>
where
R: Into<i32>,
T: Into<GuestReg>,
{
let reg = reg.into();
#[cfg(feature = "be")]
let val = GuestReg::to_be(val.into());
#[cfg(not(feature = "be"))]
let val = GuestReg::to_le(val.into());
let success = unsafe { libafl_qemu_write_reg(self.ptr, reg, addr_of!(val) as *const u8) };
if success == 0 {
Err(format!("Failed to write to register {reg}"))
@ -630,6 +661,7 @@ impl CPU {
pub fn read_reg<R, T>(&self, reg: R) -> Result<T, String>
where
R: Into<i32>,
T: From<GuestReg>,
{
unsafe {
let reg = reg.into();
@ -638,7 +670,11 @@ impl CPU {
if success == 0 {
Err(format!("Failed to read register {reg}"))
} else {
Ok(val.assume_init())
#[cfg(feature = "be")]
return Ok(GuestReg::from_be(val.assume_init()).into());
#[cfg(not(feature = "be"))]
return Ok(GuestReg::from_le(val.assume_init()).into());
}
}
}
@ -739,11 +775,7 @@ impl Emulator {
envp.push(null());
unsafe {
#[cfg(emulation_mode = "usermode")]
qemu_user_init(
argc,
argv.as_ptr() as *const *const u8,
envp.as_ptr() as *const *const u8,
);
qemu_user_init(argc, argv.as_ptr(), envp.as_ptr());
#[cfg(emulation_mode = "systemmode")]
{
qemu_init(
@ -861,7 +893,7 @@ impl Emulator {
pub fn write_reg<R, T>(&self, reg: R, val: T) -> Result<(), String>
where
T: Num + PartialOrd + Copy,
T: Num + PartialOrd + Copy + Into<GuestReg>,
R: Into<i32>,
{
self.current_cpu().unwrap().write_reg(reg, val)
@ -869,7 +901,7 @@ impl Emulator {
pub fn read_reg<R, T>(&self, reg: R) -> Result<T, String>
where
T: Num + PartialOrd + Copy,
T: Num + PartialOrd + Copy + From<GuestReg>,
R: Into<i32>,
{
self.current_cpu().unwrap().read_reg(reg)
@ -1154,6 +1186,40 @@ impl Emulator {
}
}
impl ArchExtras for Emulator {
fn read_return_address<T>(&self) -> Result<T, String>
where
T: From<GuestReg>,
{
self.current_cpu()
.ok_or("Failed to get current CPU")?
.read_return_address::<T>()
}
fn write_return_address<T>(&self, val: T) -> Result<(), String>
where
T: Into<GuestReg>,
{
self.current_cpu()
.ok_or("Failed to get current CPU")?
.write_return_address::<T>(val)
}
fn write_function_argument<T>(
&self,
conv: CallingConvention,
idx: i32,
val: T,
) -> Result<(), String>
where
T: Into<GuestReg>,
{
self.current_cpu()
.ok_or("Failed to get current CPU")?
.write_function_argument::<T>(conv, idx, val)
}
}
#[cfg(feature = "python")]
pub mod pybind {
use std::convert::TryFrom;

View File

@ -3,6 +3,8 @@ use num_enum::{IntoPrimitive, TryFromPrimitive};
use pyo3::prelude::*;
pub use strum_macros::EnumIter;
use crate::CallingConvention;
#[derive(IntoPrimitive, TryFromPrimitive, Debug, Clone, Copy, EnumIter)]
#[repr(i32)]
pub enum Regs {
@ -66,3 +68,37 @@ impl Regs {
pub const Fp: Regs = Regs::R30;
pub const Lr: Regs = Regs::R31;
}
pub type GuestReg = u32;
impl crate::ArchExtras for crate::CPU {
fn read_return_address<T>(&self) -> Result<T, String>
where
T: From<GuestReg>,
{
self.read_reg(Regs::Lr)
}
fn write_return_address<T>(&self, val: T) -> Result<(), String>
where
T: Into<GuestReg>,
{
self.write_reg(Regs::Lr, val)
}
fn write_function_argument<T>(
&self,
conv: CallingConvention,
idx: i32,
val: T,
) -> Result<(), String>
where
T: Into<GuestReg>,
{
if conv != CallingConvention::Cdecl {
return Err(format!("Unsupported calling convention: {conv:#?}"));
}
Err(format!("Unsupported argument: {idx:}"))
}
}

View File

@ -1,3 +1,5 @@
use std::mem::size_of;
use capstone::arch::BuildsCapstone;
use num_enum::{IntoPrimitive, TryFromPrimitive};
#[cfg(feature = "python")]
@ -5,6 +7,8 @@ use pyo3::prelude::*;
pub use strum_macros::EnumIter;
pub use syscall_numbers::x86::*;
use crate::{CallingConvention, GuestAddr};
#[derive(IntoPrimitive, TryFromPrimitive, Debug, Clone, Copy, EnumIter)]
#[repr(i32)]
pub enum Regs {
@ -41,3 +45,62 @@ pub fn capstone() -> capstone::arch::x86::ArchCapstoneBuilder {
.x86()
.mode(capstone::arch::x86::ArchMode::Mode32)
}
pub type GuestReg = u32;
impl crate::ArchExtras for crate::CPU {
fn read_return_address<T>(&self) -> Result<T, String>
where
T: From<GuestReg>,
{
let stack_ptr: GuestReg = self.read_reg(Regs::Esp)?;
let mut ret_addr = [0; size_of::<GuestReg>()];
unsafe { self.read_mem(stack_ptr, &mut ret_addr) };
Ok(GuestReg::from_le_bytes(ret_addr).into())
}
fn write_return_address<T>(&self, val: T) -> Result<(), String>
where
T: Into<GuestReg>,
{
let stack_ptr: GuestReg = self.read_reg(Regs::Esp)?;
let val: GuestReg = val.into();
let ret_addr = val.to_le_bytes();
unsafe { self.write_mem(stack_ptr, &ret_addr) };
Ok(())
}
fn write_function_argument<T>(
&self,
conv: CallingConvention,
idx: i32,
val: T,
) -> Result<(), String>
where
T: Into<GuestReg>,
{
if conv != CallingConvention::Cdecl {
return Err(format!("Unsupported calling convention: {conv:#?}"));
}
match idx {
0..=1 => {
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.
*/
let size: GuestAddr = size_of::<GuestReg>() as GuestAddr;
let offset = size * (idx as GuestAddr + 1);
let arg = val.to_le_bytes();
unsafe {
self.write_mem(stack_ptr + offset, &arg);
}
Ok(())
}
_ => Err(format!("Unsupported argument: {idx:}")),
}
}
}

View File

@ -4,6 +4,8 @@ use pyo3::prelude::*;
pub use strum_macros::EnumIter;
pub use syscall_numbers::mips::*;
use crate::CallingConvention;
/// Registers for the MIPS instruction set.
#[derive(IntoPrimitive, TryFromPrimitive, Debug, Clone, Copy, EnumIter)]
#[repr(i32)]
@ -62,3 +64,42 @@ impl IntoPy<PyObject> for Regs {
pub fn capstone() -> capstone::arch::mips::ArchCapstoneBuilder {
capstone::Capstone::new().mips()
}
pub type GuestReg = u32;
impl crate::ArchExtras for crate::CPU {
fn read_return_address<T>(&self) -> Result<T, String>
where
T: From<GuestReg>,
{
self.read_reg(Regs::Ra)
}
fn write_return_address<T>(&self, val: T) -> Result<(), String>
where
T: Into<GuestReg>,
{
self.write_reg(Regs::Ra, val)
}
fn write_function_argument<T>(
&self,
conv: CallingConvention,
idx: i32,
val: T,
) -> Result<(), String>
where
T: Into<GuestReg>,
{
if conv != CallingConvention::Cdecl {
return Err(format!("Unsupported calling convention: {conv:#?}"));
}
let val: GuestReg = val.into();
match idx {
0 => self.write_reg(Regs::A0, val),
1 => self.write_reg(Regs::A1, val),
_ => Err(format!("Unsupported argument: {idx:}")),
}
}
}

View File

@ -4,6 +4,8 @@ use pyo3::prelude::*;
pub use strum_macros::EnumIter;
pub use syscall_numbers::powerpc::*;
use crate::CallingConvention;
/// Registers for the MIPS instruction set.
#[derive(IntoPrimitive, TryFromPrimitive, Debug, Clone, Copy, EnumIter)]
#[repr(i32)]
@ -87,6 +89,7 @@ pub enum Regs {
#[allow(non_upper_case_globals)]
impl Regs {
pub const Pc: Regs = Regs::Nip;
pub const Sp: Regs = Regs::R1;
}
#[cfg(feature = "python")]
@ -101,3 +104,42 @@ impl IntoPy<PyObject> for Regs {
pub fn capstone() -> capstone::arch::ppc::ArchCapstoneBuilder {
capstone::Capstone::new().ppc()
}
pub type GuestReg = u32;
impl crate::ArchExtras for crate::CPU {
fn read_return_address<T>(&self) -> Result<T, String>
where
T: From<GuestReg>,
{
self.read_reg(Regs::Lr)
}
fn write_return_address<T>(&self, val: T) -> Result<(), String>
where
T: Into<GuestReg>,
{
self.write_reg(Regs::Lr, val)
}
fn write_function_argument<T>(
&self,
conv: CallingConvention,
idx: i32,
val: T,
) -> Result<(), String>
where
T: Into<GuestReg>,
{
if conv != CallingConvention::Cdecl {
return Err(format!("Unsupported calling convention: {conv:#?}"));
}
let val: GuestReg = val.into();
match idx {
0 => self.write_reg(Regs::R3, val),
1 => self.write_reg(Regs::R4, val),
_ => Err(format!("Unsupported argument: {idx:}")),
}
}
}

View File

@ -211,7 +211,7 @@ impl QemuSnapshotHelper {
{
let new_maps = self.new_maps.get_mut().unwrap();
for acc in self.accesses.iter_mut() {
for acc in &mut self.accesses {
unsafe { &mut (*acc.get()) }.dirty.retain(|page| {
if let Some(info) = self.pages.get_mut(page) {
// TODO avoid duplicated memcpy
@ -251,7 +251,7 @@ impl QemuSnapshotHelper {
self.reset_maps(emulator);
// This one is after that we remapped potential regions mapped at snapshot time but unmapped during execution
for acc in self.accesses.iter_mut() {
for acc in &mut self.accesses {
for page in unsafe { &(*acc.get()).dirty } {
for entry in self
.maps

View File

@ -1,3 +1,5 @@
use std::mem::size_of;
use capstone::arch::BuildsCapstone;
use num_enum::{IntoPrimitive, TryFromPrimitive};
#[cfg(feature = "python")]
@ -5,6 +7,8 @@ use pyo3::prelude::*;
pub use strum_macros::EnumIter;
pub use syscall_numbers::x86_64::*;
use crate::CallingConvention;
#[derive(IntoPrimitive, TryFromPrimitive, Debug, Clone, Copy, EnumIter)]
#[repr(i32)]
pub enum Regs {
@ -50,3 +54,49 @@ pub fn capstone() -> capstone::arch::x86::ArchCapstoneBuilder {
.x86()
.mode(capstone::arch::x86::ArchMode::Mode64)
}
pub type GuestReg = u64;
impl crate::ArchExtras for crate::CPU {
fn read_return_address<T>(&self) -> Result<T, String>
where
T: From<GuestReg>,
{
let stack_ptr: GuestReg = self.read_reg(Regs::Rsp)?;
let mut ret_addr = [0; size_of::<GuestReg>()];
unsafe { self.read_mem(stack_ptr, &mut ret_addr) };
Ok(GuestReg::from_le_bytes(ret_addr).into())
}
fn write_return_address<T>(&self, val: T) -> Result<(), String>
where
T: Into<GuestReg>,
{
let stack_ptr: GuestReg = self.read_reg(Regs::Rsp)?;
let val: GuestReg = val.into();
let ret_addr = val.to_le_bytes();
unsafe { self.write_mem(stack_ptr, &ret_addr) };
Ok(())
}
fn write_function_argument<T>(
&self,
conv: CallingConvention,
idx: i32,
val: T,
) -> Result<(), String>
where
T: Into<GuestReg>,
{
if conv != CallingConvention::Cdecl {
return Err(format!("Unsupported calling convention: {conv:#?}"));
}
let val: GuestReg = val.into();
match idx {
0 => self.write_reg(Regs::Rdi, val),
1 => self.write_reg(Regs::Rsi, val),
_ => Err(format!("Unsupported argument: {idx:}")),
}
}
}

View File

@ -165,7 +165,7 @@ mod observers {
let mut hasher = RandomState::with_seeds(0, 0, 0, 0).build_hasher();
for map in unsafe { &COUNTERS_MAPS } {
let slice = map.as_slice();
let ptr = slice.as_ptr() as *const u8;
let ptr = slice.as_ptr();
let map_size = slice.len() / core::mem::size_of::<u8>();
unsafe {
hasher.write(from_raw_parts(ptr, map_size));

View File

@ -22,11 +22,11 @@ pub unsafe extern "C" fn __sanitizer_cov_trace_pc_guard(guard: *mut u32) {
{
#[cfg(feature = "sancov_pcguard_edges")]
{
(EDGES_MAP_PTR as *mut u8).add(pos).write(1);
EDGES_MAP_PTR.add(pos).write(1);
}
#[cfg(feature = "sancov_pcguard_hitcounts")]
{
let addr = (EDGES_MAP_PTR as *mut u8).add(pos);
let addr = EDGES_MAP_PTR.add(pos);
let val = addr.read().wrapping_add(1);
addr.write(val);
}

View File

@ -208,7 +208,7 @@ fn postprocess(pda: &[Transition], stack_limit: usize) -> Automaton {
//let mut culled_pda_unique = HashSet::new();
for final_state in &finals {
for transition in pda.iter() {
for transition in pda {
if transition.dest == *final_state && transition.stack.len() > 0 {
blocklist.insert(transition.dest);
} else {
@ -267,7 +267,7 @@ fn postprocess(pda: &[Transition], stack_limit: usize) -> Automaton {
}
} else {
// Running FSA construction in exact approximation mode and postprocessing it like so
for transition in pda.iter() {
for transition in pda {
num_transition += 1;
let state = transition.source;
if state >= memoized.len() {