Added state restorer testcase, fixed restorer (#240)
* added state restorer testcase * fixed testcase * no_std, clippy * printing less often
This commit is contained in:
parent
ff589d9a89
commit
5542a81e12
@ -1,6 +1,5 @@
|
|||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
use libafl::inputs::{BytesInput, HasTargetBytes};
|
|
||||||
use libafl::{
|
use libafl::{
|
||||||
bolts::{current_nanos, rands::StdRand, tuples::tuple_list},
|
bolts::{current_nanos, rands::StdRand, tuples::tuple_list},
|
||||||
corpus::{InMemoryCorpus, OnDiskCorpus, QueueCorpusScheduler},
|
corpus::{InMemoryCorpus, OnDiskCorpus, QueueCorpusScheduler},
|
||||||
@ -9,6 +8,7 @@ use libafl::{
|
|||||||
feedbacks::{CrashFeedback, MapFeedbackState, MaxMapFeedback},
|
feedbacks::{CrashFeedback, MapFeedbackState, MaxMapFeedback},
|
||||||
fuzzer::{Fuzzer, StdFuzzer},
|
fuzzer::{Fuzzer, StdFuzzer},
|
||||||
generators::RandPrintablesGenerator,
|
generators::RandPrintablesGenerator,
|
||||||
|
inputs::{BytesInput, HasTargetBytes},
|
||||||
mutators::scheduled::{havoc_mutations, StdScheduledMutator},
|
mutators::scheduled::{havoc_mutations, StdScheduledMutator},
|
||||||
observers::StdMapObserver,
|
observers::StdMapObserver,
|
||||||
stages::mutational::StdMutationalStage,
|
stages::mutational::StdMutationalStage,
|
||||||
|
@ -802,10 +802,20 @@ where
|
|||||||
/// Waits for this sender to be save to unmap.
|
/// Waits for this sender to be save to unmap.
|
||||||
/// If a receiver is involved, this function should always be called.
|
/// If a receiver is involved, this function should always be called.
|
||||||
pub fn await_save_to_unmap_blocking(&self) {
|
pub fn await_save_to_unmap_blocking(&self) {
|
||||||
|
#[cfg(feature = "std")]
|
||||||
|
let mut ctr = 0_u16;
|
||||||
loop {
|
loop {
|
||||||
if self.save_to_unmap() {
|
if self.save_to_unmap() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
// We log that we're looping -> see when we're blocking.
|
||||||
|
#[cfg(feature = "std")]
|
||||||
|
{
|
||||||
|
ctr = ctr.wrapping_add(1);
|
||||||
|
if ctr == 0 {
|
||||||
|
println!("Awaiting save_to_unmap_blocking");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -819,6 +829,14 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// For debug purposes: Mark save to unmap, even though it might not have been read by a receiver yet.
|
||||||
|
/// # Safety
|
||||||
|
/// If this method is called, the page may be unmapped before it is read by any receiver.
|
||||||
|
pub unsafe fn mark_save_to_unmap(&mut self) {
|
||||||
|
// No need to do this volatile, as we should be the same thread in this scenario.
|
||||||
|
(*self.out_maps.last_mut().unwrap().page_mut()).save_to_unmap = 1;
|
||||||
|
}
|
||||||
|
|
||||||
/// Reattach to a vacant `out_map`.
|
/// Reattach to a vacant `out_map`.
|
||||||
/// It is essential, that the receiver (or someone else) keeps a pointer to this map
|
/// It is essential, that the receiver (or someone else) keeps a pointer to this map
|
||||||
/// else reattach will get a new, empty page, from the OS, or fail.
|
/// else reattach will get a new, empty page, from the OS, or fail.
|
||||||
@ -2431,6 +2449,16 @@ where
|
|||||||
self.sender.save_to_unmap()
|
self.sender.save_to_unmap()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// For debug purposes: mark the client as save to unmap, even though it might not have been read.
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
/// This should only be called in a debug scenario.
|
||||||
|
/// Calling this in other contexts may lead to a premature page unmap and result in a crash in another process,
|
||||||
|
/// or an unexpected read from an empty page in a receiving process.
|
||||||
|
pub unsafe fn mark_save_to_unmap(&mut self) {
|
||||||
|
self.sender.mark_save_to_unmap();
|
||||||
|
}
|
||||||
|
|
||||||
/// Creates a new [`LlmpClient`]
|
/// Creates a new [`LlmpClient`]
|
||||||
pub fn new(
|
pub fn new(
|
||||||
mut shmem_provider: SP,
|
mut shmem_provider: SP,
|
||||||
|
@ -46,7 +46,10 @@ where
|
|||||||
|
|
||||||
/// Create a [`StateRrestore`] from `env` variable name
|
/// Create a [`StateRrestore`] from `env` variable name
|
||||||
pub fn from_env(shmem_provider: &mut SP, env_name: &str) -> Result<Self, Error> {
|
pub fn from_env(shmem_provider: &mut SP, env_name: &str) -> Result<Self, Error> {
|
||||||
Ok(Self::new(shmem_provider.existing_from_env(env_name)?))
|
Ok(Self {
|
||||||
|
shmem: shmem_provider.existing_from_env(env_name)?,
|
||||||
|
phantom: PhantomData,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a new [`StateRestorer`].
|
/// Create a new [`StateRestorer`].
|
||||||
@ -83,7 +86,17 @@ where
|
|||||||
|
|
||||||
// write the filename to shmem
|
// write the filename to shmem
|
||||||
let filename_buf = postcard::to_allocvec(&filename)?;
|
let filename_buf = postcard::to_allocvec(&filename)?;
|
||||||
|
|
||||||
let len = filename_buf.len();
|
let len = filename_buf.len();
|
||||||
|
if len > self.shmem.len() {
|
||||||
|
return Err(Error::IllegalState(format!(
|
||||||
|
"The state restorer map is too small to fit anything, even the filename!
|
||||||
|
It needs to be at least {} bytes.
|
||||||
|
The tmpfile was written to {:?}.",
|
||||||
|
len,
|
||||||
|
temp_dir().join(&filename)
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
|
||||||
/*println!(
|
/*println!(
|
||||||
"Storing {} bytes to tmpfile {} (larger than map of {} bytes)",
|
"Storing {} bytes to tmpfile {} (larger than map of {} bytes)",
|
||||||
@ -134,16 +147,19 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The content is either the name of the tmpfile, or the serialized bytes directly, if they fit on a single page.
|
||||||
fn content(&self) -> &StateShMemContent {
|
fn content(&self) -> &StateShMemContent {
|
||||||
#[allow(clippy::cast_ptr_alignment)] // Beginning of the page will always be aligned
|
#[allow(clippy::cast_ptr_alignment)] // Beginning of the page will always be aligned
|
||||||
let ptr = self.shmem.map().as_ptr() as *const StateShMemContent;
|
let ptr = self.shmem.map().as_ptr() as *const StateShMemContent;
|
||||||
unsafe { &*(ptr) }
|
unsafe { &*(ptr) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns true, if this [`StateRestorer`] has contents.
|
||||||
pub fn has_content(&self) -> bool {
|
pub fn has_content(&self) -> bool {
|
||||||
self.content().buf_len > 0
|
self.content().buf_len > 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Restores the contents saved in this [`StateRestorer`], if any are availiable.
|
||||||
pub fn restore<S>(&self) -> Result<Option<S>, Error>
|
pub fn restore<S>(&self) -> Result<Option<S>, Error>
|
||||||
where
|
where
|
||||||
S: DeserializeOwned,
|
S: DeserializeOwned,
|
||||||
|
@ -892,7 +892,7 @@ where
|
|||||||
// If we're restarting, deserialize the old state.
|
// If we're restarting, deserialize the old state.
|
||||||
let (state, mut mgr) = if let Some((state, mgr_description)) = staterestorer.restore()? {
|
let (state, mut mgr) = if let Some((state, mgr_description)) = staterestorer.restore()? {
|
||||||
(
|
(
|
||||||
state,
|
Some(state),
|
||||||
LlmpRestartingEventManager::new(
|
LlmpRestartingEventManager::new(
|
||||||
LlmpEventManager::existing_client_from_description(
|
LlmpEventManager::existing_client_from_description(
|
||||||
new_shmem_provider,
|
new_shmem_provider,
|
||||||
@ -925,3 +925,111 @@ where
|
|||||||
Ok((state, mgr))
|
Ok((state, mgr))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
#[cfg(feature = "std")]
|
||||||
|
mod tests {
|
||||||
|
use crate::{
|
||||||
|
bolts::{
|
||||||
|
llmp::{LlmpClient, LlmpSharedMap},
|
||||||
|
rands::StdRand,
|
||||||
|
shmem::{ShMemProvider, StdShMemProvider},
|
||||||
|
staterestore::StateRestorer,
|
||||||
|
tuples::tuple_list,
|
||||||
|
},
|
||||||
|
corpus::{Corpus, InMemoryCorpus, RandCorpusScheduler, Testcase},
|
||||||
|
events::{llmp::_ENV_FUZZER_SENDER, LlmpEventManager},
|
||||||
|
executors::{ExitKind, InProcessExecutor},
|
||||||
|
inputs::BytesInput,
|
||||||
|
mutators::BitFlipMutator,
|
||||||
|
stages::StdMutationalStage,
|
||||||
|
state::StdState,
|
||||||
|
Fuzzer, StdFuzzer,
|
||||||
|
};
|
||||||
|
use core::sync::atomic::{compiler_fence, Ordering};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_mgr_state_restore() {
|
||||||
|
let rand = StdRand::with_seed(0);
|
||||||
|
|
||||||
|
let mut corpus = InMemoryCorpus::<BytesInput>::new();
|
||||||
|
let testcase = Testcase::new(vec![0; 4]);
|
||||||
|
corpus.add(testcase).unwrap();
|
||||||
|
|
||||||
|
let solutions = InMemoryCorpus::<BytesInput>::new();
|
||||||
|
|
||||||
|
let mut state = StdState::new(rand, corpus, solutions, tuple_list!());
|
||||||
|
|
||||||
|
let mut shmem_provider = StdShMemProvider::new().unwrap();
|
||||||
|
|
||||||
|
let mut llmp_client = LlmpClient::new(
|
||||||
|
shmem_provider.clone(),
|
||||||
|
LlmpSharedMap::new(0, shmem_provider.new_map(1024).unwrap()),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// A little hack for CI. Don't do that in a real-world scenario.
|
||||||
|
unsafe {
|
||||||
|
llmp_client.mark_save_to_unmap();
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut llmp_mgr =
|
||||||
|
LlmpEventManager::<BytesInput, (), _, _>::new(llmp_client, "fuzzer".to_string())
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let scheduler = RandCorpusScheduler::new();
|
||||||
|
|
||||||
|
let mut fuzzer = StdFuzzer::new(scheduler, (), ());
|
||||||
|
|
||||||
|
let mut harness = |_buf: &BytesInput| ExitKind::Ok;
|
||||||
|
let mut executor = InProcessExecutor::new(
|
||||||
|
&mut harness,
|
||||||
|
tuple_list!(),
|
||||||
|
&mut fuzzer,
|
||||||
|
&mut state,
|
||||||
|
&mut llmp_mgr,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let mutator = BitFlipMutator::new();
|
||||||
|
let mut stages = tuple_list!(StdMutationalStage::new(mutator));
|
||||||
|
|
||||||
|
// First, create a channel from the current fuzzer to the next to store state between restarts.
|
||||||
|
let mut staterestorer = StateRestorer::<StdShMemProvider>::new(
|
||||||
|
shmem_provider.new_map(256 * 1024 * 1024).unwrap(),
|
||||||
|
);
|
||||||
|
|
||||||
|
staterestorer.reset();
|
||||||
|
staterestorer
|
||||||
|
.save(&(&mut state, &llmp_mgr.describe().unwrap()))
|
||||||
|
.unwrap();
|
||||||
|
assert!(staterestorer.has_content());
|
||||||
|
|
||||||
|
// Store the information to a map.
|
||||||
|
staterestorer.write_to_env(_ENV_FUZZER_SENDER).unwrap();
|
||||||
|
|
||||||
|
compiler_fence(Ordering::SeqCst);
|
||||||
|
|
||||||
|
let sc_cpy = StateRestorer::from_env(&mut shmem_provider, _ENV_FUZZER_SENDER).unwrap();
|
||||||
|
assert!(sc_cpy.has_content());
|
||||||
|
|
||||||
|
let (mut state_clone, mgr_description) = staterestorer.restore().unwrap().unwrap();
|
||||||
|
let mut llmp_clone = LlmpEventManager::existing_client_from_description(
|
||||||
|
shmem_provider,
|
||||||
|
&mgr_description,
|
||||||
|
"fuzzer".to_string(),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
if false {
|
||||||
|
fuzzer
|
||||||
|
.fuzz_one(
|
||||||
|
&mut stages,
|
||||||
|
&mut executor,
|
||||||
|
&mut state_clone,
|
||||||
|
&mut llmp_clone,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user