From 90f0f06ef53ed34472e1d9c3382f3f51c2fe8543 Mon Sep 17 00:00:00 2001 From: Andrea Fioraldi Date: Mon, 25 Jul 2022 17:50:09 +0200 Subject: [PATCH] Raw API for full-system libafl_qemu (#692) * full system build * start supporting more cpus * first proto working * more Emulator methods * fix * fix * backdoor * fix * libvduse.a * hash * clippy * debug * working usermode * Fix userspace arm * clippy * clippy * clippy --- libafl_qemu/Cargo.toml | 4 +- libafl_qemu/build_linux.rs | 301 ++++++++++++++++++++---------- libafl_qemu/src/calls.rs | 24 ++- libafl_qemu/src/emu.rs | 362 +++++++++++++++++++++++++++++------- libafl_qemu/src/hooks.rs | 14 ++ libafl_qemu/src/lib.rs | 4 + libafl_qemu/src/snapshot.rs | 4 +- 7 files changed, 540 insertions(+), 173 deletions(-) diff --git a/libafl_qemu/Cargo.toml b/libafl_qemu/Cargo.toml index 51bd64175a..4ccc2f4402 100644 --- a/libafl_qemu/Cargo.toml +++ b/libafl_qemu/Cargo.toml @@ -12,8 +12,8 @@ edition = "2021" categories = ["development-tools::testing", "emulators", "embedded", "os", "no-std"] [features] +default = ["usermode"] python = ["pyo3", "pyo3-build-config"] -default = [] # The following architecture features are mutually exclusive. x86_64 = [] # build qemu for x86_64 (default) @@ -21,6 +21,8 @@ i386 = [] # build qemu for i386 arm = [] # build qemu for arm aarch64 = [] # build qemu for aarch64 +usermode = [] + clippy = [] # special feature for clippy, don't use in normal projects§ [dependencies] diff --git a/libafl_qemu/build_linux.rs b/libafl_qemu/build_linux.rs index 536d035c30..90613ecce9 100644 --- a/libafl_qemu/build_linux.rs +++ b/libafl_qemu/build_linux.rs @@ -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 = "03e283c85800496b60fb757d68a7df2821fb7a90"; +const QEMU_REVISION: &str = "03fad12e801581536cd10830073acfce69e381fe"; fn build_dep_check(tools: &[&str]) { for tool in tools { @@ -29,6 +29,7 @@ pub fn build() { println!("cargo:rerun-if-changed=build.rs"); println!("cargo:rerun-if-changed=src/asan-giovese.c"); println!("cargo:rerun-if-changed=src/asan-giovese.h"); + #[cfg(feature = "usermode")] println!("cargo:rerun-if-env-changed=CROSS_CC"); // Make sure we have at most one architecutre feature set @@ -54,6 +55,7 @@ pub fn build() { let jobs = env::var("NUM_JOBS"); + #[cfg(feature = "usermode")] let cross_cc = env::var("CROSS_CC").unwrap_or_else(|_| { println!("cargo:warning=CROSS_CC is not set, default to cc (things can go wrong if the selected cpu target ({}) is not the host arch ({}))", cpu_target, env::consts::ARCH); "cc".to_owned() @@ -65,6 +67,9 @@ pub fn build() { return; // only build when we're not generating docs } + let custum_qemu_dir = env::var_os("CUSTOM_QEMU_DIR").map(|x| x.to_string_lossy().to_string()); + let custum_qemu_no_build = env::var("CUSTOM_QEMU_NO_BUILD").is_ok(); + let out_dir = env::var_os("OUT_DIR").unwrap(); let out_dir = out_dir.to_string_lossy().to_string(); let out_dir_path = Path::new(&out_dir); @@ -72,89 +77,100 @@ pub fn build() { target_dir.pop(); target_dir.pop(); target_dir.pop(); - let qasan_dir = Path::new("libqasan"); - let qasan_dir = fs::canonicalize(&qasan_dir).unwrap(); - let src_dir = Path::new("src"); println!("cargo:rerun-if-changed=libqasan"); build_dep_check(&["git", "make"]); - let qemu_rev = out_dir_path.join("QEMU_REVISION"); - let qemu_path = out_dir_path.join(QEMU_DIRNAME); + let qemu_path = if let Some(qemu_dir) = custum_qemu_dir.as_ref() { + Path::new(&qemu_dir).to_path_buf() + } else { + let qemu_path = out_dir_path.join(QEMU_DIRNAME); - if qemu_rev.exists() - && fs::read_to_string(&qemu_rev).expect("Failed to read QEMU_REVISION") != QEMU_REVISION - { - drop(fs::remove_dir_all(&qemu_path)); - } + let qemu_rev = out_dir_path.join("QEMU_REVISION"); + if qemu_rev.exists() + && fs::read_to_string(&qemu_rev).expect("Failed to read QEMU_REVISION") != QEMU_REVISION + { + drop(fs::remove_dir_all(&qemu_path)); + } - if !qemu_path.is_dir() { - println!( - "cargo:warning=Qemu not found, cloning with git ({})...", - QEMU_REVISION - ); - fs::create_dir_all(&qemu_path).unwrap(); - Command::new("git") - .current_dir(&qemu_path) - .arg("init") - .status() - .unwrap(); - Command::new("git") - .current_dir(&qemu_path) - .arg("remote") - .arg("add") - .arg("origin") - .arg(QEMU_URL) - .status() - .unwrap(); - Command::new("git") - .current_dir(&qemu_path) - .arg("fetch") - .arg("--depth") - .arg("1") - .arg("origin") - .arg(QEMU_REVISION) - .status() - .unwrap(); - Command::new("git") - .current_dir(&qemu_path) - .arg("checkout") - .arg("FETCH_HEAD") - .status() - .unwrap(); - /*Command::new("git") - .current_dir(&out_dir_path) - .arg("clone") - .arg(QEMU_URL) - .status() - .unwrap(); - Command::new("git") - .current_dir(&qemu_path) - .arg("checkout") - .arg(QEMU_REVISION) - .status() - .unwrap();*/ - fs::write(&qemu_rev, QEMU_REVISION).unwrap(); - } + if !qemu_path.is_dir() { + println!( + "cargo:warning=Qemu not found, cloning with git ({})...", + QEMU_REVISION + ); + fs::create_dir_all(&qemu_path).unwrap(); + Command::new("git") + .current_dir(&qemu_path) + .arg("init") + .status() + .unwrap(); + Command::new("git") + .current_dir(&qemu_path) + .arg("remote") + .arg("add") + .arg("origin") + .arg(QEMU_URL) + .status() + .unwrap(); + Command::new("git") + .current_dir(&qemu_path) + .arg("fetch") + .arg("--depth") + .arg("1") + .arg("origin") + .arg(QEMU_REVISION) + .status() + .unwrap(); + Command::new("git") + .current_dir(&qemu_path) + .arg("checkout") + .arg("FETCH_HEAD") + .status() + .unwrap(); + fs::write(&qemu_rev, QEMU_REVISION).unwrap(); + } + + qemu_path + }; + + #[cfg(feature = "usermode")] + let target_suffix = "linux-user"; + #[cfg(not(feature = "usermode"))] + let target_suffix = "softmmu"; let build_dir = qemu_path.join("build"); + #[cfg(feature = "usermode")] let output_lib = build_dir.join(&format!("libqemu-{}.so", cpu_target)); - if !output_lib.is_file() { + #[cfg(not(feature = "usermode"))] + let output_lib = build_dir.join(&format!("libqemu-system-{}.so", cpu_target)); + + println!("cargo:rerun-if-changed={}", output_lib.to_string_lossy()); + + if !output_lib.is_file() || (custum_qemu_dir.is_some() && !custum_qemu_no_build) { /*drop( Command::new("make") .current_dir(&qemu_path) .arg("distclean") .status(), );*/ + #[cfg(feature = "usermode")] Command::new("./configure") .current_dir(&qemu_path) //.arg("--as-static-lib") .arg("--as-shared-lib") - .arg(&format!("--target-list={}-linux-user", cpu_target)) + .arg(&format!("--target-list={}-{}", cpu_target, target_suffix)) .args(&["--disable-blobs", "--disable-bsd-user", "--disable-fdt"]) .status() .expect("Configure failed"); + #[cfg(not(feature = "usermode"))] + Command::new("./configure") + .current_dir(&qemu_path) + //.arg("--as-static-lib") + .arg("--as-shared-lib") + .arg(&format!("--target-list={}-{}", cpu_target, target_suffix)) + .status() + .expect("Configure failed"); if let Ok(j) = jobs { Command::new("make") .current_dir(&qemu_path) @@ -169,17 +185,12 @@ pub fn build() { .status() .expect("Make failed"); } - //let _ = remove_file(build_dir.join(&format!("libqemu-{}.so", cpu_target))); } let mut objects = vec![]; 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("libqemuutil.a.p"), - //build_dir.join("libqom.fa.p"), - //build_dir.join("libhwcore.fa.p"), + build_dir.join(&format!("libqemu-{}-{}.fa.p", cpu_target, target_suffix)), ] { for path in fs::read_dir(dir).unwrap() { let path = path.unwrap().path(); @@ -197,6 +208,7 @@ pub fn build() { } } + #[cfg(feature = "usermode")] Command::new("ld") .current_dir(&out_dir_path) .arg("-o") @@ -219,18 +231,102 @@ pub fn build() { .status() .expect("Partial linked failure"); - drop( - Command::new("ar") - .current_dir(&out_dir_path) - .arg("crus") - .arg("libqemu-partially-linked.a") - .arg("libqemu-partially-linked.o") - .status(), - ); + #[cfg(not(feature = "usermode"))] + Command::new("ld") + .current_dir(&out_dir_path) + .arg("-o") + .arg("libqemu-partially-linked.o") + .arg("-r") + .args(objects) + .arg("--start-group") + .arg("--whole-archive") + .arg(format!("{}/libhwcore.fa", build_dir.display())) + .arg(format!("{}/libqom.fa", build_dir.display())) + .arg(format!("{}/libevent-loop-base.a", build_dir.display())) + .arg(format!("{}/libio.fa", build_dir.display())) + .arg(format!("{}/libcrypto.fa", build_dir.display())) + .arg(format!("{}/libauthz.fa", build_dir.display())) + .arg(format!("{}/libblockdev.fa", build_dir.display())) + .arg(format!("{}/libblock.fa", build_dir.display())) + .arg(format!("{}/libchardev.fa", build_dir.display())) + .arg(format!("{}/libqmp.fa", build_dir.display())) + .arg("--no-whole-archive") + .arg(format!("{}/libqemuutil.a", build_dir.display())) + .arg(format!( + "{}/subprojects/libvhost-user/libvhost-user-glib.a", + build_dir.display() + )) + .arg(format!( + "{}/subprojects/libvhost-user/libvhost-user.a", + build_dir.display() + )) + .arg(format!( + "{}/subprojects/libvduse/libvduse.a", + build_dir.display() + )) + .arg(format!("{}/libfdt.a", build_dir.display())) + .arg(format!("{}/libslirp.a", build_dir.display())) + .arg(format!("{}/libmigration.fa", build_dir.display())) + .arg(format!("{}/libhwcore.fa", build_dir.display())) + .arg(format!("{}/libqom.fa", build_dir.display())) + .arg(format!("{}/libio.fa", build_dir.display())) + .arg(format!("{}/libcrypto.fa", build_dir.display())) + .arg(format!("{}/libauthz.fa", build_dir.display())) + .arg(format!("{}/libblockdev.fa", build_dir.display())) + .arg(format!("{}/libblock.fa", build_dir.display())) + .arg(format!("{}/libchardev.fa", build_dir.display())) + .arg(format!("{}/libqmp.fa", build_dir.display())) + .arg(format!( + "--dynamic-list={}/plugins/qemu-plugins.symbols", + qemu_path.display() + )) + .status() + .expect("Partial linked failure"); + + Command::new("ar") + .current_dir(&out_dir_path) + .arg("crus") + .arg("libqemu-partially-linked.a") + .arg("libqemu-partially-linked.o") + .status() + .expect("Ar creation"); println!("cargo:rustc-link-search=native={}", out_dir); println!("cargo:rustc-link-lib=static=qemu-partially-linked"); + #[cfg(not(feature = "usermode"))] + { + println!("cargo:rustc-link-lib=png"); + println!("cargo:rustc-link-lib=z"); + println!("cargo:rustc-link-lib=gio-2.0"); + println!("cargo:rustc-link-lib=gobject-2.0"); + println!("cargo:rustc-link-lib=ncursesw"); + println!("cargo:rustc-link-lib=tinfo"); + println!("cargo:rustc-link-lib=gtk-3"); + println!("cargo:rustc-link-lib=gdk-3"); + println!("cargo:rustc-link-lib=pangocairo-1.0"); + println!("cargo:rustc-link-lib=pango-1.0"); + println!("cargo:rustc-link-lib=harfbuzz"); + println!("cargo:rustc-link-lib=atk-1.0"); + println!("cargo:rustc-link-lib=cairo-gobject"); + println!("cargo:rustc-link-lib=cairo"); + println!("cargo:rustc-link-lib=gdk_pixbuf-2.0"); + println!("cargo:rustc-link-lib=X11"); + println!("cargo:rustc-link-lib=epoxy"); + println!("cargo:rustc-link-lib=pixman-1"); + + fs::create_dir_all(target_dir.join("pc-bios")).unwrap(); + for path in fs::read_dir(build_dir.join("pc-bios")).unwrap() { + let path = path.unwrap().path(); + if path.is_file() { + if let Some(name) = path.file_name() { + fs::copy(&path, target_dir.join("pc-bios").join(name)) + .expect("Failed to copy a pc-bios folder file"); + } + } + } + } + println!("cargo:rustc-link-lib=rt"); println!("cargo:rustc-link-lib=gmodule-2.0"); println!("cargo:rustc-link-lib=glib-2.0"); @@ -246,35 +342,42 @@ pub fn build() { println!( "cargo:rustc-link-search=native={}", - &target_dir.to_string_lossy().to_string() + &target_dir.to_string_lossy() ); println!("cargo:rustc-link-lib=qemu-{}", cpu_target); println!("cargo:rustc-env=LD_LIBRARY_PATH={}", target_dir.display()); } */ - assert!(Command::new("make") - .current_dir(&out_dir_path) - .env("CC", &cross_cc) - .env("OUT_DIR", &target_dir) - .arg("-C") - .arg(&qasan_dir) - .arg("clean") - .status() - .expect("make failed") - .success()); - assert!(Command::new("make") - .current_dir(&out_dir_path) - .env("CC", &cross_cc) - .env("OUT_DIR", &target_dir) - .arg("-C") - .arg(&qasan_dir) - .status() - .expect("make failed") - .success()); + #[cfg(feature = "usermode")] + { + let qasan_dir = Path::new("libqasan"); + let qasan_dir = fs::canonicalize(&qasan_dir).unwrap(); + let src_dir = Path::new("src"); - cc::Build::new() - .warnings(false) - .file(src_dir.join("asan-giovese.c")) - .compile("asan_giovese"); + assert!(Command::new("make") + .current_dir(&out_dir_path) + .env("CC", &cross_cc) + .env("OUT_DIR", &target_dir) + .arg("-C") + .arg(&qasan_dir) + .arg("clean") + .status() + .expect("make failed") + .success()); + assert!(Command::new("make") + .current_dir(&out_dir_path) + .env("CC", &cross_cc) + .env("OUT_DIR", &target_dir) + .arg("-C") + .arg(&qasan_dir) + .status() + .expect("make failed") + .success()); + + cc::Build::new() + .warnings(false) + .file(src_dir.join("asan-giovese.c")) + .compile("asan_giovese"); + } } diff --git a/libafl_qemu/src/calls.rs b/libafl_qemu/src/calls.rs index 56cf43cc70..a0ec16dfa9 100644 --- a/libafl_qemu/src/calls.rs +++ b/libafl_qemu/src/calls.rs @@ -128,7 +128,20 @@ where return None; } - let mut code = unsafe { std::slice::from_raw_parts(emu.g2h(pc), 512) }; + #[allow(unused_mut)] + let mut code = { + #[cfg(feature = "usermode")] + unsafe { + std::slice::from_raw_parts(emu.g2h(pc), 512) + } + #[cfg(not(feature = "usermode"))] + &mut [0; 512] + }; + #[cfg(not(feature = "usermode"))] + unsafe { + emu.read_mem(pc, code) + }; // TODO handle faults + let mut iaddr = pc; 'disasm: while let Ok(insns) = h.cs.disasm_count(code, iaddr, 1) { @@ -174,7 +187,14 @@ where } iaddr += insn.bytes().len() as u64; - code = unsafe { std::slice::from_raw_parts(emu.g2h(iaddr), 512) }; + #[cfg(feature = "usermode")] + unsafe { + code = std::slice::from_raw_parts(emu.g2h(iaddr), 512); + } + #[cfg(not(feature = "usermode"))] + unsafe { + emu.read_mem(pc, code); + } // TODO handle faults } } diff --git a/libafl_qemu/src/emu.rs b/libafl_qemu/src/emu.rs index f58620d93c..4194a879d0 100644 --- a/libafl_qemu/src/emu.rs +++ b/libafl_qemu/src/emu.rs @@ -3,9 +3,11 @@ use core::{ convert::Into, ffi::c_void, - mem::MaybeUninit, - ptr::{addr_of, addr_of_mut, copy_nonoverlapping, null}, + ptr::{addr_of, addr_of_mut, null}, }; +#[cfg(feature = "usermode")] +use core::{mem::MaybeUninit, ptr::copy_nonoverlapping}; +#[cfg(feature = "usermode")] use libc::c_int; use num_enum::{IntoPrimitive, TryFromPrimitive}; use num_traits::Num; @@ -27,6 +29,8 @@ use pyo3::{prelude::*, PyIterProtocol}; pub const SKIP_EXEC_HOOK: u64 = u64::MAX; +type CPUStatePtr = *const c_void; + #[derive(IntoPrimitive, TryFromPrimitive, Debug, Clone, Copy, EnumIter, PartialEq, Eq)] #[repr(i32)] pub enum MmapPerms { @@ -180,30 +184,16 @@ impl MapInfo { } } +#[cfg(feature = "usermode")] extern "C" { fn qemu_user_init(argc: i32, argv: *const *const u8, envp: *const *const u8) -> i32; - fn libafl_qemu_write_reg(reg: i32, val: *const u8) -> i32; - fn libafl_qemu_read_reg(reg: i32, val: *mut u8) -> i32; - fn libafl_qemu_num_regs() -> i32; - fn libafl_qemu_set_breakpoint(addr: u64) -> i32; - fn libafl_qemu_remove_breakpoint(addr: u64) -> i32; - fn libafl_flush_jit(); - fn libafl_qemu_set_hook( - addr: GuestAddr, - callback: extern "C" fn(GuestAddr, u64), - data: u64, - invalidate_block: i32, - ) -> usize; - // fn libafl_qemu_remove_hook(num: usize, invalidate_block: i32) -> i32; - fn libafl_qemu_remove_hooks_at(addr: GuestAddr, invalidate_block: i32) -> usize; fn libafl_qemu_run() -> i32; + fn libafl_load_addr() -> u64; fn libafl_get_brk() -> u64; fn libafl_set_brk(brk: u64) -> u64; - fn strlen(s: *const u8) -> usize; - /// abi_long target_mmap(abi_ulong start, abi_ulong len, int target_prot, int flags, int fd, abi_ulong offset) fn target_mmap(start: u64, len: u64, target_prot: i32, flags: i32, fd: i32, offset: u64) -> u64; @@ -223,6 +213,77 @@ extern "C" { static guest_base: usize; static mut mmap_next_start: GuestAddr; + static mut libafl_on_thread_hook: unsafe extern "C" fn(u32); + + static mut libafl_pre_syscall_hook: + unsafe extern "C" fn(i32, u64, u64, u64, u64, u64, u64, u64, u64) -> SyscallHookResult; + static mut libafl_post_syscall_hook: + unsafe extern "C" fn(u64, i32, u64, u64, u64, u64, u64, u64, u64, u64) -> u64; +} + +#[cfg(not(feature = "usermode"))] +extern "C" { + fn qemu_init(argc: i32, argv: *const *const u8, envp: *const *const u8); + + fn qemu_main_loop(); + fn qemu_cleanup(); + + // void libafl_cpu_thread_fn(CPUState *cpu) + fn libafl_cpu_thread_fn(cpu: CPUStatePtr); + + // int cpu_memory_rw_debug(CPUState *cpu, target_ulong addr, + // uint8_t *buf, int len, int is_write); + fn cpu_memory_rw_debug( + cpu: CPUStatePtr, + addr: GuestAddr, + buf: *mut u8, + len: i32, + is_write: i32, + ); + + static mut libafl_start_vcpu: extern "C" fn(cpu: CPUStatePtr); + + fn libafl_save_qemu_snapshot(name: *const u8); + fn libafl_load_qemu_snapshot(name: *const u8); +} + +#[cfg(not(feature = "usermode"))] +extern "C" fn qemu_cleanup_atexit() { + unsafe { + qemu_cleanup(); + } +} + +extern "C" { + // CPUState* libafl_qemu_get_cpu(int cpu_index); + fn libafl_qemu_get_cpu(cpu_index: i32) -> CPUStatePtr; + // int libafl_qemu_num_cpus(void); + fn libafl_qemu_num_cpus() -> i32; + // CPUState* libafl_qemu_current_cpu(void); + fn libafl_qemu_current_cpu() -> CPUStatePtr; + + fn libafl_qemu_cpu_index(cpu: CPUStatePtr) -> i32; + + fn libafl_qemu_write_reg(cpu: CPUStatePtr, reg: i32, val: *const u8) -> i32; + fn libafl_qemu_read_reg(cpu: CPUStatePtr, reg: i32, val: *mut u8) -> i32; + fn libafl_qemu_num_regs(cpu: CPUStatePtr) -> i32; + + fn libafl_qemu_set_breakpoint(addr: u64) -> i32; + fn libafl_qemu_remove_breakpoint(addr: u64) -> i32; + fn libafl_flush_jit(); + fn libafl_qemu_trigger_breakpoint(cpu: CPUStatePtr); + + fn libafl_qemu_set_hook( + addr: GuestAddr, + callback: extern "C" fn(GuestAddr, u64), + data: u64, + invalidate_block: i32, + ) -> usize; + // fn libafl_qemu_remove_hook(num: usize, invalidate_block: i32) -> i32; + fn libafl_qemu_remove_hooks_at(addr: GuestAddr, invalidate_block: i32) -> usize; + + fn strlen(s: *const u8) -> usize; + // void libafl_add_edge_hook(uint64_t (*gen)(target_ulong src, target_ulong dst), void (*exec)(uint64_t id)); fn libafl_add_edge_hook( gen: Option u64>, @@ -286,12 +347,9 @@ extern "C" { data: u64, ); - static mut libafl_on_thread_hook: unsafe extern "C" fn(u32); - - static mut libafl_pre_syscall_hook: - unsafe extern "C" fn(i32, u64, u64, u64, u64, u64, u64, u64, u64) -> SyscallHookResult; - static mut libafl_post_syscall_hook: - unsafe extern "C" fn(u64, i32, u64, u64, u64, u64, u64, u64, u64, u64) -> u64; + // void libafl_add_backdoor_hook(void (*exec)(uint64_t id, uint64_t data), + // uint64_t data) + fn libafl_add_backdoor_hook(exec: extern "C" fn(GuestAddr, u64), data: u64); fn libafl_qemu_add_gdb_cmd( callback: extern "C" fn(*const u8, usize, *const ()) -> i32, @@ -300,6 +358,7 @@ extern "C" { fn libafl_qemu_gdb_reply(buf: *const u8, len: usize); } +#[cfg(feature = "usermode")] #[cfg_attr(feature = "python", pyclass(unsendable))] pub struct GuestMaps { orig_c_iter: *const c_void, @@ -307,6 +366,7 @@ pub struct GuestMaps { } // Consider a private new only for Emulator +#[cfg(feature = "usermode")] impl GuestMaps { #[must_use] pub(crate) fn new() -> Self { @@ -320,6 +380,7 @@ impl GuestMaps { } } +#[cfg(feature = "usermode")] impl Iterator for GuestMaps { type Item = MapInfo; @@ -340,7 +401,7 @@ impl Iterator for GuestMaps { } } -#[cfg(feature = "python")] +#[cfg(all(feature = "usermode", feature = "python"))] #[pyproto] impl PyIterProtocol for GuestMaps { fn __iter__(slf: PyRef) -> PyRef { @@ -351,6 +412,7 @@ impl PyIterProtocol for GuestMaps { } } +#[cfg(feature = "usermode")] impl Drop for GuestMaps { fn drop(&mut self) { unsafe { @@ -378,6 +440,110 @@ extern "C" fn gdb_cmd(buf: *const u8, len: usize, data: *const ()) -> i32 { } } +#[derive(Debug)] +#[repr(C)] +pub struct CPU { + ptr: CPUStatePtr, +} + +#[allow(clippy::unused_self)] +impl CPU { + #[must_use] + pub fn emulator(&self) -> Emulator { + Emulator::new_empty() + } + + #[must_use] + #[allow(clippy::cast_sign_loss)] + pub fn index(&self) -> usize { + unsafe { libafl_qemu_cpu_index(self.ptr) as usize } + } + + pub fn trigger_breakpoint(&self) { + unsafe { + libafl_qemu_trigger_breakpoint(self.ptr); + } + } + + #[cfg(feature = "usermode")] + #[must_use] + pub fn g2h(&self, addr: GuestAddr) -> *mut T { + unsafe { (addr as usize + guest_base) as *mut T } + } + + #[cfg(feature = "usermode")] + #[must_use] + pub fn h2g(&self, addr: *const T) -> GuestAddr { + unsafe { (addr as usize - guest_base) as GuestAddr } + } + + /// Write a value to a guest address. + /// + /// # Safety + /// This will write to a translated guest address (using `g2h`). + /// It just adds `guest_base` and writes to that location, without checking the bounds. + /// This may only be safely used for valid guest addresses! + pub unsafe fn write_mem(&self, addr: GuestAddr, buf: &[u8]) { + #[cfg(feature = "usermode")] + { + let host_addr = Emulator::new_empty().g2h(addr); + copy_nonoverlapping(buf.as_ptr(), host_addr, buf.len()); + } + #[cfg(not(feature = "usermode"))] + cpu_memory_rw_debug(self.ptr, addr, buf.as_ptr() as *mut u8, buf.len() as i32, 1); + } + + /// Read a value from a guest address. + /// + /// # Safety + /// This will read from a translated guest address (using `g2h`). + /// It just adds `guest_base` and writes to that location, without checking the bounds. + /// This may only be safely used for valid guest addresses! + pub unsafe fn read_mem(&self, addr: GuestAddr, buf: &mut [u8]) { + #[cfg(feature = "usermode")] + { + let host_addr = Emulator::new_empty().g2h(addr); + copy_nonoverlapping(host_addr, buf.as_mut_ptr(), buf.len()); + } + #[cfg(not(feature = "usermode"))] + cpu_memory_rw_debug(self.ptr, addr, buf.as_mut_ptr(), buf.len() as i32, 0); + } + + #[must_use] + pub fn num_regs(&self) -> i32 { + unsafe { libafl_qemu_num_regs(self.ptr) } + } + + pub fn write_reg(&self, reg: R, val: T) -> Result<(), String> + where + T: Num + PartialOrd + Copy, + R: Into, + { + let reg = reg.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)) + } else { + Ok(()) + } + } + + pub fn read_reg(&self, reg: R) -> Result + where + T: Num + PartialOrd + Copy, + R: Into, + { + let reg = reg.into(); + let mut val = T::zero(); + let success = unsafe { libafl_qemu_read_reg(self.ptr, reg, addr_of_mut!(val) as *mut u8) }; + if success == 0 { + Err(format!("Failed to read register {}", reg)) + } else { + Ok(val) + } + } +} + static mut EMULATOR_IS_INITIALIZED: bool = false; #[derive(Clone, Debug)] @@ -408,11 +574,21 @@ impl Emulator { #[allow(clippy::cast_possible_wrap)] let argc = argv.len() as i32; unsafe { + #[cfg(feature = "usermode")] qemu_user_init( argc, argv.as_ptr() as *const *const u8, envp.as_ptr() as *const *const u8, ); + #[cfg(not(feature = "usermode"))] + { + qemu_init( + argc, + argv.as_ptr() as *const *const u8, + envp.as_ptr() as *const *const u8, + ); + libc::atexit(qemu_cleanup_atexit); + } EMULATOR_IS_INITIALIZED = true; } Emulator { _private: () } @@ -423,37 +599,70 @@ impl Emulator { Emulator { _private: () } } + #[cfg(not(feature = "usermode"))] + pub fn start(&self, cpu: &CPU) { + unsafe { + libafl_cpu_thread_fn(cpu.ptr); + } + } + /// This function gets the memory mappings from the emulator. + #[cfg(feature = "usermode")] #[must_use] pub fn mappings(&self) -> GuestMaps { GuestMaps::new() } - /// Write a value to a guest address. - /// - /// # Safety - /// This will write to a translated guest address (using `g2h`). - /// It just adds `guest_base` and writes to that location, without checking the bounds. - /// This may only be safely used for valid guest addresses! - pub unsafe fn write_mem(&self, addr: GuestAddr, buf: &[u8]) { - let host_addr = self.g2h(addr); - copy_nonoverlapping(buf.as_ptr(), host_addr, buf.len()); + #[must_use] + #[allow(clippy::cast_possible_wrap)] + #[allow(clippy::cast_sign_loss)] + pub fn num_cpus(&self) -> usize { + unsafe { libafl_qemu_num_cpus() as usize } + } + + #[must_use] + pub fn current_cpu(&self) -> Option { + let ptr = unsafe { libafl_qemu_current_cpu() }; + if ptr.is_null() { + None + } else { + Some(CPU { ptr }) + } + } + + #[must_use] + #[allow(clippy::cast_possible_wrap)] + pub fn cpu_from_index(&self, index: usize) -> CPU { + unsafe { + CPU { + ptr: libafl_qemu_get_cpu(index as i32), + } + } + } + + #[cfg(feature = "usermode")] + #[must_use] + pub fn g2h(&self, addr: GuestAddr) -> *mut T { + unsafe { (addr as usize + guest_base) as *mut T } + } + + #[cfg(feature = "usermode")] + #[must_use] + pub fn h2g(&self, addr: *const T) -> GuestAddr { + unsafe { (addr as usize - guest_base) as GuestAddr } + } + + pub unsafe fn write_mem(&self, addr: GuestAddr, buf: &[u8]) { + self.current_cpu().unwrap().write_mem(addr, buf); } - /// Read a value from a guest address. - /// - /// # Safety - /// This will read from a translated guest address (using `g2h`). - /// It just adds `guest_base` and writes to that location, without checking the bounds. - /// This may only be safely used for valid guest addresses! pub unsafe fn read_mem(&self, addr: GuestAddr, buf: &mut [u8]) { - let host_addr = self.g2h(addr); - copy_nonoverlapping(host_addr, buf.as_mut_ptr(), buf.len()); + self.current_cpu().unwrap().read_mem(addr, buf); } #[must_use] pub fn num_regs(&self) -> i32 { - unsafe { libafl_qemu_num_regs() } + self.current_cpu().unwrap().num_regs() } pub fn write_reg(&self, reg: R, val: T) -> Result<(), String> @@ -461,13 +670,7 @@ impl Emulator { T: Num + PartialOrd + Copy, R: Into, { - let reg = reg.into(); - let success = unsafe { libafl_qemu_write_reg(reg, addr_of!(val) as *const u8) }; - if success == 0 { - Err(format!("Failed to write to register {}", reg)) - } else { - Ok(()) - } + self.current_cpu().unwrap().write_reg(reg, val) } pub fn read_reg(&self, reg: R) -> Result @@ -475,14 +678,7 @@ impl Emulator { T: Num + PartialOrd + Copy, R: Into, { - let reg = reg.into(); - let mut val = T::zero(); - let success = unsafe { libafl_qemu_read_reg(reg, addr_of_mut!(val) as *mut u8) }; - if success == 0 { - Err(format!("Failed to read register {}", reg)) - } else { - Ok(val) - } + self.current_cpu().unwrap().read_reg(reg) } pub fn set_breakpoint(&self, addr: GuestAddr) { @@ -518,47 +714,47 @@ impl Emulator { /// Should, in general, be safe to call. /// Of course, the emulated target is not contained securely and can corrupt state or interact with the operating system. pub unsafe fn run(&self) { + #[cfg(feature = "usermode")] libafl_qemu_run(); + #[cfg(not(feature = "usermode"))] + qemu_main_loop(); } - #[must_use] - pub fn g2h(&self, addr: GuestAddr) -> *mut T { - unsafe { (addr as usize + guest_base) as *mut T } - } - - #[must_use] - pub fn h2g(&self, addr: *const T) -> GuestAddr { - unsafe { (addr as usize - guest_base) as GuestAddr } - } - + #[cfg(feature = "usermode")] #[must_use] pub fn binary_path<'a>(&self) -> &'a str { unsafe { from_utf8_unchecked(from_raw_parts(exec_path, strlen(exec_path))) } } + #[cfg(feature = "usermode")] #[must_use] pub fn load_addr(&self) -> GuestAddr { unsafe { libafl_load_addr() as GuestAddr } } + #[cfg(feature = "usermode")] #[must_use] pub fn get_brk(&self) -> GuestAddr { unsafe { libafl_get_brk() as GuestAddr } } + #[cfg(feature = "usermode")] pub fn set_brk(&self, brk: GuestAddr) { unsafe { libafl_set_brk(brk.into()) }; } + #[cfg(feature = "usermode")] #[must_use] pub fn get_mmap_start(&self) -> GuestAddr { unsafe { mmap_next_start } } + #[cfg(feature = "usermode")] pub fn set_mmap_start(&self, start: GuestAddr) { unsafe { mmap_next_start = start }; } + #[cfg(feature = "usermode")] fn mmap( &self, addr: GuestAddr, @@ -574,6 +770,7 @@ impl Emulator { } } + #[cfg(feature = "usermode")] pub fn map_private( &self, addr: GuestAddr, @@ -585,6 +782,7 @@ impl Emulator { .map(|addr| addr as GuestAddr) } + #[cfg(feature = "usermode")] pub fn map_fixed( &self, addr: GuestAddr, @@ -601,6 +799,7 @@ impl Emulator { .map(|addr| addr as GuestAddr) } + #[cfg(feature = "usermode")] pub fn mprotect(&self, addr: GuestAddr, size: usize, perms: MmapPerms) -> Result<(), String> { let res = unsafe { target_mprotect(addr.into(), size as u64, perms.into()) }; if res == 0 { @@ -610,6 +809,7 @@ impl Emulator { } } + #[cfg(feature = "usermode")] pub fn unmap(&self, addr: GuestAddr, size: usize) -> Result<(), String> { if unsafe { target_munmap(addr.into(), size as u64) } == 0 { Ok(()) @@ -680,12 +880,37 @@ impl Emulator { unsafe { libafl_add_cmp_hook(gen, exec1, exec2, exec4, exec8, data) } } + pub fn add_backdoor_hook(&self, exec: extern "C" fn(GuestAddr, u64), data: u64) { + unsafe { libafl_add_backdoor_hook(exec, data) }; + } + + #[cfg(not(feature = "usermode"))] + pub fn set_vcpu_start(&self, hook: extern "C" fn(cpu: CPU)) { + unsafe { + libafl_start_vcpu = core::mem::transmute(hook); + } + } + + #[cfg(feature = "usermode")] pub fn set_on_thread_hook(&self, hook: extern "C" fn(tid: u32)) { unsafe { libafl_on_thread_hook = hook; } } + /*#[cfg(not(feature = "usermode"))] + pub fn save_snapshot(&self, name: &str) { + let s = CString::new(name).expect("Invalid snapshot name"); + unsafe { libafl_save_qemu_snapshot(s.as_ptr() as *const _) }; + } + + #[cfg(not(feature = "usermode"))] + pub fn load_snapshot(&self, name: &str) { + let s = CString::new(name).expect("Invalid snapshot name"); + unsafe { libafl_load_qemu_snapshot(s.as_ptr() as *const _) }; + }*/ + + #[cfg(feature = "usermode")] pub fn set_pre_syscall_hook( &self, hook: extern "C" fn(i32, u64, u64, u64, u64, u64, u64, u64, u64) -> SyscallHookResult, @@ -695,6 +920,7 @@ impl Emulator { } } + #[cfg(feature = "usermode")] pub fn set_post_syscall_hook( &self, hook: extern "C" fn(u64, i32, u64, u64, u64, u64, u64, u64, u64, u64) -> u64, diff --git a/libafl_qemu/src/hooks.rs b/libafl_qemu/src/hooks.rs index 908b0a60d1..4c0e04a6f3 100644 --- a/libafl_qemu/src/hooks.rs +++ b/libafl_qemu/src/hooks.rs @@ -23,6 +23,7 @@ use crate::{ enum Hook { Function(*const c_void), Closure(FatPtr), + #[cfg(feature = "usermode")] Once(FatPtr), Empty, } @@ -425,7 +426,9 @@ define_cmp_exec_hook!(exec_cmp2_hook_wrapper, 2, u16); define_cmp_exec_hook!(exec_cmp4_hook_wrapper, 3, u32); define_cmp_exec_hook!(exec_cmp8_hook_wrapper, 4, u64); +#[cfg(feature = "usermode")] static mut ON_THREAD_HOOKS: Vec = vec![]; +#[cfg(feature = "usermode")] extern "C" fn on_thread_hooks_wrapper(tid: u32) where I: Input, @@ -461,7 +464,9 @@ where } } +#[cfg(feature = "usermode")] static mut SYSCALL_HOOKS: Vec = vec![]; +#[cfg(feature = "usermode")] extern "C" fn syscall_hooks_wrapper( sys_num: i32, a0: u64, @@ -561,7 +566,9 @@ where } } +#[cfg(feature = "usermode")] static mut SYSCALL_POST_HOOKS: Vec = vec![]; +#[cfg(feature = "usermode")] extern "C" fn syscall_after_hooks_wrapper( result: u64, sys_num: i32, @@ -1419,6 +1426,7 @@ where } } + #[cfg(feature = "usermode")] pub fn thread_creation(&self, hook: fn(&mut Self, Option<&mut S>, tid: u32)) { unsafe { ON_THREAD_HOOKS.push(Hook::Function(hook as *const libc::c_void)); @@ -1427,6 +1435,7 @@ where .set_on_thread_hook(on_thread_hooks_wrapper::); } + #[cfg(feature = "usermode")] pub fn thread_creation_closure( &self, hook: Box, u32) + 'a>, @@ -1438,6 +1447,7 @@ where .set_on_thread_hook(on_thread_hooks_wrapper::); } + #[cfg(feature = "usermode")] pub fn thread_creation_once(&self, hook: Box, u32) + 'a>) { unsafe { ON_THREAD_HOOKS.push(Hook::Once(transmute(hook))); @@ -1446,6 +1456,7 @@ where .set_on_thread_hook(on_thread_hooks_wrapper::); } + #[cfg(feature = "usermode")] #[allow(clippy::type_complexity)] pub fn syscalls( &self, @@ -1470,6 +1481,7 @@ where .set_pre_syscall_hook(syscall_hooks_wrapper::); } + #[cfg(feature = "usermode")] #[allow(clippy::type_complexity)] pub fn syscalls_closure( &self, @@ -1496,6 +1508,7 @@ where .set_pre_syscall_hook(syscall_hooks_wrapper::); } + #[cfg(feature = "usermode")] #[allow(clippy::type_complexity)] pub fn after_syscalls( &self, @@ -1521,6 +1534,7 @@ where .set_post_syscall_hook(syscall_after_hooks_wrapper::); } + #[cfg(feature = "usermode")] #[allow(clippy::type_complexity)] pub fn after_syscalls_closure( &self, diff --git a/libafl_qemu/src/lib.rs b/libafl_qemu/src/lib.rs index fa3b8c1b0b..046fd923c4 100644 --- a/libafl_qemu/src/lib.rs +++ b/libafl_qemu/src/lib.rs @@ -50,9 +50,13 @@ pub mod edges; pub use edges::QemuEdgeCoverageHelper; pub mod cmplog; pub use cmplog::QemuCmpLogHelper; +#[cfg(feature = "usermode")] pub mod snapshot; +#[cfg(feature = "usermode")] pub use snapshot::QemuSnapshotHelper; +#[cfg(feature = "usermode")] pub mod asan; +#[cfg(feature = "usermode")] pub use asan::{init_with_asan, QemuAsanHelper}; pub mod calls; diff --git a/libafl_qemu/src/snapshot.rs b/libafl_qemu/src/snapshot.rs index fc9ba747aa..eae4b2ad8d 100644 --- a/libafl_qemu/src/snapshot.rs +++ b/libafl_qemu/src/snapshot.rs @@ -358,9 +358,7 @@ where 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::() - .unwrap(); + let h = hooks.match_helper_mut::().unwrap(); 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) {