From 7b0039606b0c58435fa8cc53471d75fd13f1dca3 Mon Sep 17 00:00:00 2001 From: Andrea Fioraldi Date: Tue, 22 Nov 2022 10:33:15 +0100 Subject: [PATCH] Forksrv adaptive map size and AFL++ CmpLog support (#896) * AFL++ cmplog map * map size opt in forkserver * MapObserver::downsize_map and adaptive map size in forkserver * fix fokserver_simple cmd opts * clippy * fuzzbench forkserver with cmplog * delete makefile in fuzzbench forkserver * fuzzbench_forkserver is persistent * ForkserverExecutorBuilder::build_dynamic_map * fix * clippy * fix * fix macos * fix compilation * fix bugs * fixes Co-authored-by: Dominik Maier Co-authored-by: Dominik Maier --- .github/workflows/build_and_test.yml | 5 +- fuzzers/forkserver_simple/build.rs | 36 ++- fuzzers/forkserver_simple/src/main.rs | 42 +-- fuzzers/fuzzbench_forkserver/.gitignore | 2 + fuzzers/fuzzbench_forkserver/Cargo.toml | 20 ++ fuzzers/fuzzbench_forkserver/src/main.rs | 384 +++++++++++++++++++++++ libafl/Cargo.toml | 1 + libafl/src/bolts/ownedref.rs | 86 +++++ libafl/src/executors/forkserver.rs | 203 +++++++++--- libafl/src/observers/cmp.rs | 248 ++++++++++++++- libafl/src/observers/map.rs | 18 ++ libafl_sugar/src/forkserver.rs | 4 +- 12 files changed, 954 insertions(+), 95 deletions(-) create mode 100644 fuzzers/fuzzbench_forkserver/.gitignore create mode 100644 fuzzers/fuzzbench_forkserver/Cargo.toml create mode 100644 fuzzers/fuzzbench_forkserver/src/main.rs diff --git a/.github/workflows/build_and_test.yml b/.github/workflows/build_and_test.yml index b7d53a5d6f..0fe350e86b 100644 --- a/.github/workflows/build_and_test.yml +++ b/.github/workflows/build_and_test.yml @@ -153,8 +153,6 @@ jobs: - name: set mold linker as default linker if: runner.os == 'Linux' # mold only support linux until now uses: rui314/setup-mold@v1 - - name: enable mult-thread for `make` - run: export MAKEFLAGS="-j$(expr $(nproc) \+ 1)" - name: Add nightly rustfmt and clippy run: rustup toolchain install nightly --component rustfmt --component clippy --allow-downgrade - name: Add no_std toolchain @@ -170,6 +168,9 @@ jobs: macos: llvm libpng nasm coreutils z3 bash - name: pip install run: python3 -m pip install msgpack jinja2 + # Note that nproc needs to have coreutils installed on macOS, so the order of CI commands matters. + - name: enable mult-thread for `make` + run: export MAKEFLAGS="-j$(expr $(nproc) \+ 1)" - name: install cargo-make uses: baptiste0928/cargo-install@v1.3.0 with: diff --git a/fuzzers/forkserver_simple/build.rs b/fuzzers/forkserver_simple/build.rs index fcbde46935..4767182cc5 100644 --- a/fuzzers/forkserver_simple/build.rs +++ b/fuzzers/forkserver_simple/build.rs @@ -15,10 +15,10 @@ fn main() { let cwd = env::current_dir().unwrap().to_string_lossy().to_string(); let afl = format!("{}/AFLplusplus", &cwd); - let afl_gcc = format!("{}/AFLplusplus/afl-cc", &cwd); + let afl_cc = format!("{}/AFLplusplus/afl-cc", &cwd); let afl_path = Path::new(&afl); - let afl_gcc_path = Path::new(&afl_gcc); + let afl_cc_path = Path::new(&afl_cc); if !afl_path.is_dir() { println!("cargo:warning=AFL++ not found, downloading..."); @@ -29,19 +29,29 @@ fn main() { .unwrap(); } - if !afl_gcc_path.is_file() { - Command::new("make") - .arg("all") - .current_dir(&afl_path) - .status() - .unwrap(); + if !afl_cc_path.is_file() { + let mut afl_cc_make = Command::new("make"); + afl_cc_make.arg("all").current_dir(afl_path); + if let Ok(llvm_config) = env::var("LLVM_CONFIG") { + if !llvm_config.is_empty() { + afl_cc_make.env("LLVM_CONFIG", llvm_config); + } + } + afl_cc_make.status().unwrap(); } - Command::new(afl_gcc_path) - .args(&["src/program.c", "-o"]) - .arg(&format!("{}/target/release/program", &cwd)) - .status() - .unwrap(); + let mut compile_command = Command::new(afl_cc_path); + compile_command + .args(["src/program.c", "-o"]) + .arg(&format!("{}/target/release/program", &cwd)); + + if let Ok(llvm_config) = env::var("LLVM_CONFIG") { + if !llvm_config.is_empty() { + compile_command.env("LLVM_CONFIG", llvm_config); + } + } + + compile_command.status().unwrap(); println!("cargo:rerun-if-changed=build.rs"); println!("cargo:rerun-if-changed=src/"); diff --git a/fuzzers/forkserver_simple/src/main.rs b/fuzzers/forkserver_simple/src/main.rs index 5ccaf6e466..4d4b0ee501 100644 --- a/fuzzers/forkserver_simple/src/main.rs +++ b/fuzzers/forkserver_simple/src/main.rs @@ -2,28 +2,27 @@ use core::time::Duration; use std::path::PathBuf; use clap::{self, Parser}; -#[cfg(not(target_vendor = "apple"))] -use libafl::bolts::shmem::StdShMemProvider; -#[cfg(target_vendor = "apple")] -use libafl::bolts::shmem::UnixShMemProvider; use libafl::{ bolts::{ current_nanos, rands::StdRand, - shmem::{ShMem, ShMemProvider}, - tuples::{tuple_list, Merge}, + shmem::{ShMem, ShMemProvider, UnixShMemProvider}, + tuples::{tuple_list, MatchName, Merge}, AsMutSlice, }, corpus::{Corpus, InMemoryCorpus, OnDiskCorpus}, events::SimpleEventManager, - executors::forkserver::{ForkserverExecutor, TimeoutForkserverExecutor}, + executors::{ + forkserver::{ForkserverExecutor, TimeoutForkserverExecutor}, + HasObservers, + }, feedback_and_fast, feedback_or, feedbacks::{CrashFeedback, MaxMapFeedback, TimeFeedback}, fuzzer::{Fuzzer, StdFuzzer}, inputs::BytesInput, monitors::SimpleMonitor, mutators::{scheduled::havoc_mutations, tokens_mutations, StdScheduledMutator, Tokens}, - observers::{ConstMapObserver, HitcountsMapObserver, TimeObserver}, + observers::{HitcountsMapObserver, MapObserver, StdMapObserver, TimeObserver}, schedulers::{IndexesLenTimeMinimizerScheduler, QueueScheduler}, stages::mutational::StdMutationalStage, state::{HasCorpus, HasMetadata, StdState}, @@ -73,7 +72,6 @@ struct Opt { name = "arguments", num_args(1..), allow_hyphen_values = true, - default_value = "[].to_vec()" )] arguments: Vec, @@ -89,19 +87,15 @@ struct Opt { #[allow(clippy::similar_names)] pub fn main() { + const MAP_SIZE: usize = 65536; + let opt = Opt::parse(); let corpus_dirs: Vec = [opt.in_dir].to_vec(); - const MAP_SIZE: usize = 65536; - - // The default, OS-specific privider for shared memory - #[cfg(target_vendor = "apple")] + // The unix shmem provider supported by AFL++ for shared memory let mut shmem_provider = UnixShMemProvider::new().unwrap(); - #[cfg(not(target_vendor = "apple"))] - let mut shmem_provider = StdShMemProvider::new().unwrap(); - // The coverage map shared between observer and executor let mut shmem = shmem_provider.new_shmem(MAP_SIZE).unwrap(); // let the forkserver know the shmid @@ -109,10 +103,7 @@ pub fn main() { let shmem_buf = shmem.as_mut_slice(); // Create an observation channel using the signals map - let edges_observer = HitcountsMapObserver::new(ConstMapObserver::<_, MAP_SIZE>::new( - "shared_mem", - shmem_buf, - )); + let edges_observer = HitcountsMapObserver::new(StdMapObserver::new("shared_mem", shmem_buf)); // Create an observation channel to keep track of the execution time let time_observer = TimeObserver::new("time"); @@ -172,15 +163,24 @@ pub fn main() { let args = opt.arguments; let mut tokens = Tokens::new(); - let forkserver = ForkserverExecutor::builder() + let mut forkserver = ForkserverExecutor::builder() .program(opt.executable) .debug_child(debug_child) .shmem_provider(&mut shmem_provider) .autotokens(&mut tokens) .parse_afl_cmdline(args) + .coverage_map_size(MAP_SIZE) .build(tuple_list!(time_observer, edges_observer)) .unwrap(); + if let Some(dynamic_map_size) = forkserver.coverage_map_size() { + forkserver + .observers_mut() + .match_name_mut::>>("shared_mem") + .unwrap() + .downsize_map(dynamic_map_size); + } + let mut executor = TimeoutForkserverExecutor::with_signal( forkserver, Duration::from_millis(opt.timeout), diff --git a/fuzzers/fuzzbench_forkserver/.gitignore b/fuzzers/fuzzbench_forkserver/.gitignore new file mode 100644 index 0000000000..d3561edaf7 --- /dev/null +++ b/fuzzers/fuzzbench_forkserver/.gitignore @@ -0,0 +1,2 @@ +libpng-* +fuzzer diff --git a/fuzzers/fuzzbench_forkserver/Cargo.toml b/fuzzers/fuzzbench_forkserver/Cargo.toml new file mode 100644 index 0000000000..6e15343fc6 --- /dev/null +++ b/fuzzers/fuzzbench_forkserver/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "fuzzbench_forkserver" +version = "0.8.2" +authors = ["Andrea Fioraldi ", "Dominik Maier "] +edition = "2021" + +[profile.release] +lto = true +codegen-units = 1 +opt-level = 3 +debug = true + +[build-dependencies] +cc = { version = "1.0", features = ["parallel"] } +which = { version = "4.0.2" } + +[dependencies] +libafl = { path = "../../libafl/" } +clap = { version = "4.0", features = ["default"] } +nix = "0.25" diff --git a/fuzzers/fuzzbench_forkserver/src/main.rs b/fuzzers/fuzzbench_forkserver/src/main.rs new file mode 100644 index 0000000000..21a5d853fa --- /dev/null +++ b/fuzzers/fuzzbench_forkserver/src/main.rs @@ -0,0 +1,384 @@ +use core::{cell::RefCell, time::Duration}; +use std::{ + env, + fs::{self, OpenOptions}, + io::Write, + path::PathBuf, + process, +}; + +use clap::{Arg, ArgAction, Command}; +use libafl::{ + bolts::{ + current_nanos, current_time, + rands::StdRand, + shmem::{ShMem, ShMemProvider, UnixShMemProvider}, + tuples::{tuple_list, Merge}, + AsMutSlice, + }, + corpus::{Corpus, OnDiskCorpus}, + events::SimpleEventManager, + executors::forkserver::{ForkserverExecutor, TimeoutForkserverExecutor}, + feedback_and_fast, feedback_or, + feedbacks::{CrashFeedback, MaxMapFeedback, TimeFeedback}, + fuzzer::{Fuzzer, StdFuzzer}, + inputs::BytesInput, + monitors::SimpleMonitor, + mutators::{ + scheduled::havoc_mutations, token_mutations::I2SRandReplace, tokens_mutations, + StdMOptMutator, StdScheduledMutator, Tokens, + }, + observers::{AFLCmpMap, HitcountsMapObserver, StdCmpObserver, StdMapObserver, TimeObserver}, + schedulers::{ + powersched::PowerSchedule, IndexesLenTimeMinimizerScheduler, StdWeightedScheduler, + }, + stages::{ + calibrate::CalibrationStage, power::StdPowerMutationalStage, StdMutationalStage, + TracingStage, + }, + state::{HasCorpus, HasMetadata, StdState}, + Error, +}; +use nix::sys::signal::Signal; + +pub fn main() { + let res = match Command::new(env!("CARGO_PKG_NAME")) + .version(env!("CARGO_PKG_VERSION")) + .author("AFLplusplus team") + .about("LibAFL-based fuzzer for Fuzzbench") + .arg( + Arg::new("out") + .short('o') + .long("output") + .help("The directory to place finds in ('corpus')"), + ) + .arg( + Arg::new("in") + .short('i') + .long("input") + .help("The directory to read initial inputs from ('seeds')"), + ) + .arg( + Arg::new("tokens") + .short('x') + .long("tokens") + .help("A file to read tokens from, to be used during fuzzing"), + ) + .arg( + Arg::new("logfile") + .short('l') + .long("logfile") + .help("Duplicates all output to this file") + .default_value("libafl.log"), + ) + .arg( + Arg::new("timeout") + .short('t') + .long("timeout") + .help("Timeout for each individual execution, in milliseconds") + .default_value("1200"), + ) + .arg( + Arg::new("exec") + .help("The instrumented binary we want to fuzz") + .required(true), + ) + .arg( + Arg::new("debug-child") + .short('d') + .long("debug-child") + .help("If not set, the child's stdout and stderror will be redirected to /dev/null") + .action(ArgAction::SetTrue), + ) + .arg( + Arg::new("signal") + .short('s') + .long("signal") + .help("Signal used to stop child") + .default_value("SIGKILL"), + ) + .arg( + Arg::new("cmplog") + .short('c') + .long("cmplog") + .help("The instrumented binary with cmplog"), + ) + .arg(Arg::new("arguments")) + .try_get_matches() + { + Ok(res) => res, + Err(err) => { + println!( + "Syntax: {}, [-x dictionary] -o corpus_dir -i seed_dir\n{:?}", + env::current_exe() + .unwrap_or_else(|_| "fuzzer".into()) + .to_string_lossy(), + err, + ); + return; + } + }; + + println!( + "Workdir: {:?}", + env::current_dir().unwrap().to_string_lossy().to_string() + ); + + // For fuzzbench, crashes and finds are inside the same `corpus` directory, in the "queue" and "crashes" subdir. + let mut out_dir = PathBuf::from( + res.get_one::("out") + .expect("The --output parameter is missing") + .to_string(), + ); + if fs::create_dir(&out_dir).is_err() { + println!("Out dir at {:?} already exists.", &out_dir); + if !out_dir.is_dir() { + println!("Out dir at {:?} is not a valid directory!", &out_dir); + return; + } + } + let mut crashes = out_dir.clone(); + crashes.push("crashes"); + out_dir.push("queue"); + + let in_dir = PathBuf::from( + res.get_one::("in") + .expect("The --input parameter is missing") + .to_string(), + ); + if !in_dir.is_dir() { + println!("In dir at {:?} is not a valid directory!", &in_dir); + return; + } + + let tokens = res.get_one::("tokens").map(PathBuf::from); + + let logfile = PathBuf::from(res.get_one::("logfile").unwrap().to_string()); + + let timeout = Duration::from_millis( + res.get_one::("timeout") + .unwrap() + .to_string() + .parse() + .expect("Could not parse timeout in milliseconds"), + ); + + let executable = res + .get_one::("exec") + .expect("The executable is missing") + .to_string(); + + let debug_child = res.get_flag("debug-child"); + + let signal = str::parse::( + &res.get_one::("signal") + .expect("The --signal parameter is missing") + .to_string(), + ) + .unwrap(); + + let cmplog_exec = res + .get_one::("cmplog") + .map(std::string::ToString::to_string); + + let arguments = res + .get_many::("arguments") + .map(|v| v.map(std::string::ToString::to_string).collect::>()) + .unwrap_or_default(); + + fuzz( + out_dir, + crashes, + &in_dir, + tokens, + &logfile, + timeout, + executable, + debug_child, + signal, + &cmplog_exec, + &arguments, + ) + .expect("An error occurred while fuzzing"); +} + +/// The actual fuzzer +fn fuzz( + corpus_dir: PathBuf, + objective_dir: PathBuf, + seed_dir: &PathBuf, + tokenfile: Option, + logfile: &PathBuf, + timeout: Duration, + executable: String, + debug_child: bool, + signal: Signal, + cmplog_exec: &Option, + arguments: &[String], +) -> Result<(), Error> { + // a large initial map size that should be enough + // to house all potential coverage maps for our targets + // (we will eventually reduce the used size according to the actual map) + const MAP_SIZE: usize = 2_621_440; + + let log = RefCell::new(OpenOptions::new().append(true).create(true).open(logfile)?); + + // 'While the monitor are state, they are usually used in the broker - which is likely never restarted + let monitor = SimpleMonitor::new(|s| { + println!("{}", s); + writeln!(log.borrow_mut(), "{:?} {}", current_time(), s).unwrap(); + }); + + // The event manager handle the various events generated during the fuzzing loop + // such as the notification of the addition of a new item to the corpus + let mut mgr = SimpleEventManager::new(monitor); + + // The unix shmem provider for shared memory, to match AFL++'s shared memory at the target side + let mut shmem_provider = UnixShMemProvider::new().unwrap(); + + // The coverage map shared between observer and executor + let mut shmem = shmem_provider.new_shmem(MAP_SIZE).unwrap(); + // let the forkserver know the shmid + shmem.write_to_env("__AFL_SHM_ID").unwrap(); + let shmem_buf = shmem.as_mut_slice(); + + // Create an observation channel using the hitcounts map of AFL++ + let edges_observer = HitcountsMapObserver::new(StdMapObserver::new("shared_mem", shmem_buf)); + + // Create an observation channel to keep track of the execution time + let time_observer = TimeObserver::new("time"); + + let map_feedback = MaxMapFeedback::new_tracking(&edges_observer, true, false); + + let calibration = CalibrationStage::new(&map_feedback); + + // Feedback to rate the interestingness of an input + // This one is composed by two Feedbacks in OR + let mut feedback = feedback_or!( + // New maximization map feedback linked to the edges observer and the feedback state + map_feedback, + // Time feedback, this one does not need a feedback state + TimeFeedback::new_with_observer(&time_observer) + ); + + // A feedback to choose if an input is a solution or not + // We want to do the same crash deduplication that AFL does + let mut objective = feedback_and_fast!( + // Must be a crash + CrashFeedback::new(), + // Take it onlt if trigger new coverage over crashes + MaxMapFeedback::new(&edges_observer) + ); + + // create a State from scratch + let mut state = StdState::new( + // RNG + StdRand::with_seed(current_nanos()), + // Corpus that will be evolved, we keep it in memory for performance + OnDiskCorpus::::new(corpus_dir).unwrap(), + // Corpus in which we store solutions (crashes in this example), + // on disk so the user can get them after stopping the fuzzer + OnDiskCorpus::new(objective_dir).unwrap(), + // States of the feedbacks. + // The feedbacks can report the data that should persist in the State. + &mut feedback, + // Same for objective feedbacks + &mut objective, + ) + .unwrap(); + + println!("Let's fuzz :)"); + + // Setup a MOPT mutator + let mutator = StdMOptMutator::new( + &mut state, + havoc_mutations().merge(tokens_mutations()), + 7, + 5, + )?; + + let power = StdPowerMutationalStage::new(mutator, &edges_observer); + + // A minimization+queue policy to get testcasess from the corpus + let scheduler = IndexesLenTimeMinimizerScheduler::new(StdWeightedScheduler::with_schedule( + PowerSchedule::EXPLORE, + )); + + // A fuzzer with feedbacks and a corpus scheduler + let mut fuzzer = StdFuzzer::new(scheduler, feedback, objective); + + let mut tokens = Tokens::new(); + let forkserver = ForkserverExecutor::builder() + .program(executable) + .debug_child(debug_child) + .shmem_provider(&mut shmem_provider) + .autotokens(&mut tokens) + .parse_afl_cmdline(arguments) + .coverage_map_size(MAP_SIZE) + .is_persistent(true) + .build_dynamic_map(edges_observer, tuple_list!(time_observer)) + .unwrap(); + + let mut executor = TimeoutForkserverExecutor::with_signal(forkserver, timeout, signal) + .expect("Failed to create the executor."); + + // Read tokens + if let Some(tokenfile) = tokenfile { + tokens.add_from_file(tokenfile)?; + } + if !tokens.is_empty() { + state.add_metadata(tokens); + } + + state + .load_initial_inputs(&mut fuzzer, &mut executor, &mut mgr, &[seed_dir.clone()]) + .unwrap_or_else(|_| { + println!("Failed to load initial corpus at {:?}", &seed_dir); + process::exit(0); + }); + println!("We imported {} inputs from disk.", state.corpus().count()); + + if let Some(exec) = &cmplog_exec { + // The cmplog map shared between observer and executor + let mut cmplog_shmem = shmem_provider + .new_shmem(core::mem::size_of::()) + .unwrap(); + // let the forkserver know the shmid + cmplog_shmem.write_to_env("__AFL_CMPLOG_SHM_ID").unwrap(); + let cmpmap = unsafe { cmplog_shmem.as_object_mut::() }; + + let cmplog_observer = StdCmpObserver::new("cmplog", cmpmap, true); + + let cmplog_forkserver = ForkserverExecutor::builder() + .program(exec) + .debug_child(debug_child) + .shmem_provider(&mut shmem_provider) + .parse_afl_cmdline(arguments) + .is_persistent(true) + .build(tuple_list!(cmplog_observer)) + .unwrap(); + + let cmplog_executor = + TimeoutForkserverExecutor::with_signal(cmplog_forkserver, timeout * 10, signal) + .expect("Failed to create the executor."); + + let tracing = TracingStage::new(cmplog_executor); + + // Setup a randomic Input2State stage + let i2s = + StdMutationalStage::new(StdScheduledMutator::new(tuple_list!(I2SRandReplace::new()))); + + // The order of the stages matter! + let mut stages = tuple_list!(calibration, tracing, i2s, power); + + fuzzer.fuzz_loop(&mut stages, &mut executor, &mut state, &mut mgr)?; + } else { + // The order of the stages matter! + let mut stages = tuple_list!(calibration, power); + + fuzzer.fuzz_loop(&mut stages, &mut executor, &mut state, &mut mgr)?; + } + + // Never reached + Ok(()) +} diff --git a/libafl/Cargo.toml b/libafl/Cargo.toml index ed4fe610f8..eb8b465024 100644 --- a/libafl/Cargo.toml +++ b/libafl/Cargo.toml @@ -61,6 +61,7 @@ erased-serde = { version = "0.3.21", default-features = false, features = ["allo postcard = { version = "1.0", features = ["alloc"] } # no_std compatible serde serialization fromat bincode = {version = "1.3", optional = true } static_assertions = "1.1.0" +c2rust-bitfields = { version = "0.3", features = ["no_std"] } num_enum = { version = "0.5.7", default-features = false } typed-builder = "0.10.0" # Implement the builder pattern at compiletime ahash = { version = "0.7", default-features=false } # The hash function already used in hashbrown diff --git a/libafl/src/bolts/ownedref.rs b/libafl/src/bolts/ownedref.rs index c6a90acf3a..e533cb36bf 100644 --- a/libafl/src/bolts/ownedref.rs +++ b/libafl/src/bolts/ownedref.rs @@ -23,6 +23,26 @@ pub trait IntoOwned { fn into_owned(self) -> Self; } +/// Trait to downsize slice references +pub trait DownsizeSlice { + /// Reduce the size of the slice + fn downsize(&mut self, len: usize); +} + +impl<'a, T> DownsizeSlice for &'a [T] { + fn downsize(&mut self, len: usize) { + *self = &self[..len]; + } +} + +impl<'a, T> DownsizeSlice for &'a mut [T] { + fn downsize(&mut self, len: usize) { + let mut value = core::mem::take(self); + value = unsafe { value.get_unchecked_mut(..len) }; + let _ = core::mem::replace(self, value); + } +} + /// Wrap a reference and convert to a [`Box`] on serialize #[derive(Clone, Debug)] pub enum OwnedRef<'a, T> @@ -239,6 +259,39 @@ impl<'a, T> OwnedSlice<'a, T> { inner: OwnedSliceInner::RefRaw(ptr, len), } } + + /// Downsize the inner slice or vec returning the old size on success or `None` on failure + pub fn downsize(&mut self, new_len: usize) -> Option { + match &mut self.inner { + OwnedSliceInner::RefRaw(_rr, len) => { + let tmp = *len; + if new_len <= tmp { + *len = new_len; + Some(tmp) + } else { + None + } + } + OwnedSliceInner::Ref(r) => { + let tmp = r.len(); + if new_len <= tmp { + r.downsize(new_len); + Some(tmp) + } else { + None + } + } + OwnedSliceInner::Owned(v) => { + let tmp = v.len(); + if new_len <= tmp { + v.truncate(new_len); + Some(tmp) + } else { + None + } + } + } + } } impl<'a, 'it, T> IntoIterator for &'it OwnedSlice<'a, T> { @@ -429,6 +482,39 @@ impl<'a, T: 'a + Sized> OwnedSliceMut<'a, T> { } } } + + /// Downsize the inner slice or vec returning the old size on success or `None` on failure + pub fn downsize(&mut self, new_len: usize) -> Option { + match &mut self.inner { + OwnedSliceMutInner::RefRaw(_rr, len) => { + let tmp = *len; + if new_len <= tmp { + *len = new_len; + Some(tmp) + } else { + None + } + } + OwnedSliceMutInner::Ref(r) => { + let tmp = r.len(); + if new_len <= tmp { + r.downsize(new_len); + Some(tmp) + } else { + None + } + } + OwnedSliceMutInner::Owned(v) => { + let tmp = v.len(); + if new_len <= tmp { + v.truncate(new_len); + Some(tmp) + } else { + None + } + } + } + } } impl<'a, T: Sized> AsSlice for OwnedSliceMut<'a, T> { diff --git a/libafl/src/executors/forkserver.rs b/libafl/src/executors/forkserver.rs index c936f2830e..0780bd0e07 100644 --- a/libafl/src/executors/forkserver.rs +++ b/libafl/src/executors/forkserver.rs @@ -27,14 +27,16 @@ use crate::{ bolts::{ fs::{InputFile, INPUTFILE_STD}, os::{dup2, pipes::Pipe}, - shmem::{ShMem, ShMemProvider, StdShMemProvider}, + shmem::{ShMem, ShMemProvider, UnixShMemProvider}, + tuples::Prepend, AsMutSlice, AsSlice, }, executors::{Executor, ExitKind, HasObservers}, inputs::{HasTargetBytes, Input, UsesInput}, mutators::Tokens, observers::{ - get_asan_runtime_flags_with_log_path, AsanBacktraceObserver, ObserversTuple, UsesObservers, + get_asan_runtime_flags_with_log_path, AsanBacktraceObserver, MapObserver, Observer, + ObserversTuple, UsesObservers, }, state::UsesState, Error, @@ -44,9 +46,23 @@ const FORKSRV_FD: i32 = 198; #[allow(clippy::cast_possible_wrap)] const FS_OPT_ENABLED: i32 = 0x80000001_u32 as i32; #[allow(clippy::cast_possible_wrap)] +const FS_OPT_MAPSIZE: i32 = 0x40000000_u32 as i32; +#[allow(clippy::cast_possible_wrap)] const FS_OPT_SHDMEM_FUZZ: i32 = 0x01000000_u32 as i32; #[allow(clippy::cast_possible_wrap)] const FS_OPT_AUTODICT: i32 = 0x10000000_u32 as i32; + +// #[allow(clippy::cast_possible_wrap)] +// const FS_OPT_MAX_MAPSIZE: i32 = ((0x00fffffe_u32 >> 1) + 1) as i32; // 8388608 +const fn fs_opt_get_mapsize(x: i32) -> i32 { + ((x & 0x00fffffe) >> 1) + 1 +} +/* const fn fs_opt_set_mapsize(x: usize) -> usize { + if x <= 1 { + if x > FS_OPT_MAX_MAPSIZE { 0 } else { (x - 1) << 1 } + } else { 0 } +} */ + /// The length of header bytes which tells shmem size const SHMEM_FUZZ_HDR_SIZE: usize = 4; const MAX_FILE: usize = 1024 * 1024; @@ -511,6 +527,7 @@ where phantom: PhantomData, /// Cache that indicates if we have a `ASan` observer registered. has_asan_observer: Option, + map_size: Option, } impl Debug for ForkserverExecutor @@ -531,10 +548,10 @@ where } } -impl ForkserverExecutor<(), (), StdShMemProvider> { +impl ForkserverExecutor<(), (), UnixShMemProvider> { /// Builder for `ForkserverExecutor` #[must_use] - pub fn builder() -> ForkserverExecutorBuilder<'static, StdShMemProvider> { + pub fn builder() -> ForkserverExecutorBuilder<'static, UnixShMemProvider> { ForkserverExecutorBuilder::new() } } @@ -542,7 +559,7 @@ impl ForkserverExecutor<(), (), StdShMemProvider> { impl ForkserverExecutor where OT: ObserversTuple, - S: UsesState, + S: UsesInput, SP: ShMemProvider, { /// The `target` binary that's going to run. @@ -564,6 +581,11 @@ where pub fn input_file(&self) -> &InputFile { &self.input_file } + + /// The coverage map size if specified by the target + pub fn coverage_map_size(&self) -> Option { + self.map_size + } } /// The builder for `ForkserverExecutor` @@ -581,6 +603,8 @@ pub struct ForkserverExecutorBuilder<'a, SP> { autotokens: Option<&'a mut Tokens>, input_filename: Option, shmem_provider: Option<&'a mut SP>, + map_size: Option, + real_map_size: i32, } impl<'a, SP> ForkserverExecutorBuilder<'a, SP> { @@ -592,6 +616,80 @@ impl<'a, SP> ForkserverExecutorBuilder<'a, SP> { S: UsesInput, S::Input: Input + HasTargetBytes, SP: ShMemProvider, + { + let (forkserver, input_file, map) = self.build_helper()?; + + let target = self.program.take().unwrap(); + println!( + "ForkserverExecutor: program: {:?}, arguments: {:?}, use_stdin: {:?}", + target, + self.arguments.clone(), + self.use_stdin + ); + + Ok(ForkserverExecutor { + target, + args: self.arguments.clone(), + input_file, + uses_shmem_testcase: self.uses_shmem_testcase, + forkserver, + observers, + map, + phantom: PhantomData, + has_asan_observer: None, // initialized on first use + map_size: self.map_size, + }) + } + + /// Builds `ForkserverExecutor` downsizing the coverage map to fit exaclty the AFL++ map size. + #[allow(clippy::pedantic)] + pub fn build_dynamic_map( + &mut self, + mut map_observer: MO, + other_observers: OT, + ) -> Result, Error> + where + MO: Observer + MapObserver, // TODO maybe enforce Entry = u8 for the cov map + OT: ObserversTuple + Prepend, + S: UsesInput, + S::Input: Input + HasTargetBytes, + SP: ShMemProvider, + { + let (forkserver, input_file, map) = self.build_helper()?; + + let target = self.program.take().unwrap(); + println!( + "ForkserverExecutor: program: {:?}, arguments: {:?}, use_stdin: {:?}, map_size: {:?}", + target, + self.arguments.clone(), + self.use_stdin, + self.map_size + ); + + if let Some(dynamic_map_size) = self.map_size { + map_observer.downsize_map(dynamic_map_size); + } + + let observers: (MO, OT) = other_observers.prepend(map_observer); + + Ok(ForkserverExecutor { + target, + args: self.arguments.clone(), + input_file, + uses_shmem_testcase: self.uses_shmem_testcase, + forkserver, + observers, + map, + phantom: PhantomData, + has_asan_observer: None, // initialized on first use + map_size: self.map_size, + }) + } + + #[allow(clippy::pedantic)] + fn build_helper(&mut self) -> Result<(Forkserver, InputFile, Option), Error> + where + SP: ShMemProvider, { let input_filename = match &self.input_filename { Some(name) => name.clone(), @@ -613,22 +711,18 @@ impl<'a, SP> ForkserverExecutorBuilder<'a, SP> { } }; - let (target, mut forkserver) = match &self.program { - Some(t) => { - let forkserver = Forkserver::new( - t.clone(), - self.arguments.clone(), - self.envs.clone(), - input_file.as_raw_fd(), - self.use_stdin, - 0, - self.is_persistent, - self.is_deferred_frksrv, - self.debug_child, - )?; - - (t.clone(), forkserver) - } + let mut forkserver = match &self.program { + Some(t) => Forkserver::new( + t.clone(), + self.arguments.clone(), + self.envs.clone(), + input_file.as_raw_fd(), + self.use_stdin, + 0, + self.is_persistent, + self.is_deferred_frksrv, + self.debug_child, + )?, None => { return Err(Error::illegal_argument( "ForkserverExecutorBuilder::build: target file not found".to_string(), @@ -648,7 +742,8 @@ impl<'a, SP> ForkserverExecutorBuilder<'a, SP> { // if status & FS_OPT_ENABLED == FS_OPT_ENABLED && (status & FS_OPT_SHDMEM_FUZZ == FS_OPT_SHDMEM_FUZZ - || status & FS_OPT_AUTODICT == FS_OPT_AUTODICT) + || status & FS_OPT_AUTODICT == FS_OPT_AUTODICT + || status & FS_OPT_MAPSIZE == FS_OPT_MAPSIZE) { let mut send_status = FS_OPT_ENABLED; @@ -658,9 +753,25 @@ impl<'a, SP> ForkserverExecutorBuilder<'a, SP> { self.uses_shmem_testcase = true; } - if (status & FS_OPT_AUTODICT == FS_OPT_AUTODICT) && self.autotokens.is_some() { - println!("Using AUTODICT feature"); - send_status |= FS_OPT_AUTODICT; + if status & FS_OPT_MAPSIZE == FS_OPT_MAPSIZE { + let mut map_size = fs_opt_get_mapsize(status); + // When 0, we assume that map_size was filled by the user or const + /* TODO autofill map size from the observer + + if map_size > 0 { + self.map_size = Some(map_size as usize); + } + */ + + self.real_map_size = map_size; + if map_size % 64 != 0 { + map_size = ((map_size + 63) >> 6) << 6; + } + + assert!(self.map_size.is_none() || map_size as usize <= self.map_size.unwrap()); + + println!("Target MAP SIZE = {:#x}", self.real_map_size); + self.map_size = Some(map_size as usize); } let send_len = forkserver.write_ctl(send_status)?; @@ -698,24 +809,7 @@ impl<'a, SP> ForkserverExecutorBuilder<'a, SP> { println!("Forkserver Options are not available."); } - println!( - "ForkserverExecutor: program: {:?}, arguments: {:?}, use_stdin: {:?}", - target, - self.arguments.clone(), - self.use_stdin - ); - - Ok(ForkserverExecutor { - target, - args: self.arguments.clone(), - input_file, - uses_shmem_testcase: self.uses_shmem_testcase, - forkserver, - observers, - map, - phantom: PhantomData, - has_asan_observer: None, // initialized on first use - }) + Ok((forkserver, input_file, map)) } /// Use autodict? @@ -757,26 +851,28 @@ impl<'a, SP> ForkserverExecutorBuilder<'a, SP> { } } -impl<'a> ForkserverExecutorBuilder<'a, StdShMemProvider> { +impl<'a> ForkserverExecutorBuilder<'a, UnixShMemProvider> { /// Creates a new `AFL`-style [`ForkserverExecutor`] with the given target, arguments and observers. /// This is the builder for `ForkserverExecutor` /// This Forkserver will attempt to provide inputs over shared mem when `shmem_provider` is given. /// Else this forkserver will try to write the input to `.cur_input` file. /// If `debug_child` is set, the child will print to `stdout`/`stderr`. #[must_use] - pub fn new() -> ForkserverExecutorBuilder<'a, StdShMemProvider> { + pub fn new() -> ForkserverExecutorBuilder<'a, UnixShMemProvider> { ForkserverExecutorBuilder { program: None, arguments: vec![], envs: vec![], debug_child: false, - use_stdin: true, + use_stdin: false, uses_shmem_testcase: false, is_persistent: false, is_deferred_frksrv: false, autotokens: None, input_filename: None, shmem_provider: None, + map_size: None, + real_map_size: 0, } } @@ -878,6 +974,13 @@ impl<'a> ForkserverExecutorBuilder<'a, StdShMemProvider> { self } + #[must_use] + /// Call this to set a defauult const coverage map size + pub fn coverage_map_size(mut self, size: usize) -> Self { + self.map_size = Some(size); + self + } + /// Shmem provider for forkserver's shared memory testcase feature. pub fn shmem_provider( self, @@ -895,11 +998,13 @@ impl<'a> ForkserverExecutorBuilder<'a, StdShMemProvider> { autotokens: self.autotokens, input_filename: self.input_filename, shmem_provider: Some(shmem_provider), + map_size: self.map_size, + real_map_size: self.real_map_size, } } } -impl<'a> Default for ForkserverExecutorBuilder<'a, StdShMemProvider> { +impl<'a> Default for ForkserverExecutorBuilder<'a, UnixShMemProvider> { fn default() -> Self { Self::new() } @@ -1111,7 +1216,7 @@ mod tests { use crate::{ bolts::{ - shmem::{ShMem, ShMemProvider, StdShMemProvider}, + shmem::{ShMem, ShMemProvider, UnixShMemProvider}, tuples::tuple_list, AsMutSlice, }, @@ -1127,7 +1232,7 @@ mod tests { let bin = OsString::from("echo"); let args = vec![OsString::from("@@")]; - let mut shmem_provider = StdShMemProvider::new().unwrap(); + let mut shmem_provider = UnixShMemProvider::new().unwrap(); let mut shmem = shmem_provider.new_shmem(MAP_SIZE).unwrap(); shmem.write_to_env("__AFL_SHM_ID").unwrap(); diff --git a/libafl/src/observers/cmp.rs b/libafl/src/observers/cmp.rs index 49b0cd9822..d4794dd88f 100644 --- a/libafl/src/observers/cmp.rs +++ b/libafl/src/observers/cmp.rs @@ -6,10 +6,12 @@ use alloc::{ }; use core::{fmt::Debug, marker::PhantomData}; -use serde::{de::DeserializeOwned, Deserialize, Serialize}; +use c2rust_bitfields::BitfieldStruct; +use serde::{de::DeserializeOwned, Deserialize, Deserializer, Serialize, Serializer}; use crate::{ bolts::{ownedref::OwnedRefMut, tuples::Named, AsMutSlice, AsSlice}, + executors::ExitKind, inputs::UsesInput, observers::Observer, state::HasMetadata, @@ -201,18 +203,19 @@ where pub struct StdCmpObserver<'a, CM, S> where CM: CmpMap + Serialize, - S: UsesInput, + S: UsesInput + HasMetadata, { cmp_map: OwnedRefMut<'a, CM>, size: Option>, name: String, + add_meta: bool, phantom: PhantomData, } impl<'a, CM, S> CmpObserver for StdCmpObserver<'a, CM, S> where CM: CmpMap + Serialize + DeserializeOwned, - S: UsesInput + Debug, + S: UsesInput + Debug + HasMetadata, { /// Get the number of usable cmps (all by default) fn usable_count(&self) -> usize { @@ -234,18 +237,30 @@ where impl<'a, CM, S> Observer for StdCmpObserver<'a, CM, S> where CM: CmpMap + Serialize + DeserializeOwned, - S: UsesInput + Debug, + S: UsesInput + Debug + HasMetadata, { fn pre_exec(&mut self, _state: &mut S, _input: &S::Input) -> Result<(), Error> { self.cmp_map.as_mut().reset()?; Ok(()) } + + fn post_exec( + &mut self, + state: &mut S, + _input: &S::Input, + _exit_kind: &ExitKind, + ) -> Result<(), Error> { + if self.add_meta { + self.add_cmpvalues_meta(state); + } + Ok(()) + } } impl<'a, CM, S> Named for StdCmpObserver<'a, CM, S> where CM: CmpMap + Serialize + DeserializeOwned, - S: UsesInput, + S: UsesInput + HasMetadata, { fn name(&self) -> &str { &self.name @@ -255,27 +270,244 @@ where impl<'a, CM, S> StdCmpObserver<'a, CM, S> where CM: CmpMap + Serialize + DeserializeOwned, - S: UsesInput, + S: UsesInput + HasMetadata, { /// Creates a new [`StdCmpObserver`] with the given name and map. #[must_use] - pub fn new(name: &'static str, map: &'a mut CM) -> Self { + pub fn new(name: &'static str, map: &'a mut CM, add_meta: bool) -> Self { Self { name: name.to_string(), size: None, cmp_map: OwnedRefMut::Ref(map), + add_meta, phantom: PhantomData, } } /// Creates a new [`StdCmpObserver`] with the given name, map and reference to variable size. #[must_use] - pub fn with_size(name: &'static str, map: &'a mut CM, size: &'a mut usize) -> Self { + pub fn with_size( + name: &'static str, + map: &'a mut CM, + add_meta: bool, + size: &'a mut usize, + ) -> Self { Self { name: name.to_string(), size: Some(OwnedRefMut::Ref(size)), cmp_map: OwnedRefMut::Ref(map), + add_meta, phantom: PhantomData, } } } + +/* From AFL++ cmplog.h + +#define CMP_MAP_W 65536 +#define CMP_MAP_H 32 +#define CMP_MAP_RTN_H (CMP_MAP_H / 4) + +struct cmp_header { + + unsigned hits : 24; + unsigned id : 24; + unsigned shape : 5; + unsigned type : 2; + unsigned attribute : 4; + unsigned overflow : 1; + unsigned reserved : 4; + +} __attribute__((packed)); + +struct cmp_operands { + + u64 v0; + u64 v1; + u64 v0_128; + u64 v1_128; + +} __attribute__((packed)); + +struct cmpfn_operands { + + u8 v0[31]; + u8 v0_len; + u8 v1[31]; + u8 v1_len; + +} __attribute__((packed)); + +typedef struct cmp_operands cmp_map_list[CMP_MAP_H]; + +struct cmp_map { + + struct cmp_header headers[CMP_MAP_W]; + struct cmp_operands log[CMP_MAP_W][CMP_MAP_H]; + +}; +*/ + +/// The AFL++ `CMP_MAP_W` +pub const AFL_CMP_MAP_W: usize = 65536; +/// The AFL++ `CMP_MAP_H` +pub const AFL_CMP_MAP_H: usize = 32; +/// The AFL++ `CMP_MAP_RTN_H` +pub const AFL_CMP_MAP_RTN_H: usize = AFL_CMP_MAP_H / 4; + +/// The AFL++ `CMP_TYPE_INS` +pub const AFL_CMP_TYPE_INS: u32 = 1; +/// The AFL++ `CMP_TYPE_RTN` +pub const AFL_CMP_TYPE_RTN: u32 = 2; + +/// The AFL++ `cmp_header` struct +#[derive(Debug, Copy, Clone, BitfieldStruct)] +#[repr(C, packed)] +pub struct AFLCmpHeader { + #[bitfield(name = "hits", ty = "u32", bits = "0..=23")] + #[bitfield(name = "id", ty = "u32", bits = "24..=47")] + #[bitfield(name = "shape", ty = "u32", bits = "48..=52")] + #[bitfield(name = "_type", ty = "u32", bits = "53..=54")] + #[bitfield(name = "attribute", ty = "u32", bits = "55..=58")] + #[bitfield(name = "overflow", ty = "u32", bits = "59..=59")] + #[bitfield(name = "reserved", ty = "u32", bits = "60..=63")] + data: [u8; 8], +} + +/// The AFL++ `cmp_operands` struct +#[derive(Default, Debug, Clone, Copy)] +#[repr(C, packed)] +pub struct AFLCmpOperands { + v0: u64, + v1: u64, + v0_128: u64, + v1_128: u64, +} + +/// The AFL++ `cmpfn_operands` struct +#[derive(Default, Debug, Clone, Copy)] +#[repr(C, packed)] +pub struct AFLCmpFnOperands { + v0: [u8; 31], + v0_len: u8, + v1: [u8; 31], + v1_len: u8, +} + +/// A proxy union to avoid casting operands as in AFL++ +#[derive(Clone, Copy)] +#[repr(C, packed)] +pub union AFLCmpVals { + operands: [[AFLCmpOperands; AFL_CMP_MAP_H]; AFL_CMP_MAP_W], + fn_operands: [[AFLCmpFnOperands; AFL_CMP_MAP_RTN_H]; AFL_CMP_MAP_W], +} + +impl Debug for AFLCmpVals { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.debug_struct("AFLCmpVals").finish_non_exhaustive() + } +} + +/// The AFL++ `cmp_map` struct, use with `StdCmpObserver` +#[derive(Debug, Clone, Copy)] +#[repr(C, packed)] +pub struct AFLCmpMap { + headers: [AFLCmpHeader; AFL_CMP_MAP_W], + vals: AFLCmpVals, +} + +impl Serialize for AFLCmpMap { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + let slice = unsafe { + core::slice::from_raw_parts( + (self as *const Self) as *const u8, + core::mem::size_of::(), + ) + }; + serializer.serialize_bytes(slice) + } +} + +impl<'de> Deserialize<'de> for AFLCmpMap { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let bytes = Vec::::deserialize(deserializer)?; + let map: Self = unsafe { core::ptr::read(bytes.as_ptr() as *const _) }; + Ok(map) + } +} + +impl CmpMap for AFLCmpMap { + fn len(&self) -> usize { + AFL_CMP_MAP_W + } + + fn executions_for(&self, idx: usize) -> usize { + self.headers[idx].hits() as usize + } + + fn usable_executions_for(&self, idx: usize) -> usize { + if self.headers[idx]._type() == AFL_CMP_TYPE_INS { + if self.executions_for(idx) < AFL_CMP_MAP_H { + self.executions_for(idx) + } else { + AFL_CMP_MAP_H + } + } else if self.executions_for(idx) < AFL_CMP_MAP_RTN_H { + self.executions_for(idx) + } else { + AFL_CMP_MAP_RTN_H + } + } + + fn values_of(&self, idx: usize, execution: usize) -> Option { + if self.headers[idx]._type() == AFL_CMP_TYPE_INS { + unsafe { + match self.headers[idx].shape() { + 0 => Some(CmpValues::U8(( + self.vals.operands[idx][execution].v0 as u8, + self.vals.operands[idx][execution].v1 as u8, + ))), + 1 => Some(CmpValues::U16(( + self.vals.operands[idx][execution].v0 as u16, + self.vals.operands[idx][execution].v1 as u16, + ))), + 3 => Some(CmpValues::U32(( + self.vals.operands[idx][execution].v0 as u32, + self.vals.operands[idx][execution].v1 as u32, + ))), + 7 => Some(CmpValues::U64(( + self.vals.operands[idx][execution].v0, + self.vals.operands[idx][execution].v1, + ))), + // TODO handle 128 bits cmps + // other => panic!("Invalid CmpLog shape {}", other), + _ => None, + } + } + } else { + unsafe { + Some(CmpValues::Bytes(( + self.vals.fn_operands[idx][execution].v0 + [..=(self.headers[idx].shape() as usize)] + .to_vec(), + self.vals.fn_operands[idx][execution].v1 + [..=(self.headers[idx].shape() as usize)] + .to_vec(), + ))) + } + } + } + + fn reset(&mut self) -> Result<(), Error> { + // For performance, we reset just the headers + self.headers = unsafe { core::mem::zeroed() }; + // self.vals.operands = unsafe { core::mem::zeroed() }; + Ok(()) + } +} diff --git a/libafl/src/observers/map.rs b/libafl/src/observers/map.rs index 937e3bc3c9..b7e99d61ed 100644 --- a/libafl/src/observers/map.rs +++ b/libafl/src/observers/map.rs @@ -124,6 +124,12 @@ pub trait MapObserver: HasLen + Named + Serialize + serde::de::DeserializeOwned /// Get the number of set entries with the specified indexes fn how_many_set(&self, indexes: &[usize]) -> usize; + + /// Resize the inner map to be smaller (and thus faster to process) + /// It returns Some(old size) on success, None on failure + fn downsize_map(&mut self, _new_len: usize) -> Option { + None + } } /// A Simple iterator calling `MapObserver::get` @@ -417,6 +423,10 @@ where } res } + + fn downsize_map(&mut self, new_len: usize) -> Option { + self.map.downsize(new_len) + } } impl<'a, T, const DIFFERENTIAL: bool> AsSlice for StdMapObserver<'a, T, DIFFERENTIAL> @@ -1292,6 +1302,10 @@ where fn how_many_set(&self, indexes: &[usize]) -> usize { self.base.how_many_set(indexes) } + + fn downsize_map(&mut self, new_len: usize) -> Option { + self.base.downsize_map(new_len) + } } impl AsSlice for HitcountsMapObserver @@ -1516,6 +1530,10 @@ where fn how_many_set(&self, indexes: &[usize]) -> usize { self.base.how_many_set(indexes) } + + fn downsize_map(&mut self, new_len: usize) -> Option { + self.base.downsize_map(new_len) + } } impl AsSlice for HitcountsIterableMapObserver diff --git a/libafl_sugar/src/forkserver.rs b/libafl_sugar/src/forkserver.rs index 4c27e84349..1942e1724f 100644 --- a/libafl_sugar/src/forkserver.rs +++ b/libafl_sugar/src/forkserver.rs @@ -8,7 +8,7 @@ use libafl::{ current_nanos, launcher::Launcher, rands::StdRand, - shmem::{ShMem, ShMemProvider, StdShMemProvider}, + shmem::{ShMem, ShMemProvider, UnixShMemProvider}, tuples::{tuple_list, Merge}, AsMutSlice, }, @@ -108,7 +108,7 @@ impl<'a, const MAP_SIZE: usize> ForkserverBytesCoverageSugar<'a, MAP_SIZE> { crashes.push("crashes"); out_dir.push("queue"); - let shmem_provider = StdShMemProvider::new().expect("Failed to init shared memory"); + let shmem_provider = UnixShMemProvider::new().expect("Failed to init shared memory"); let mut shmem_provider_client = shmem_provider.clone(); let monitor = MultiMonitor::new(|s| println!("{s}"));