diff --git a/libafl_qemu/Cargo.toml b/libafl_qemu/Cargo.toml index d53e98f3b0..3d39a6cfce 100644 --- a/libafl_qemu/Cargo.toml +++ b/libafl_qemu/Cargo.toml @@ -22,6 +22,8 @@ aarch64 = [] # build qemu for aarch64 clippy = [] # special feature for clippy, don't use in normal projects§ +systemmode = [] # Emulate system images instead of user-mode binaries + [dependencies] libafl = { path = "../libafl", version = "0.7.1" } libafl_targets = { path = "../libafl_targets", version = "0.7.1" } diff --git a/libafl_qemu/build.rs b/libafl_qemu/build.rs index 5beec3039c..1c6ee21041 100644 --- a/libafl_qemu/build.rs +++ b/libafl_qemu/build.rs @@ -145,6 +145,9 @@ fn main() { } let build_dir = qemu_path.join("build"); + #[cfg(feature = "systemmode")] + let output_lib = build_dir.join(&format!("libqemu-system-{}.so", cpu_target)); + #[cfg(not(feature = "systemmode"))] let output_lib = build_dir.join(&format!("libqemu-{}.so", cpu_target)); if !output_lib.is_file() { drop( @@ -157,7 +160,7 @@ fn main() { .current_dir(&qemu_path) //.arg("--as-static-lib") .arg("--as-shared-lib") - .arg(&format!("--target-list={}-linux-user", cpu_target)) + .arg(&format!("--target-list={}-linux-user,{}-softmmu", cpu_target,cpu_target)) .args(&[ "--audio-drv-list=", "--disable-blobs", @@ -170,7 +173,7 @@ fn main() { "--disable-curl", "--disable-curses", "--disable-dmg", - "--disable-fdt", + "--enable-fdt", "--disable-gcrypt", "--disable-glusterfs", "--disable-gnutls", @@ -199,7 +202,7 @@ fn main() { "--disable-smartcard", "--disable-snappy", "--disable-spice", - "--disable-system", + "--enable-system", "--disable-tools", "--disable-tpm", "--disable-usb-redir", @@ -248,6 +251,9 @@ fn main() { let mut objects = vec![]; for dir in &[ build_dir.join("libcommon.fa.p"), + #[cfg(feature = "systemmode")] + build_dir.join(&format!("libqemu-{}-softmmu.fa.p", cpu_target)), + #[cfg(not(feature = "systemmode"))] build_dir.join(&format!("libqemu-{}-linux-user.fa.p", cpu_target)), build_dir.join("libcommon-user.fa.p"), //build_dir.join("libqemuutil.a.p"), @@ -315,6 +321,13 @@ fn main() { #[cfg(not(feature = "python"))] { + #[cfg(feature = "systemmode")] + fs::copy( + build_dir.join(&format!("libqemu-system-{}.so", cpu_target)), + target_dir.join(&format!("libqemu-system-{}.so", cpu_target)), + ) + .expect("Failed to copy the QEMU shared object"); + #[cfg(not(feature = "systemmode"))] fs::copy( build_dir.join(&format!("libqemu-{}.so", cpu_target)), target_dir.join(&format!("libqemu-{}.so", cpu_target)), @@ -325,6 +338,9 @@ fn main() { "cargo:rustc-link-search=native={}", &target_dir.to_string_lossy().to_string() ); + #[cfg(feature = "systemmode")] + println!("cargo:rustc-link-lib=qemu-system-{}", cpu_target); + #[cfg(not(feature = "systemmode"))] println!("cargo:rustc-link-lib=qemu-{}", cpu_target); println!("cargo:rustc-env=LD_LIBRARY_PATH={}", target_dir.display()); diff --git a/libafl_qemu/src/emu.rs b/libafl_qemu/src/emu.rs index b4485bbc3f..85cafc43fb 100644 --- a/libafl_qemu/src/emu.rs +++ b/libafl_qemu/src/emu.rs @@ -1,5 +1,6 @@ //! Expose QEMU user `LibAFL` C api to Rust +use libc::c_char; use core::{ convert::Into, ffi::c_void, @@ -170,12 +171,18 @@ impl MapInfo { } extern "C" { + #[cfg(not(feature = "systemmode"))] fn qemu_user_init(argc: i32, argv: *const *const u8, envp: *const *const u8) -> i32; + #[cfg(feature = "systemmode")] + fn libafl_qemu_sys_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; + #[cfg(not(feature = "systemmode"))] fn libafl_qemu_set_breakpoint(addr: u64) -> i32; + #[cfg(feature = "systemmode")] + fn libafl_qemu_set_native_breakpoint(addr: u64) -> i32; fn libafl_qemu_remove_breakpoint(addr: u64) -> i32; fn libafl_qemu_set_hook(addr: u64, callback: extern "C" fn(u64), val: u64) -> i32; fn libafl_qemu_remove_hook(addr: u64) -> i32; @@ -184,6 +191,15 @@ extern "C" { fn libafl_get_brk() -> u64; fn libafl_set_brk(brk: u64) -> u64; + #[cfg(feature = "systemmode")] + fn libafl_phys_write(addr: u64, buf: *const u8, len: i32); + #[cfg(feature = "systemmode")] + fn libafl_phys_read(addr: u64, buf: *mut u8, len: i32); + #[cfg(feature = "systemmode")] + fn libafl_snapshot_save(name: *const c_char) -> i32; + #[cfg(feature = "systemmode")] + fn libafl_snapshot_load(name: *const c_char) -> i32; + 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) @@ -318,11 +334,18 @@ impl Emulator { #[allow(clippy::cast_possible_wrap)] let argc = argv.len() as i32; unsafe { + #[cfg(not(feature = "systemmode"))] qemu_user_init( argc, argv.as_ptr() as *const *const u8, envp.as_ptr() as *const *const u8, ); + #[cfg(feature = "systemmode")] + libafl_qemu_sys_init( + argc, + argv.as_ptr() as *const *const u8, + envp.as_ptr() as *const *const u8, + ); EMULATOR_IS_INITIALIZED = true; } Emulator { _private: () } @@ -338,6 +361,11 @@ impl Emulator { GuestMaps::new() } + #[cfg(feature = "systemmode")] + pub unsafe fn write_mem(&self, addr: u64, buf: &[u8]) { + unsafe { libafl_phys_write(addr, buf.as_ptr(), buf.len().try_into().unwrap()); } + } + #[cfg(not(feature = "systemmode"))] pub unsafe fn write_mem(&self, addr: u64, buf: &[T]) { let host_addr = self.g2h(addr); copy_nonoverlapping( @@ -347,6 +375,11 @@ impl Emulator { ); } + #[cfg(feature = "systemmode")] + pub unsafe fn read_mem(&self, addr: u64, buf: &mut [u8]) { + unsafe { libafl_phys_read(addr, buf.as_mut_ptr(), buf.len().try_into().unwrap()); } + } + #[cfg(not(feature = "systemmode"))] pub unsafe fn read_mem(&self, addr: u64, buf: &mut [T]) { let host_addr = self.g2h(addr); copy_nonoverlapping( @@ -392,6 +425,9 @@ impl Emulator { pub fn set_breakpoint(&self, addr: u64) { unsafe { + #[cfg(feature = "systemmode")] + libafl_qemu_set_native_breakpoint(addr); + #[cfg(not(feature = "systemmode"))] libafl_qemu_set_breakpoint(addr); } } @@ -619,6 +655,25 @@ impl Emulator { libafl_post_syscall_hook = hook; } } + #[cfg(feature = "systemmode")] + pub fn snapshot_save(&self, name: &str) -> bool{ + let cname = std::ffi::CString::new(name).expect("Snapshot name not CString compatible"); + let ret = unsafe { libafl_snapshot_save(cname.as_ptr()) }; + match ret { + 0 => false, + _ => true, + } + } + + #[cfg(feature = "systemmode")] + pub fn snapshot_load(&self, name: &str) -> bool{ + let cname = std::ffi::CString::new(name).expect("Snapshot name not CString compatible"); + let ret = unsafe { libafl_snapshot_load(cname.as_ptr()) }; + match ret { + 0 => false, + _ => true, + } + } } #[cfg(feature = "python")] diff --git a/libafl_qemu/src/lib.rs b/libafl_qemu/src/lib.rs index 499c82d3fd..e7bbc3aaf3 100644 --- a/libafl_qemu/src/lib.rs +++ b/libafl_qemu/src/lib.rs @@ -35,10 +35,14 @@ pub use edges::QemuEdgeCoverageHelper; pub mod cmplog; #[cfg(target_os = "linux")] pub use cmplog::QemuCmpLogHelper; -#[cfg(target_os = "linux")] +#[cfg(all(target_os = "linux",not(feature = "systemmode")))] pub mod snapshot; -#[cfg(target_os = "linux")] +#[cfg(all(target_os = "linux",not(feature = "systemmode")))] pub use snapshot::QemuSnapshotHelper; +#[cfg(all(target_os = "linux",feature = "systemmode"))] +pub mod snapshot_sys; +#[cfg(all(target_os = "linux",feature = "systemmode"))] +pub use snapshot_sys::QemuSysSnapshotHelper; #[cfg(target_os = "linux")] pub mod asan; #[cfg(target_os = "linux")] diff --git a/libafl_qemu/src/snapshot_sys.rs b/libafl_qemu/src/snapshot_sys.rs new file mode 100644 index 0000000000..0441c1b037 --- /dev/null +++ b/libafl_qemu/src/snapshot_sys.rs @@ -0,0 +1,63 @@ +use crate::Emulator; +use crate::QemuExecutor; +use crate::QemuHelper; +use crate::QemuHelperTuple; +use libafl::{executors::ExitKind, inputs::Input, observers::ObserversTuple, state::HasMetadata}; + +use crate::{ + emu, +}; +// TODO be thread-safe maybe with https://amanieu.github.io/thread_local-rs/thread_local/index.html +#[derive(Debug)] +pub struct QemuSysSnapshotHelper { + pub empty: bool, +} + +impl QemuSysSnapshotHelper { + #[must_use] + pub fn new() -> Self { + Self { + empty: true, + } + } + + pub fn snapshot(&mut self, emulator: &Emulator) { + if self.empty { + let ret = emulator.snapshot_save("Start"); + if !ret { panic!("QemuSysSnapshotHelper failed to take a snapshot") }; + self.empty = false; + } + } + pub fn reset(&mut self, emulator: &Emulator) { + let ret = emulator.snapshot_load("Start"); + if !ret { panic!("QemuSysSnapshotHelper failed to load a snapshot") }; + } +} + +impl Default for QemuSysSnapshotHelper { + fn default() -> Self { + Self::new() + } +} + +impl QemuHelper for QemuSysSnapshotHelper +where + I: Input, + S: HasMetadata, +{ + fn init<'a, H, OT, QT>(&self, _executor: &QemuExecutor<'a, H, I, OT, QT, S>) + where + H: FnMut(&I) -> ExitKind, + OT: ObserversTuple, + QT: QemuHelperTuple, + { + } + + fn pre_exec(&mut self, emulator: &Emulator, _input: &I) { + if self.empty { + self.snapshot(emulator); + } else { + self.reset(emulator); + } + } +} \ No newline at end of file