Second round of usermode snapshot fixes (#2140)

* Added brk callback for snapshot. Added filter feature for snapshot

* cargo fmt

* clippy

* Update imports

* update bindings

* updated bindings

* Added additional check of brk syscall result

* change snapshot restore debug level from info to debug

* add warning comment

---------

Co-authored-by: Romain Malmain <romain.malmain@pm.me>
This commit is contained in:
cube0x8 2024-05-22 14:30:09 +03:00 committed by GitHub
parent 336d7fcc4f
commit 4b67b55b29
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 84 additions and 10 deletions

View File

@ -33,6 +33,8 @@ pub use injections::QemuInjectionHelper;
#[cfg(all(emulation_mode = "usermode", not(cpu_target = "hexagon")))] #[cfg(all(emulation_mode = "usermode", not(cpu_target = "hexagon")))]
pub mod snapshot; pub mod snapshot;
#[cfg(all(emulation_mode = "usermode", not(cpu_target = "hexagon")))] #[cfg(all(emulation_mode = "usermode", not(cpu_target = "hexagon")))]
pub use snapshot::IntervalSnapshotFilter;
#[cfg(all(emulation_mode = "usermode", not(cpu_target = "hexagon")))]
pub use snapshot::QemuSnapshotHelper; pub use snapshot::QemuSnapshotHelper;
#[cfg(all(emulation_mode = "usermode", not(cpu_target = "hexagon")))] #[cfg(all(emulation_mode = "usermode", not(cpu_target = "hexagon")))]

View File

@ -25,11 +25,11 @@ use crate::SYS_mmap2;
use crate::SYS_newfstatat; use crate::SYS_newfstatat;
use crate::{ use crate::{
asan::QemuAsanHelper, asan::QemuAsanHelper,
helpers::{QemuHelper, QemuHelperTuple}, helpers::{QemuHelper, QemuHelperTuple, Range},
hooks::{Hook, QemuHooks}, hooks::{Hook, QemuHooks},
qemu::SyscallHookResult, qemu::SyscallHookResult,
Qemu, SYS_fstat, SYS_fstatfs, SYS_futex, SYS_getrandom, SYS_mprotect, SYS_mremap, SYS_munmap, Qemu, SYS_brk, SYS_fstat, SYS_fstatfs, SYS_futex, SYS_getrandom, SYS_mprotect, SYS_mremap,
SYS_pread64, SYS_read, SYS_readlinkat, SYS_statfs, SYS_munmap, SYS_pread64, SYS_read, SYS_readlinkat, SYS_statfs,
}; };
// TODO use the functions provided by Qemu // TODO use the functions provided by Qemu
@ -73,6 +73,17 @@ pub struct MappingInfo {
pub size: usize, pub size: usize,
} }
/// Filter used to select which pages should be snapshotted or not.
///
/// It is supposed to be used primarily for debugging, its usage is discouraged.
/// If you end up needing it, you most likely have an issue with the snapshot system.
/// If this is the case, please [fill in an issue on the main repository](https://github.com/AFLplusplus/LibAFL/issues).
pub enum IntervalSnapshotFilter {
All,
AllowList(Vec<Range<GuestAddr>>),
DenyList(Vec<Range<GuestAddr>>),
}
pub struct QemuSnapshotHelper { pub struct QemuSnapshotHelper {
pub accesses: ThreadLocal<UnsafeCell<SnapshotAccessInfo>>, pub accesses: ThreadLocal<UnsafeCell<SnapshotAccessInfo>>,
pub maps: MappingInfo, pub maps: MappingInfo,
@ -84,6 +95,7 @@ pub struct QemuSnapshotHelper {
pub stop_execution: Option<StopExecutionCallback>, pub stop_execution: Option<StopExecutionCallback>,
pub empty: bool, pub empty: bool,
pub accurate_unmap: bool, pub accurate_unmap: bool,
pub interval_filter: Vec<IntervalSnapshotFilter>,
} }
impl core::fmt::Debug for QemuSnapshotHelper { impl core::fmt::Debug for QemuSnapshotHelper {
@ -114,6 +126,24 @@ impl QemuSnapshotHelper {
stop_execution: None, stop_execution: None,
empty: true, empty: true,
accurate_unmap: false, accurate_unmap: false,
interval_filter: Vec::<IntervalSnapshotFilter>::new(),
}
}
#[must_use]
pub fn with_filters(interval_filter: Vec<IntervalSnapshotFilter>) -> Self {
Self {
accesses: ThreadLocal::new(),
maps: MappingInfo::default(),
new_maps: Mutex::new(MappingInfo::default()),
pages: HashMap::default(),
brk: 0,
mmap_start: 0,
mmap_limit: 0,
stop_execution: None,
empty: true,
accurate_unmap: false,
interval_filter,
} }
} }
@ -130,6 +160,7 @@ impl QemuSnapshotHelper {
stop_execution: Some(stop_execution), stop_execution: Some(stop_execution),
empty: true, empty: true,
accurate_unmap: false, accurate_unmap: false,
interval_filter: Vec::<IntervalSnapshotFilter>::new(),
} }
} }
@ -137,6 +168,25 @@ impl QemuSnapshotHelper {
self.accurate_unmap = true; self.accurate_unmap = true;
} }
pub fn to_skip(&self, addr: GuestAddr) -> bool {
for filter in &self.interval_filter {
match filter {
IntervalSnapshotFilter::All => return false,
IntervalSnapshotFilter::AllowList(allow_list) => {
if allow_list.iter().any(|range| range.contains(&addr)) {
return false;
}
}
IntervalSnapshotFilter::DenyList(deny_list) => {
if deny_list.iter().any(|range| range.contains(&addr)) {
return true;
}
}
}
}
false
}
#[allow(clippy::uninit_assumed_init)] #[allow(clippy::uninit_assumed_init)]
pub fn snapshot(&mut self, qemu: Qemu) { pub fn snapshot(&mut self, qemu: Qemu) {
log::info!("Start snapshot"); log::info!("Start snapshot");
@ -146,6 +196,10 @@ impl QemuSnapshotHelper {
for map in qemu.mappings() { for map in qemu.mappings() {
let mut addr = map.start(); let mut addr = map.start();
while addr < map.end() { while addr < map.end() {
if self.to_skip(addr) {
addr += SNAPSHOT_PAGE_SIZE as GuestAddr;
continue;
}
let mut info = SnapshotPageInfo { let mut info = SnapshotPageInfo {
addr, addr,
perms: map.flags(), perms: map.flags(),
@ -224,6 +278,10 @@ impl QemuSnapshotHelper {
let mut addr = map.start(); let mut addr = map.start();
// assert_eq!(addr & SNAPSHOT_PAGE_MASK, 0); // assert_eq!(addr & SNAPSHOT_PAGE_MASK, 0);
while addr < map.end() { while addr < map.end() {
if self.to_skip(addr) {
addr += SNAPSHOT_PAGE_SIZE as GuestAddr;
continue;
}
if let Some(saved_page) = saved_pages_list.remove(&addr) { if let Some(saved_page) = saved_pages_list.remove(&addr) {
if saved_page.perms.readable() { if saved_page.perms.readable() {
let mut current_page_content: MaybeUninit<[u8; SNAPSHOT_PAGE_SIZE]> = let mut current_page_content: MaybeUninit<[u8; SNAPSHOT_PAGE_SIZE]> =
@ -326,7 +384,7 @@ impl QemuSnapshotHelper {
{ {
let new_maps = self.new_maps.get_mut().unwrap(); let new_maps = self.new_maps.get_mut().unwrap();
log::info!("Start restore"); log::debug!("Start restore");
for acc in &mut self.accesses { for acc in &mut self.accesses {
unsafe { &mut (*acc.get()) }.dirty.retain(|page| { unsafe { &mut (*acc.get()) }.dirty.retain(|page| {
@ -416,7 +474,7 @@ impl QemuSnapshotHelper {
#[cfg(feature = "paranoid_debug")] #[cfg(feature = "paranoid_debug")]
self.check_snapshot(qemu); self.check_snapshot(qemu);
log::info!("End restore"); log::debug!("End restore");
} }
pub fn is_unmap_allowed(&mut self, start: GuestAddr, mut size: usize) -> bool { pub fn is_unmap_allowed(&mut self, start: GuestAddr, mut size: usize) -> bool {
@ -589,14 +647,18 @@ impl QemuSnapshotHelper {
} }
} }
let mut to_unmap = vec![];
for entry in new_maps.tree.query(0..GuestAddr::MAX) { for entry in new_maps.tree.query(0..GuestAddr::MAX) {
drop(qemu.unmap( to_unmap.push((*entry.interval, entry.value.changed, entry.value.perms));
entry.interval.start, }
(entry.interval.end - entry.interval.start) as usize, for (i, ..) in to_unmap {
)); drop(qemu.unmap(i.start, (i.end - i.start) as usize));
new_maps.tree.delete(i);
} }
*new_maps = self.maps.clone(); new_maps.tree.clear();
new_maps.tree = self.maps.tree.clone();
new_maps.size = self.maps.size;
} }
} }
@ -757,6 +819,16 @@ where
let h = hooks.match_helper_mut::<QemuSnapshotHelper>().unwrap(); let h = hooks.match_helper_mut::<QemuSnapshotHelper>().unwrap();
h.access(a0, a1 as usize); h.access(a0, a1 as usize);
} }
SYS_brk => {
let h = hooks.match_helper_mut::<QemuSnapshotHelper>().unwrap();
if h.brk != result && result != 0 {
/* brk has changed. we change mapping from the snapshotted brk address to the new target_brk
* If no brk mapping has been made until now, change_mapped won't change anything and just create a new mapping.
* It is safe to assume RW perms here
*/
h.change_mapped(h.brk, (result - h.brk) as usize, Some(MmapPerms::ReadWrite));
}
}
// mmap syscalls // mmap syscalls
sys_const => { sys_const => {
if result == GuestAddr::MAX if result == GuestAddr::MAX