Save and restore CPU state in libafl_qemu (#907)

* libafl_qemu: fix systemmode with slirp dependency

libslirp will be dropped from future QEMU releases (see https://wiki.qemu.org/ChangeLog/7.0).
This change adds the "slirp" feature,
which links with the host-systems libslirp.

* libafl_qemu: enable systemmode snapshots, vm_start

Re-enable snapshot functions.
Start the VM before qemu_main_loop.

* libafl_qemu: allow synchronous snapshotting

Add a flag to take snapshots synchronosly.
This should be used to take or load snapshots while the emulator is not
running.

* libafl_qemu: fallback cpu for read-/write_mem

In systemmode, current_cpu may not be set.
In such cases use the first cpus memory access methods.

* fuzzers: add example for libafl_qemu in systemmode

* libafl_qemu: update libafl-qemu-bridge revision

* libafl_qemu: add memory access by physcial address

* fix liabfl_qemu example

Use GuestAddr and physical memory access

* ci: install libslirp-dev for libafl_qemu

* fuzzers/qemu_systemmode: clean up example

* libafl_qemu: remove obsolete functions

emu::libafl_cpu_thread_fn
emu::libafl_start_vcpu
emu::start

* fuzzers/qemu_systemmode: simplify example

* improve build_linux.rs

* Update qemu_systemmode fuzzer

* upd

* clippy

* Save and restore CPU state in libafl_qemu

* clippy

* Clone

* upd

* upd

Co-authored-by: Alwin Berger <alwin.berger@tu-dortmund.de>
This commit is contained in:
Andrea Fioraldi 2022-11-22 16:29:43 +01:00 committed by GitHub
parent 7b0039606b
commit 3f627aaf0b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 67 additions and 11 deletions

View File

@ -5,9 +5,9 @@ int BREAKPOINT() {
}
int LLVMFuzzerTestOneInput(unsigned int* Data, unsigned int Size) {
if (Data[3] == 0) {while(1){}} // cause a timeout
//if (Data[3] == 0) {while(1){}} // cause a timeout
for (int i=0; i<Size; i++) {
if (Data[i] > 0xFFd0 && Data[i] < 0xFFFF) {return 1;} // cause qemu to crash
//if (Data[i] > 0xFFd0 && Data[i] < 0xFFFF) {return 1;} // cause qemu to crash
for (int j=i+1; j<Size; j++) {
if (Data[j] == 0) {continue;}
if (Data[j]>Data[i]) {
@ -35,4 +35,4 @@ int LLVMFuzzerTestOneInput(unsigned int* Data, unsigned int Size) {
int main() {
LLVMFuzzerTestOneInput(FUZZ_INPUT, 50);
}
}

View File

@ -99,6 +99,10 @@ pub fn fuzz() {
// saved_regs.push(emu.cpu_from_index(0).read_reg(r).unwrap());
//}
let saved_cpu_states: Vec<_> = (0..emu.num_cpus())
.map(|i| emu.cpu_from_index(i).save_state())
.collect();
// The wrapped harness function, calling out to the LLVM-style harness
let mut harness = |input: &BytesInput| {
let target = input.target_bytes();
@ -129,7 +133,11 @@ pub fn fuzz() {
None => ExitKind::Crash,
};
emu.load_snapshot("start", true);
for (i, s) in saved_cpu_states.iter().enumerate() {
emu.cpu_from_index(i).restore_state(s);
}
// emu.load_snapshot("start", true);
ret
}

View File

@ -4,7 +4,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 = "658565bec0a68a84c733217fa9b7802562096559";
const QEMU_REVISION: &str = "f26a5ca6137bb5d4d0dcfe5451fb16d4c0551c4e";
fn build_dep_check(tools: &[&str]) {
for tool in tools {
@ -45,7 +45,6 @@ pub fn build() {
println!("cargo:rerun-if-env-changed=EMULATION_MODE");
println!("cargo:rerun-if-changed=build.rs");
println!("cargo:rerun-if-env-changed=CROSS_CC");
// Make sure we have at most one architecutre feature set
// Else, we default to `x86_64` - having a default makes CI easier :)

View File

@ -1,12 +1,12 @@
//! Expose QEMU user `LibAFL` C api to Rust
#[cfg(emulation_mode = "usermode")]
use core::mem::MaybeUninit;
use core::{
convert::Into,
ffi::c_void,
ptr::{addr_of, addr_of_mut, null},
ptr::{addr_of, addr_of_mut, copy_nonoverlapping, null},
};
#[cfg(emulation_mode = "usermode")]
use core::{mem::MaybeUninit, ptr::copy_nonoverlapping};
use std::{ffi::CString, slice::from_raw_parts, str::from_utf8_unchecked};
#[cfg(emulation_mode = "usermode")]
@ -30,7 +30,8 @@ use pyo3::{prelude::*, PyIterProtocol};
pub const SKIP_EXEC_HOOK: u64 = u64::MAX;
type CPUStatePtr = *const c_void;
type CPUStatePtr = *mut c_void;
type CPUArchStatePtr = *mut c_void;
#[derive(IntoPrimitive, TryFromPrimitive, Debug, Clone, Copy, EnumIter, PartialEq, Eq)]
#[repr(i32)]
@ -254,6 +255,8 @@ extern "C" fn qemu_cleanup_atexit() {
}
extern "C" {
fn libafl_qemu_arch_state_size() -> usize;
// CPUState* libafl_qemu_get_cpu(int cpu_index);
fn libafl_qemu_get_cpu(cpu_index: i32) -> CPUStatePtr;
// int libafl_qemu_num_cpus(void);
@ -356,6 +359,9 @@ extern "C" {
);
fn libafl_qemu_gdb_reply(buf: *const u8, len: usize);
fn libafl_qemu_cpu_arch_state(cpu: CPUStatePtr) -> CPUArchStatePtr;
// fn libafl_qemu_arch_state_cpu(env: CPUArchStatePtr) -> CPUStatePtr;
fn cpu_reset(cpu: CPUStatePtr);
}
@ -437,6 +443,26 @@ extern "C" fn gdb_cmd(buf: *const u8, len: usize, data: *const ()) -> i32 {
}
}
#[derive(Clone, Debug)]
#[repr(C)]
pub struct SavedCPUState {
data: Vec<u8>,
}
impl SavedCPUState {
#[must_use]
#[allow(clippy::uninit_vec)]
fn uninit() -> Self {
unsafe {
let len = libafl_qemu_arch_state_size();
let mut data = Vec::with_capacity(len);
data.set_len(len);
Self { data }
}
}
}
#[derive(Debug)]
#[repr(C)]
pub struct CPU {
@ -540,9 +566,32 @@ impl CPU {
}
}
pub fn cpu_reset(&self) {
pub fn reset(&self) {
unsafe { cpu_reset(self.ptr) };
}
#[must_use]
pub fn save_state(&self) -> SavedCPUState {
let mut saved = SavedCPUState::uninit();
unsafe {
copy_nonoverlapping(
libafl_qemu_cpu_arch_state(self.ptr) as *mut u8,
saved.data.as_mut_ptr(),
saved.data.len(),
);
}
saved
}
pub fn restore_state(&self, saved: &SavedCPUState) {
unsafe {
copy_nonoverlapping(
saved.data.as_ptr(),
libafl_qemu_cpu_arch_state(self.ptr) as *mut u8,
saved.data.len(),
);
}
}
}
static mut EMULATOR_IS_INITIALIZED: bool = false;