QEMU generic memory iterator (#2148)

* QEMU generic memory iterator + Refactoring

* Generic Memory Iterator (systemmode only for now): It is now possible to iterator over memory ranges, independently of the address kind

* Refactoring or Emulator / Qemu structures: they are now handled separately in different files

* Refactoring of Exit Handlers: Result / Error structs have been clarified

* Simple handler for signals

* add new `check-cfg` calls for libafl qemu
This commit is contained in:
Romain Malmain 2024-05-07 10:46:15 +02:00 committed by GitHub
parent a16fb88f3e
commit bed500471a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
49 changed files with 2790 additions and 2577 deletions

View File

@ -51,8 +51,8 @@ use libafl_qemu::{
elf::EasyElf,
filter_qemu_args,
hooks::QemuHooks,
GuestReg, MmapPerms, Qemu, QemuExitReason, QemuExitReasonError, QemuForkExecutor,
QemuShutdownCause, Regs,
GuestReg, MmapPerms, Qemu, QemuExitError, QemuExitReason, QemuForkExecutor, QemuShutdownCause,
Regs,
};
#[cfg(unix)]
use nix::unistd::dup;
@ -328,7 +328,7 @@ fn fuzz(
Ok(QemuExitReason::End(QemuShutdownCause::HostSignal(Signal::SigInterrupt))) => {
process::exit(0)
}
Err(QemuExitReasonError::UnexpectedExit) => return ExitKind::Crash,
Err(QemuExitError::UnexpectedExit) => return ExitKind::Crash,
_ => panic!("Unexpected QEMU exit."),
}
}

View File

@ -60,8 +60,8 @@ use libafl_qemu::{
MmapPerms,
Qemu,
QemuExecutor,
QemuExitError,
QemuExitReason,
QemuExitReasonError,
QemuShutdownCause,
Regs,
};
@ -350,7 +350,7 @@ fn fuzz(
Ok(QemuExitReason::End(QemuShutdownCause::HostSignal(Signal::SigInterrupt))) => {
process::exit(0)
}
Err(QemuExitReasonError::UnexpectedExit) => return ExitKind::Crash,
Err(QemuExitError::UnexpectedExit) => return ExitKind::Crash,
_ => panic!("Unexpected QEMU exit."),
}
}

View File

@ -29,8 +29,8 @@ use libafl_bolts::{
use libafl_qemu::{
edges::{QemuEdgeCoverageChildHelper, EDGES_MAP_PTR, EDGES_MAP_SIZE_IN_USE},
elf::EasyElf,
ArchExtras, CallingConvention, GuestAddr, GuestReg, MmapPerms, Qemu, QemuExitReason,
QemuExitReasonError, QemuForkExecutor, QemuHooks, QemuShutdownCause, Regs,
ArchExtras, CallingConvention, GuestAddr, GuestReg, MmapPerms, Qemu, QemuExitError,
QemuExitReason, QemuForkExecutor, QemuHooks, QemuShutdownCause, Regs,
};
#[derive(Default)]
@ -211,7 +211,7 @@ pub fn fuzz() -> Result<(), Error> {
Ok(QemuExitReason::End(QemuShutdownCause::HostSignal(Signal::SigInterrupt))) => {
process::exit(0)
}
Err(QemuExitReasonError::UnexpectedExit) => return ExitKind::Crash,
Err(QemuExitError::UnexpectedExit) => return ExitKind::Crash,
_ => panic!("Unexpected QEMU exit."),
}
}

View File

@ -5,7 +5,7 @@ use std::{env, path::PathBuf, process};
use libafl::{
corpus::{Corpus, InMemoryCorpus, OnDiskCorpus},
events::{launcher::Launcher, EventConfig, CTRL_C_EXIT},
events::{launcher::Launcher, EventConfig},
executors::ExitKind,
feedback_or, feedback_or_fast,
feedbacks::{CrashFeedback, MaxMapFeedback, TimeFeedback, TimeoutFeedback},
@ -29,13 +29,13 @@ use libafl_bolts::{
};
use libafl_qemu::{
breakpoint::Breakpoint,
command::{Command, EmulatorMemoryChunk, EndCommand, StartCommand},
command::{Command, EndCommand, StartCommand},
edges::{edges_map_mut_ptr, QemuEdgeCoverageHelper, EDGES_MAP_SIZE_IN_USE, MAX_EDGES_FOUND},
elf::EasyElf,
emu::Emulator,
executor::{stateful::StatefulQemuExecutor, QemuExecutorState},
EmuExitReasonError, FastSnapshotManager, GuestPhysAddr, GuestReg, HandlerError, HandlerResult,
QemuHooks, StdEmuExitHandler,
EmulatorMemoryChunk, FastSnapshotManager, GuestPhysAddr, GuestReg, QemuHooks,
StdEmulatorExitHandler,
};
// use libafl_qemu::QemuSnapshotBuilder; // for normal qemu snapshot
@ -93,7 +93,7 @@ pub fn fuzz() {
let emu_snapshot_manager = FastSnapshotManager::new(false);
// Choose Exit Handler
let emu_exit_handler = StdEmuExitHandler::new(emu_snapshot_manager);
let emu_exit_handler = StdEmulatorExitHandler::new(emu_snapshot_manager);
// Create emulator
let emu = Emulator::new(&args, &env, emu_exit_handler).unwrap();
@ -126,29 +126,10 @@ pub fn fuzz() {
// The wrapped harness function, calling out to the LLVM-style harness
let mut harness =
|input: &BytesInput, qemu_executor_state: &mut QemuExecutorState<_, _>| unsafe {
match emu.run(input, qemu_executor_state) {
Ok(handler_result) => match handler_result {
HandlerResult::UnhandledExit(unhandled_exit) => {
panic!("Unhandled exit: {}", unhandled_exit)
}
HandlerResult::EndOfRun(exit_kind) => return exit_kind,
HandlerResult::Interrupted => {
std::process::exit(CTRL_C_EXIT);
}
},
Err(handler_error) => match handler_error {
HandlerError::QemuExitReasonError(emu_exit_reason_error) => {
match emu_exit_reason_error {
EmuExitReasonError::UnknownKind => panic!("unknown kind"),
EmuExitReasonError::UnexpectedExit => return ExitKind::Crash,
_ => {
panic!("Emu Exit unhandled error: {:?}", emu_exit_reason_error)
}
}
}
_ => panic!("Unhandled error: {:?}", handler_error),
},
}
emu.run(input, qemu_executor_state)
.unwrap()
.try_into()
.unwrap()
};
// Create an observation channel using the coverage map

View File

@ -5,7 +5,7 @@ use std::{env, path::PathBuf, process};
use libafl::{
corpus::{Corpus, InMemoryCorpus, OnDiskCorpus},
events::{launcher::Launcher, EventConfig, CTRL_C_EXIT},
events::{launcher::Launcher, EventConfig},
executors::ExitKind,
feedback_or, feedback_or_fast,
feedbacks::{CrashFeedback, MaxMapFeedback, TimeFeedback, TimeoutFeedback},
@ -22,7 +22,7 @@ use libafl::{
use libafl_bolts::{
core_affinity::Cores,
current_nanos,
os::unix_signals::Signal,
os::unix_signals::{Signal, CTRL_C_EXIT},
ownedref::OwnedMutSlice,
rands::StdRand,
shmem::{ShMemProvider, StdShMemProvider},
@ -32,8 +32,7 @@ use libafl_bolts::{
use libafl_qemu::{
edges::{edges_map_mut_ptr, QemuEdgeCoverageHelper, EDGES_MAP_SIZE_IN_USE, MAX_EDGES_FOUND},
elf::EasyElf,
emu::Qemu,
QemuExecutor, QemuExitReason, QemuExitReasonError, QemuHooks, QemuShutdownCause, Regs,
Qemu, QemuExecutor, QemuExitError, QemuExitReason, QemuHooks, QemuShutdownCause, Regs,
};
use libafl_qemu_sys::GuestPhysAddr;
@ -128,7 +127,7 @@ pub fn fuzz() {
Ok(QemuExitReason::End(QemuShutdownCause::HostSignal(
Signal::SigInterrupt,
))) => process::exit(CTRL_C_EXIT),
Err(QemuExitReasonError::UnexpectedExit) => return ExitKind::Crash,
Err(QemuExitError::UnexpectedExit) => return ExitKind::Crash,
_ => panic!("Unexpected QEMU exit."),
}

View File

@ -5,8 +5,7 @@ use std::{env, path::PathBuf, process};
use libafl::{
corpus::{Corpus, InMemoryCorpus, OnDiskCorpus},
events::{launcher::Launcher, EventConfig, CTRL_C_EXIT},
executors::ExitKind,
events::{launcher::Launcher, EventConfig},
feedback_or, feedback_or_fast,
feedbacks::{CrashFeedback, MaxMapFeedback, TimeFeedback, TimeoutFeedback},
fuzzer::{Fuzzer, StdFuzzer},
@ -31,8 +30,7 @@ use libafl_qemu::{
edges::{edges_map_mut_ptr, QemuEdgeCoverageHelper, EDGES_MAP_SIZE_IN_USE, MAX_EDGES_FOUND},
emu::Emulator,
executor::{stateful::StatefulQemuExecutor, QemuExecutorState},
EmuExitReasonError, FastSnapshotManager, HandlerError, HandlerResult, QemuHooks,
StdEmuExitHandler,
FastSnapshotManager, QemuHooks, StdEmulatorExitHandler,
};
// use libafl_qemu::QemuSnapshotBuilder; for normal qemu snapshot
@ -56,8 +54,8 @@ pub fn fuzz() {
let env: Vec<(String, String)> = env::vars().collect();
// let emu_snapshot_manager = QemuSnapshotBuilder::new(true);
let emu_snapshot_manager = FastSnapshotManager::new(false); // Create a snapshot manager (normal or fast for now).
let emu_exit_handler: StdEmuExitHandler<FastSnapshotManager> =
StdEmuExitHandler::new(emu_snapshot_manager); // Create an exit handler: it is the entity taking the decision of what should be done when QEMU returns.
let emu_exit_handler: StdEmulatorExitHandler<FastSnapshotManager> =
StdEmulatorExitHandler::new(emu_snapshot_manager); // Create an exit handler: it is the entity taking the decision of what should be done when QEMU returns.
let emu = Emulator::new(&args, &env, emu_exit_handler).unwrap(); // Create the emulator
let devices = emu.list_devices();
@ -66,30 +64,10 @@ pub fn fuzz() {
// The wrapped harness function, calling out to the LLVM-style harness
let mut harness =
|input: &BytesInput, qemu_executor_state: &mut QemuExecutorState<_, _>| unsafe {
match emu.run(input, qemu_executor_state) {
Ok(handler_result) => match handler_result {
HandlerResult::UnhandledExit(unhandled_exit) => {
panic!("Unhandled exit: {}", unhandled_exit)
}
HandlerResult::EndOfRun(exit_kind) => exit_kind,
HandlerResult::Interrupted => {
println!("Interrupted.");
std::process::exit(CTRL_C_EXIT);
}
},
Err(handler_error) => match handler_error {
HandlerError::QemuExitReasonError(emu_exit_reason_error) => {
match emu_exit_reason_error {
EmuExitReasonError::UnknownKind => panic!("unknown kind"),
EmuExitReasonError::UnexpectedExit => ExitKind::Crash,
_ => {
panic!("Emu Exit unhandled error: {:?}", emu_exit_reason_error)
}
}
}
_ => panic!("Unhandled error: {:?}", handler_error),
},
}
emu.run(input, qemu_executor_state)
.unwrap()
.try_into()
.unwrap()
};
// Create an observation channel using the coverage map

View File

@ -36,7 +36,9 @@ use libafl_bolts::{
tuples::{Handle, Handler},
};
#[cfg(feature = "std")]
use libafl_bolts::{llmp::LlmpConnection, shmem::StdShMemProvider, staterestore::StateRestorer};
use libafl_bolts::{
llmp::LlmpConnection, os::CTRL_C_EXIT, shmem::StdShMemProvider, staterestore::StateRestorer,
};
use libafl_bolts::{
llmp::{self, LlmpClient, LlmpClientDescription, Tag},
shmem::ShMemProvider,
@ -1581,7 +1583,7 @@ where
compiler_fence(Ordering::SeqCst);
if child_status == crate::events::CTRL_C_EXIT || staterestorer.wants_to_exit() {
if child_status == CTRL_C_EXIT || staterestorer.wants_to_exit() {
// if ctrl-c is pressed, we end up in this branch
if let Err(err) = mgr.detach_from_broker(self.broker_port) {
log::error!("Failed to detach from broker: {err}");

View File

@ -31,7 +31,7 @@ use ahash::RandomState;
#[cfg(feature = "std")]
pub use launcher::*;
#[cfg(all(unix, feature = "std"))]
use libafl_bolts::os::unix_signals::{siginfo_t, ucontext_t, Handler, Signal};
use libafl_bolts::os::unix_signals::{siginfo_t, ucontext_t, Handler, Signal, CTRL_C_EXIT};
#[cfg(feature = "adaptive_serialization")]
use libafl_bolts::tuples::{Handle, MatchNameRef};
use libafl_bolts::{current_time, ClientId};
@ -55,13 +55,6 @@ use crate::{
state::HasScalabilityMonitor,
};
/// The special exit code when the target exited throught ctrl-c
#[cfg(unix)]
pub const CTRL_C_EXIT: i32 = 100;
/// The special exit code when the target exited throught ctrl-c
#[cfg(windows)]
pub const CTRL_C_EXIT: i32 = -1073741510;
/// Check if ctrl-c is sent with this struct
#[cfg(all(unix, feature = "std"))]
pub static mut EVENTMGR_SIGHANDLER_STATE: ShutdownSignalData = ShutdownSignalData {};

View File

@ -18,7 +18,7 @@ use libafl_bolts::os::unix_signals::setup_signal_handler;
use libafl_bolts::os::{fork, ForkResult};
use libafl_bolts::ClientId;
#[cfg(feature = "std")]
use libafl_bolts::{shmem::ShMemProvider, staterestore::StateRestorer};
use libafl_bolts::{os::CTRL_C_EXIT, shmem::ShMemProvider, staterestore::StateRestorer};
#[cfg(feature = "std")]
use serde::{de::DeserializeOwned, Serialize};
@ -520,7 +520,7 @@ where
compiler_fence(Ordering::SeqCst);
if child_status == crate::events::CTRL_C_EXIT || staterestorer.wants_to_exit() {
if child_status == CTRL_C_EXIT || staterestorer.wants_to_exit() {
return Err(Error::shutting_down());
}

View File

@ -9,6 +9,8 @@ pub mod unix_shmem_server;
#[cfg(unix)]
pub mod unix_signals;
#[cfg(unix)]
pub use unix_signals::CTRL_C_EXIT;
#[cfg(all(unix, feature = "std"))]
pub mod pipes;
@ -28,9 +30,10 @@ use std::{fs::File, os::fd::AsRawFd, sync::OnceLock};
#[cfg(all(windows, feature = "std"))]
#[allow(missing_docs, overflowing_literals)]
pub mod windows_exceptions;
#[cfg(unix)]
use libc::pid_t;
#[cfg(all(windows, feature = "std"))]
pub use windows_exceptions::CTRL_C_EXIT;
/// A file that we keep open, pointing to /dev/null
#[cfg(all(feature = "std", unix))]

View File

@ -20,6 +20,9 @@ pub use libc::c_ulong;
#[cfg(feature = "std")]
use nix::errno::{errno, Errno};
/// The special exit code when the target exited through ctrl-c
pub const CTRL_C_EXIT: i32 = 100;
/// ARMv7-specific representation of a saved context
#[cfg(target_arch = "arm")]
#[derive(Debug)]
@ -302,6 +305,19 @@ pub enum Signal {
SigTrap = SIGTRAP,
}
#[cfg(feature = "std")]
impl Signal {
/// Handle an incoming signal
pub fn handle(&self) {
match self {
Signal::SigInterrupt | Signal::SigQuit | Signal::SigTerm => {
std::process::exit(CTRL_C_EXIT)
}
_ => {}
}
}
}
impl TryFrom<&str> for Signal {
type Error = Error;

View File

@ -25,6 +25,9 @@ pub use windows::Win32::{
use crate::Error;
/// The special exit code when the target exited through ctrl-c
pub const CTRL_C_EXIT: i32 = -1073741510;
// For VEH
const EXCEPTION_CONTINUE_EXECUTION: c_long = -1;

View File

@ -89,7 +89,8 @@ paste = "1"
enum-map = "2.7"
serde_yaml = { version = "0.8", optional = true } # For parsing the injections yaml file
toml = { version = "0.4.2", optional = true } # For parsing the injections toml file
pyo3 = { version = "0.18", optional = true }
pyo3 = { version = "0.18", optional = true , features = ["multiple-pymethods"]}
bytes-utils = "0.1"
# Document all features of this crate (for `cargo doc`)
document-features = { version = "0.2", optional = true }

View File

@ -11,10 +11,12 @@ mod host_specific {
#[rustversion::nightly]
fn main() {
println!("cargo:rustc-cfg=nightly");
println!("cargo::rustc-check-cfg=cfg(nightly)");
host_specific::build();
}
#[rustversion::not(nightly)]
fn main() {
println!("cargo::rustc-check-cfg=cfg(nightly)");
host_specific::build();
}

View File

@ -37,6 +37,7 @@ pub fn build() {
let runtime_bindings_file = out_dir.join("libafl_qemu_bindings.rs");
let stub_runtime_bindings_file = src_dir.join("runtime/libafl_qemu_stub_bindings.rs");
println!("cargo::rustc-check-cfg=cfg(emulation_mode, values(\"usermode\", \"systemmode\"))");
println!("cargo:rustc-cfg=emulation_mode=\"{emulation_mode}\"");
println!("cargo:rerun-if-env-changed=EMULATION_MODE");
@ -65,6 +66,7 @@ pub fn build() {
};
println!("cargo:rerun-if-env-changed=CPU_TARGET");
println!("cargo:rustc-cfg=cpu_target=\"{cpu_target}\"");
println!("cargo::rustc-check-cfg=cfg(cpu_target, values(\"x86_64\", \"arm\", \"aarch64\", \"i386\", \"mips\", \"ppc\", \"hexagon\"))");
let cross_cc = if (emulation_mode == "usermode") && (qemu_asan || qemu_asan_guest) {
// TODO try to autodetect a cross compiler with the arch name (e.g. aarch64-linux-gnu-gcc)

View File

@ -84,6 +84,7 @@ const WRAPPER_HEADER: &str = r#"
#include "libafl/exit.h"
#include "libafl/hook.h"
#include "libafl/jit.h"
#include "libafl/utils.h"
"#;

View File

@ -11,7 +11,7 @@ use crate::cargo_add_rpath;
const QEMU_URL: &str = "https://github.com/AFLplusplus/qemu-libafl-bridge";
const QEMU_DIRNAME: &str = "qemu-libafl-bridge";
const QEMU_REVISION: &str = "538e6b02c36838e0de469b39dd03fef05678444f";
const QEMU_REVISION: &str = "9f3e2399ee9b106dfbb8c3afcdfdf30e235fc88f";
#[allow(clippy::module_name_repetitions)]
pub struct BuildResult {

View File

@ -8,6 +8,15 @@ mod host_specific {
}
}
#[rustversion::nightly]
fn main() {
println!("cargo:rustc-cfg=nightly");
println!("cargo::rustc-check-cfg=cfg(nightly)");
host_specific::build();
}
#[rustversion::not(nightly)]
fn main() {
println!("cargo::rustc-check-cfg=cfg(nightly)");
host_specific::build();
}

View File

@ -46,17 +46,18 @@ pub fn build() {
"usermode".to_string()
})
};
println!("cargo::rustc-check-cfg=cfg(emulation_mode, values(\"usermode\", \"systemmode\"))");
println!("cargo:rustc-cfg=emulation_mode=\"{emulation_mode}\"");
println!("cargo:rerun-if-env-changed=EMULATION_MODE");
// Make sure we have at most one architecutre feature set
// Else, we default to `x86_64` - having a default makes CI easier :)
assert_unique_feature!("arm", "aarch64", "i386", "i86_64", "mips", "ppc", "hexagon");
assert_unique_feature!("arm", "aarch64", "i386", "x86_64", "mips", "ppc", "hexagon");
// Make sure that we don't have BE set for any architecture other than arm and mips
// Sure aarch64 may support BE, but its not in common usage and we don't
// need it yet and so haven't tested it
assert_unique_feature!("be", "aarch64", "i386", "i86_64", "hexagon");
assert_unique_feature!("be", "aarch64", "i386", "x86_64", "hexagon");
let cpu_target = if cfg!(feature = "x86_64") {
"x86_64".to_string()
@ -82,6 +83,7 @@ pub fn build() {
};
println!("cargo:rerun-if-env-changed=CPU_TARGET");
println!("cargo:rustc-cfg=cpu_target=\"{cpu_target}\"");
println!("cargo::rustc-check-cfg=cfg(cpu_target, values(\"x86_64\", \"arm\", \"aarch64\", \"i386\", \"mips\", \"ppc\", \"hexagon\"))");
let jobs = env::var("NUM_JOBS")
.ok()

View File

@ -1,3 +1,4 @@
#![cfg_attr(nightly, feature(used_with_arg))]
/*!
`libafl_qemu_sys` is the crate exporting C symbols from QEMU.
Have a look at `libafl_qemu` for higher-level abstractions.

View File

@ -2340,7 +2340,6 @@ pub type DeviceReset = ::std::option::Option<unsafe extern "C" fn(dev: *mut Devi
#[derive(Debug, Copy, Clone)]
pub struct DeviceClass {
pub parent_class: ObjectClass,
#[doc = " @categories: device categories device belongs to"]
pub categories: [::std::os::raw::c_ulong; 1usize],
#[doc = " @fw_name: name used to identify device to firmware interfaces"]
pub fw_name: *const ::std::os::raw::c_char,
@ -12055,8 +12054,7 @@ extern "C" {
}
pub const libafl_exit_reason_kind_INTERNAL: libafl_exit_reason_kind = libafl_exit_reason_kind(0);
pub const libafl_exit_reason_kind_BREAKPOINT: libafl_exit_reason_kind = libafl_exit_reason_kind(1);
pub const libafl_exit_reason_kind_SYNC_BACKDOOR: libafl_exit_reason_kind =
libafl_exit_reason_kind(2);
pub const libafl_exit_reason_kind_SYNC_EXIT: libafl_exit_reason_kind = libafl_exit_reason_kind(2);
impl ::std::ops::BitOr<libafl_exit_reason_kind> for libafl_exit_reason_kind {
type Output = Self;
#[inline]
@ -12119,21 +12117,18 @@ fn bindgen_test_layout_libafl_exit_reason_breakpoint() {
}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct libafl_exit_reason_sync_backdoor {}
pub struct libafl_exit_reason_sync_exit {}
#[test]
fn bindgen_test_layout_libafl_exit_reason_sync_backdoor() {
fn bindgen_test_layout_libafl_exit_reason_sync_exit() {
assert_eq!(
::std::mem::size_of::<libafl_exit_reason_sync_backdoor>(),
::std::mem::size_of::<libafl_exit_reason_sync_exit>(),
0usize,
concat!("Size of: ", stringify!(libafl_exit_reason_sync_backdoor))
concat!("Size of: ", stringify!(libafl_exit_reason_sync_exit))
);
assert_eq!(
::std::mem::align_of::<libafl_exit_reason_sync_backdoor>(),
::std::mem::align_of::<libafl_exit_reason_sync_exit>(),
1usize,
concat!(
"Alignment of ",
stringify!(libafl_exit_reason_sync_backdoor)
)
concat!("Alignment of ", stringify!(libafl_exit_reason_sync_exit))
);
}
#[repr(C)]
@ -12200,7 +12195,7 @@ pub struct libafl_exit_reason {
pub union libafl_exit_reason__bindgen_ty_1 {
pub internal: libafl_exit_reason_internal,
pub breakpoint: libafl_exit_reason_breakpoint,
pub backdoor: libafl_exit_reason_sync_backdoor,
pub sync_exit: libafl_exit_reason_sync_exit,
}
#[test]
fn bindgen_test_layout_libafl_exit_reason__bindgen_ty_1() {
@ -12241,13 +12236,13 @@ fn bindgen_test_layout_libafl_exit_reason__bindgen_ty_1() {
)
);
assert_eq!(
unsafe { ::std::ptr::addr_of!((*ptr).backdoor) as usize - ptr as usize },
unsafe { ::std::ptr::addr_of!((*ptr).sync_exit) as usize - ptr as usize },
0usize,
concat!(
"Offset of field: ",
stringify!(libafl_exit_reason__bindgen_ty_1),
"::",
stringify!(backdoor)
stringify!(sync_exit)
)
);
}
@ -13890,6 +13885,9 @@ extern "C" {
extern "C" {
pub fn libafl_jit_trace_block_single(data: u64, id: u64) -> usize;
}
extern "C" {
pub fn libafl_qemu_host_page_size() -> usize;
}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct kvm_dirty_gfn {

View File

@ -26,6 +26,7 @@ pub const __USE_ATFILE: u32 = 1;
pub const __USE_FORTIFY_LEVEL: u32 = 0;
pub const __GLIBC_USE_DEPRECATED_GETS: u32 = 0;
pub const __GLIBC_USE_DEPRECATED_SCANF: u32 = 0;
pub const __GLIBC_USE_C2X_STRTOL: u32 = 0;
pub const _STDC_PREDEF_H: u32 = 1;
pub const __STDC_IEC_559__: u32 = 1;
pub const __STDC_IEC_60559_BFP__: u32 = 201404;
@ -34,7 +35,7 @@ pub const __STDC_IEC_60559_COMPLEX__: u32 = 201404;
pub const __STDC_ISO_10646__: u32 = 201706;
pub const __GNU_LIBRARY__: u32 = 6;
pub const __GLIBC__: u32 = 2;
pub const __GLIBC_MINOR__: u32 = 35;
pub const __GLIBC_MINOR__: u32 = 39;
pub const _SYS_CDEFS_H: u32 = 1;
pub const __glibc_c99_flexarr_available: u32 = 1;
pub const __LDOUBLE_REDIRECTS_TO_FLOAT128_ABI: u32 = 0;
@ -58,6 +59,7 @@ pub const _BITS_TIME64_H: u32 = 1;
pub const _BITS_WCHAR_H: u32 = 1;
pub const _BITS_STDINT_INTN_H: u32 = 1;
pub const _BITS_STDINT_UINTN_H: u32 = 1;
pub const _BITS_STDINT_LEAST_H: u32 = 1;
pub const INT8_MIN: i32 = -128;
pub const INT16_MIN: i32 = -32768;
pub const INT32_MIN: i32 = -2147483648;

View File

@ -8,7 +8,7 @@ use pyo3::prelude::*;
pub use strum_macros::EnumIter;
pub use syscall_numbers::aarch64::*;
use crate::{sync_exit::BackdoorArgs, CallingConvention};
use crate::{sync_exit::ExitArgs, CallingConvention};
#[derive(IntoPrimitive, TryFromPrimitive, Debug, Clone, Copy, EnumIter)]
#[repr(i32)]
@ -49,19 +49,19 @@ pub enum Regs {
Pstate = 33,
}
static BACKDOOR_ARCH_REGS: OnceLock<EnumMap<BackdoorArgs, Regs>> = OnceLock::new();
static EXIT_ARCH_REGS: OnceLock<EnumMap<ExitArgs, Regs>> = OnceLock::new();
pub fn get_backdoor_arch_regs() -> &'static EnumMap<BackdoorArgs, Regs> {
BACKDOOR_ARCH_REGS.get_or_init(|| {
pub fn get_exit_arch_regs() -> &'static EnumMap<ExitArgs, Regs> {
EXIT_ARCH_REGS.get_or_init(|| {
enum_map! {
BackdoorArgs::Ret => Regs::X0,
BackdoorArgs::Cmd => Regs::X0,
BackdoorArgs::Arg1 => Regs::X1,
BackdoorArgs::Arg2 => Regs::X2,
BackdoorArgs::Arg3 => Regs::X3,
BackdoorArgs::Arg4 => Regs::X4,
BackdoorArgs::Arg5 => Regs::X5,
BackdoorArgs::Arg6 => Regs::X6,
ExitArgs::Ret => Regs::X0,
ExitArgs::Cmd => Regs::X0,
ExitArgs::Arg1 => Regs::X1,
ExitArgs::Arg2 => Regs::X2,
ExitArgs::Arg3 => Regs::X3,
ExitArgs::Arg4 => Regs::X4,
ExitArgs::Arg5 => Regs::X5,
ExitArgs::Arg6 => Regs::X6,
}
})
}

View File

@ -8,7 +8,7 @@ use pyo3::prelude::*;
pub use strum_macros::EnumIter;
pub use syscall_numbers::arm::*;
use crate::{sync_exit::BackdoorArgs, CallingConvention};
use crate::{sync_exit::ExitArgs, CallingConvention};
/// Registers for the ARM instruction set.
#[derive(IntoPrimitive, TryFromPrimitive, Debug, Clone, Copy, EnumIter)]
@ -33,19 +33,19 @@ pub enum Regs {
R25 = 25,
}
static BACKDOOR_ARCH_REGS: OnceLock<EnumMap<BackdoorArgs, Regs>> = OnceLock::new();
static EXIT_ARCH_REGS: OnceLock<EnumMap<ExitArgs, Regs>> = OnceLock::new();
pub fn get_backdoor_arch_regs() -> &'static EnumMap<BackdoorArgs, Regs> {
BACKDOOR_ARCH_REGS.get_or_init(|| {
pub fn get_exit_arch_regs() -> &'static EnumMap<ExitArgs, Regs> {
EXIT_ARCH_REGS.get_or_init(|| {
enum_map! {
BackdoorArgs::Ret => Regs::R0,
BackdoorArgs::Cmd => Regs::R0,
BackdoorArgs::Arg1 => Regs::R1,
BackdoorArgs::Arg2 => Regs::R2,
BackdoorArgs::Arg3 => Regs::R3,
BackdoorArgs::Arg4 => Regs::R4,
BackdoorArgs::Arg5 => Regs::R5,
BackdoorArgs::Arg6 => Regs::R6,
ExitArgs::Ret => Regs::R0,
ExitArgs::Cmd => Regs::R0,
ExitArgs::Arg1 => Regs::R1,
ExitArgs::Arg2 => Regs::R2,
ExitArgs::Arg3 => Regs::R3,
ExitArgs::Arg4 => Regs::R4,
ExitArgs::Arg5 => Regs::R5,
ExitArgs::Arg6 => Regs::R6,
}
})
}

View File

@ -6,7 +6,7 @@ use num_enum::{IntoPrimitive, TryFromPrimitive};
use pyo3::prelude::*;
pub use strum_macros::EnumIter;
use crate::{sync_exit::BackdoorArgs, CallingConvention};
use crate::{sync_exit::ExitArgs, CallingConvention};
#[derive(IntoPrimitive, TryFromPrimitive, Debug, Clone, Copy, EnumIter)]
#[repr(i32)]
@ -64,19 +64,19 @@ pub enum Regs {
Pktcnthi = 51,
}
static BACKDOOR_ARCH_REGS: OnceLock<EnumMap<BackdoorArgs, Regs>> = OnceLock::new();
static EXIT_ARCH_REGS: OnceLock<EnumMap<ExitArgs, Regs>> = OnceLock::new();
pub fn get_backdoor_arch_regs() -> &'static EnumMap<BackdoorArgs, Regs> {
BACKDOOR_ARCH_REGS.get_or_init(|| {
pub fn get_exit_arch_regs() -> &'static EnumMap<ExitArgs, Regs> {
EXIT_ARCH_REGS.get_or_init(|| {
enum_map! {
BackdoorArgs::Ret => Regs::R0,
BackdoorArgs::Cmd => Regs::R0,
BackdoorArgs::Arg1 => Regs::R1,
BackdoorArgs::Arg2 => Regs::R2,
BackdoorArgs::Arg3 => Regs::R3,
BackdoorArgs::Arg4 => Regs::R4,
BackdoorArgs::Arg5 => Regs::R5,
BackdoorArgs::Arg6 => Regs::R6,
ExitArgs::Ret => Regs::R0,
ExitArgs::Cmd => Regs::R0,
ExitArgs::Arg1 => Regs::R1,
ExitArgs::Arg2 => Regs::R2,
ExitArgs::Arg3 => Regs::R3,
ExitArgs::Arg4 => Regs::R4,
ExitArgs::Arg5 => Regs::R5,
ExitArgs::Arg6 => Regs::R6,
}
})
}

View File

@ -8,7 +8,7 @@ use pyo3::prelude::*;
pub use strum_macros::EnumIter;
pub use syscall_numbers::x86::*;
use crate::{sync_exit::BackdoorArgs, CallingConvention, GuestAddr};
use crate::{sync_exit::ExitArgs, CallingConvention, GuestAddr};
#[derive(IntoPrimitive, TryFromPrimitive, Debug, Clone, Copy, EnumIter)]
#[repr(i32)]
@ -25,19 +25,19 @@ pub enum Regs {
Eflags = 9,
}
static BACKDOOR_ARCH_REGS: OnceLock<EnumMap<BackdoorArgs, Regs>> = OnceLock::new();
static EXIT_ARCH_REGS: OnceLock<EnumMap<ExitArgs, Regs>> = OnceLock::new();
pub fn get_backdoor_arch_regs() -> &'static EnumMap<BackdoorArgs, Regs> {
BACKDOOR_ARCH_REGS.get_or_init(|| {
pub fn get_exit_arch_regs() -> &'static EnumMap<ExitArgs, Regs> {
EXIT_ARCH_REGS.get_or_init(|| {
enum_map! {
BackdoorArgs::Ret => Regs::Eax,
BackdoorArgs::Cmd => Regs::Eax,
BackdoorArgs::Arg1 => Regs::Edi,
BackdoorArgs::Arg2 => Regs::Esi,
BackdoorArgs::Arg3 => Regs::Edx,
BackdoorArgs::Arg4 => Regs::Ebx,
BackdoorArgs::Arg5 => Regs::Ecx,
BackdoorArgs::Arg6 => Regs::Ebp,
ExitArgs::Ret => Regs::Eax,
ExitArgs::Cmd => Regs::Eax,
ExitArgs::Arg1 => Regs::Edi,
ExitArgs::Arg2 => Regs::Esi,
ExitArgs::Arg3 => Regs::Edx,
ExitArgs::Arg4 => Regs::Ebx,
ExitArgs::Arg5 => Regs::Ecx,
ExitArgs::Arg6 => Regs::Ebp,
}
})
}

View File

@ -7,7 +7,7 @@ use pyo3::prelude::*;
pub use strum_macros::EnumIter;
pub use syscall_numbers::mips::*;
use crate::{sync_exit::BackdoorArgs, CallingConvention};
use crate::{sync_exit::ExitArgs, CallingConvention};
/// Registers for the MIPS instruction set.
#[derive(IntoPrimitive, TryFromPrimitive, Debug, Clone, Copy, EnumIter)]
@ -49,19 +49,19 @@ pub enum Regs {
Pc = 37,
}
static BACKDOOR_ARCH_REGS: OnceLock<EnumMap<BackdoorArgs, Regs>> = OnceLock::new();
static EXIT_ARCH_REGS: OnceLock<EnumMap<ExitArgs, Regs>> = OnceLock::new();
pub fn get_backdoor_arch_regs() -> &'static EnumMap<BackdoorArgs, Regs> {
BACKDOOR_ARCH_REGS.get_or_init(|| {
pub fn get_exit_arch_regs() -> &'static EnumMap<ExitArgs, Regs> {
EXIT_ARCH_REGS.get_or_init(|| {
enum_map! {
BackdoorArgs::Ret => Regs::V0,
BackdoorArgs::Cmd => Regs::V0,
BackdoorArgs::Arg1 => Regs::A0,
BackdoorArgs::Arg2 => Regs::A1,
BackdoorArgs::Arg3 => Regs::A2,
BackdoorArgs::Arg4 => Regs::A3,
BackdoorArgs::Arg5 => Regs::T0,
BackdoorArgs::Arg6 => Regs::T1,
ExitArgs::Ret => Regs::V0,
ExitArgs::Cmd => Regs::V0,
ExitArgs::Arg1 => Regs::A0,
ExitArgs::Arg2 => Regs::A1,
ExitArgs::Arg3 => Regs::A2,
ExitArgs::Arg4 => Regs::A3,
ExitArgs::Arg5 => Regs::T0,
ExitArgs::Arg6 => Regs::T1,
}
})
}

View File

@ -7,7 +7,7 @@ use pyo3::prelude::*;
pub use strum_macros::EnumIter;
pub use syscall_numbers::powerpc::*;
use crate::{sync_exit::BackdoorArgs, CallingConvention};
use crate::{sync_exit::ExitArgs, CallingConvention};
/// Registers for the MIPS instruction set.
#[derive(IntoPrimitive, TryFromPrimitive, Debug, Clone, Copy, EnumIter)]
@ -88,19 +88,19 @@ pub enum Regs {
Fpscr = 70,
}
static BACKDOOR_ARCH_REGS: OnceLock<EnumMap<BackdoorArgs, Regs>> = OnceLock::new();
static EXIT_ARCH_REGS: OnceLock<EnumMap<ExitArgs, Regs>> = OnceLock::new();
pub fn get_backdoor_arch_regs() -> &'static EnumMap<BackdoorArgs, Regs> {
BACKDOOR_ARCH_REGS.get_or_init(|| {
pub fn get_exit_arch_regs() -> &'static EnumMap<ExitArgs, Regs> {
EXIT_ARCH_REGS.get_or_init(|| {
enum_map! {
BackdoorArgs::Ret => Regs::R3,
BackdoorArgs::Cmd => Regs::R0,
BackdoorArgs::Arg1 => Regs::R3,
BackdoorArgs::Arg2 => Regs::R4,
BackdoorArgs::Arg3 => Regs::R5,
BackdoorArgs::Arg4 => Regs::R6,
BackdoorArgs::Arg5 => Regs::R7,
BackdoorArgs::Arg6 => Regs::R8,
ExitArgs::Ret => Regs::R3,
ExitArgs::Cmd => Regs::R0,
ExitArgs::Arg1 => Regs::R3,
ExitArgs::Arg2 => Regs::R4,
ExitArgs::Arg3 => Regs::R5,
ExitArgs::Arg4 => Regs::R6,
ExitArgs::Arg5 => Regs::R7,
ExitArgs::Arg6 => Regs::R8,
}
})
}

View File

@ -8,7 +8,7 @@ use pyo3::prelude::*;
pub use strum_macros::EnumIter;
pub use syscall_numbers::x86_64::*;
use crate::{sync_exit::BackdoorArgs, CallingConvention};
use crate::{sync_exit::ExitArgs, CallingConvention};
#[derive(IntoPrimitive, TryFromPrimitive, Debug, Clone, Copy, EnumIter)]
#[repr(i32)]
@ -33,19 +33,19 @@ pub enum Regs {
Rflags = 17,
}
static BACKDOOR_ARCH_REGS: OnceLock<EnumMap<BackdoorArgs, Regs>> = OnceLock::new();
static EXIT_ARCH_REGS: OnceLock<EnumMap<ExitArgs, Regs>> = OnceLock::new();
pub fn get_backdoor_arch_regs() -> &'static EnumMap<BackdoorArgs, Regs> {
BACKDOOR_ARCH_REGS.get_or_init(|| {
pub fn get_exit_arch_regs() -> &'static EnumMap<ExitArgs, Regs> {
EXIT_ARCH_REGS.get_or_init(|| {
enum_map! {
BackdoorArgs::Ret => Regs::Rax,
BackdoorArgs::Cmd => Regs::Rax,
BackdoorArgs::Arg1 => Regs::Rdi,
BackdoorArgs::Arg2 => Regs::Rsi,
BackdoorArgs::Arg3 => Regs::Rdx,
BackdoorArgs::Arg4 => Regs::R10,
BackdoorArgs::Arg5 => Regs::R8,
BackdoorArgs::Arg6 => Regs::R9,
ExitArgs::Ret => Regs::Rax,
ExitArgs::Cmd => Regs::Rax,
ExitArgs::Arg1 => Regs::Rdi,
ExitArgs::Arg2 => Regs::Rsi,
ExitArgs::Arg3 => Regs::Rdx,
ExitArgs::Arg4 => Regs::R10,
ExitArgs::Arg5 => Regs::R8,
ExitArgs::Arg6 => Regs::R9,
}
})
}

View File

@ -1,24 +1,28 @@
#[cfg(emulation_mode = "systemmode")]
use std::collections::HashSet;
use std::fmt::{Debug, Display, Formatter};
use std::{
fmt::{Debug, Display, Formatter},
sync::OnceLock,
};
use enum_map::Enum;
use enum_map::{enum_map, Enum, EnumMap};
use libafl::{
executors::ExitKind,
inputs::HasTargetBytes,
state::{HasExecutions, State},
};
use libafl_bolts::AsSlice;
use libafl_qemu_sys::{GuestPhysAddr, GuestVirtAddr};
use num_enum::TryFromPrimitive;
use libafl_qemu_sys::{GuestAddr, GuestPhysAddr, GuestVirtAddr};
use num_enum::{TryFromPrimitive, TryFromPrimitiveError};
#[cfg(emulation_mode = "systemmode")]
use crate::QemuInstrumentationPagingFilter;
use crate::{
executor::QemuExecutorState, sync_exit::SyncBackdoorError, EmuExitHandler, Emulator,
GuestAddrKind, GuestReg, HandlerError, HasInstrumentationFilter, InnerHandlerResult,
InputLocation, IsFilter, IsSnapshotManager, Qemu, QemuHelperTuple,
QemuInstrumentationAddressRangeFilter, Regs, StdEmuExitHandler, StdInstrumentationFilter, CPU,
executor::QemuExecutorState, get_exit_arch_regs, sync_exit::ExitArgs, Emulator,
EmulatorExitHandler, EmulatorMemoryChunk, ExitHandlerError, ExitHandlerResult, GuestReg,
HasInstrumentationFilter, InputLocation, IsFilter, IsSnapshotManager, Qemu, QemuHelperTuple,
QemuInstrumentationAddressRangeFilter, Regs, StdEmulatorExitHandler, StdInstrumentationFilter,
CPU,
};
pub const VERSION: u64 = bindings::LIBAFL_QEMU_HDR_VERSION_NUMBER as u64;
@ -39,7 +43,7 @@ mod bindings {
#[derive(Debug, Clone, TryFromPrimitive)]
#[repr(u64)]
pub enum NativeBackdoorCommand {
pub enum NativeCommand {
StartVirt = bindings::LibaflQemuCommand_LIBAFL_QEMU_COMMAND_START_VIRT.0 as u64, // Shortcut for Save + InputVirt
StartPhys = bindings::LibaflQemuCommand_LIBAFL_QEMU_COMMAND_START_PHYS.0 as u64, // Shortcut for Save + InputPhys
InputVirt = bindings::LibaflQemuCommand_LIBAFL_QEMU_COMMAND_INPUT_VIRT.0 as u64, // The address is a virtual address using the paging currently running in the VM.
@ -64,7 +68,7 @@ pub trait IsCommand<QT, S, E>
where
QT: QemuHelperTuple<S>,
S: State + HasExecutions,
E: EmuExitHandler<QT, S>,
E: EmulatorExitHandler<QT, S>,
{
/// Used to know whether the command can be run during a backdoor, or if it is necessary to go out of
/// the QEMU VM to run the command.
@ -81,7 +85,7 @@ where
qemu_executor_state: &mut QemuExecutorState<QT, S>,
input: &S::Input,
ret_reg: Option<Regs>,
) -> Result<InnerHandlerResult, HandlerError>;
) -> Result<Option<ExitHandlerResult>, ExitHandlerError>;
}
#[cfg(emulation_mode = "systemmode")]
@ -102,8 +106,123 @@ pub enum Command {
AddressRangeFilterCommand(AddressRangeFilterCommand),
}
pub static EMU_EXIT_KIND_MAP: OnceLock<EnumMap<NativeExitKind, Option<ExitKind>>> = OnceLock::new();
#[derive(Debug, Clone)]
pub enum CommandError {
UnknownCommand(GuestReg),
RegError(String),
VersionDifference(u64),
}
impl From<TryFromPrimitiveError<NativeCommand>> for CommandError {
fn from(error: TryFromPrimitiveError<NativeCommand>) -> Self {
CommandError::UnknownCommand(error.number.try_into().unwrap())
}
}
impl From<String> for CommandError {
fn from(error_string: String) -> Self {
CommandError::RegError(error_string)
}
}
impl TryFrom<Qemu> for Command {
type Error = CommandError;
#[allow(clippy::too_many_lines)]
fn try_from(qemu: Qemu) -> Result<Self, Self::Error> {
let arch_regs_map: &'static EnumMap<ExitArgs, Regs> = get_exit_arch_regs();
let cmd_id: GuestReg = qemu.read_reg::<Regs, GuestReg>(arch_regs_map[ExitArgs::Cmd])?;
Ok(match u64::from(cmd_id).try_into()? {
NativeCommand::Save => Command::SaveCommand(SaveCommand),
NativeCommand::Load => Command::LoadCommand(LoadCommand),
NativeCommand::InputVirt => {
let virt_addr: GuestVirtAddr = qemu.read_reg(arch_regs_map[ExitArgs::Arg1])?;
let max_input_size: GuestReg = qemu.read_reg(arch_regs_map[ExitArgs::Arg2])?;
Command::InputCommand(InputCommand::new(
EmulatorMemoryChunk::virt(
virt_addr,
max_input_size,
qemu.current_cpu().unwrap(),
),
qemu.current_cpu().unwrap(),
))
}
NativeCommand::InputPhys => {
let phys_addr: GuestPhysAddr = qemu.read_reg(arch_regs_map[ExitArgs::Arg1])?;
let max_input_size: GuestReg = qemu.read_reg(arch_regs_map[ExitArgs::Arg2])?;
Command::InputCommand(InputCommand::new(
EmulatorMemoryChunk::phys(
phys_addr,
max_input_size,
Some(qemu.current_cpu().unwrap()),
),
qemu.current_cpu().unwrap(),
))
}
NativeCommand::End => {
let native_exit_kind: GuestReg = qemu.read_reg(arch_regs_map[ExitArgs::Arg1])?;
let native_exit_kind: Result<NativeExitKind, _> =
u64::from(native_exit_kind).try_into();
let exit_kind = native_exit_kind.ok().and_then(|k| {
EMU_EXIT_KIND_MAP.get_or_init(|| {
enum_map! {
NativeExitKind::Unknown => None,
NativeExitKind::Ok => Some(ExitKind::Ok),
NativeExitKind::Crash => Some(ExitKind::Crash)
}
})[k]
});
Command::EndCommand(EndCommand::new(exit_kind))
}
NativeCommand::StartPhys => {
let input_phys_addr: GuestPhysAddr =
qemu.read_reg(arch_regs_map[ExitArgs::Arg1])?;
let max_input_size: GuestReg = qemu.read_reg(arch_regs_map[ExitArgs::Arg2])?;
Command::StartCommand(StartCommand::new(EmulatorMemoryChunk::phys(
input_phys_addr,
max_input_size,
Some(qemu.current_cpu().unwrap()),
)))
}
NativeCommand::StartVirt => {
let input_virt_addr: GuestVirtAddr =
qemu.read_reg(arch_regs_map[ExitArgs::Arg1])?;
let max_input_size: GuestReg = qemu.read_reg(arch_regs_map[ExitArgs::Arg2])?;
Command::StartCommand(StartCommand::new(EmulatorMemoryChunk::virt(
input_virt_addr,
max_input_size,
qemu.current_cpu().unwrap(),
)))
}
NativeCommand::Version => {
let client_version = qemu.read_reg(arch_regs_map[ExitArgs::Arg1])?;
Command::VersionCommand(VersionCommand::new(client_version))
}
NativeCommand::VaddrFilterAllowRange => {
let vaddr_start: GuestAddr = qemu.read_reg(arch_regs_map[ExitArgs::Arg1])?;
let vaddr_end: GuestAddr = qemu.read_reg(arch_regs_map[ExitArgs::Arg2])?;
Command::AddressRangeFilterCommand(FilterCommand::new(
#[allow(clippy::single_range_in_vec_init)]
QemuInstrumentationAddressRangeFilter::AllowList(vec![vaddr_start..vaddr_end]),
))
}
})
}
}
// TODO: Replace with enum_dispatch implementation
impl<SM, QT, S> IsCommand<QT, S, StdEmuExitHandler<SM>> for Command
impl<SM, QT, S> IsCommand<QT, S, StdEmulatorExitHandler<SM>> for Command
where
SM: IsSnapshotManager,
QT: QemuHelperTuple<S> + StdInstrumentationFilter<S> + Debug,
@ -113,90 +232,94 @@ where
fn usable_at_runtime(&self) -> bool {
match self {
Command::SaveCommand(cmd) => {
<SaveCommand as IsCommand<QT, S, StdEmuExitHandler<SM>>>::usable_at_runtime(cmd)
}
Command::LoadCommand(cmd) => {
<LoadCommand as IsCommand<QT, S, StdEmuExitHandler<SM>>>::usable_at_runtime(cmd)
}
Command::InputCommand(cmd) => {
<InputCommand as IsCommand<QT, S, StdEmuExitHandler<SM>>>::usable_at_runtime(cmd)
}
Command::StartCommand(cmd) => {
<StartCommand as IsCommand<QT, S, StdEmuExitHandler<SM>>>::usable_at_runtime(cmd)
}
Command::EndCommand(cmd) => {
<EndCommand as IsCommand<QT, S, StdEmuExitHandler<SM>>>::usable_at_runtime(cmd)
}
Command::VersionCommand(cmd) => {
<VersionCommand as IsCommand<QT, S, StdEmuExitHandler<SM>>>::usable_at_runtime(cmd)
}
#[cfg(emulation_mode = "systemmode")]
Command::PagingFilterCommand(cmd) => {
<PagingFilterCommand as IsCommand<QT, S, StdEmuExitHandler<SM>>>::usable_at_runtime(
<SaveCommand as IsCommand<QT, S, StdEmulatorExitHandler<SM>>>::usable_at_runtime(
cmd,
)
}
Command::LoadCommand(cmd) => {
<LoadCommand as IsCommand<QT, S, StdEmulatorExitHandler<SM>>>::usable_at_runtime(
cmd,
)
}
Command::InputCommand(cmd) => {
<InputCommand as IsCommand<QT, S, StdEmulatorExitHandler<SM>>>::usable_at_runtime(
cmd,
)
}
Command::StartCommand(cmd) => {
<StartCommand as IsCommand<QT, S, StdEmulatorExitHandler<SM>>>::usable_at_runtime(
cmd,
)
}
Command::EndCommand(cmd) => {
<EndCommand as IsCommand<QT, S, StdEmulatorExitHandler<SM>>>::usable_at_runtime(cmd)
}
Command::VersionCommand(cmd) => {
<VersionCommand as IsCommand<QT, S, StdEmulatorExitHandler<SM>>>::usable_at_runtime(
cmd,
)
}
#[cfg(emulation_mode = "systemmode")]
Command::PagingFilterCommand(cmd) => <PagingFilterCommand as IsCommand<
QT,
S,
StdEmulatorExitHandler<SM>,
>>::usable_at_runtime(cmd),
Command::AddressRangeFilterCommand(cmd) => <AddressRangeFilterCommand as IsCommand<
QT,
S,
StdEmuExitHandler<SM>,
StdEmulatorExitHandler<SM>,
>>::usable_at_runtime(cmd),
}
}
fn run(
&self,
emu: &Emulator<QT, S, StdEmuExitHandler<SM>>,
emu: &Emulator<QT, S, StdEmulatorExitHandler<SM>>,
qemu_executor_state: &mut QemuExecutorState<QT, S>,
input: &S::Input,
ret_reg: Option<Regs>,
) -> Result<InnerHandlerResult, HandlerError> {
) -> Result<Option<ExitHandlerResult>, ExitHandlerError> {
match self {
Command::SaveCommand(cmd) => {
<SaveCommand as IsCommand<QT, S, StdEmuExitHandler<SM>>>::run(
cmd,
emu,
qemu_executor_state,
input,
ret_reg,
)
}
Command::LoadCommand(cmd) => {
<LoadCommand as IsCommand<QT, S, StdEmuExitHandler<SM>>>::run(
cmd,
emu,
qemu_executor_state,
input,
ret_reg,
)
}
Command::SaveCommand(cmd) => <SaveCommand as IsCommand<
QT,
S,
StdEmulatorExitHandler<SM>,
>>::run(
cmd, emu, qemu_executor_state, input, ret_reg
),
Command::LoadCommand(cmd) => <LoadCommand as IsCommand<
QT,
S,
StdEmulatorExitHandler<SM>,
>>::run(
cmd, emu, qemu_executor_state, input, ret_reg
),
Command::InputCommand(cmd) => <InputCommand as IsCommand<
QT,
S,
StdEmuExitHandler<SM>,
StdEmulatorExitHandler<SM>,
>>::run(
cmd, emu, qemu_executor_state, input, ret_reg
),
Command::StartCommand(cmd) => <StartCommand as IsCommand<
QT,
S,
StdEmuExitHandler<SM>,
StdEmulatorExitHandler<SM>,
>>::run(
cmd, emu, qemu_executor_state, input, ret_reg
),
Command::EndCommand(cmd) => <EndCommand as IsCommand<
QT,
S,
StdEmulatorExitHandler<SM>,
>>::run(
cmd, emu, qemu_executor_state, input, ret_reg
),
Command::EndCommand(cmd) => {
<EndCommand as IsCommand<QT, S, StdEmuExitHandler<SM>>>::run(
cmd,
emu,
qemu_executor_state,
input,
ret_reg,
)
}
Command::VersionCommand(cmd) => <VersionCommand as IsCommand<
QT,
S,
StdEmuExitHandler<SM>,
StdEmulatorExitHandler<SM>,
>>::run(
cmd, emu, qemu_executor_state, input, ret_reg
),
@ -204,12 +327,12 @@ where
Command::PagingFilterCommand(cmd) => <PagingFilterCommand as IsCommand<
QT,
S,
StdEmuExitHandler<SM>,
StdEmulatorExitHandler<SM>,
>>::run(
cmd, emu, qemu_executor_state, input, ret_reg
),
Command::AddressRangeFilterCommand(cmd) => {
<AddressRangeFilterCommand as IsCommand<QT, S, StdEmuExitHandler<SM>>>::run(
<AddressRangeFilterCommand as IsCommand<QT, S, StdEmulatorExitHandler<SM>>>::run(
cmd,
emu,
qemu_executor_state,
@ -221,17 +344,10 @@ where
}
}
#[derive(Debug, Clone)]
pub struct EmulatorMemoryChunk {
addr: GuestAddrKind,
size: GuestReg,
cpu: Option<CPU>,
}
#[derive(Debug, Clone)]
pub struct SaveCommand;
impl<SM, QT, S> IsCommand<QT, S, StdEmuExitHandler<SM>> for SaveCommand
impl<SM, QT, S> IsCommand<QT, S, StdEmulatorExitHandler<SM>> for SaveCommand
where
SM: IsSnapshotManager,
QT: QemuHelperTuple<S> + StdInstrumentationFilter<S> + Debug,
@ -244,7 +360,7 @@ where
fn run(
&self,
emu: &Emulator<QT, S, StdEmuExitHandler<SM>>,
emu: &Emulator<QT, S, StdEmulatorExitHandler<SM>>,
#[cfg(emulation_mode = "systemmode")] qemu_executor_state: &mut QemuExecutorState<QT, S>,
#[cfg(not(emulation_mode = "systemmode"))] _qemu_executor_state: &mut QemuExecutorState<
QT,
@ -252,14 +368,14 @@ where
>,
_input: &S::Input,
_ret_reg: Option<Regs>,
) -> Result<InnerHandlerResult, HandlerError> {
) -> Result<Option<ExitHandlerResult>, ExitHandlerError> {
let qemu = emu.qemu();
let emu_exit_handler = emu.exit_handler().borrow_mut();
let snapshot_id = emu_exit_handler.snapshot_manager_borrow_mut().save(qemu);
emu_exit_handler
.set_snapshot_id(snapshot_id)
.map_err(|_| HandlerError::MultipleSnapshotDefinition)?;
.map_err(|_| ExitHandlerError::MultipleSnapshotDefinition)?;
#[cfg(emulation_mode = "systemmode")]
{
@ -278,14 +394,14 @@ where
*paging_filter = QemuInstrumentationPagingFilter::AllowList(allowed_paging_ids);
}
Ok(InnerHandlerResult::Continue)
Ok(None)
}
}
#[derive(Debug, Clone)]
pub struct LoadCommand;
impl<SM, QT, S> IsCommand<QT, S, StdEmuExitHandler<SM>> for LoadCommand
impl<SM, QT, S> IsCommand<QT, S, StdEmulatorExitHandler<SM>> for LoadCommand
where
SM: IsSnapshotManager,
QT: QemuHelperTuple<S> + StdInstrumentationFilter<S> + Debug,
@ -298,23 +414,23 @@ where
fn run(
&self,
emu: &Emulator<QT, S, StdEmuExitHandler<SM>>,
emu: &Emulator<QT, S, StdEmulatorExitHandler<SM>>,
_qemu_executor_state: &mut QemuExecutorState<QT, S>,
_input: &S::Input,
_ret_reg: Option<Regs>,
) -> Result<InnerHandlerResult, HandlerError> {
) -> Result<Option<ExitHandlerResult>, ExitHandlerError> {
let qemu = emu.qemu();
let emu_exit_handler = emu.exit_handler().borrow_mut();
let snapshot_id = emu_exit_handler
.snapshot_id()
.ok_or(HandlerError::SnapshotNotFound)?;
.ok_or(ExitHandlerError::SnapshotNotFound)?;
emu_exit_handler
.snapshot_manager_borrow_mut()
.restore(&snapshot_id, qemu)?;
Ok(InnerHandlerResult::Continue)
Ok(None)
}
}
@ -324,7 +440,7 @@ pub struct InputCommand {
cpu: CPU,
}
impl<SM, QT, S> IsCommand<QT, S, StdEmuExitHandler<SM>> for InputCommand
impl<SM, QT, S> IsCommand<QT, S, StdEmulatorExitHandler<SM>> for InputCommand
where
SM: IsSnapshotManager,
QT: QemuHelperTuple<S> + StdInstrumentationFilter<S> + Debug,
@ -337,11 +453,11 @@ where
fn run(
&self,
emu: &Emulator<QT, S, StdEmuExitHandler<SM>>,
emu: &Emulator<QT, S, StdEmulatorExitHandler<SM>>,
_qemu_executor_state: &mut QemuExecutorState<QT, S>,
input: &S::Input,
ret_reg: Option<Regs>,
) -> Result<InnerHandlerResult, HandlerError> {
) -> Result<Option<ExitHandlerResult>, ExitHandlerError> {
let qemu = emu.qemu();
let ret_value = self.location.write(qemu, input.target_bytes().as_slice());
@ -350,7 +466,7 @@ where
self.cpu.write_reg(reg, ret_value).unwrap();
}
Ok(InnerHandlerResult::Continue)
Ok(None)
}
}
@ -359,7 +475,7 @@ pub struct StartCommand {
input_location: EmulatorMemoryChunk,
}
impl<SM, QT, S> IsCommand<QT, S, StdEmuExitHandler<SM>> for StartCommand
impl<SM, QT, S> IsCommand<QT, S, StdEmulatorExitHandler<SM>> for StartCommand
where
SM: IsSnapshotManager,
QT: QemuHelperTuple<S> + StdInstrumentationFilter<S> + Debug,
@ -372,18 +488,18 @@ where
fn run(
&self,
emu: &Emulator<QT, S, StdEmuExitHandler<SM>>,
emu: &Emulator<QT, S, StdEmulatorExitHandler<SM>>,
_qemu_executor_state: &mut QemuExecutorState<QT, S>,
input: &S::Input,
ret_reg: Option<Regs>,
) -> Result<InnerHandlerResult, HandlerError> {
) -> Result<Option<ExitHandlerResult>, ExitHandlerError> {
let emu_exit_handler = emu.exit_handler().borrow_mut();
let qemu = emu.qemu();
let snapshot_id = emu_exit_handler.snapshot_manager_borrow_mut().save(qemu);
emu_exit_handler
.set_snapshot_id(snapshot_id)
.map_err(|_| HandlerError::MultipleSnapshotDefinition)?;
.map_err(|_| ExitHandlerError::MultipleSnapshotDefinition)?;
emu_exit_handler
.set_input_location(InputLocation::new(
@ -401,14 +517,14 @@ where
qemu.write_reg(reg, ret_value).unwrap();
}
Ok(InnerHandlerResult::Continue)
Ok(None)
}
}
#[derive(Debug, Clone)]
pub struct EndCommand(Option<ExitKind>);
impl<SM, QT, S> IsCommand<QT, S, StdEmuExitHandler<SM>> for EndCommand
impl<SM, QT, S> IsCommand<QT, S, StdEmulatorExitHandler<SM>> for EndCommand
where
SM: IsSnapshotManager,
QT: QemuHelperTuple<S> + StdInstrumentationFilter<S> + Debug,
@ -421,29 +537,29 @@ where
fn run(
&self,
emu: &Emulator<QT, S, StdEmuExitHandler<SM>>,
emu: &Emulator<QT, S, StdEmulatorExitHandler<SM>>,
_qemu_executor_state: &mut QemuExecutorState<QT, S>,
_input: &S::Input,
_ret_reg: Option<Regs>,
) -> Result<InnerHandlerResult, HandlerError> {
) -> Result<Option<ExitHandlerResult>, ExitHandlerError> {
let emu_exit_handler = emu.exit_handler().borrow_mut();
let snapshot_id = emu_exit_handler
.snapshot_id()
.ok_or(HandlerError::SnapshotNotFound)?;
.ok_or(ExitHandlerError::SnapshotNotFound)?;
emu_exit_handler
.snapshot_manager_borrow_mut()
.restore(&snapshot_id, emu.qemu())?;
Ok(InnerHandlerResult::EndOfRun(self.0.unwrap()))
Ok(Some(ExitHandlerResult::EndOfRun(self.0.unwrap())))
}
}
#[derive(Debug, Clone)]
pub struct VersionCommand(u64);
impl<SM, QT, S> IsCommand<QT, S, StdEmuExitHandler<SM>> for VersionCommand
impl<SM, QT, S> IsCommand<QT, S, StdEmulatorExitHandler<SM>> for VersionCommand
where
SM: IsSnapshotManager,
QT: QemuHelperTuple<S> + StdInstrumentationFilter<S> + Debug,
@ -456,18 +572,18 @@ where
fn run(
&self,
_emu: &Emulator<QT, S, StdEmuExitHandler<SM>>,
_emu: &Emulator<QT, S, StdEmulatorExitHandler<SM>>,
_qemu_executor_state: &mut QemuExecutorState<QT, S>,
_input: &S::Input,
_ret_reg: Option<Regs>,
) -> Result<InnerHandlerResult, HandlerError> {
) -> Result<Option<ExitHandlerResult>, ExitHandlerError> {
let guest_version = self.0;
if VERSION == guest_version {
Ok(InnerHandlerResult::Continue)
Ok(None)
} else {
Err(HandlerError::SyncBackdoorError(
SyncBackdoorError::VersionDifference(guest_version),
Err(ExitHandlerError::CommandError(
CommandError::VersionDifference(guest_version),
))
}
}
@ -482,7 +598,7 @@ where
}
#[cfg(emulation_mode = "systemmode")]
impl<SM, QT, S> IsCommand<QT, S, StdEmuExitHandler<SM>> for PagingFilterCommand
impl<SM, QT, S> IsCommand<QT, S, StdEmulatorExitHandler<SM>> for PagingFilterCommand
where
SM: IsSnapshotManager,
QT: QemuHelperTuple<S> + StdInstrumentationFilter<S> + Debug,
@ -495,11 +611,11 @@ where
fn run(
&self,
_emu: &Emulator<QT, S, StdEmuExitHandler<SM>>,
_emu: &Emulator<QT, S, StdEmulatorExitHandler<SM>>,
qemu_executor_state: &mut QemuExecutorState<QT, S>,
_input: &S::Input,
_ret_reg: Option<Regs>,
) -> Result<InnerHandlerResult, HandlerError> {
) -> Result<Option<ExitHandlerResult>, ExitHandlerError> {
let qemu_helpers = qemu_executor_state.hooks_mut().helpers_mut();
let paging_filter =
@ -509,11 +625,11 @@ where
*paging_filter = self.filter.clone();
Ok(InnerHandlerResult::Continue)
Ok(None)
}
}
impl<SM, QT, S> IsCommand<QT, S, StdEmuExitHandler<SM>> for AddressRangeFilterCommand
impl<SM, QT, S> IsCommand<QT, S, StdEmulatorExitHandler<SM>> for AddressRangeFilterCommand
where
SM: IsSnapshotManager,
QT: QemuHelperTuple<S> + StdInstrumentationFilter<S> + Debug,
@ -527,11 +643,11 @@ where
#[allow(clippy::type_complexity)] // TODO: refactor with correct type.
fn run(
&self,
_emu: &Emulator<QT, S, StdEmuExitHandler<SM>>,
_emu: &Emulator<QT, S, StdEmulatorExitHandler<SM>>,
qemu_executor_state: &mut QemuExecutorState<QT, S>,
_input: &S::Input,
_ret_reg: Option<Regs>,
) -> Result<InnerHandlerResult, HandlerError> {
) -> Result<Option<ExitHandlerResult>, ExitHandlerError> {
let qemu_helpers = qemu_executor_state.hooks_mut().helpers_mut();
let addr_range_filter =
@ -541,7 +657,7 @@ where
*addr_range_filter = self.filter.clone();
Ok(InnerHandlerResult::Continue)
Ok(None)
}
}
@ -568,13 +684,13 @@ impl Display for Command {
Command::SaveCommand(_) => write!(f, "Save VM"),
Command::LoadCommand(_) => write!(f, "Reload VM"),
Command::InputCommand(input_command) => {
write!(f, "Set fuzzing input @{}", input_command.location.addr)
write!(f, "Set fuzzing input @{}", input_command.location.addr())
}
Command::StartCommand(start_command) => {
write!(
f,
"Start fuzzing with input @{}",
start_command.input_location.addr
start_command.input_location.addr()
)
}
Command::EndCommand(end_command) => write!(f, "Exit of kind {:?}", end_command.0),
@ -613,66 +729,13 @@ impl InputCommand {
}
}
impl EmulatorMemoryChunk {
#[must_use]
pub fn phys(addr: GuestPhysAddr, size: GuestReg, cpu: Option<CPU>) -> Self {
Self {
addr: GuestAddrKind::Physical(addr),
size,
cpu,
}
}
#[must_use]
pub fn virt(addr: GuestVirtAddr, size: GuestReg, cpu: CPU) -> Self {
Self {
addr: GuestAddrKind::Virtual(addr),
size,
cpu: Some(cpu),
}
}
/// Returns the number of bytes effectively written.
#[must_use]
pub fn write(&self, qemu: &Qemu, input: &[u8]) -> GuestReg {
let max_len: usize = self.size.try_into().unwrap();
let input_sliced = if input.len() > max_len {
&input[0..max_len]
} else {
input
};
match self.addr {
GuestAddrKind::Physical(hwaddr) => unsafe {
#[cfg(emulation_mode = "usermode")]
{
// For now the default behaviour is to fall back to virtual addresses
qemu.write_mem(hwaddr.try_into().unwrap(), input_sliced);
}
#[cfg(emulation_mode = "systemmode")]
{
qemu.write_phys_mem(hwaddr, input_sliced);
}
},
GuestAddrKind::Virtual(vaddr) => unsafe {
self.cpu
.as_ref()
.unwrap()
.write_mem(vaddr.try_into().unwrap(), input_sliced);
},
};
input_sliced.len().try_into().unwrap()
}
}
impl Display for InputCommand {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{} (0x{:x} max nb bytes)",
self.location.addr, self.location.size
self.location.addr(),
self.location.size()
)
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,22 +1,15 @@
use std::{
collections::HashMap,
ffi::{c_void, CStr, CString},
fmt::Debug,
mem::MaybeUninit,
ptr::null_mut,
sync::atomic::{AtomicU64, Ordering},
};
use libafl::state::{HasExecutions, State};
use libafl_qemu_sys::{
libafl_load_qemu_snapshot, libafl_qemu_current_paging_id, libafl_save_qemu_snapshot,
qemu_cleanup, qemu_main_loop, vm_start, GuestAddr, GuestPhysAddr, GuestVirtAddr,
};
use libafl_qemu_sys::GuestPhysAddr;
use crate::{
emu::{libafl_page_from_addr, IsSnapshotManager},
EmuExitHandler, Emulator, MemAccessInfo, Qemu, QemuExitReason, QemuExitReasonError,
QemuHelperTuple, SnapshotId, SnapshotManagerError, CPU,
emu::IsSnapshotManager, DeviceSnapshotFilter, Emulator, EmulatorExitHandler, Qemu,
QemuHelperTuple, SnapshotId, SnapshotManagerError,
};
impl SnapshotId {
@ -25,9 +18,7 @@ impl SnapshotId {
let unique_id = UNIQUE_ID.fetch_add(1, Ordering::SeqCst);
SnapshotId {
id: unique_id.clone(),
}
SnapshotId { id: unique_id }
}
fn inner(&self) -> u64 {
@ -84,7 +75,7 @@ impl FastSnapshotManager {
}
pub unsafe fn get(&self, id: &SnapshotId) -> FastSnapshotPtr {
self.snapshots.get(id).unwrap().clone()
*self.snapshots.get(id).unwrap()
}
}
@ -136,18 +127,18 @@ impl IsSnapshotManager for FastSnapshotManager {
snapshot_id: &SnapshotId,
qemu: &Qemu,
) -> Result<(), SnapshotManagerError> {
let fast_snapshot_ptr = self
let fast_snapshot_ptr = *self
.snapshots
.get(snapshot_id)
.ok_or(SnapshotManagerError::SnapshotIdNotFound(
snapshot_id.clone(),
))?
.clone();
.ok_or(SnapshotManagerError::SnapshotIdNotFound(*snapshot_id))?;
qemu.restore_fast_snapshot(fast_snapshot_ptr);
unsafe {
qemu.restore_fast_snapshot(fast_snapshot_ptr);
}
if self.check_memory_consistency {
let nb_inconsistencies = qemu.check_fast_snapshot_memory_consistency(fast_snapshot_ptr);
let nb_inconsistencies =
unsafe { qemu.check_fast_snapshot_memory_consistency(fast_snapshot_ptr) };
if nb_inconsistencies > 0 {
return Err(SnapshotManagerError::MemoryInconsistencies(
@ -160,251 +151,11 @@ impl IsSnapshotManager for FastSnapshotManager {
}
}
pub enum DeviceSnapshotFilter {
All,
AllowList(Vec<String>),
DenyList(Vec<String>),
}
impl DeviceSnapshotFilter {
fn enum_id(&self) -> libafl_qemu_sys::DeviceSnapshotKind {
match self {
DeviceSnapshotFilter::All => libafl_qemu_sys::DeviceSnapshotKind_DEVICE_SNAPSHOT_ALL,
DeviceSnapshotFilter::AllowList(_) => {
libafl_qemu_sys::DeviceSnapshotKind_DEVICE_SNAPSHOT_ALLOWLIST
}
DeviceSnapshotFilter::DenyList(_) => {
libafl_qemu_sys::DeviceSnapshotKind_DEVICE_SNAPSHOT_DENYLIST
}
}
}
fn devices(&self, v: &mut Vec<*mut i8>) -> *mut *mut i8 {
v.clear();
match self {
DeviceSnapshotFilter::All => null_mut(),
DeviceSnapshotFilter::AllowList(l) | DeviceSnapshotFilter::DenyList(l) => {
for name in l {
v.push(name.as_bytes().as_ptr() as *mut i8);
}
v.push(core::ptr::null_mut());
v.as_mut_ptr()
}
}
}
}
pub(super) extern "C" fn qemu_cleanup_atexit() {
unsafe {
qemu_cleanup();
}
}
impl CPU {
#[must_use]
pub fn get_phys_addr(&self, vaddr: GuestAddr) -> Option<GuestPhysAddr> {
unsafe {
let page = libafl_page_from_addr(vaddr);
let mut attrs = MaybeUninit::<libafl_qemu_sys::MemTxAttrs>::uninit();
let paddr = libafl_qemu_sys::cpu_get_phys_page_attrs_debug(
self.ptr,
page as GuestVirtAddr,
attrs.as_mut_ptr(),
);
if paddr == (-1i64 as GuestPhysAddr) {
None
} else {
Some(paddr)
}
}
}
#[must_use]
pub fn get_phys_addr_tlb(
&self,
vaddr: GuestAddr,
info: MemAccessInfo,
is_store: bool,
) -> Option<GuestPhysAddr> {
unsafe {
let pminfo = libafl_qemu_sys::make_plugin_meminfo(
info.oi,
if is_store {
libafl_qemu_sys::qemu_plugin_mem_rw_QEMU_PLUGIN_MEM_W
} else {
libafl_qemu_sys::qemu_plugin_mem_rw_QEMU_PLUGIN_MEM_R
},
);
let phwaddr = libafl_qemu_sys::qemu_plugin_get_hwaddr(pminfo, vaddr as GuestVirtAddr);
if phwaddr.is_null() {
None
} else {
Some(libafl_qemu_sys::qemu_plugin_hwaddr_phys_addr(phwaddr) as GuestPhysAddr)
}
}
}
#[must_use]
pub fn current_paging_id(&self) -> Option<GuestPhysAddr> {
let paging_id = unsafe { libafl_qemu_current_paging_id(self.ptr) };
if paging_id == 0 {
None
} else {
Some(paging_id)
}
}
/// 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]) {
// TODO use gdbstub's target_cpu_memory_rw_debug
libafl_qemu_sys::cpu_memory_rw_debug(
self.ptr,
addr as GuestVirtAddr,
buf.as_ptr() as *mut _,
buf.len(),
true,
);
}
/// 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]) {
// TODO use gdbstub's target_cpu_memory_rw_debug
libafl_qemu_sys::cpu_memory_rw_debug(
self.ptr,
addr as GuestVirtAddr,
buf.as_mut_ptr() as *mut _,
buf.len(),
false,
);
}
#[must_use]
pub fn page_size(&self) -> usize {
unsafe { libafl_qemu_sys::qemu_target_page_size() }
}
}
#[allow(clippy::unused_self)]
impl Qemu {
/// Write a value to a phsical guest address, including ROM areas.
pub unsafe fn write_phys_mem(&self, paddr: GuestPhysAddr, buf: &[u8]) {
libafl_qemu_sys::cpu_physical_memory_rw(
paddr,
buf.as_ptr() as *mut _,
buf.len() as u64,
true,
);
}
/// Read a value from a physical guest address.
pub unsafe fn read_phys_mem(&self, paddr: GuestPhysAddr, buf: &mut [u8]) {
libafl_qemu_sys::cpu_physical_memory_rw(
paddr,
buf.as_mut_ptr() as *mut _,
buf.len() as u64,
false,
);
}
/// This function will run the emulator until the next breakpoint / sync exit, or until finish.
/// It is a low-level function and simply kicks QEMU.
/// # Safety
///
/// 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) -> Result<QemuExitReason, QemuExitReasonError> {
vm_start();
qemu_main_loop();
self.post_run()
}
pub fn save_snapshot(&self, name: &str, sync: bool) {
let s = CString::new(name).expect("Invalid snapshot name");
unsafe { libafl_save_qemu_snapshot(s.as_ptr() as *const _, sync) };
}
pub fn load_snapshot(&self, name: &str, sync: bool) {
let s = CString::new(name).expect("Invalid snapshot name");
unsafe { libafl_load_qemu_snapshot(s.as_ptr() as *const _, sync) };
}
#[must_use]
pub fn create_fast_snapshot(&self, track: bool) -> FastSnapshotPtr {
unsafe {
libafl_qemu_sys::syx_snapshot_new(
track,
true,
libafl_qemu_sys::DeviceSnapshotKind_DEVICE_SNAPSHOT_ALL,
null_mut(),
)
}
}
#[must_use]
pub fn create_fast_snapshot_filter(
&self,
track: bool,
device_filter: &DeviceSnapshotFilter,
) -> FastSnapshotPtr {
let mut v = vec![];
unsafe {
libafl_qemu_sys::syx_snapshot_new(
track,
true,
device_filter.enum_id(),
device_filter.devices(&mut v),
)
}
}
pub fn restore_fast_snapshot(&self, snapshot: FastSnapshotPtr) {
unsafe { libafl_qemu_sys::syx_snapshot_root_restore(snapshot) }
}
pub fn check_fast_snapshot_memory_consistency(&self, snapshot: FastSnapshotPtr) -> u64 {
unsafe { libafl_qemu_sys::syx_snapshot_check_memory_consistency(snapshot) }
}
pub fn list_devices(&self) -> Vec<String> {
let mut r = vec![];
unsafe {
let devices = libafl_qemu_sys::device_list_all();
if devices.is_null() {
return r;
}
let mut ptr = devices;
while !(*ptr).is_null() {
let c_str: &CStr = CStr::from_ptr(*ptr);
let name = c_str.to_str().unwrap().to_string();
r.push(name);
ptr = ptr.add(1);
}
libc::free(devices as *mut c_void);
r
}
}
}
impl<QT, S, E> Emulator<QT, S, E>
where
QT: QemuHelperTuple<S>,
S: State + HasExecutions,
E: EmuExitHandler<QT, S>,
E: EmulatorExitHandler<QT, S>,
{
/// Write a value to a phsical guest address, including ROM areas.
pub unsafe fn write_phys_mem(&self, paddr: GuestPhysAddr, buf: &[u8]) {
@ -438,11 +189,11 @@ where
self.qemu.create_fast_snapshot_filter(track, device_filter)
}
pub fn restore_fast_snapshot(&self, snapshot: FastSnapshotPtr) {
pub unsafe fn restore_fast_snapshot(&self, snapshot: FastSnapshotPtr) {
self.qemu.restore_fast_snapshot(snapshot)
}
pub fn check_fast_snapshot_memory_consistency(&self, snapshot: FastSnapshotPtr) -> u64 {
pub unsafe fn check_fast_snapshot_memory_consistency(&self, snapshot: FastSnapshotPtr) -> u64 {
self.qemu.check_fast_snapshot_memory_consistency(snapshot)
}

View File

@ -1,365 +1,16 @@
use core::{mem::MaybeUninit, ptr::copy_nonoverlapping};
use std::{cell::OnceCell, slice::from_raw_parts, str::from_utf8_unchecked};
use libafl_qemu_sys::{
exec_path, free_self_maps, guest_base, libafl_dump_core_hook, libafl_force_dfl, libafl_get_brk,
libafl_load_addr, libafl_maps_first, libafl_maps_next, libafl_qemu_run, libafl_set_brk,
mmap_next_start, pageflags_get_root, read_self_maps, strlen, GuestAddr, GuestUsize,
IntervalTreeNode, IntervalTreeRoot, MapInfo, MmapPerms, VerifyAccess,
};
use libc::c_int;
#[cfg(feature = "python")]
use pyo3::prelude::*;
use libafl_qemu_sys::{GuestAddr, MmapPerms, VerifyAccess};
use crate::{
emu::{HasExecutions, State},
sync_exit::SyncBackdoorError,
EmuExitHandler, Emulator, HookData, NewThreadHookId, PostSyscallHookId, PreSyscallHookId, Qemu,
QemuExitReason, QemuExitReasonError, QemuHelperTuple, SyscallHookResult, CPU,
Emulator, EmulatorExitHandler, GuestMaps, HookData, NewThreadHookId, PostSyscallHookId,
PreSyscallHookId, QemuHelperTuple, SyscallHookResult,
};
#[derive(Debug, Clone)]
pub enum HandlerError {
EmuExitReasonError(QemuExitReasonError),
SyncBackdoorError(SyncBackdoorError),
MultipleInputDefinition,
}
#[cfg_attr(feature = "python", pyclass(unsendable))]
pub struct GuestMaps {
self_maps_root: *mut IntervalTreeRoot,
pageflags_node: *mut IntervalTreeNode,
}
// Consider a private new only for Emulator
impl GuestMaps {
#[must_use]
pub(crate) fn new() -> Self {
unsafe {
let pageflags_root = pageflags_get_root();
let self_maps_root = read_self_maps();
let pageflags_first = libafl_maps_first(pageflags_root);
Self {
self_maps_root,
pageflags_node: pageflags_first,
}
}
}
}
impl Iterator for GuestMaps {
type Item = MapInfo;
#[allow(clippy::uninit_assumed_init)]
fn next(&mut self) -> Option<Self::Item> {
unsafe {
let mut ret = MaybeUninit::uninit();
self.pageflags_node =
libafl_maps_next(self.pageflags_node, self.self_maps_root, ret.as_mut_ptr());
let ret = ret.assume_init();
if ret.is_valid {
Some(ret.into())
} else {
None
}
}
}
}
#[cfg(feature = "python")]
#[pymethods]
impl GuestMaps {
fn __iter__(slf: PyRef<Self>) -> PyRef<Self> {
slf
}
fn __next__(mut slf: PyRefMut<Self>) -> Option<PyObject> {
Python::with_gil(|py| slf.next().map(|x| x.into_py(py)))
}
}
impl Drop for GuestMaps {
fn drop(&mut self) {
unsafe {
free_self_maps(self.self_maps_root);
}
}
}
impl CPU {
/// 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 = Qemu::get().unwrap().g2h(addr);
copy_nonoverlapping(buf.as_ptr(), host_addr, buf.len());
}
/// 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 = Qemu::get().unwrap().g2h(addr);
copy_nonoverlapping(host_addr, buf.as_mut_ptr(), buf.len());
}
#[must_use]
pub fn page_size(&self) -> usize {
thread_local! {
static PAGE_SIZE: OnceCell<usize> = const { OnceCell::new() };
}
PAGE_SIZE.with(|s| {
*s.get_or_init(|| {
unsafe { libc::sysconf(libc::_SC_PAGE_SIZE) }
.try_into()
.expect("Invalid page size")
})
})
}
}
#[allow(clippy::unused_self)]
impl Qemu {
#[must_use]
pub fn mappings(&self) -> GuestMaps {
GuestMaps::new()
}
#[must_use]
pub fn g2h<T>(&self, addr: GuestAddr) -> *mut T {
unsafe { (addr as usize + guest_base) as *mut T }
}
#[must_use]
pub fn h2g<T>(&self, addr: *const T) -> GuestAddr {
unsafe { (addr as usize - guest_base) as GuestAddr }
}
#[must_use]
pub fn access_ok(&self, kind: VerifyAccess, addr: GuestAddr, size: usize) -> bool {
self.current_cpu()
.unwrap_or_else(|| self.cpu_from_index(0))
.access_ok(kind, addr, size)
}
pub fn force_dfl(&self) {
unsafe {
libafl_force_dfl = 1;
}
}
/// This function will run the emulator until the next breakpoint, or until finish.
/// # Safety
///
/// 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) -> Result<QemuExitReason, QemuExitReasonError> {
libafl_qemu_run();
self.post_run()
}
#[must_use]
pub fn binary_path<'a>(&self) -> &'a str {
unsafe { from_utf8_unchecked(from_raw_parts(exec_path, strlen(exec_path))) }
}
#[must_use]
pub fn load_addr(&self) -> GuestAddr {
unsafe { libafl_load_addr() as GuestAddr }
}
#[must_use]
pub fn get_brk(&self) -> GuestAddr {
unsafe { libafl_get_brk() as GuestAddr }
}
pub fn set_brk(&self, brk: GuestAddr) {
unsafe { libafl_set_brk(brk.into()) };
}
#[must_use]
pub fn get_mmap_start(&self) -> GuestAddr {
unsafe { mmap_next_start }
}
pub fn set_mmap_start(&self, start: GuestAddr) {
unsafe { mmap_next_start = start };
}
#[allow(clippy::cast_sign_loss)]
fn mmap(
self,
addr: GuestAddr,
size: usize,
perms: MmapPerms,
flags: c_int,
) -> Result<GuestAddr, ()> {
let res = unsafe {
libafl_qemu_sys::target_mmap(addr, size as GuestUsize, perms.into(), flags, -1, 0)
};
if res <= 0 {
Err(())
} else {
Ok(res as GuestAddr)
}
}
pub fn map_private(
&self,
addr: GuestAddr,
size: usize,
perms: MmapPerms,
) -> Result<GuestAddr, String> {
self.mmap(addr, size, perms, libc::MAP_PRIVATE | libc::MAP_ANONYMOUS)
.map_err(|()| format!("Failed to map {addr}"))
.map(|addr| addr as GuestAddr)
}
pub fn map_fixed(
&self,
addr: GuestAddr,
size: usize,
perms: MmapPerms,
) -> Result<GuestAddr, String> {
self.mmap(
addr,
size,
perms,
libc::MAP_FIXED | libc::MAP_PRIVATE | libc::MAP_ANONYMOUS,
)
.map_err(|()| format!("Failed to map {addr}"))
.map(|addr| addr as GuestAddr)
}
pub fn mprotect(&self, addr: GuestAddr, size: usize, perms: MmapPerms) -> Result<(), String> {
let res = unsafe {
libafl_qemu_sys::target_mprotect(addr.into(), size as GuestUsize, perms.into())
};
if res == 0 {
Ok(())
} else {
Err(format!("Failed to mprotect {addr}"))
}
}
pub fn unmap(&self, addr: GuestAddr, size: usize) -> Result<(), String> {
if unsafe { libafl_qemu_sys::target_munmap(addr.into(), size as GuestUsize) } == 0 {
Ok(())
} else {
Err(format!("Failed to unmap {addr}"))
}
}
#[allow(clippy::type_complexity)]
pub fn add_pre_syscall_hook<T: Into<HookData>>(
&self,
data: T,
callback: extern "C" fn(
T,
i32,
GuestAddr,
GuestAddr,
GuestAddr,
GuestAddr,
GuestAddr,
GuestAddr,
GuestAddr,
GuestAddr,
) -> SyscallHookResult,
) -> PreSyscallHookId {
unsafe {
let data: u64 = data.into().0;
let callback: extern "C" fn(
u64,
i32,
GuestAddr,
GuestAddr,
GuestAddr,
GuestAddr,
GuestAddr,
GuestAddr,
GuestAddr,
GuestAddr,
) -> libafl_qemu_sys::syshook_ret = core::mem::transmute(callback);
let num = libafl_qemu_sys::libafl_add_pre_syscall_hook(Some(callback), data);
PreSyscallHookId(num)
}
}
#[allow(clippy::type_complexity)]
pub fn add_post_syscall_hook<T: Into<HookData>>(
&self,
data: T,
callback: extern "C" fn(
T,
GuestAddr,
i32,
GuestAddr,
GuestAddr,
GuestAddr,
GuestAddr,
GuestAddr,
GuestAddr,
GuestAddr,
GuestAddr,
) -> GuestAddr,
) -> PostSyscallHookId {
unsafe {
let data: u64 = data.into().0;
let callback: extern "C" fn(
u64,
GuestAddr,
i32,
GuestAddr,
GuestAddr,
GuestAddr,
GuestAddr,
GuestAddr,
GuestAddr,
GuestAddr,
GuestAddr,
) -> GuestAddr = core::mem::transmute(callback);
let num = libafl_qemu_sys::libafl_add_post_syscall_hook(Some(callback), data);
PostSyscallHookId(num)
}
}
pub fn add_new_thread_hook<T: Into<HookData>>(
&self,
data: T,
callback: extern "C" fn(T, tid: u32) -> bool,
) -> NewThreadHookId {
unsafe {
let data: u64 = data.into().0;
let callback: extern "C" fn(u64, u32) -> bool = core::mem::transmute(callback);
let num = libafl_qemu_sys::libafl_add_new_thread_hook(Some(callback), data);
NewThreadHookId(num)
}
}
#[allow(clippy::type_complexity)]
pub fn set_crash_hook(&self, callback: extern "C" fn(i32)) {
unsafe {
libafl_dump_core_hook = callback;
}
}
}
impl<QT, S, E> Emulator<QT, S, E>
where
QT: QemuHelperTuple<S>,
S: State + HasExecutions,
E: EmuExitHandler<QT, S>,
E: EmulatorExitHandler<QT, S>,
{
/// This function gets the memory mappings from the emulator.
#[must_use]

View File

@ -8,7 +8,9 @@ use core::{
};
#[cfg(feature = "fork")]
use libafl::{events::EventManager, executors::InProcessForkExecutor, state::HasLastReportTime};
use libafl::{
events::EventManager, executors::InProcessForkExecutor, state::HasLastReportTime, HasMetadata,
};
use libafl::{
events::{EventFirer, EventRestarter},
executors::{
@ -20,7 +22,7 @@ use libafl::{
fuzzer::HasObjective,
observers::{ObserversTuple, UsesObservers},
state::{HasCorpus, HasExecutions, HasSolutions, State, UsesState},
Error, HasMetadata,
Error,
};
#[cfg(feature = "fork")]
use libafl_bolts::shmem::ShMemProvider;

View File

@ -17,12 +17,12 @@ use num_enum::{IntoPrimitive, TryFromPrimitive};
use rangemap::RangeMap;
use crate::{
emu::{EmuError, MemAccessInfo, SyscallHookResult},
helpers::{
calls::FullBacktraceCollector, HasInstrumentationFilter, IsFilter, QemuHelper,
QemuHelperTuple, QemuInstrumentationAddressRangeFilter,
},
hooks::{Hook, QemuHooks},
qemu::{MemAccessInfo, QemuInitError, SyscallHookResult},
snapshot::QemuSnapshotHelper,
sys::TCGTemp,
GuestAddr, Qemu, Regs,
@ -668,7 +668,7 @@ static mut ASAN_INITED: bool = false;
pub fn init_qemu_with_asan(
args: &mut Vec<String>,
env: &mut [(String, String)],
) -> Result<(Qemu, Pin<Box<AsanGiovese>>), EmuError> {
) -> Result<(Qemu, Pin<Box<AsanGiovese>>), QemuInitError> {
let current = env::current_exe().unwrap();
let asan_lib = fs::canonicalize(current)
.unwrap()

View File

@ -12,12 +12,12 @@ use libafl::{inputs::UsesInput, HasMetadata};
#[cfg(not(feature = "clippy"))]
use crate::sys::libafl_tcg_gen_asan;
use crate::{
emu::{EmuError, MemAccessInfo, Qemu},
helpers::{
HasInstrumentationFilter, IsFilter, QemuHelper, QemuHelperTuple,
QemuInstrumentationAddressRangeFilter,
},
hooks::{Hook, QemuHooks},
qemu::{MemAccessInfo, Qemu, QemuInitError},
sys::TCGTemp,
GuestAddr, MapInfo,
};
@ -27,7 +27,7 @@ static mut ASAN_GUEST_INITED: bool = false;
pub fn init_qemu_with_asan_guest(
args: &mut Vec<String>,
env: &mut [(String, String)],
) -> Result<(Qemu, String), EmuError> {
) -> Result<(Qemu, String), QemuInitError> {
let current = env::current_exe().unwrap();
let asan_lib = fs::canonicalize(current)
.unwrap()

View File

@ -12,12 +12,12 @@ use thread_local::ThreadLocal;
use crate::{
capstone,
emu::ArchExtras,
helpers::{
HasInstrumentationFilter, IsFilter, QemuHelper, QemuHelperTuple,
QemuInstrumentationAddressRangeFilter,
},
hooks::{Hook, QemuHooks},
qemu::ArchExtras,
Qemu,
};

View File

@ -12,7 +12,7 @@ pub use libafl_targets::{
use serde::{Deserialize, Serialize};
#[cfg(emulation_mode = "usermode")]
use crate::{capstone, emu::ArchExtras, CallingConvention, Qemu};
use crate::{capstone, qemu::ArchExtras, CallingConvention, Qemu};
use crate::{
helpers::{
hash_me, HasInstrumentationFilter, IsFilter, QemuHelper, QemuHelperTuple,

View File

@ -539,8 +539,7 @@ where
let paging_id = hooks
.qemu()
.current_cpu()
.map(|cpu| cpu.current_paging_id())
.flatten();
.and_then(|cpu| cpu.current_paging_id());
if !h.must_instrument(src, paging_id) && !h.must_instrument(dest, paging_id) {
return None;
@ -609,8 +608,7 @@ where
let paging_id = hooks
.qemu()
.current_cpu()
.map(|cpu| cpu.current_paging_id())
.flatten();
.and_then(|cpu| cpu.current_paging_id());
if !h.must_instrument(src, paging_id) && !h.must_instrument(dest, paging_id) {
return None;
@ -676,8 +674,7 @@ where
let paging_id = hooks
.qemu()
.current_cpu()
.map(|cpu| cpu.current_paging_id())
.flatten();
.and_then(|cpu| cpu.current_paging_id());
if !h.must_instrument(pc, paging_id) {
return None;

View File

@ -21,7 +21,7 @@ use serde::{Deserialize, Serialize};
#[cfg(not(cpu_target = "hexagon"))]
use crate::SYS_execve;
use crate::{
elf::EasyElf, emu::ArchExtras, CallingConvention, Hook, Qemu, QemuHelper, QemuHelperTuple,
elf::EasyElf, qemu::ArchExtras, CallingConvention, Hook, Qemu, QemuHelper, QemuHelperTuple,
QemuHooks, SyscallHookResult,
};
#[cfg(cpu_target = "hexagon")]

View File

@ -1,5 +1,5 @@
use core::{fmt::Debug, ops::Range};
use std::{collections::HashSet, hash::BuildHasher};
use std::{cell::UnsafeCell, collections::HashSet, hash::BuildHasher};
use libafl::{executors::ExitKind, inputs::UsesInput, observers::ObserversTuple};
use libafl_bolts::tuples::{MatchFirstType, SplitBorrowExtractFirstType};
@ -137,6 +137,19 @@ where
}
}
impl<S> HasInstrumentationFilter<(), S> for ()
where
S: UsesInput,
{
fn filter(&self) -> &() {
self
}
fn filter_mut(&mut self) -> &mut () {
self
}
}
impl<Head, F, S> HasInstrumentationFilter<F, S> for (Head, ())
where
Head: QemuHelper<S> + HasInstrumentationFilter<F, S>,
@ -272,6 +285,31 @@ pub trait StdInstrumentationFilter<S: UsesInput>:
{
}
static mut EMPTY_ADDRESS_FILTER: UnsafeCell<QemuInstrumentationAddressRangeFilter> =
UnsafeCell::new(QemuFilterList::None);
static mut EMPTY_PAGING_FILTER: UnsafeCell<QemuInstrumentationPagingFilter> =
UnsafeCell::new(QemuFilterList::None);
impl<S> HasInstrumentationFilter<QemuInstrumentationAddressRangeFilter, S> for () {
fn filter(&self) -> &QemuInstrumentationAddressRangeFilter {
&QemuFilterList::None
}
fn filter_mut(&mut self) -> &mut QemuInstrumentationAddressRangeFilter {
unsafe { EMPTY_ADDRESS_FILTER.get_mut() }
}
}
impl<S> HasInstrumentationFilter<QemuInstrumentationPagingFilter, S> for () {
fn filter(&self) -> &QemuInstrumentationPagingFilter {
&QemuFilterList::None
}
fn filter_mut(&mut self) -> &mut QemuInstrumentationPagingFilter {
unsafe { EMPTY_PAGING_FILTER.get_mut() }
}
}
#[cfg(emulation_mode = "systemmode")]
impl<Head, S> StdInstrumentationFilter<S> for (Head, ())
where
@ -290,12 +328,26 @@ where
{
}
#[cfg(emulation_mode = "systemmode")]
impl<S> StdInstrumentationFilter<S> for () where S: UsesInput {}
#[cfg(emulation_mode = "usermode")]
impl<S> StdInstrumentationFilter<S> for () where S: UsesInput {}
pub trait IsFilter: Debug {
type FilterParameter;
fn allowed(&self, filter_parameter: Self::FilterParameter) -> bool;
}
impl IsFilter for () {
type FilterParameter = ();
fn allowed(&self, _filter_parameter: Self::FilterParameter) -> bool {
true
}
}
pub trait IsAddressFilter: IsFilter<FilterParameter = GuestAddr> {}
#[cfg(emulation_mode = "systemmode")]

View File

@ -25,9 +25,9 @@ use crate::SYS_mmap2;
use crate::SYS_newfstatat;
use crate::{
asan::QemuAsanHelper,
emu::SyscallHookResult,
helpers::{QemuHelper, QemuHelperTuple},
hooks::{Hook, QemuHooks},
qemu::SyscallHookResult,
Qemu, SYS_fstat, SYS_fstatfs, SYS_futex, SYS_getrandom, SYS_mprotect, SYS_mremap, SYS_munmap,
SYS_pread64, SYS_read, SYS_readlinkat, SYS_statfs,
};

View File

@ -19,10 +19,10 @@ use libafl::{
};
use libafl_qemu_sys::{CPUArchStatePtr, FatPtr, GuestAddr, GuestUsize};
pub use crate::emu::SyscallHookResult;
pub use crate::qemu::SyscallHookResult;
use crate::{
emu::{MemAccessInfo, Qemu, SKIP_EXEC_HOOK},
helpers::QemuHelperTuple,
qemu::{MemAccessInfo, Qemu, SKIP_EXEC_HOOK},
sys::TCGTemp,
BackdoorHookId, BlockHookId, CmpHookId, EdgeHookId, HookId, InstructionHookId, ReadHookId,
WriteHookId,

View File

@ -1,4 +1,3 @@
#![cfg_attr(nightly, feature(used_with_arg))]
//! Welcome to `LibAFL` QEMU
//!
//! __Warning__: The documentation is built by default for `x86_64` in `usermode`. To access the documentation of other architectures or `systemmode`, the documentation must be rebuilt with the right features.
@ -49,6 +48,9 @@ pub use executor::QemuExecutor;
#[cfg(feature = "fork")]
pub use executor::QemuForkExecutor;
pub mod qemu;
pub use qemu::*;
pub mod emu;
pub use emu::*;
@ -89,16 +91,19 @@ pub fn python_module(py: Python, m: &PyModule) -> PyResult<()> {
m.add_submodule(regsm)?;
let mmapm = PyModule::new(py, "mmap")?;
for r in emu::MmapPerms::iter() {
for r in sys::MmapPerms::iter() {
let v: i32 = r.into();
mmapm.add(&format!("{r:?}"), v)?;
}
m.add_submodule(mmapm)?;
m.add_class::<emu::MapInfo>()?;
m.add_class::<emu::GuestMaps>()?;
m.add_class::<emu::SyscallHookResult>()?;
m.add_class::<emu::pybind::Qemu>()?;
m.add_class::<sys::MapInfo>()?;
#[cfg(emulation_mode = "usermode")]
m.add_class::<qemu::GuestMaps>()?;
m.add_class::<qemu::SyscallHookResult>()?;
m.add_class::<qemu::pybind::Qemu>()?;
Ok(())
}

1216
libafl_qemu/src/qemu/mod.rs Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,440 @@
use std::{
ffi::{c_void, CStr, CString},
marker::PhantomData,
mem::MaybeUninit,
ptr::null_mut,
slice,
};
use bytes_utils::SegmentedBuf;
use libafl_qemu_sys::{
libafl_load_qemu_snapshot, libafl_page_from_addr, libafl_qemu_current_paging_id,
libafl_save_qemu_snapshot, qemu_cleanup, qemu_main_loop, vm_start, GuestAddr, GuestPhysAddr,
GuestUsize, GuestVirtAddr,
};
use num_traits::Zero;
use crate::{
EmulatorMemoryChunk, FastSnapshotPtr, GuestAddrKind, MemAccessInfo, Qemu, QemuExitError,
QemuExitReason, CPU,
};
pub(super) extern "C" fn qemu_cleanup_atexit() {
unsafe {
qemu_cleanup();
}
}
pub enum DeviceSnapshotFilter {
All,
AllowList(Vec<String>),
DenyList(Vec<String>),
}
#[derive(Debug, Clone)]
#[allow(dead_code)]
pub struct PhysMemoryChunk {
addr: GuestPhysAddr,
size: usize,
qemu: Qemu,
cpu: CPU,
}
pub struct PhysMemoryIter {
addr: GuestAddrKind, // This address is correct when the iterator enters next, except if the remaining len is 0
remaining_len: usize,
qemu: Qemu,
cpu: CPU,
}
#[allow(dead_code)]
pub struct HostMemoryIter<'a> {
addr: GuestPhysAddr, // This address is correct when the iterator enters next, except if the remaining len is 0
remaining_len: usize,
qemu: Qemu,
cpu: CPU,
phantom: PhantomData<&'a ()>,
}
impl DeviceSnapshotFilter {
fn enum_id(&self) -> libafl_qemu_sys::DeviceSnapshotKind {
match self {
DeviceSnapshotFilter::All => libafl_qemu_sys::DeviceSnapshotKind_DEVICE_SNAPSHOT_ALL,
DeviceSnapshotFilter::AllowList(_) => {
libafl_qemu_sys::DeviceSnapshotKind_DEVICE_SNAPSHOT_ALLOWLIST
}
DeviceSnapshotFilter::DenyList(_) => {
libafl_qemu_sys::DeviceSnapshotKind_DEVICE_SNAPSHOT_DENYLIST
}
}
}
fn devices(&self, v: &mut Vec<*mut i8>) -> *mut *mut i8 {
v.clear();
match self {
DeviceSnapshotFilter::All => null_mut(),
DeviceSnapshotFilter::AllowList(l) | DeviceSnapshotFilter::DenyList(l) => {
for name in l {
v.push(name.as_bytes().as_ptr() as *mut i8);
}
v.push(core::ptr::null_mut());
v.as_mut_ptr()
}
}
}
}
impl CPU {
#[must_use]
pub fn get_phys_addr(&self, vaddr: GuestVirtAddr) -> Option<GuestPhysAddr> {
unsafe {
let page = libafl_page_from_addr(vaddr as GuestUsize) as GuestVirtAddr;
let mut attrs = MaybeUninit::<libafl_qemu_sys::MemTxAttrs>::uninit();
let paddr = libafl_qemu_sys::cpu_get_phys_page_attrs_debug(
self.ptr,
page as GuestVirtAddr,
attrs.as_mut_ptr(),
);
if paddr == (-1i64 as GuestPhysAddr) {
None
} else {
Some(paddr)
}
}
}
#[must_use]
pub fn get_phys_addr_tlb(
&self,
vaddr: GuestAddr,
info: MemAccessInfo,
is_store: bool,
) -> Option<GuestPhysAddr> {
unsafe {
let pminfo = libafl_qemu_sys::make_plugin_meminfo(
info.oi,
if is_store {
libafl_qemu_sys::qemu_plugin_mem_rw_QEMU_PLUGIN_MEM_W
} else {
libafl_qemu_sys::qemu_plugin_mem_rw_QEMU_PLUGIN_MEM_R
},
);
let phwaddr = libafl_qemu_sys::qemu_plugin_get_hwaddr(pminfo, vaddr as GuestVirtAddr);
if phwaddr.is_null() {
None
} else {
Some(libafl_qemu_sys::qemu_plugin_hwaddr_phys_addr(phwaddr) as GuestPhysAddr)
}
}
}
#[must_use]
pub fn current_paging_id(&self) -> Option<GuestPhysAddr> {
let paging_id = unsafe { libafl_qemu_current_paging_id(self.ptr) };
if paging_id == 0 {
None
} else {
Some(paging_id)
}
}
/// 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]) {
// TODO use gdbstub's target_cpu_memory_rw_debug
libafl_qemu_sys::cpu_memory_rw_debug(
self.ptr,
addr as GuestVirtAddr,
buf.as_ptr() as *mut _,
buf.len(),
true,
);
}
/// 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]) {
// TODO use gdbstub's target_cpu_memory_rw_debug
libafl_qemu_sys::cpu_memory_rw_debug(
self.ptr,
addr as GuestVirtAddr,
buf.as_mut_ptr() as *mut _,
buf.len(),
false,
);
}
}
#[allow(clippy::unused_self)]
impl Qemu {
pub fn guest_page_size(&self) -> usize {
4096
}
/// Write a value to a physical guest address, including ROM areas.
pub unsafe fn write_phys_mem(&self, paddr: GuestPhysAddr, buf: &[u8]) {
libafl_qemu_sys::cpu_physical_memory_rw(
paddr,
buf.as_ptr() as *mut _,
buf.len() as u64,
true,
);
}
/// Read a value from a physical guest address.
pub unsafe fn read_phys_mem(&self, paddr: GuestPhysAddr, buf: &mut [u8]) {
libafl_qemu_sys::cpu_physical_memory_rw(
paddr,
buf.as_mut_ptr() as *mut _,
buf.len() as u64,
false,
);
}
/// This function will run the emulator until the next breakpoint / sync exit, or until finish.
/// It is a low-level function and simply kicks QEMU.
/// # Safety
///
/// 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) -> Result<QemuExitReason, QemuExitError> {
vm_start();
qemu_main_loop();
self.post_run()
}
pub fn save_snapshot(&self, name: &str, sync: bool) {
let s = CString::new(name).expect("Invalid snapshot name");
unsafe { libafl_save_qemu_snapshot(s.as_ptr() as *const _, sync) };
}
pub fn load_snapshot(&self, name: &str, sync: bool) {
let s = CString::new(name).expect("Invalid snapshot name");
unsafe { libafl_load_qemu_snapshot(s.as_ptr() as *const _, sync) };
}
#[must_use]
pub fn create_fast_snapshot(&self, track: bool) -> FastSnapshotPtr {
unsafe {
libafl_qemu_sys::syx_snapshot_new(
track,
true,
libafl_qemu_sys::DeviceSnapshotKind_DEVICE_SNAPSHOT_ALL,
null_mut(),
)
}
}
#[must_use]
pub fn create_fast_snapshot_filter(
&self,
track: bool,
device_filter: &DeviceSnapshotFilter,
) -> FastSnapshotPtr {
let mut v = vec![];
unsafe {
libafl_qemu_sys::syx_snapshot_new(
track,
true,
device_filter.enum_id(),
device_filter.devices(&mut v),
)
}
}
pub unsafe fn restore_fast_snapshot(&self, snapshot: FastSnapshotPtr) {
libafl_qemu_sys::syx_snapshot_root_restore(snapshot)
}
pub unsafe fn check_fast_snapshot_memory_consistency(&self, snapshot: FastSnapshotPtr) -> u64 {
libafl_qemu_sys::syx_snapshot_check_memory_consistency(snapshot)
}
pub fn list_devices(&self) -> Vec<String> {
let mut r = vec![];
unsafe {
let devices = libafl_qemu_sys::device_list_all();
if devices.is_null() {
return r;
}
let mut ptr = devices;
while !(*ptr).is_null() {
let c_str: &CStr = CStr::from_ptr(*ptr);
let name = c_str.to_str().unwrap().to_string();
r.push(name);
ptr = ptr.add(1);
}
libc::free(devices as *mut c_void);
r
}
}
#[must_use]
pub fn target_page_size(&self) -> usize {
unsafe { libafl_qemu_sys::qemu_target_page_size() }
}
}
impl EmulatorMemoryChunk {
pub fn phys_iter(&self, qemu: Qemu) -> PhysMemoryIter {
PhysMemoryIter {
addr: self.addr,
remaining_len: self.size as usize,
qemu,
cpu: if let Some(cpu) = self.cpu {
cpu
} else {
qemu.current_cpu().unwrap()
},
}
}
pub fn host_iter(&self, qemu: Qemu) -> Box<dyn Iterator<Item = &[u8]>> {
Box::new(
self.phys_iter(qemu)
.map(move |phys_mem_chunk| HostMemoryIter {
addr: phys_mem_chunk.addr,
remaining_len: phys_mem_chunk.size,
qemu,
cpu: phys_mem_chunk.cpu,
phantom: PhantomData,
})
.flatten()
.into_iter(),
)
}
pub fn to_host_segmented_buf(&self, qemu: Qemu) -> SegmentedBuf<&[u8]> {
self.host_iter(qemu).collect()
}
}
impl PhysMemoryChunk {
pub fn new(addr: GuestPhysAddr, size: usize, qemu: Qemu, cpu: CPU) -> Self {
Self {
addr,
size,
qemu,
cpu,
}
}
}
impl<'a> Iterator for HostMemoryIter<'a> {
type Item = &'a [u8];
fn next(&mut self) -> Option<Self::Item> {
if self.remaining_len.is_zero() {
None
} else {
// Host memory allocation is always host-page aligned, so we can freely go from host page to host page.
let start_host_addr: *const u8 =
unsafe { libafl_qemu_sys::libafl_paddr2host(self.cpu.ptr, self.addr, false) };
let host_page_size = Qemu::get().unwrap().host_page_size();
let mut size_taken: usize = std::cmp::min(
(start_host_addr as usize).next_multiple_of(host_page_size),
self.remaining_len,
);
self.remaining_len -= size_taken;
self.addr += size_taken as GuestPhysAddr;
// Now self.addr is host-page aligned
while self.remaining_len > 0 {
let next_page_host_addr: *const u8 =
unsafe { libafl_qemu_sys::libafl_paddr2host(self.cpu.ptr, self.addr, false) };
// Non-contiguous, we stop here for the slice
if next_page_host_addr != start_host_addr {
unsafe { return Some(slice::from_raw_parts(start_host_addr, size_taken)) }
}
// The host memory is contiguous, we can widen the slice up to the next host page
size_taken += std::cmp::min(self.remaining_len, host_page_size);
self.remaining_len -= size_taken;
self.addr += size_taken as GuestPhysAddr;
}
// We finished to explore the memory, return the last slice.
assert_eq!(self.remaining_len, 0);
unsafe { return Some(slice::from_raw_parts(start_host_addr, size_taken)) }
}
}
}
impl Iterator for PhysMemoryIter {
type Item = PhysMemoryChunk;
fn next(&mut self) -> Option<Self::Item> {
if self.remaining_len.is_zero() {
None
} else {
// Physical memory allocation is always physical-page aligned, so we can freely go from host page to host page.
let vaddr = match &mut self.addr {
GuestAddrKind::Virtual(vaddr) => vaddr,
GuestAddrKind::Physical(paddr) => {
let sz = self.remaining_len;
self.remaining_len = 0;
return Some(PhysMemoryChunk::new(*paddr, sz, self.qemu, self.cpu));
}
};
let start_phys_addr: GuestPhysAddr = self.cpu.get_phys_addr(*vaddr)?;
let phys_page_size = self.qemu.guest_page_size();
// TODO: Turn this into a generic function
let mut size_taken: usize = std::cmp::min(
(start_phys_addr as usize).next_multiple_of(phys_page_size),
self.remaining_len,
);
self.remaining_len -= size_taken;
*vaddr += size_taken as GuestPhysAddr;
// Now self.addr is host-page aligned
while self.remaining_len > 0 {
let next_page_phys_addr: GuestPhysAddr = self.cpu.get_phys_addr(*vaddr)?;
// Non-contiguous, we stop here for the slice
if next_page_phys_addr != start_phys_addr {
return Some(PhysMemoryChunk::new(
start_phys_addr,
size_taken,
self.qemu,
self.cpu,
));
}
// The host memory is contiguous, we can widen the slice up to the next host page
size_taken += std::cmp::min(self.remaining_len, phys_page_size);
self.remaining_len -= size_taken;
*vaddr += size_taken as GuestPhysAddr;
}
// We finished to explore the memory, return the last slice.
assert_eq!(self.remaining_len, 0);
Some(PhysMemoryChunk::new(
start_phys_addr,
size_taken,
self.qemu,
self.cpu,
))
}
}
}

View File

@ -0,0 +1,444 @@
use std::{
intrinsics::copy_nonoverlapping, mem::MaybeUninit, slice::from_raw_parts,
str::from_utf8_unchecked,
};
use libafl_qemu_sys::{
exec_path, free_self_maps, guest_base, libafl_dump_core_hook, libafl_force_dfl, libafl_get_brk,
libafl_load_addr, libafl_maps_first, libafl_maps_next, libafl_qemu_run, libafl_set_brk,
mmap_next_start, pageflags_get_root, read_self_maps, strlen, GuestAddr, GuestUsize,
IntervalTreeNode, IntervalTreeRoot, MapInfo, MmapPerms, VerifyAccess,
};
use libc::c_int;
#[cfg(feature = "python")]
use pyo3::{pyclass, pymethods, IntoPy, PyObject, PyRef, PyRefMut, Python};
use crate::{
HookData, NewThreadHookId, PostSyscallHookId, PreSyscallHookId, Qemu, QemuExitError,
QemuExitReason, SyscallHookResult, CPU,
};
#[cfg_attr(feature = "python", pyclass(unsendable))]
pub struct GuestMaps {
self_maps_root: *mut IntervalTreeRoot,
pageflags_node: *mut IntervalTreeNode,
}
// Consider a private new only for Emulator
impl GuestMaps {
#[must_use]
pub(crate) fn new() -> Self {
unsafe {
let pageflags_root = pageflags_get_root();
let self_maps_root = read_self_maps();
let pageflags_first = libafl_maps_first(pageflags_root);
Self {
self_maps_root,
pageflags_node: pageflags_first,
}
}
}
}
impl Iterator for GuestMaps {
type Item = MapInfo;
#[allow(clippy::uninit_assumed_init)]
fn next(&mut self) -> Option<Self::Item> {
unsafe {
let mut ret = MaybeUninit::uninit();
self.pageflags_node =
libafl_maps_next(self.pageflags_node, self.self_maps_root, ret.as_mut_ptr());
let ret = ret.assume_init();
if ret.is_valid {
Some(ret.into())
} else {
None
}
}
}
}
#[cfg(feature = "python")]
#[pymethods]
impl GuestMaps {
fn __iter__(slf: PyRef<Self>) -> PyRef<Self> {
slf
}
fn __next__(mut slf: PyRefMut<Self>) -> Option<PyObject> {
Python::with_gil(|py| slf.next().map(|x| x.into_py(py)))
}
}
impl Drop for GuestMaps {
fn drop(&mut self) {
unsafe {
free_self_maps(self.self_maps_root);
}
}
}
impl CPU {
/// 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 = Qemu::get().unwrap().g2h(addr);
copy_nonoverlapping(buf.as_ptr(), host_addr, buf.len());
}
/// 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 = Qemu::get().unwrap().g2h(addr);
copy_nonoverlapping(host_addr, buf.as_mut_ptr(), buf.len());
}
}
#[allow(clippy::unused_self)]
impl Qemu {
#[must_use]
pub fn mappings(&self) -> GuestMaps {
GuestMaps::new()
}
#[must_use]
pub fn g2h<T>(&self, addr: GuestAddr) -> *mut T {
unsafe { (addr as usize + guest_base) as *mut T }
}
#[must_use]
pub fn h2g<T>(&self, addr: *const T) -> GuestAddr {
unsafe { (addr as usize - guest_base) as GuestAddr }
}
#[must_use]
pub fn access_ok(&self, kind: VerifyAccess, addr: GuestAddr, size: usize) -> bool {
self.current_cpu()
.unwrap_or_else(|| self.cpu_from_index(0))
.access_ok(kind, addr, size)
}
pub fn force_dfl(&self) {
unsafe {
libafl_force_dfl = 1;
}
}
/// This function will run the emulator until the next breakpoint, or until finish.
/// # Safety
///
/// 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) -> Result<QemuExitReason, QemuExitError> {
libafl_qemu_run();
self.post_run()
}
#[must_use]
pub fn binary_path<'a>(&self) -> &'a str {
unsafe { from_utf8_unchecked(from_raw_parts(exec_path, strlen(exec_path))) }
}
#[must_use]
pub fn load_addr(&self) -> GuestAddr {
unsafe { libafl_load_addr() as GuestAddr }
}
#[must_use]
pub fn get_brk(&self) -> GuestAddr {
unsafe { libafl_get_brk() as GuestAddr }
}
pub fn set_brk(&self, brk: GuestAddr) {
unsafe { libafl_set_brk(brk.into()) };
}
#[must_use]
pub fn get_mmap_start(&self) -> GuestAddr {
unsafe { mmap_next_start }
}
pub fn set_mmap_start(&self, start: GuestAddr) {
unsafe { mmap_next_start = start };
}
#[allow(clippy::cast_sign_loss)]
fn mmap(
self,
addr: GuestAddr,
size: usize,
perms: MmapPerms,
flags: c_int,
) -> Result<GuestAddr, ()> {
let res = unsafe {
libafl_qemu_sys::target_mmap(addr, size as GuestUsize, perms.into(), flags, -1, 0)
};
if res <= 0 {
Err(())
} else {
Ok(res as GuestAddr)
}
}
pub fn map_private(
&self,
addr: GuestAddr,
size: usize,
perms: MmapPerms,
) -> Result<GuestAddr, String> {
self.mmap(addr, size, perms, libc::MAP_PRIVATE | libc::MAP_ANONYMOUS)
.map_err(|()| format!("Failed to map {addr}"))
.map(|addr| addr as GuestAddr)
}
pub fn map_fixed(
&self,
addr: GuestAddr,
size: usize,
perms: MmapPerms,
) -> Result<GuestAddr, String> {
self.mmap(
addr,
size,
perms,
libc::MAP_FIXED | libc::MAP_PRIVATE | libc::MAP_ANONYMOUS,
)
.map_err(|()| format!("Failed to map {addr}"))
.map(|addr| addr as GuestAddr)
}
pub fn mprotect(&self, addr: GuestAddr, size: usize, perms: MmapPerms) -> Result<(), String> {
let res = unsafe {
libafl_qemu_sys::target_mprotect(addr.into(), size as GuestUsize, perms.into())
};
if res == 0 {
Ok(())
} else {
Err(format!("Failed to mprotect {addr}"))
}
}
pub fn unmap(&self, addr: GuestAddr, size: usize) -> Result<(), String> {
if unsafe { libafl_qemu_sys::target_munmap(addr.into(), size as GuestUsize) } == 0 {
Ok(())
} else {
Err(format!("Failed to unmap {addr}"))
}
}
#[allow(clippy::type_complexity)]
pub fn add_pre_syscall_hook<T: Into<HookData>>(
&self,
data: T,
callback: extern "C" fn(
T,
i32,
GuestAddr,
GuestAddr,
GuestAddr,
GuestAddr,
GuestAddr,
GuestAddr,
GuestAddr,
GuestAddr,
) -> SyscallHookResult,
) -> PreSyscallHookId {
unsafe {
let data: u64 = data.into().0;
let callback: extern "C" fn(
u64,
i32,
GuestAddr,
GuestAddr,
GuestAddr,
GuestAddr,
GuestAddr,
GuestAddr,
GuestAddr,
GuestAddr,
) -> libafl_qemu_sys::syshook_ret = core::mem::transmute(callback);
let num = libafl_qemu_sys::libafl_add_pre_syscall_hook(Some(callback), data);
PreSyscallHookId(num)
}
}
#[allow(clippy::type_complexity)]
pub fn add_post_syscall_hook<T: Into<HookData>>(
&self,
data: T,
callback: extern "C" fn(
T,
GuestAddr,
i32,
GuestAddr,
GuestAddr,
GuestAddr,
GuestAddr,
GuestAddr,
GuestAddr,
GuestAddr,
GuestAddr,
) -> GuestAddr,
) -> PostSyscallHookId {
unsafe {
let data: u64 = data.into().0;
let callback: extern "C" fn(
u64,
GuestAddr,
i32,
GuestAddr,
GuestAddr,
GuestAddr,
GuestAddr,
GuestAddr,
GuestAddr,
GuestAddr,
GuestAddr,
) -> GuestAddr = core::mem::transmute(callback);
let num = libafl_qemu_sys::libafl_add_post_syscall_hook(Some(callback), data);
PostSyscallHookId(num)
}
}
pub fn add_new_thread_hook<T: Into<HookData>>(
&self,
data: T,
callback: extern "C" fn(T, tid: u32) -> bool,
) -> NewThreadHookId {
unsafe {
let data: u64 = data.into().0;
let callback: extern "C" fn(u64, u32) -> bool = core::mem::transmute(callback);
let num = libafl_qemu_sys::libafl_add_new_thread_hook(Some(callback), data);
NewThreadHookId(num)
}
}
#[allow(clippy::type_complexity)]
pub fn set_crash_hook(&self, callback: extern "C" fn(i32)) {
unsafe {
libafl_dump_core_hook = callback;
}
}
}
#[cfg(feature = "python")]
pub mod pybind {
use libafl_qemu_sys::{GuestAddr, MmapPerms};
use pyo3::{
exceptions::PyValueError, pymethods, types::PyInt, FromPyObject, PyObject, PyResult, Python,
};
use crate::{pybind::Qemu, SyscallHookResult};
static mut PY_SYSCALL_HOOK: Option<PyObject> = None;
extern "C" fn py_syscall_hook_wrapper(
_data: u64,
sys_num: i32,
a0: u64,
a1: u64,
a2: u64,
a3: u64,
a4: u64,
a5: u64,
a6: u64,
a7: u64,
) -> SyscallHookResult {
unsafe { PY_SYSCALL_HOOK.as_ref() }.map_or_else(
|| SyscallHookResult::new(None),
|obj| {
let args = (sys_num, a0, a1, a2, a3, a4, a5, a6, a7);
Python::with_gil(|py| {
let ret = obj.call1(py, args).expect("Error in the syscall hook");
let any = ret.as_ref(py);
if any.is_none() {
SyscallHookResult::new(None)
} else {
let a: Result<&PyInt, _> = any.downcast();
if let Ok(i) = a {
SyscallHookResult::new(Some(
i.extract().expect("Invalid syscall hook return value"),
))
} else {
SyscallHookResult::extract(any)
.expect("The syscall hook must return a SyscallHookResult")
}
}
})
},
)
}
#[pymethods]
impl Qemu {
fn g2h(&self, addr: GuestAddr) -> u64 {
self.qemu.g2h::<*const u8>(addr) as u64
}
fn h2g(&self, addr: u64) -> GuestAddr {
self.qemu.h2g(addr as *const u8)
}
fn binary_path(&self) -> String {
self.qemu.binary_path().to_owned()
}
fn load_addr(&self) -> GuestAddr {
self.qemu.load_addr()
}
fn map_private(&self, addr: GuestAddr, size: usize, perms: i32) -> PyResult<GuestAddr> {
if let Ok(p) = MmapPerms::try_from(perms) {
self.qemu
.map_private(addr, size, p)
.map_err(PyValueError::new_err)
} else {
Err(PyValueError::new_err("Invalid perms"))
}
}
fn map_fixed(&self, addr: GuestAddr, size: usize, perms: i32) -> PyResult<GuestAddr> {
if let Ok(p) = MmapPerms::try_from(perms) {
self.qemu
.map_fixed(addr, size, p)
.map_err(PyValueError::new_err)
} else {
Err(PyValueError::new_err("Invalid perms"))
}
}
fn mprotect(&self, addr: GuestAddr, size: usize, perms: i32) -> PyResult<()> {
if let Ok(p) = MmapPerms::try_from(perms) {
self.qemu
.mprotect(addr, size, p)
.map_err(PyValueError::new_err)
} else {
Err(PyValueError::new_err("Invalid perms"))
}
}
fn unmap(&self, addr: GuestAddr, size: usize) -> PyResult<()> {
self.qemu.unmap(addr, size).map_err(PyValueError::new_err)
}
fn set_syscall_hook(&self, hook: PyObject) {
unsafe {
PY_SYSCALL_HOOK = Some(hook);
}
self.qemu
.add_pre_syscall_hook(0u64, py_syscall_hook_wrapper);
}
}
}

View File

@ -1,40 +1,11 @@
use std::{
fmt::{Display, Formatter},
sync::OnceLock,
};
use std::fmt::{Display, Formatter};
use enum_map::{enum_map, Enum, EnumMap};
use libafl::{
executors::ExitKind,
state::{HasExecutions, State},
};
use libafl_qemu_sys::{GuestAddr, GuestPhysAddr, GuestVirtAddr};
use num_enum::TryFromPrimitiveError;
use enum_map::Enum;
use crate::{
command::{
Command, EmulatorMemoryChunk, EndCommand, FilterCommand, InputCommand, LoadCommand,
NativeBackdoorCommand, NativeExitKind, SaveCommand, StartCommand, VersionCommand,
},
get_backdoor_arch_regs, EmuExitHandler, Emulator, GuestReg, QemuHelperTuple,
QemuInstrumentationAddressRangeFilter, Regs, CPU,
};
#[derive(Debug, Clone)]
pub enum SyncBackdoorError {
UnknownCommand(GuestReg),
RegError(String),
VersionDifference(u64),
}
impl From<String> for SyncBackdoorError {
fn from(error_string: String) -> Self {
SyncBackdoorError::RegError(error_string)
}
}
use crate::{command::Command, get_exit_arch_regs, GuestReg, Regs, CPU};
#[derive(Debug, Clone, Enum)]
pub enum BackdoorArgs {
pub enum ExitArgs {
Ret,
Cmd,
Arg1,
@ -45,177 +16,35 @@ pub enum BackdoorArgs {
Arg6,
}
static EMU_EXIT_KIND_MAP: OnceLock<EnumMap<NativeExitKind, Option<ExitKind>>> = OnceLock::new();
impl From<TryFromPrimitiveError<NativeBackdoorCommand>> for SyncBackdoorError {
fn from(error: TryFromPrimitiveError<NativeBackdoorCommand>) -> Self {
SyncBackdoorError::UnknownCommand(error.number.try_into().unwrap())
}
}
#[derive(Debug, Clone)]
pub struct SyncBackdoor {
pub struct SyncExit {
command: Command,
arch_regs_map: &'static EnumMap<BackdoorArgs, Regs>,
}
impl SyncBackdoor {
impl SyncExit {
#[must_use]
pub fn new(command: Command) -> Self {
Self { command }
}
#[must_use]
pub fn command(&self) -> &Command {
&self.command
}
pub fn ret(&self, cpu: &CPU, value: GuestReg) -> Result<(), SyncBackdoorError> {
Ok(cpu.write_reg(self.arch_regs_map[BackdoorArgs::Ret], value)?)
pub fn ret(&self, cpu: &CPU, value: GuestReg) {
cpu.write_reg(get_exit_arch_regs()[ExitArgs::Ret], value)
.unwrap();
}
#[must_use]
pub fn ret_reg(&self) -> Regs {
self.arch_regs_map[BackdoorArgs::Ret]
get_exit_arch_regs()[ExitArgs::Ret]
}
}
impl Display for SyncBackdoor {
impl Display for SyncExit {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.command)
}
}
impl<QT, S, E> TryFrom<&Emulator<QT, S, E>> for SyncBackdoor
where
E: EmuExitHandler<QT, S>,
QT: QemuHelperTuple<S>,
S: State + HasExecutions,
{
type Error = SyncBackdoorError;
#[allow(clippy::too_many_lines)]
fn try_from(emu: &Emulator<QT, S, E>) -> Result<Self, Self::Error> {
let arch_regs_map: &'static EnumMap<BackdoorArgs, Regs> = get_backdoor_arch_regs();
let cmd_id: GuestReg = emu
.qemu()
.read_reg::<Regs, GuestReg>(arch_regs_map[BackdoorArgs::Cmd])?;
Ok(match u64::from(cmd_id).try_into()? {
NativeBackdoorCommand::Save => SyncBackdoor {
command: Command::SaveCommand(SaveCommand),
arch_regs_map,
},
NativeBackdoorCommand::Load => SyncBackdoor {
command: Command::LoadCommand(LoadCommand),
arch_regs_map,
},
NativeBackdoorCommand::InputVirt => {
let virt_addr: GuestVirtAddr =
emu.qemu().read_reg(arch_regs_map[BackdoorArgs::Arg1])?;
let max_input_size: GuestReg =
emu.qemu().read_reg(arch_regs_map[BackdoorArgs::Arg2])?;
SyncBackdoor {
command: Command::InputCommand(InputCommand::new(
EmulatorMemoryChunk::virt(
virt_addr,
max_input_size,
emu.qemu().current_cpu().unwrap().clone(),
),
emu.qemu().current_cpu().unwrap(),
)),
arch_regs_map,
}
}
NativeBackdoorCommand::InputPhys => {
let phys_addr: GuestPhysAddr =
emu.qemu().read_reg(arch_regs_map[BackdoorArgs::Arg1])?;
let max_input_size: GuestReg =
emu.qemu().read_reg(arch_regs_map[BackdoorArgs::Arg2])?;
SyncBackdoor {
command: Command::InputCommand(InputCommand::new(
EmulatorMemoryChunk::phys(
phys_addr,
max_input_size,
Some(emu.qemu().current_cpu().unwrap().clone()),
),
emu.qemu().current_cpu().unwrap(),
)),
arch_regs_map,
}
}
NativeBackdoorCommand::End => {
let native_exit_kind: GuestReg =
emu.qemu().read_reg(arch_regs_map[BackdoorArgs::Arg1])?;
let native_exit_kind: Result<NativeExitKind, _> =
u64::from(native_exit_kind).try_into();
let exit_kind = native_exit_kind.ok().and_then(|k| {
EMU_EXIT_KIND_MAP.get_or_init(|| {
enum_map! {
NativeExitKind::Unknown => None,
NativeExitKind::Ok => Some(ExitKind::Ok),
NativeExitKind::Crash => Some(ExitKind::Crash)
}
})[k]
});
SyncBackdoor {
command: Command::EndCommand(EndCommand::new(exit_kind)),
arch_regs_map,
}
}
NativeBackdoorCommand::StartPhys => {
let input_phys_addr: GuestPhysAddr =
emu.qemu().read_reg(arch_regs_map[BackdoorArgs::Arg1])?;
let max_input_size: GuestReg =
emu.qemu().read_reg(arch_regs_map[BackdoorArgs::Arg2])?;
SyncBackdoor {
command: Command::StartCommand(StartCommand::new(EmulatorMemoryChunk::phys(
input_phys_addr,
max_input_size,
Some(emu.qemu().current_cpu().unwrap().clone()),
))),
arch_regs_map,
}
}
NativeBackdoorCommand::StartVirt => {
let input_virt_addr: GuestVirtAddr =
emu.qemu().read_reg(arch_regs_map[BackdoorArgs::Arg1])?;
let max_input_size: GuestReg =
emu.qemu().read_reg(arch_regs_map[BackdoorArgs::Arg2])?;
SyncBackdoor {
command: Command::StartCommand(StartCommand::new(EmulatorMemoryChunk::virt(
input_virt_addr,
max_input_size,
emu.qemu().current_cpu().unwrap().clone(),
))),
arch_regs_map,
}
}
NativeBackdoorCommand::Version => {
let client_version = emu.qemu().read_reg(arch_regs_map[BackdoorArgs::Arg1])?;
SyncBackdoor {
command: Command::VersionCommand(VersionCommand::new(client_version)),
arch_regs_map,
}
}
NativeBackdoorCommand::VaddrFilterAllowRange => {
let vaddr_start: GuestAddr =
emu.qemu().read_reg(arch_regs_map[BackdoorArgs::Arg1])?;
let vaddr_end: GuestAddr =
emu.qemu().read_reg(arch_regs_map[BackdoorArgs::Arg2])?;
SyncBackdoor {
command: Command::AddressRangeFilterCommand(FilterCommand::new(
#[allow(clippy::single_range_in_vec_init)]
QemuInstrumentationAddressRangeFilter::AllowList(vec![
vaddr_start..vaddr_end,
]),
)),
arch_regs_map,
}
}
})
}
}

View File

@ -35,7 +35,7 @@ use libafl_bolts::{
tuples::{tuple_list, Handler, Merge},
AsSlice,
};
pub use libafl_qemu::emu::Qemu;
pub use libafl_qemu::qemu::Qemu;
#[cfg(not(any(feature = "mips", feature = "hexagon")))]
use libafl_qemu::QemuCmpLogHelper;
use libafl_qemu::{edges, QemuEdgeCoverageHelper, QemuExecutor, QemuHooks};
@ -454,7 +454,7 @@ pub mod pybind {
use std::path::PathBuf;
use libafl_bolts::core_affinity::Cores;
use libafl_qemu::emu::pybind::Qemu;
use libafl_qemu::qemu::pybind::Qemu;
use pyo3::{prelude::*, types::PyBytes};
use crate::qemu;