Fix GuestMaps iterator in LibAFL QEMU. (#2041)

* Fix maps iterator.

* Use native QEMU structs instead of pointer casting.

* Update stub bindings.

* Maps operations stored in usermode.
This commit is contained in:
Romain Malmain 2024-04-12 14:40:53 +02:00 committed by GitHub
parent 8b9b5a8767
commit ec935bf95f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 263 additions and 37 deletions

View File

@ -45,6 +45,9 @@ const WRAPPER_HEADER: &str = r#"
#include "user/safe-syscall.h" #include "user/safe-syscall.h"
#include "qemu/selfmap.h" #include "qemu/selfmap.h"
#include "cpu_loop-common.h" #include "cpu_loop-common.h"
#include "qemu/selfmap.h"
#include "libafl/user.h"
#else #else
@ -55,8 +58,8 @@ const WRAPPER_HEADER: &str = r#"
#include "sysemu/tcg.h" #include "sysemu/tcg.h"
#include "sysemu/replay.h" #include "sysemu/replay.h"
#include "libafl_extras/syx-snapshot/device-save.h" #include "libafl/syx-snapshot/device-save.h"
#include "libafl_extras/syx-snapshot/syx-snapshot.h" #include "libafl/syx-snapshot/syx-snapshot.h"
#endif #endif
@ -76,9 +79,9 @@ const WRAPPER_HEADER: &str = r#"
#include "qemu/plugin-memory.h" #include "qemu/plugin-memory.h"
#include "libafl_extras/exit.h" #include "libafl/exit.h"
#include "libafl_extras/hook.h" #include "libafl/hook.h"
#include "libafl_extras/jit.h" #include "libafl/jit.h"
"#; "#;
@ -142,6 +145,8 @@ pub fn generate(
.allowlist_type("libafl_exit_reason_sync_backdoor") .allowlist_type("libafl_exit_reason_sync_backdoor")
.allowlist_type("libafl_exit_reason_breakpoint") .allowlist_type("libafl_exit_reason_breakpoint")
.allowlist_type("Syx.*") .allowlist_type("Syx.*")
.allowlist_type("libafl_mapinfo")
.allowlist_type("IntervalTreeRoot")
.allowlist_function("qemu_user_init") .allowlist_function("qemu_user_init")
.allowlist_function("target_mmap") .allowlist_function("target_mmap")
.allowlist_function("target_mprotect") .allowlist_function("target_mprotect")
@ -159,6 +164,8 @@ pub fn generate(
.allowlist_function("syx_.*") .allowlist_function("syx_.*")
.allowlist_function("device_list_all") .allowlist_function("device_list_all")
.allowlist_function("libafl_.*") .allowlist_function("libafl_.*")
.allowlist_function("read_self_maps")
.allowlist_function("free_self_maps")
.blocklist_function("main_loop_wait") // bindgen issue #1313 .blocklist_function("main_loop_wait") // bindgen issue #1313
.parse_callbacks(Box::new(bindgen::CargoCallbacks::new())); .parse_callbacks(Box::new(bindgen::CargoCallbacks::new()));

View File

@ -8,7 +8,7 @@ use which::which;
const QEMU_URL: &str = "https://github.com/AFLplusplus/qemu-libafl-bridge"; const QEMU_URL: &str = "https://github.com/AFLplusplus/qemu-libafl-bridge";
const QEMU_DIRNAME: &str = "qemu-libafl-bridge"; const QEMU_DIRNAME: &str = "qemu-libafl-bridge";
const QEMU_REVISION: &str = "e99b9da6585504a8333f2846a61de487f94d3476"; const QEMU_REVISION: &str = "50b0c90e0aab07643ccb58cfbbef742bcfb8b7d1";
#[allow(clippy::module_name_repetitions)] #[allow(clippy::module_name_repetitions)]
pub struct BuildResult { pub struct BuildResult {

View File

@ -98,7 +98,7 @@ macro_rules! extern_c_checked {
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
use core::ops::BitAnd; use core::ops::BitAnd;
use std::{ffi::c_void, slice::from_raw_parts, str::from_utf8_unchecked}; use std::ffi::c_void;
#[cfg(all(not(feature = "clippy"), target_os = "linux"))] #[cfg(all(not(feature = "clippy"), target_os = "linux"))]
pub use bindings::*; pub use bindings::*;
@ -127,7 +127,7 @@ pub struct MapInfo {
start: GuestAddr, start: GuestAddr,
end: GuestAddr, end: GuestAddr,
offset: GuestAddr, offset: GuestAddr,
path: *const u8, path: Option<String>,
flags: i32, flags: i32,
is_priv: i32, is_priv: i32,
} }
@ -227,17 +227,8 @@ impl MapInfo {
} }
#[must_use] #[must_use]
pub fn path(&self) -> Option<&str> { pub fn path(&self) -> Option<&String> {
if self.path.is_null() { self.path.as_ref()
None
} else {
unsafe {
Some(from_utf8_unchecked(from_raw_parts(
self.path,
strlen(self.path),
)))
}
}
} }
#[must_use] #[must_use]

View File

@ -1,10 +1,10 @@
use core::ffi::c_void; use core::{slice::from_raw_parts, str::from_utf8_unchecked};
use num_enum::{IntoPrimitive, TryFromPrimitive}; use num_enum::{IntoPrimitive, TryFromPrimitive};
use paste::paste; use paste::paste;
use strum_macros::EnumIter; use strum_macros::EnumIter;
use crate::{extern_c_checked, GuestAddr, MapInfo}; use crate::{extern_c_checked, libafl_mapinfo, strlen, GuestAddr, MapInfo};
extern_c_checked! { extern_c_checked! {
pub fn qemu_user_init(argc: i32, argv: *const *const u8, envp: *const *const u8) -> i32; pub fn qemu_user_init(argc: i32, argv: *const *const u8, envp: *const *const u8) -> i32;
@ -15,12 +15,6 @@ extern_c_checked! {
pub fn libafl_get_brk() -> u64; pub fn libafl_get_brk() -> u64;
pub fn libafl_set_brk(brk: u64) -> u64; pub fn libafl_set_brk(brk: u64) -> u64;
pub fn read_self_maps() -> *const c_void;
pub fn free_self_maps(map_info: *const c_void);
pub fn libafl_maps_first(root: *const c_void) -> *const c_void;
pub fn libafl_maps_next(node: *const c_void, ret: *mut MapInfo, is_root: bool) -> *const c_void;
pub static exec_path: *const u8; pub static exec_path: *const u8;
pub static guest_base: usize; pub static guest_base: usize;
pub static mut mmap_next_start: GuestAddr; pub static mut mmap_next_start: GuestAddr;
@ -35,3 +29,30 @@ pub enum VerifyAccess {
Read = libc::PROT_READ, Read = libc::PROT_READ,
Write = libc::PROT_READ | libc::PROT_WRITE, Write = libc::PROT_READ | libc::PROT_WRITE,
} }
impl From<libafl_mapinfo> for MapInfo {
fn from(map_info: libafl_mapinfo) -> Self {
let path: Option<String> = if map_info.path.is_null() {
None
} else {
unsafe {
Some(
from_utf8_unchecked(from_raw_parts(
map_info.path as *const u8,
strlen(map_info.path as *const u8),
))
.to_string(),
)
}
};
MapInfo {
start: map_info.start,
end: map_info.end,
offset: map_info.offset,
path,
flags: map_info.flags,
is_priv: map_info.is_priv,
}
}
}

View File

@ -11650,6 +11650,95 @@ impl Default for RBNode {
} }
#[repr(C)] #[repr(C)]
#[derive(Debug, Copy, Clone)] #[derive(Debug, Copy, Clone)]
pub struct RBRoot {
pub rb_node: *mut RBNode,
}
#[test]
fn bindgen_test_layout_RBRoot() {
const UNINIT: ::std::mem::MaybeUninit<RBRoot> = ::std::mem::MaybeUninit::uninit();
let ptr = UNINIT.as_ptr();
assert_eq!(
::std::mem::size_of::<RBRoot>(),
8usize,
concat!("Size of: ", stringify!(RBRoot))
);
assert_eq!(
::std::mem::align_of::<RBRoot>(),
8usize,
concat!("Alignment of ", stringify!(RBRoot))
);
assert_eq!(
unsafe { ::std::ptr::addr_of!((*ptr).rb_node) as usize - ptr as usize },
0usize,
concat!(
"Offset of field: ",
stringify!(RBRoot),
"::",
stringify!(rb_node)
)
);
}
impl Default for RBRoot {
fn default() -> Self {
let mut s = ::std::mem::MaybeUninit::<Self>::uninit();
unsafe {
::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1);
s.assume_init()
}
}
}
#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct RBRootLeftCached {
pub rb_root: RBRoot,
pub rb_leftmost: *mut RBNode,
}
#[test]
fn bindgen_test_layout_RBRootLeftCached() {
const UNINIT: ::std::mem::MaybeUninit<RBRootLeftCached> = ::std::mem::MaybeUninit::uninit();
let ptr = UNINIT.as_ptr();
assert_eq!(
::std::mem::size_of::<RBRootLeftCached>(),
16usize,
concat!("Size of: ", stringify!(RBRootLeftCached))
);
assert_eq!(
::std::mem::align_of::<RBRootLeftCached>(),
8usize,
concat!("Alignment of ", stringify!(RBRootLeftCached))
);
assert_eq!(
unsafe { ::std::ptr::addr_of!((*ptr).rb_root) as usize - ptr as usize },
0usize,
concat!(
"Offset of field: ",
stringify!(RBRootLeftCached),
"::",
stringify!(rb_root)
)
);
assert_eq!(
unsafe { ::std::ptr::addr_of!((*ptr).rb_leftmost) as usize - ptr as usize },
8usize,
concat!(
"Offset of field: ",
stringify!(RBRootLeftCached),
"::",
stringify!(rb_leftmost)
)
);
}
impl Default for RBRootLeftCached {
fn default() -> Self {
let mut s = ::std::mem::MaybeUninit::<Self>::uninit();
unsafe {
::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1);
s.assume_init()
}
}
}
#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct IntervalTreeNode { pub struct IntervalTreeNode {
pub rb: RBNode, pub rb: RBNode,
pub start: u64, pub start: u64,
@ -11720,6 +11809,7 @@ impl Default for IntervalTreeNode {
} }
} }
} }
pub type IntervalTreeRoot = RBRootLeftCached;
#[repr(C)] #[repr(C)]
#[derive(Debug, Copy, Clone)] #[derive(Debug, Copy, Clone)]
pub struct tb_tc { pub struct tb_tc {
@ -11983,6 +12073,14 @@ extern "C" {
extern "C" { extern "C" {
pub fn target_munmap(start: abi_ulong, len: abi_ulong) -> ::std::os::raw::c_int; pub fn target_munmap(start: abi_ulong, len: abi_ulong) -> ::std::os::raw::c_int;
} }
extern "C" {
#[doc = " read_self_maps:\n\n Read /proc/self/maps and return a tree of MapInfo structures."]
pub fn read_self_maps() -> *mut IntervalTreeRoot;
}
extern "C" {
#[doc = " free_self_maps:\n @info: an interval tree\n\n Free a tree of MapInfo structures."]
pub fn free_self_maps(root: *mut IntervalTreeRoot);
}
extern "C" { extern "C" {
pub fn libafl_breakpoint_invalidate(cpu: *mut CPUState, pc: target_ulong); pub fn libafl_breakpoint_invalidate(cpu: *mut CPUState, pc: target_ulong);
} }
@ -12311,6 +12409,109 @@ extern "C" {
} }
#[repr(C)] #[repr(C)]
#[derive(Debug, Copy, Clone)] #[derive(Debug, Copy, Clone)]
pub struct libafl_mapinfo {
pub start: target_ulong,
pub end: target_ulong,
pub offset: target_ulong,
pub path: *const ::std::os::raw::c_char,
pub flags: ::std::os::raw::c_int,
pub is_priv: ::std::os::raw::c_int,
}
#[test]
fn bindgen_test_layout_libafl_mapinfo() {
const UNINIT: ::std::mem::MaybeUninit<libafl_mapinfo> = ::std::mem::MaybeUninit::uninit();
let ptr = UNINIT.as_ptr();
assert_eq!(
::std::mem::size_of::<libafl_mapinfo>(),
40usize,
concat!("Size of: ", stringify!(libafl_mapinfo))
);
assert_eq!(
::std::mem::align_of::<libafl_mapinfo>(),
8usize,
concat!("Alignment of ", stringify!(libafl_mapinfo))
);
assert_eq!(
unsafe { ::std::ptr::addr_of!((*ptr).start) as usize - ptr as usize },
0usize,
concat!(
"Offset of field: ",
stringify!(libafl_mapinfo),
"::",
stringify!(start)
)
);
assert_eq!(
unsafe { ::std::ptr::addr_of!((*ptr).end) as usize - ptr as usize },
8usize,
concat!(
"Offset of field: ",
stringify!(libafl_mapinfo),
"::",
stringify!(end)
)
);
assert_eq!(
unsafe { ::std::ptr::addr_of!((*ptr).offset) as usize - ptr as usize },
16usize,
concat!(
"Offset of field: ",
stringify!(libafl_mapinfo),
"::",
stringify!(offset)
)
);
assert_eq!(
unsafe { ::std::ptr::addr_of!((*ptr).path) as usize - ptr as usize },
24usize,
concat!(
"Offset of field: ",
stringify!(libafl_mapinfo),
"::",
stringify!(path)
)
);
assert_eq!(
unsafe { ::std::ptr::addr_of!((*ptr).flags) as usize - ptr as usize },
32usize,
concat!(
"Offset of field: ",
stringify!(libafl_mapinfo),
"::",
stringify!(flags)
)
);
assert_eq!(
unsafe { ::std::ptr::addr_of!((*ptr).is_priv) as usize - ptr as usize },
36usize,
concat!(
"Offset of field: ",
stringify!(libafl_mapinfo),
"::",
stringify!(is_priv)
)
);
}
impl Default for libafl_mapinfo {
fn default() -> Self {
let mut s = ::std::mem::MaybeUninit::<Self>::uninit();
unsafe {
::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1);
s.assume_init()
}
}
}
extern "C" {
pub fn libafl_maps_first(map_info: *mut IntervalTreeRoot) -> *mut IntervalTreeNode;
}
extern "C" {
pub fn libafl_maps_next(
node: *mut IntervalTreeNode,
ret: *mut libafl_mapinfo,
) -> *mut IntervalTreeNode;
}
#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct AccelCPUClass { pub struct AccelCPUClass {
pub parent_class: ObjectClass, pub parent_class: ObjectClass,
pub cpu_class_init: ::std::option::Option<unsafe extern "C" fn(cc: *mut CPUClass)>, pub cpu_class_init: ::std::option::Option<unsafe extern "C" fn(cc: *mut CPUClass)>,
@ -13669,6 +13870,9 @@ extern "C" {
extern "C" { extern "C" {
pub fn libafl_qemu_remove_new_thread_hook(num: usize) -> ::std::os::raw::c_int; pub fn libafl_qemu_remove_new_thread_hook(num: usize) -> ::std::os::raw::c_int;
} }
extern "C" {
pub fn libafl_tcg_gen_asan(addr: *mut TCGTemp, size: usize);
}
extern "C" { extern "C" {
pub fn libafl_jit_trace_edge_hitcount(data: u64, id: u64) -> usize; pub fn libafl_jit_trace_edge_hitcount(data: u64, id: u64) -> usize;
} }

View File

@ -1,11 +1,11 @@
use core::{ffi::c_void, mem::MaybeUninit, ptr::copy_nonoverlapping}; use core::{mem::MaybeUninit, ptr::copy_nonoverlapping};
use std::{cell::OnceCell, slice::from_raw_parts, str::from_utf8_unchecked}; use std::{cell::OnceCell, slice::from_raw_parts, str::from_utf8_unchecked};
use libafl_qemu_sys::{ use libafl_qemu_sys::{
exec_path, free_self_maps, guest_base, libafl_dump_core_hook, libafl_force_dfl, libafl_get_brk, exec_path, free_self_maps, guest_base, libafl_dump_core_hook, libafl_force_dfl, libafl_get_brk,
libafl_load_addr, libafl_maps_first, libafl_maps_next, libafl_qemu_run, libafl_set_brk, libafl_load_addr, libafl_maps_first, libafl_maps_next, libafl_qemu_run, libafl_set_brk,
mmap_next_start, read_self_maps, strlen, GuestAddr, GuestUsize, MapInfo, MmapPerms, mmap_next_start, read_self_maps, strlen, GuestAddr, GuestUsize, IntervalTreeNode,
VerifyAccess, IntervalTreeRoot, MapInfo, MmapPerms, VerifyAccess,
}; };
use libc::c_int; use libc::c_int;
#[cfg(feature = "python")] #[cfg(feature = "python")]
@ -27,8 +27,8 @@ pub enum HandlerError {
#[cfg_attr(feature = "python", pyclass(unsendable))] #[cfg_attr(feature = "python", pyclass(unsendable))]
pub struct GuestMaps { pub struct GuestMaps {
maps_root: *const c_void, maps_root: *mut IntervalTreeRoot,
maps_node: *const c_void, maps_node: *mut IntervalTreeNode,
} }
// Consider a private new only for Emulator // Consider a private new only for Emulator
@ -51,13 +51,16 @@ impl Iterator for GuestMaps {
#[allow(clippy::uninit_assumed_init)] #[allow(clippy::uninit_assumed_init)]
fn next(&mut self) -> Option<Self::Item> { fn next(&mut self) -> Option<Self::Item> {
if self.maps_node.is_null() {
return None;
}
unsafe { unsafe {
let mut ret = MaybeUninit::uninit(); let mut ret = MaybeUninit::uninit();
self.maps_node = libafl_maps_next(self.maps_node, ret.as_mut_ptr(), false);
Some(ret.assume_init()) self.maps_node = libafl_maps_next(self.maps_node, ret.as_mut_ptr());
if self.maps_node.is_null() {
None
} else {
Some(ret.assume_init().into())
}
} }
} }
} }