Generic QEMU snapshot checking (#2240)

* generic snapshot checking.
This commit is contained in:
Romain Malmain 2024-05-23 22:57:13 +02:00 committed by GitHub
parent 1fafaf6454
commit 5fbe2415e1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 109 additions and 27 deletions

View File

@ -90,7 +90,7 @@ pub fn fuzz() {
// Choose Snapshot Builder // Choose Snapshot Builder
// let emu_snapshot_manager = QemuSnapshotBuilder::new(true); // let emu_snapshot_manager = QemuSnapshotBuilder::new(true);
let emu_snapshot_manager = FastSnapshotManager::new(false); let emu_snapshot_manager = FastSnapshotManager::new();
// Choose Exit Handler // Choose Exit Handler
let emu_exit_handler = StdEmulatorExitHandler::new(emu_snapshot_manager); let emu_exit_handler = StdEmulatorExitHandler::new(emu_snapshot_manager);

View File

@ -55,7 +55,7 @@ pub fn fuzz() {
let env: Vec<(String, String)> = env::vars().collect(); let env: Vec<(String, String)> = env::vars().collect();
// let emu_snapshot_manager = QemuSnapshotBuilder::new(true); // 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_snapshot_manager = FastSnapshotManager::new(); // Create a snapshot manager (normal or fast for now).
let emu_exit_handler: StdEmulatorExitHandler<FastSnapshotManager> = 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. StdEmulatorExitHandler::new(emu_snapshot_manager); // Create an exit handler: it is the entity taking the decision of what should be done when QEMU returns.

View File

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

View File

@ -317,6 +317,11 @@ where
.snapshot_manager_borrow_mut() .snapshot_manager_borrow_mut()
.restore(&snapshot_id, qemu)?; .restore(&snapshot_id, qemu)?;
#[cfg(feature = "paranoid_debug")]
emu_exit_handler
.snapshot_manager_borrow()
.check(&snapshot_id, emu.qemu())?;
Ok(None) Ok(None)
} }
} }
@ -445,6 +450,11 @@ where
.snapshot_manager_borrow_mut() .snapshot_manager_borrow_mut()
.restore(&snapshot_id, emu.qemu())?; .restore(&snapshot_id, emu.qemu())?;
#[cfg(feature = "paranoid_debug")]
emu_exit_handler
.snapshot_manager_borrow()
.check(&snapshot_id, emu.qemu())?;
Ok(Some(ExitHandlerResult::EndOfRun(self.0.unwrap()))) Ok(Some(ExitHandlerResult::EndOfRun(self.0.unwrap())))
} }
} }

View File

@ -35,7 +35,8 @@ use crate::{
sys::TCGTemp, sys::TCGTemp,
BackdoorHookId, BlockHookId, CmpHookId, EdgeHookId, EmulatorMemoryChunk, GuestReg, HookData, BackdoorHookId, BlockHookId, CmpHookId, EdgeHookId, EmulatorMemoryChunk, GuestReg, HookData,
HookId, InstructionHookId, MemAccessInfo, Qemu, QemuExitError, QemuExitReason, QemuHelperTuple, HookId, InstructionHookId, MemAccessInfo, Qemu, QemuExitError, QemuExitReason, QemuHelperTuple,
QemuInitError, QemuShutdownCause, ReadHookId, Regs, StdInstrumentationFilter, WriteHookId, CPU, QemuInitError, QemuShutdownCause, QemuSnapshotCheckResult, ReadHookId, Regs,
StdInstrumentationFilter, WriteHookId, CPU,
}; };
#[cfg(emulation_mode = "usermode")] #[cfg(emulation_mode = "usermode")]
@ -96,6 +97,7 @@ where
pub enum ExitHandlerError { pub enum ExitHandlerError {
QemuExitReasonError(EmulatorExitError), QemuExitReasonError(EmulatorExitError),
SMError(SnapshotManagerError), SMError(SnapshotManagerError),
SMCheckError(SnapshotManagerCheckError),
CommandError(CommandError), CommandError(CommandError),
UnhandledSignal(Signal), UnhandledSignal(Signal),
MultipleSnapshotDefinition, MultipleSnapshotDefinition,
@ -109,6 +111,12 @@ pub enum SnapshotManagerError {
MemoryInconsistencies(u64), MemoryInconsistencies(u64),
} }
#[derive(Debug, Clone)]
pub enum SnapshotManagerCheckError {
SnapshotManagerError(SnapshotManagerError),
SnapshotCheckError(QemuSnapshotCheckResult),
}
impl<CM, E, QT, S> TryFrom<ExitHandlerResult<CM, E, QT, S>> for ExitKind impl<CM, E, QT, S> TryFrom<ExitHandlerResult<CM, E, QT, S>> for ExitKind
where where
CM: CommandManager<E, QT, S> + Debug, CM: CommandManager<E, QT, S> + Debug,
@ -163,6 +171,12 @@ impl From<SnapshotManagerError> for ExitHandlerError {
} }
} }
impl From<SnapshotManagerCheckError> for ExitHandlerError {
fn from(sm_check_error: SnapshotManagerCheckError) -> Self {
ExitHandlerError::SMCheckError(sm_check_error)
}
}
#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)] #[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)]
pub struct SnapshotId { pub struct SnapshotId {
id: u64, id: u64,
@ -175,6 +189,27 @@ pub trait IsSnapshotManager: Debug + Clone {
snapshot_id: &SnapshotId, snapshot_id: &SnapshotId,
qemu: &Qemu, qemu: &Qemu,
) -> Result<(), SnapshotManagerError>; ) -> Result<(), SnapshotManagerError>;
fn do_check(
&self,
reference_snapshot_id: &SnapshotId,
qemu: &Qemu,
) -> Result<QemuSnapshotCheckResult, SnapshotManagerError>;
fn check(
&self,
reference_snapshot_id: &SnapshotId,
qemu: &Qemu,
) -> Result<(), SnapshotManagerCheckError> {
let check_result = self
.do_check(reference_snapshot_id, qemu)
.map_err(SnapshotManagerCheckError::SnapshotManagerError)?;
if check_result == QemuSnapshotCheckResult::default() {
Ok(())
} else {
Err(SnapshotManagerCheckError::SnapshotCheckError(check_result))
}
}
} }
// TODO: Rework with generics for command handlers? // TODO: Rework with generics for command handlers?

View File

@ -9,7 +9,8 @@ use libafl_qemu_sys::GuestPhysAddr;
use crate::{ use crate::{
command::CommandManager, emu::IsSnapshotManager, DeviceSnapshotFilter, Emulator, command::CommandManager, emu::IsSnapshotManager, DeviceSnapshotFilter, Emulator,
EmulatorExitHandler, Qemu, QemuHelperTuple, SnapshotId, SnapshotManagerError, EmulatorExitHandler, Qemu, QemuHelperTuple, QemuSnapshotCheckResult, SnapshotId,
SnapshotManagerError,
}; };
impl SnapshotId { impl SnapshotId {
@ -50,6 +51,17 @@ impl IsSnapshotManager for SnapshotManager {
SnapshotManager::Fast(fast_sm) => fast_sm.restore(snapshot_id, qemu), SnapshotManager::Fast(fast_sm) => fast_sm.restore(snapshot_id, qemu),
} }
} }
fn do_check(
&self,
reference_snapshot_id: &SnapshotId,
qemu: &Qemu,
) -> Result<QemuSnapshotCheckResult, SnapshotManagerError> {
match self {
SnapshotManager::Qemu(qemu_sm) => qemu_sm.do_check(reference_snapshot_id, qemu),
SnapshotManager::Fast(fast_sm) => fast_sm.do_check(reference_snapshot_id, qemu),
}
}
} }
pub type FastSnapshotPtr = *mut libafl_qemu_sys::SyxSnapshot; pub type FastSnapshotPtr = *mut libafl_qemu_sys::SyxSnapshot;
@ -57,20 +69,18 @@ pub type FastSnapshotPtr = *mut libafl_qemu_sys::SyxSnapshot;
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct FastSnapshotManager { pub struct FastSnapshotManager {
snapshots: HashMap<SnapshotId, FastSnapshotPtr>, snapshots: HashMap<SnapshotId, FastSnapshotPtr>,
check_memory_consistency: bool,
} }
impl Default for FastSnapshotManager { impl Default for FastSnapshotManager {
fn default() -> Self { fn default() -> Self {
Self::new(false) Self::new()
} }
} }
impl FastSnapshotManager { impl FastSnapshotManager {
pub fn new(check_memory_consistency: bool) -> Self { pub fn new() -> Self {
Self { Self {
snapshots: HashMap::new(), snapshots: HashMap::new(),
check_memory_consistency,
} }
} }
@ -112,6 +122,15 @@ impl IsSnapshotManager for QemuSnapshotManager {
qemu.load_snapshot(self.snapshot_id_to_name(snapshot_id).as_str(), self.is_sync); qemu.load_snapshot(self.snapshot_id_to_name(snapshot_id).as_str(), self.is_sync);
Ok(()) Ok(())
} }
fn do_check(
&self,
_reference_snapshot_id: &SnapshotId,
_qemu: &Qemu,
) -> Result<QemuSnapshotCheckResult, SnapshotManagerError> {
// We consider the qemu implementation to be 'ideal' for now.
Ok(QemuSnapshotCheckResult::default())
}
} }
impl IsSnapshotManager for FastSnapshotManager { impl IsSnapshotManager for FastSnapshotManager {
@ -136,19 +155,20 @@ impl IsSnapshotManager for FastSnapshotManager {
qemu.restore_fast_snapshot(fast_snapshot_ptr); qemu.restore_fast_snapshot(fast_snapshot_ptr);
} }
if self.check_memory_consistency {
let nb_inconsistencies =
unsafe { qemu.check_fast_snapshot_memory_consistency(fast_snapshot_ptr) };
if nb_inconsistencies > 0 {
return Err(SnapshotManagerError::MemoryInconsistencies(
nb_inconsistencies,
));
}
}
Ok(()) Ok(())
} }
fn do_check(
&self,
reference_snapshot_id: &SnapshotId,
qemu: &Qemu,
) -> Result<QemuSnapshotCheckResult, SnapshotManagerError> {
let fast_snapshot_ptr = *self.snapshots.get(reference_snapshot_id).ok_or(
SnapshotManagerError::SnapshotIdNotFound(*reference_snapshot_id),
)?;
unsafe { Ok(qemu.check_fast_snapshot(fast_snapshot_ptr)) }
}
} }
impl<CM, E, QT, S> Emulator<CM, E, QT, S> impl<CM, E, QT, S> Emulator<CM, E, QT, S>
@ -194,10 +214,6 @@ where
self.qemu.restore_fast_snapshot(snapshot) self.qemu.restore_fast_snapshot(snapshot)
} }
pub unsafe fn check_fast_snapshot_memory_consistency(&self, snapshot: FastSnapshotPtr) -> u64 {
self.qemu.check_fast_snapshot_memory_consistency(snapshot)
}
pub fn list_devices(&self) -> Vec<String> { pub fn list_devices(&self) -> Vec<String> {
self.qemu.list_devices() self.qemu.list_devices()
} }

View File

@ -106,6 +106,20 @@ pub enum QemuExitError {
UnexpectedExit, // Qemu exited without going through an expected exit point. Can be caused by a crash for example. UnexpectedExit, // Qemu exited without going through an expected exit point. Can be caused by a crash for example.
} }
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct QemuSnapshotCheckResult {
nb_page_inconsistencies: u64,
}
/// Represents a QEMU snapshot check result for which no error was detected
impl Default for QemuSnapshotCheckResult {
fn default() -> Self {
Self {
nb_page_inconsistencies: 0,
}
}
}
/// The thin wrapper around QEMU. /// The thin wrapper around QEMU.
/// It is considered unsafe to use it directly. /// It is considered unsafe to use it directly.
/// Prefer using `Emulator` instead in case of doubt. /// Prefer using `Emulator` instead in case of doubt.

View File

@ -16,7 +16,7 @@ use num_traits::Zero;
use crate::{ use crate::{
EmulatorMemoryChunk, FastSnapshotPtr, GuestAddrKind, MemAccessInfo, Qemu, QemuExitError, EmulatorMemoryChunk, FastSnapshotPtr, GuestAddrKind, MemAccessInfo, Qemu, QemuExitError,
QemuExitReason, CPU, QemuExitReason, QemuSnapshotCheckResult, CPU,
}; };
pub(super) extern "C" fn qemu_cleanup_atexit() { pub(super) extern "C" fn qemu_cleanup_atexit() {
@ -256,8 +256,15 @@ impl Qemu {
libafl_qemu_sys::syx_snapshot_root_restore(snapshot) libafl_qemu_sys::syx_snapshot_root_restore(snapshot)
} }
pub unsafe fn check_fast_snapshot_memory_consistency(&self, snapshot: FastSnapshotPtr) -> u64 { pub unsafe fn check_fast_snapshot(
libafl_qemu_sys::syx_snapshot_check_memory_consistency(snapshot) &self,
ref_snapshot: FastSnapshotPtr,
) -> QemuSnapshotCheckResult {
let check_result = libafl_qemu_sys::syx_snapshot_check(ref_snapshot);
QemuSnapshotCheckResult {
nb_page_inconsistencies: check_result.nb_inconsistencies,
}
} }
pub fn list_devices(&self) -> Vec<String> { pub fn list_devices(&self) -> Vec<String> {