Improve libafl_qemu snapshots (#484)

* mprotect

* expose EnumIter

* thread safe mem snapshot

* update qemu hash

* clippy

* child helpers

* fixes

* fix build

* fix dep
This commit is contained in:
Andrea Fioraldi 2022-02-09 09:40:59 +01:00 committed by GitHub
parent 6bfbdd6318
commit 63d89463a3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 272 additions and 78 deletions

View File

@ -60,7 +60,7 @@ num_enum = { version = "0.5.4", default-features = false }
typed-builder = "0.9.1" # Implement the builder pattern at compiletime
ahash = { version = "0.7", default-features=false, features=["compile-time-rng"] } # The hash function already used in hashbrown
intervaltree = { version = "0.2.7", default-features = false, features = ["serde"] }
backtrace = {version = "0.3.62", optional = true} # Used to get the stacktrace in StacktraceObserver
backtrace = {version = "0.3", optional = true} # Used to get the stacktrace in StacktraceObserver
serde_json = { version = "1.0", optional = true, default-features = false, features = ["alloc"] }
miniz_oxide = { version = "0.5", optional = true}

View File

@ -36,7 +36,7 @@ capstone = "0.10.0"
color-backtrace ={ version = "0.5", features = [ "resolve-modules" ] }
termcolor = "1.1.2"
serde = "1.0"
backtrace = { version = "0.3.58", default-features = false, features = ["std", "serde"] }
backtrace = { version = "0.3", default-features = false, features = ["std", "serde"] }
num-traits = "0.2.14"
ahash = "0.7"
paste = "1.0"

View File

@ -34,7 +34,9 @@ goblin = "0.4.2"
libc = "0.2"
strum = "0.21"
strum_macros = "0.21"
syscall-numbers = "2.0.0"
syscall-numbers = "2.0"
bio = "0.39"
thread_local = "1.1.3"
#pyo3 = { version = "0.15", features = ["extension-module"], optional = true }
pyo3 = { version = "0.15", optional = true }

View File

@ -3,7 +3,7 @@ use which::which;
const QEMU_URL: &str = "https://github.com/AFLplusplus/qemu-libafl-bridge";
const QEMU_DIRNAME: &str = "qemu-libafl-bridge";
const QEMU_REVISION: &str = "fa2b9c4a25f548f15b3d1b1afcfdb75cc7165f9a";
const QEMU_REVISION: &str = "152fdbe024493f31e60060714caee3b90fdf3d9e";
fn build_dep_check(tools: &[&str]) {
for tool in tools {
@ -221,7 +221,6 @@ fn main() {
"--disable-vvfat",
"--disable-xen",
"--disable-xen-pci-passthrough",
"--disable-xfsctl",
])
.status()
.expect("Configure failed");
@ -248,7 +247,7 @@ fn main() {
for dir in &[
build_dir.join("libcommon.fa.p"),
build_dir.join(&format!("libqemu-{}-linux-user.fa.p", cpu_target)),
build_dir.join("libcommon-user.fa.p"),
//build_dir.join("libcommon-user.fa.p"),
//build_dir.join("libqemuutil.a.p"),
//build_dir.join("libqom.fa.p"),
//build_dir.join("libhwcore.fa.p"),

View File

@ -1,5 +1,5 @@
use num_enum::{IntoPrimitive, TryFromPrimitive};
use strum_macros::EnumIter;
pub use strum_macros::EnumIter;
#[cfg(feature = "python")]
use pyo3::prelude::*;

View File

@ -1,5 +1,5 @@
use num_enum::{IntoPrimitive, TryFromPrimitive};
use strum_macros::EnumIter;
pub use strum_macros::EnumIter;
#[cfg(feature = "python")]
use pyo3::prelude::*;

View File

@ -162,8 +162,9 @@ pub fn init_with_asan(args: &mut Vec<String>, env: &mut [(String, String)]) -> E
Emulator::new(args, env)
}
pub type QemuAsanChildHelper = QemuAsanHelper;
#[derive(Debug)]
// TODO intrumentation filter
pub struct QemuAsanHelper {
enabled: bool,
filter: QemuInstrumentationFilter,
@ -418,6 +419,8 @@ where
I: Input,
S: HasMetadata,
{
const HOOKS_DO_SIDE_EFFECTS: bool = false;
fn init<'a, H, OT, QT>(&self, executor: &QemuExecutor<'a, H, I, OT, QT, S>)
where
H: FnMut(&I) -> ExitKind,

View File

@ -8,7 +8,7 @@ use serde::{Deserialize, Serialize};
use crate::{
emu::Emulator,
executor::QemuExecutor,
helper::{QemuHelper, QemuHelperTuple, QemuInstrumentationFilter},
helper::{hash_me, QemuHelper, QemuHelperTuple, QemuInstrumentationFilter},
};
#[derive(Debug, Default, Serialize, Deserialize)]
@ -78,6 +78,57 @@ where
}
}
#[derive(Debug)]
pub struct QemuCmpLogChildHelper {
filter: QemuInstrumentationFilter,
}
impl QemuCmpLogChildHelper {
#[must_use]
pub fn new() -> Self {
Self {
filter: QemuInstrumentationFilter::None,
}
}
#[must_use]
pub fn with_instrumentation_filter(filter: QemuInstrumentationFilter) -> Self {
Self { filter }
}
#[must_use]
pub fn must_instrument(&self, addr: u64) -> bool {
self.filter.allowed(addr)
}
}
impl Default for QemuCmpLogChildHelper {
fn default() -> Self {
Self::new()
}
}
impl<I, S> QemuHelper<I, S> for QemuCmpLogChildHelper
where
I: Input,
S: HasMetadata,
{
const HOOKS_DO_SIDE_EFFECTS: bool = false;
fn init<'a, H, OT, QT>(&self, executor: &QemuExecutor<'a, H, I, OT, QT, S>)
where
H: FnMut(&I) -> ExitKind,
OT: ObserversTuple<I, S>,
QT: QemuHelperTuple<I, S>,
{
executor.hook_cmp_generation(gen_hashed_cmp_ids::<I, QT, S>);
executor.emulator().set_exec_cmp8_hook(trace_cmp8_cmplog);
executor.emulator().set_exec_cmp4_hook(trace_cmp4_cmplog);
executor.emulator().set_exec_cmp2_hook(trace_cmp2_cmplog);
executor.emulator().set_exec_cmp1_hook(trace_cmp1_cmplog);
}
}
pub fn gen_unique_cmp_ids<I, QT, S>(
_emulator: &Emulator,
helpers: &mut QT,
@ -110,6 +161,26 @@ where
}))
}
pub fn gen_hashed_cmp_ids<I, QT, S>(
_emulator: &Emulator,
helpers: &mut QT,
_state: &mut S,
pc: u64,
_size: usize,
) -> Option<u64>
where
S: HasMetadata,
I: Input,
QT: QemuHelperTuple<I, S>,
{
if let Some(h) = helpers.match_first_type::<QemuCmpLogChildHelper>() {
if !h.must_instrument(pc) {
return None;
}
}
Some(hash_me(pc))
}
pub extern "C" fn trace_cmp1_cmplog(id: u64, v0: u8, v1: u8) {
unsafe {
__libafl_targets_cmplog_instructions(id as usize, 1, u64::from(v0), u64::from(v1));

View File

@ -1,13 +1,15 @@
use hashbrown::{hash_map::Entry, HashMap};
use libafl::{executors::ExitKind, inputs::Input, observers::ObserversTuple, state::HasMetadata};
pub use libafl_targets::{EDGES_MAP, EDGES_MAP_SIZE, MAX_EDGES_NUM};
pub use libafl_targets::{
edges_max_num, EDGES_MAP, EDGES_MAP_PTR, EDGES_MAP_PTR_SIZE, EDGES_MAP_SIZE, MAX_EDGES_NUM,
};
use serde::{Deserialize, Serialize};
use std::{cell::UnsafeCell, cmp::max};
use crate::{
emu::Emulator,
executor::QemuExecutor,
helper::{QemuHelper, QemuHelperTuple, QemuInstrumentationFilter},
helper::{hash_me, QemuHelper, QemuHelperTuple, QemuInstrumentationFilter},
};
#[derive(Debug, Default, Serialize, Deserialize)]
@ -74,15 +76,58 @@ where
}
}
thread_local!(static PREV_LOC : UnsafeCell<u64> = UnsafeCell::new(0));
pub type QemuEdgeCoverageWithBlocksHelper = QemuEdgeCoverageChildHelper;
fn hash_me(mut x: u64) -> u64 {
x = (x.overflowing_shr(16).0 ^ x).overflowing_mul(0x45d9f3b).0;
x = (x.overflowing_shr(16).0 ^ x).overflowing_mul(0x45d9f3b).0;
x = (x.overflowing_shr(16).0 ^ x) ^ x;
x
#[derive(Debug)]
pub struct QemuEdgeCoverageChildHelper {
filter: QemuInstrumentationFilter,
}
impl QemuEdgeCoverageChildHelper {
#[must_use]
pub fn new() -> Self {
Self {
filter: QemuInstrumentationFilter::None,
}
}
#[must_use]
pub fn with_instrumentation_filter(filter: QemuInstrumentationFilter) -> Self {
Self { filter }
}
#[must_use]
pub fn must_instrument(&self, addr: u64) -> bool {
self.filter.allowed(addr)
}
}
impl Default for QemuEdgeCoverageChildHelper {
fn default() -> Self {
Self::new()
}
}
impl<I, S> QemuHelper<I, S> for QemuEdgeCoverageChildHelper
where
I: Input,
S: HasMetadata,
{
const HOOKS_DO_SIDE_EFFECTS: bool = false;
fn init<'a, H, OT, QT>(&self, executor: &QemuExecutor<'a, H, I, OT, QT, S>)
where
H: FnMut(&I) -> ExitKind,
OT: ObserversTuple<I, S>,
QT: QemuHelperTuple<I, S>,
{
executor.hook_edge_generation(gen_unique_edge_ids::<I, QT, S>);
executor.emulator().set_exec_edge_hook(trace_edge_hitcount);
}
}
thread_local!(static PREV_LOC : UnsafeCell<u64> = UnsafeCell::new(0));
pub fn gen_unique_edge_ids<I, QT, S>(
_emulator: &Emulator,
helpers: &mut QT,
@ -140,7 +185,7 @@ where
I: Input,
QT: QemuHelperTuple<I, S>,
{
if let Some(h) = helpers.match_first_type::<QemuEdgeCoverageHelper>() {
if let Some(h) = helpers.match_first_type::<QemuEdgeCoverageChildHelper>() {
if !h.must_instrument(src) && !h.must_instrument(dest) {
return None;
}
@ -181,8 +226,9 @@ pub fn gen_hashed_block_ids<I, QT, S>(
pub extern "C" fn trace_block_transition_hitcount(id: u64) {
unsafe {
PREV_LOC.with(|prev_loc| {
let x = ((*prev_loc.get() ^ id) as usize) & (EDGES_MAP_SIZE - 1);
EDGES_MAP[x] = EDGES_MAP[x].wrapping_add(1);
let x = ((*prev_loc.get() ^ id) as usize) & (EDGES_MAP_PTR_SIZE - 1);
let entry = EDGES_MAP_PTR.add(x);
*entry = (*entry).wrapping_add(1);
*prev_loc.get() = id.overflowing_shr(1).0;
});
}
@ -191,8 +237,9 @@ pub extern "C" fn trace_block_transition_hitcount(id: u64) {
pub extern "C" fn trace_block_transition_single(id: u64) {
unsafe {
PREV_LOC.with(|prev_loc| {
let x = ((*prev_loc.get() ^ id) as usize) & (EDGES_MAP_SIZE - 1);
EDGES_MAP[x] = 1;
let x = ((*prev_loc.get() ^ id) as usize) & (EDGES_MAP_PTR_SIZE - 1);
let entry = EDGES_MAP_PTR.add(x);
*entry = 1;
*prev_loc.get() = id.overflowing_shr(1).0;
});
}

View File

@ -27,7 +27,7 @@ use pyo3::{prelude::*, PyIterProtocol};
pub const SKIP_EXEC_HOOK: u64 = u64::MAX;
#[derive(IntoPrimitive, TryFromPrimitive, Debug, Clone, Copy, EnumIter)]
#[derive(IntoPrimitive, TryFromPrimitive, Debug, Clone, Copy, EnumIter, PartialEq)]
#[repr(i32)]
pub enum MmapPerms {
None = 0,
@ -201,7 +201,7 @@ extern "C" {
fn target_mmap(start: u64, len: u64, target_prot: i32, flags: i32, fd: i32, offset: u64)
-> u64;
/// int target_mprotect(abi_ulong start, abi_ulong len, int prot);
/// int target_mprotect(abi_ulong start, abi_ulong len, int prot)
fn target_mprotect(start: u64, len: u64, target_prot: i32) -> i32;
/// int target_munmap(abi_ulong start, abi_ulong len)

View File

@ -11,6 +11,8 @@ pub trait QemuHelper<I, S>: 'static + Debug
where
I: Input,
{
const HOOKS_DO_SIDE_EFFECTS: bool = true;
fn init<'a, H, OT, QT>(&self, _executor: &QemuExecutor<'a, H, I, OT, QT, S>)
where
H: FnMut(&I) -> ExitKind,
@ -114,3 +116,11 @@ impl QemuInstrumentationFilter {
}
}
}
#[must_use]
pub fn hash_me(mut x: u64) -> u64 {
x = (x.overflowing_shr(16).0 ^ x).overflowing_mul(0x45d9f3b).0;
x = (x.overflowing_shr(16).0 ^ x).overflowing_mul(0x45d9f3b).0;
x = (x.overflowing_shr(16).0 ^ x) ^ x;
x
}

View File

@ -1,5 +1,5 @@
use num_enum::{IntoPrimitive, TryFromPrimitive};
use strum_macros::EnumIter;
pub use strum_macros::EnumIter;
#[cfg(feature = "python")]
use pyo3::prelude::*;

View File

@ -1,11 +1,17 @@
use bio::data_structures::interval_tree::IntervalTree;
use libafl::{executors::ExitKind, inputs::Input, observers::ObserversTuple, state::HasMetadata};
use std::collections::HashMap;
use std::{
cell::UnsafeCell,
collections::{HashMap, HashSet},
sync::Mutex,
};
use thread_local::ThreadLocal;
use crate::{
emu::Emulator,
emu::{Emulator, MmapPerms},
executor::QemuExecutor,
helper::{QemuHelper, QemuHelperTuple},
GuestAddr, SYS_mmap, SYS_mremap,
GuestAddr, SYS_mmap, SYS_mprotect, SYS_mremap,
};
pub const SNAPSHOT_PAGE_SIZE: usize = 4096;
@ -13,19 +19,32 @@ pub const SNAPSHOT_PAGE_SIZE: usize = 4096;
#[derive(Debug)]
pub struct SnapshotPageInfo {
pub addr: GuestAddr,
pub dirty: bool,
pub data: [u8; SNAPSHOT_PAGE_SIZE],
pub perms: MmapPerms,
pub private: bool,
pub data: Option<Box<[u8; SNAPSHOT_PAGE_SIZE]>>,
}
#[derive(Default, Debug)]
pub struct SnapshotAccessInfo {
pub access_cache: [GuestAddr; 4],
pub access_cache_idx: usize,
pub dirty: HashSet<GuestAddr>,
}
impl SnapshotAccessInfo {
pub fn clear(&mut self) {
self.access_cache_idx = 0;
self.access_cache = [GuestAddr::MAX; 4];
self.dirty.clear();
}
}
#[derive(Debug)]
// TODO be thread-safe maybe with https://amanieu.github.io/thread_local-rs/thread_local/index.html
pub struct QemuSnapshotHelper {
pub access_cache: [GuestAddr; 4],
pub access_cache_idx: usize,
pub accesses: ThreadLocal<UnsafeCell<SnapshotAccessInfo>>,
pub new_maps: Mutex<IntervalTree<GuestAddr, Option<MmapPerms>>>,
pub pages: HashMap<GuestAddr, SnapshotPageInfo>,
pub dirty: Vec<GuestAddr>,
pub brk: GuestAddr,
pub new_maps: Vec<(GuestAddr, usize)>,
pub empty: bool,
}
@ -33,32 +52,33 @@ impl QemuSnapshotHelper {
#[must_use]
pub fn new() -> Self {
Self {
access_cache: [GuestAddr::MAX; 4],
access_cache_idx: 0,
accesses: ThreadLocal::new(),
new_maps: Mutex::new(IntervalTree::new()),
pages: HashMap::default(),
dirty: vec![],
brk: 0,
new_maps: vec![],
empty: true,
}
}
#[allow(clippy::uninit_assumed_init)]
pub fn snapshot(&mut self, emulator: &Emulator) {
self.brk = emulator.get_brk();
self.pages.clear();
for map in emulator.mappings() {
// TODO track all the pages OR track mproctect
if !map.flags().is_w() {
continue;
}
let mut addr = map.start();
while addr < map.end() {
let mut info = SnapshotPageInfo {
addr,
dirty: false,
data: [0; SNAPSHOT_PAGE_SIZE],
perms: map.flags(),
private: map.is_priv(),
data: None,
};
unsafe { emulator.read_mem(addr, &mut info.data) };
if map.flags().is_w() {
unsafe {
info.data = Some(Box::new(core::mem::MaybeUninit::uninit().assume_init()));
emulator.read_mem(addr, &mut info.data.as_mut().unwrap()[..]);
}
}
self.pages.insert(addr, info);
addr += SNAPSHOT_PAGE_SIZE as GuestAddr;
}
@ -67,22 +87,20 @@ impl QemuSnapshotHelper {
}
pub fn page_access(&mut self, page: GuestAddr) {
if self.access_cache[0] == page
|| self.access_cache[1] == page
|| self.access_cache[2] == page
|| self.access_cache[3] == page
unsafe {
let acc = self.accesses.get_or_default().get();
if (*acc).access_cache[0] == page
|| (*acc).access_cache[1] == page
|| (*acc).access_cache[2] == page
|| (*acc).access_cache[3] == page
{
return;
}
self.access_cache[self.access_cache_idx] = page;
self.access_cache_idx = (self.access_cache_idx + 1) & 3;
if let Some(info) = self.pages.get_mut(&page) {
if info.dirty {
return;
let idx = (*acc).access_cache_idx;
(*acc).access_cache[idx] = page;
(*acc).access_cache_idx = (idx + 1) & 3;
(*acc).dirty.insert(page);
}
info.dirty = true;
}
self.dirty.push(page);
}
pub fn access(&mut self, addr: GuestAddr, size: usize) {
@ -96,27 +114,62 @@ impl QemuSnapshotHelper {
}
pub fn reset(&mut self, emulator: &Emulator) {
self.access_cache = [GuestAddr::MAX; 4];
self.access_cache_idx = 0;
while let Some(page) = self.dirty.pop() {
if let Some(info) = self.pages.get_mut(&page) {
unsafe { emulator.write_mem(page, &info.data) };
info.dirty = false;
self.reset_maps(emulator);
for acc in self.accesses.iter_mut() {
for page in unsafe { &(*acc.get()).dirty } {
if let Some(info) = self.pages.get_mut(page) {
// TODO avoid duplicated memcpy
if let Some(data) = info.data.as_ref() {
unsafe { emulator.write_mem(*page, &data[..]) };
}
}
}
unsafe { (*acc.get()).clear() };
}
emulator.set_brk(self.brk);
self.reset_maps(emulator);
}
pub fn add_mapped(&mut self, start: GuestAddr, size: usize) {
self.new_maps.push((start, size));
pub fn add_mapped(&mut self, start: GuestAddr, mut size: usize, perms: Option<MmapPerms>) {
if size % SNAPSHOT_PAGE_SIZE != 0 {
size = size + (SNAPSHOT_PAGE_SIZE - size % SNAPSHOT_PAGE_SIZE);
}
self.new_maps
.lock()
.unwrap()
.insert(start..start + (size as GuestAddr), perms);
}
pub fn reset_maps(&mut self, emulator: &Emulator) {
for (addr, size) in &self.new_maps {
drop(emulator.unmap(*addr, *size));
let new_maps = self.new_maps.get_mut().unwrap();
for r in new_maps.find(0..GuestAddr::MAX) {
let addr = r.interval().start;
let end = r.interval().end;
let perms = r.data();
let mut page = addr & (SNAPSHOT_PAGE_SIZE as GuestAddr - 1);
let mut prev = None;
while page < end {
if let Some(info) = self.pages.get(&page) {
if let Some((addr, size)) = prev {
drop(emulator.unmap(addr, size));
}
self.new_maps.clear();
prev = None;
if let Some(p) = perms {
if info.perms != *p {
drop(emulator.mprotect(page, SNAPSHOT_PAGE_SIZE, info.perms));
}
}
} else if let Some((_, size)) = &mut prev {
*size += SNAPSHOT_PAGE_SIZE;
} else {
prev = Some((page, SNAPSHOT_PAGE_SIZE));
}
page += SNAPSHOT_PAGE_SIZE as GuestAddr;
}
if let Some((addr, size)) = prev {
drop(emulator.unmap(addr, size));
}
}
*new_maps = IntervalTree::new();
}
}
@ -262,15 +315,24 @@ where
return result;
}
if i64::from(sys_num) == SYS_mmap {
if let Ok(prot) = MmapPerms::try_from(a2 as i32) {
let h = helpers
.match_first_type_mut::<QemuSnapshotHelper>()
.unwrap();
h.add_mapped(result as GuestAddr, a1 as usize);
h.add_mapped(result as GuestAddr, a1 as usize, Some(prot));
}
} else if i64::from(sys_num) == SYS_mremap {
let h = helpers
.match_first_type_mut::<QemuSnapshotHelper>()
.unwrap();
h.add_mapped(a0 as GuestAddr, a2 as usize);
h.add_mapped(result as GuestAddr, a2 as usize, None);
} else if i64::from(sys_num) == SYS_mprotect {
if let Ok(prot) = MmapPerms::try_from(a2 as i32) {
let h = helpers
.match_first_type_mut::<QemuSnapshotHelper>()
.unwrap();
h.add_mapped(a0 as GuestAddr, a2 as usize, Some(prot));
}
}
result
}

View File

@ -1,5 +1,5 @@
use num_enum::{IntoPrimitive, TryFromPrimitive};
use strum_macros::EnumIter;
pub use strum_macros::EnumIter;
#[cfg(feature = "python")]
use pyo3::prelude::*;