SyncFromBrokerStage to sync from a broker with a different Input type (#997)

* ConverterLlmpEventManager

* docs

* SyncFromBrokerStage

* fix

* separate InputConverter

* nautilus

* nautilus_sync

* send testcases

* upd nautilus

* meta

* fix

* clippy

* fix

* Update build_and_test.yml

* fix

* fix

* Use find_libpython

* ci

* upd qemu
This commit is contained in:
Andrea Fioraldi 2023-01-20 17:16:29 +01:00 committed by GitHub
parent 5cdb7f7b05
commit 7fd9ac0952
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 1095 additions and 12 deletions

View File

@ -173,7 +173,7 @@ jobs:
run: rustup toolchain install nightly --component rustfmt --component clippy --allow-downgrade run: rustup toolchain install nightly --component rustfmt --component clippy --allow-downgrade
- name: Add no_std toolchain - name: Add no_std toolchain
run: rustup toolchain install nightly-x86_64-unknown-linux-gnu ; rustup component add rust-src --toolchain nightly-x86_64-unknown-linux-gnu run: rustup toolchain install nightly-x86_64-unknown-linux-gnu ; rustup component add rust-src --toolchain nightly-x86_64-unknown-linux-gnu
- name: Install python - name: Install python (macOS)
# Removing macOS things already installed in CI against failed linking # Removing macOS things already installed in CI against failed linking
if: runner.os == 'macOS' if: runner.os == 'macOS'
run: rm /usr/local/bin/2to3* /usr/local/bin/idle3* /usr/local/bin/pydoc3* /usr/local/bin/python3*; brew install --force-bottle --overwrite python run: rm /usr/local/bin/2to3* /usr/local/bin/idle3* /usr/local/bin/pydoc3* /usr/local/bin/python3*; brew install --force-bottle --overwrite python
@ -184,7 +184,7 @@ jobs:
# update bash for macos to support `declare -A` command` # update bash for macos to support `declare -A` command`
macos: llvm libpng nasm coreutils z3 bash wget macos: llvm libpng nasm coreutils z3 bash wget
- name: pip install - name: pip install
run: python3 -m pip install msgpack jinja2 run: python3 -m pip install msgpack jinja2 find_libpython
# Note that nproc needs to have coreutils installed on macOS, so the order of CI commands matters. # Note that nproc needs to have coreutils installed on macOS, so the order of CI commands matters.
- name: enable mult-thread for `make` - name: enable mult-thread for `make`
run: export MAKEFLAGS="-j$(expr $(nproc) \+ 1)" run: export MAKEFLAGS="-j$(expr $(nproc) \+ 1)"

3
fuzzers/nautilus_sync/.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
fuzzer_libpng_nautilus
grammar.json
libpng-*

View File

@ -0,0 +1,32 @@
[package]
name = "nautilus_sync"
version = "0.9.0"
authors = ["Andrea Fioraldi <andreafioraldi@gmail.com>", "Dominik Maier <domenukk@gmail.com>"]
edition = "2021"
[features]
default = ["std"]
std = []
[profile.dev]
panic = "abort"
debug = true
[profile.release]
panic = "abort"
lto = true
codegen-units = 1
opt-level = 3
debug = true
[dependencies]
libafl = { path = "../../libafl/", features = ["default", "nautilus"] }
libafl_targets = { path = "../../libafl_targets/", features = ["sancov_pcguard_hitcounts", "libfuzzer"] }
# TODO Include it only when building cc
libafl_cc = { path = "../../libafl_cc/" }
clap = { version = "4.0", features = ["derive"] }
mimalloc = { version = "*", default-features = false }
[lib]
name = "nautilus_sync"
crate-type = ["staticlib"]

View File

@ -0,0 +1,125 @@
# Variables
[env]
FUZZER_NAME='fuzzer_libpng_nautilus'
CARGO_TARGET_DIR = { value = "${PROJECT_DIR}/target", condition = { env_not_set = ["CARGO_TARGET_DIR"] } }
LIBAFL_CC = '${CARGO_TARGET_DIR}/release/libafl_cc'
LIBAFL_CXX = '${CARGO_TARGET_DIR}/release/libafl_cxx'
FUZZER = '${CARGO_TARGET_DIR}/release/${FUZZER_NAME}'
PROJECT_DIR = { script = ["pwd"] }
[tasks.unsupported]
script_runner="@shell"
script='''
echo "Cargo-make not integrated yet on this platform"
'''
# libpng
[tasks.libpng]
linux_alias = "libpng_unix"
mac_alias = "libpng_unix"
windows_alias = "unsupported"
[tasks.libpng_unix]
condition = { files_not_exist = ["./libpng-1.6.37"]}
script_runner="@shell"
script='''
wget https://deac-fra.dl.sourceforge.net/project/libpng/libpng16/1.6.37/libpng-1.6.37.tar.xz
tar -xvf libpng-1.6.37.tar.xz
'''
# Compilers
[tasks.cxx]
linux_alias = "cxx_unix"
mac_alias = "cxx_unix"
windows_alias = "unsupported"
[tasks.cxx_unix]
command = "cargo"
args = ["build" , "--release"]
[tasks.cc]
linux_alias = "cc_unix"
mac_alias = "cc_unix"
windows_alias = "unsupported"
[tasks.cc_unix]
command = "cargo"
args = ["build" , "--release"]
# Library
[tasks.lib]
linux_alias = "lib_unix"
mac_alias = "lib_unix"
windows_alias = "unsupported"
[tasks.lib_unix]
script_runner="@shell"
script='''
cd libpng-1.6.37 && ./configure --enable-shared=no --with-pic=yes --enable-hardware-optimizations=yes
cd "${PROJECT_DIR}"
cp ../baby_fuzzer_nautilus/grammar.json .
make -C libpng-1.6.37 CC="${CARGO_TARGET_DIR}/release/libafl_cc" CXX="${CARGO_TARGET_DIR}/release/libafl_cxx"
'''
dependencies = [ "libpng", "cxx", "cc" ]
# Harness
[tasks.fuzzer]
linux_alias = "fuzzer_unix"
mac_alias = "fuzzer_unix"
windows_alias = "unsupported"
[tasks.fuzzer_unix]
command = "${CARGO_TARGET_DIR}/release/libafl_cxx"
args = ["${PROJECT_DIR}/../libfuzzer_libpng/harness.cc", "${PROJECT_DIR}/libpng-1.6.37/.libs/libpng16.a", "-I", "${PROJECT_DIR}/libpng-1.6.37/", "-o", "${FUZZER_NAME}", "-lm", "-lz"]
dependencies = [ "lib", "cxx", "cc" ]
# Run the fuzzer
[tasks.run]
linux_alias = "run_unix"
mac_alias = "run_unix"
windows_alias = "unsupported"
[tasks.run_unix]
script_runner = "@shell"
script='''
./${FUZZER_NAME} --cores 0
'''
dependencies = [ "fuzzer" ]
[tasks.run_unix_sync]
script_runner = "@shell"
script='''
./${FUZZER_NAME} --cores 0 -b 1337
'''
dependencies = [ "fuzzer" ]
# Test
[tasks.test]
linux_alias = "test_unix"
mac_alias = "test_unix"
windows_alias = "unsupported"
[tasks.test_unix]
script_runner = "@shell"
script='''
rm -rf libafl_unix_shmem_server || true
timeout 11s ./${FUZZER_NAME} --cores 0 2>/dev/null &
'''
dependencies = [ "fuzzer" ]
# Clean up
[tasks.clean]
linux_alias = "clean_unix"
mac_alias = "clean_unix"
windows_alias = "unsupported"
[tasks.clean_unix]
# Disable default `clean` definition
clear = true
script_runner="@shell"
script='''
rm -f ./${FUZZER_NAME}
make -C libpng-1.6.37 clean
cargo clean
'''

View File

@ -0,0 +1 @@
nightly

View File

@ -0,0 +1,59 @@
use std::{env, process::Command, str};
use libafl_cc::{ClangWrapper, CompilerWrapper};
fn find_libpython() -> Result<String, String> {
match Command::new("python3")
.args(&["-m", "find_libpython"])
.output()
{
Ok(output) => {
let shared_obj = str::from_utf8(&output.stdout).unwrap_or_default().trim();
if shared_obj.is_empty() {
return Err("Empty return from python3 -m find_libpython".to_string());
}
Ok(shared_obj.to_owned())
}
Err(err) => Err(format!(
"Could not execute python3 -m find_libpython: {err:?}"
)),
}
}
pub fn main() {
let args: Vec<String> = env::args().collect();
if args.len() > 1 {
let mut dir = env::current_exe().unwrap();
let wrapper_name = dir.file_name().unwrap().to_str().unwrap();
let is_cpp = match wrapper_name[wrapper_name.len()-2..].to_lowercase().as_str() {
"cc" => false,
"++" | "pp" | "xx" => true,
_ => panic!("Could not figure out if c or c++ wrapper was called. Expected {dir:?} to end with c or cxx"),
};
dir.pop();
let libpython = find_libpython().expect("Failed to find libpython");
let mut cc = ClangWrapper::new();
if let Some(code) = cc
.cpp(is_cpp)
// silence the compiler wrapper output, needed for some configure scripts.
.silence(true)
.parse_args(&args)
.expect("Failed to parse the command line")
.link_staticlib(&dir, "nautilus_sync")
.add_arg("-fsanitize-coverage=trace-pc-guard")
// needed by Nautilus
.add_link_arg(libpython)
.add_link_arg("-lutil")
.run()
.expect("Failed to run the wrapped compiler")
{
std::process::exit(code);
}
} else {
panic!("LibAFL CC: No Arguments given");
}
}

View File

@ -0,0 +1,5 @@
pub mod libafl_cc;
fn main() {
libafl_cc::main();
}

View File

@ -0,0 +1,262 @@
use mimalloc::MiMalloc;
#[global_allocator]
static GLOBAL: MiMalloc = MiMalloc;
#[cfg(windows)]
use std::ptr::write_volatile;
use std::{env, net::SocketAddr, path::PathBuf, time::Duration};
use clap::{self, Parser};
use libafl::{
bolts::{
core_affinity::Cores,
current_nanos,
launcher::Launcher,
rands::StdRand,
shmem::{ShMemProvider, StdShMemProvider},
tuples::tuple_list,
},
corpus::{InMemoryCorpus, OnDiskCorpus},
events::{llmp::LlmpEventConverter, EventConfig},
executors::{inprocess::InProcessExecutor, ExitKind},
feedback_or,
feedbacks::{CrashFeedback, MaxMapFeedback, NautilusChunksMetadata, NautilusFeedback},
fuzzer::{Fuzzer, StdFuzzer},
generators::{NautilusContext, NautilusGenerator},
inputs::{NautilusInput, NautilusToBytesInputConverter},
monitors::SimpleMonitor,
mutators::{
NautilusRandomMutator, NautilusRecursionMutator, NautilusSpliceMutator, StdScheduledMutator,
},
none_input_converter,
schedulers::QueueScheduler,
stages::{mutational::StdMutationalStage, sync::SyncFromBrokerStage},
state::{HasMetadata, StdState},
Error,
};
use libafl_targets::{libfuzzer_initialize, libfuzzer_test_one_input, std_edges_map_observer};
/// Parse a millis string to a [`Duration`]. Used for arg parsing.
fn timeout_from_millis_str(time: &str) -> Result<Duration, Error> {
Ok(Duration::from_millis(time.parse()?))
}
/// The commandline args this fuzzer accepts
#[derive(Debug, Parser)]
#[command(
name = "libfuzzer_libpng_launcher",
about = "A libfuzzer-like fuzzer for libpng with llmp-multithreading support and a launcher",
author = "Andrea Fioraldi <andreafioraldi@gmail.com>, Dominik Maier <domenukk@gmail.com>"
)]
struct Opt {
#[arg(
short,
long,
value_parser = Cores::from_cmdline,
help = "Spawn a client in each of the provided cores. Broker runs in the 0th core. 'all' to select all available cores. 'none' to run a client without binding to any core. eg: '1,2-4,6' selects the cores 1,2,3,4,6.",
name = "CORES"
)]
cores: Cores,
#[arg(
short = 'p',
long,
help = "Choose the broker TCP port, default is 1337",
name = "PORT",
default_value = "1338"
)]
broker_port: u16,
#[arg(
short = 'b',
long,
help = "Specify a BytesInput broker TCP port",
name = "BYTESPORT"
)]
bytes_broker_port: Option<u16>,
#[arg(short = 'a', long, help = "Specify a remote broker", name = "REMOTE")]
remote_broker_addr: Option<SocketAddr>,
#[arg(
short,
long,
help = "Set the output directory, default is ./out",
name = "OUTPUT",
default_value = "./out"
)]
output: PathBuf,
#[arg(
value_parser = timeout_from_millis_str,
short,
long,
help = "Set the exeucution timeout in milliseconds, default is 10000",
name = "TIMEOUT",
default_value = "10000"
)]
timeout: Duration,
}
/// The main fn, `no_mangle` as it is a C symbol
#[no_mangle]
pub fn libafl_main() {
// Registry the metadata types used in this fuzzer
// Needed only on no_std
//RegistryBuilder::register::<Tokens>();
let opt = Opt::parse();
let broker_port = opt.broker_port;
let cores = opt.cores;
println!(
"Workdir: {:?}",
env::current_dir().unwrap().to_string_lossy().to_string()
);
let shmem_provider = StdShMemProvider::new().expect("Failed to init shared memory");
// The Monitor trait define how the fuzzer stats are reported to the user
let monitor = SimpleMonitor::new(|s| println!("{s}"));
let context = NautilusContext::from_file(15, "grammar.json");
let mut event_converter = opt.bytes_broker_port.map(|port| {
LlmpEventConverter::new_on_port(
shmem_provider.clone(),
port,
Some(NautilusToBytesInputConverter::new(&context)),
none_input_converter!(),
)
.unwrap()
});
let mut run_client = |state: Option<_>, mut mgr, _core_id| {
let mut bytes = vec![];
// The closure that we want to fuzz
let mut harness = |input: &NautilusInput| {
input.unparse(&context, &mut bytes);
libfuzzer_test_one_input(&bytes);
ExitKind::Ok
};
// Create an observation channel using the coverage map
let observer = unsafe { std_edges_map_observer("edges") };
// Feedback to rate the interestingness of an input
let mut feedback = feedback_or!(
MaxMapFeedback::new(&observer),
NautilusFeedback::new(&context)
);
// A feedback to choose if an input is a solution or not
let mut objective = CrashFeedback::new();
// create a State from scratch
let mut state = state.unwrap_or_else(|| {
StdState::new(
// RNG
StdRand::with_seed(current_nanos()),
// Corpus that will be evolved, we keep it in memory for performance
InMemoryCorpus::new(),
// Corpus in which we store solutions (crashes in this example),
// on disk so the user can get them after stopping the fuzzer
OnDiskCorpus::new(PathBuf::from("./crashes")).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()
});
if state.metadata().get::<NautilusChunksMetadata>().is_none() {
state.add_metadata(NautilusChunksMetadata::new("/tmp/".into()));
}
// A queue policy to get testcasess from the corpus
let scheduler = QueueScheduler::new();
// A fuzzer with feedbacks and a corpus scheduler
let mut fuzzer = StdFuzzer::new(scheduler, feedback, objective);
// Create the executor for an in-process function with just one observer
let mut executor = InProcessExecutor::new(
&mut harness,
tuple_list!(observer),
&mut fuzzer,
&mut state,
&mut mgr,
)
.expect("Failed to create the Executor");
// The actual target run starts here.
// Call LLVMFUzzerInitialize() if present.
let args: Vec<String> = env::args().collect();
if libfuzzer_initialize(&args) == -1 {
println!("Warning: LLVMFuzzerInitialize failed with -1")
}
let mut generator = NautilusGenerator::new(&context);
// Generate 8 initial inputs
state
.generate_initial_inputs_forced(&mut fuzzer, &mut executor, &mut generator, &mut mgr, 8)
.expect("Failed to generate the initial corpus");
// Setup a mutational stage with a basic bytes mutator
let mutator = StdScheduledMutator::with_max_stack_pow(
tuple_list!(
NautilusRandomMutator::new(&context),
NautilusRandomMutator::new(&context),
NautilusRandomMutator::new(&context),
NautilusRandomMutator::new(&context),
NautilusRandomMutator::new(&context),
NautilusRandomMutator::new(&context),
NautilusRecursionMutator::new(&context),
NautilusSpliceMutator::new(&context),
NautilusSpliceMutator::new(&context),
NautilusSpliceMutator::new(&context),
),
2,
);
if let Some(conv) = event_converter.take() {
let mut stages = tuple_list!(
StdMutationalStage::new(mutator),
SyncFromBrokerStage::new(conv)
);
fuzzer
.fuzz_loop(&mut stages, &mut executor, &mut state, &mut mgr)
.expect("Error in the fuzzing loop");
} else {
let mut stages = tuple_list!(StdMutationalStage::new(mutator));
fuzzer
.fuzz_loop(&mut stages, &mut executor, &mut state, &mut mgr)
.expect("Error in the fuzzing loop");
}
Ok(())
};
match Launcher::builder()
.shmem_provider(shmem_provider)
.configuration(EventConfig::from_name("nautilus"))
.monitor(monitor)
.run_client(&mut run_client)
.cores(&cores)
.broker_port(broker_port)
.remote_broker_addr(opt.remote_broker_addr)
.stdout_file(Some("/dev/null"))
.build()
.launch()
{
Ok(()) => (),
Err(Error::ShuttingDown) => println!("Fuzzing stopped by user. Good bye."),
Err(err) => panic!("Failed to run launcher: {:?}", err),
}
}

View File

@ -100,7 +100,7 @@ concat-idents = { version = "1.1.3", optional = true }
# AGPL # AGPL
# !!! this create requires nightly # !!! this create requires nightly
grammartec = { version = "0.2", optional = true } grammartec = { version = "0.3", optional = true }
[target.'cfg(unix)'.dependencies] [target.'cfg(unix)'.dependencies]
libc = "0.2" # For (*nix) libc libc = "0.2" # For (*nix) libc

View File

@ -47,7 +47,7 @@ use crate::{
}, },
executors::{Executor, HasObservers}, executors::{Executor, HasObservers},
fuzzer::{EvaluatorObservers, ExecutionProcessor}, fuzzer::{EvaluatorObservers, ExecutionProcessor},
inputs::{Input, UsesInput}, inputs::{Input, InputConverter, UsesInput},
monitors::Monitor, monitors::Monitor,
state::{HasClientPerfMonitor, HasExecutions, HasMetadata, UsesState}, state::{HasClientPerfMonitor, HasExecutions, HasMetadata, UsesState},
Error, Error,
@ -1014,6 +1014,352 @@ where
} }
} }
/// A manager-like llmp client that converts between input types
pub struct LlmpEventConverter<IC, ICB, DI, S, SP>
where
S: UsesInput,
SP: ShMemProvider + 'static,
IC: InputConverter<From = S::Input, To = DI>,
ICB: InputConverter<From = DI, To = S::Input>,
DI: Input,
{
llmp: LlmpClient<SP>,
/// The custom buf handler
custom_buf_handlers: Vec<Box<CustomBufHandlerFn<S>>>,
#[cfg(feature = "llmp_compression")]
compressor: GzipCompressor,
converter: Option<IC>,
converter_back: Option<ICB>,
phantom: PhantomData<S>,
}
impl<IC, ICB, DI, S, SP> core::fmt::Debug for LlmpEventConverter<IC, ICB, DI, S, SP>
where
SP: ShMemProvider + 'static,
S: UsesInput,
IC: InputConverter<From = S::Input, To = DI>,
ICB: InputConverter<From = DI, To = S::Input>,
DI: Input,
{
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
let mut debug_struct = f.debug_struct("LlmpEventConverter");
let debug = debug_struct.field("llmp", &self.llmp);
//.field("custom_buf_handlers", &self.custom_buf_handlers)
#[cfg(feature = "llmp_compression")]
let debug = debug.field("compressor", &self.compressor);
debug
.field("converter", &self.converter)
.field("converter_back", &self.converter_back)
.field("phantom", &self.phantom)
.finish_non_exhaustive()
}
}
impl<IC, ICB, DI, S, SP> LlmpEventConverter<IC, ICB, DI, S, SP>
where
S: UsesInput + HasExecutions + HasClientPerfMonitor,
SP: ShMemProvider + 'static,
IC: InputConverter<From = S::Input, To = DI>,
ICB: InputConverter<From = DI, To = S::Input>,
DI: Input,
{
/// Create a client from a raw llmp client
pub fn new(
llmp: LlmpClient<SP>,
converter: Option<IC>,
converter_back: Option<ICB>,
) -> Result<Self, Error> {
Ok(Self {
llmp,
#[cfg(feature = "llmp_compression")]
compressor: GzipCompressor::new(COMPRESS_THRESHOLD),
converter,
converter_back,
phantom: PhantomData,
custom_buf_handlers: vec![],
})
}
/// Create a client from port and the input converters
#[cfg(feature = "std")]
pub fn new_on_port(
shmem_provider: SP,
port: u16,
converter: Option<IC>,
converter_back: Option<ICB>,
) -> Result<Self, Error> {
Ok(Self {
llmp: LlmpClient::create_attach_to_tcp(shmem_provider, port)?,
#[cfg(feature = "llmp_compression")]
compressor: GzipCompressor::new(COMPRESS_THRESHOLD),
converter,
converter_back,
phantom: PhantomData,
custom_buf_handlers: vec![],
})
}
/// If a client respawns, it may reuse the existing connection, previously stored by [`LlmpClient::to_env()`].
#[cfg(feature = "std")]
pub fn existing_client_from_env(
shmem_provider: SP,
env_name: &str,
converter: Option<IC>,
converter_back: Option<ICB>,
) -> Result<Self, Error> {
Ok(Self {
llmp: LlmpClient::on_existing_from_env(shmem_provider, env_name)?,
#[cfg(feature = "llmp_compression")]
compressor: GzipCompressor::new(COMPRESS_THRESHOLD),
phantom: PhantomData,
converter,
converter_back,
custom_buf_handlers: vec![],
})
}
// TODO other new_* routines
/// Check if it can convert the input
pub fn can_convert(&self) -> bool {
self.converter.is_some()
}
/// Check if it can convert the input back
pub fn can_convert_back(&self) -> bool {
self.converter_back.is_some()
}
/// Describe the client event mgr's llmp parts in a restorable fashion
pub fn describe(&self) -> Result<LlmpClientDescription, Error> {
self.llmp.describe()
}
/// Write the config for a client [`EventManager`] to env vars, a new client can reattach using [`LlmpEventConverter::existing_client_from_env()`].
#[cfg(feature = "std")]
pub fn to_env(&self, env_name: &str) {
self.llmp.to_env(env_name).unwrap();
}
// Handle arriving events in the client
fn handle_in_client<E, EM, Z>(
&mut self,
fuzzer: &mut Z,
executor: &mut E,
state: &mut S,
manager: &mut EM,
_client_id: u32,
event: Event<DI>,
) -> Result<(), Error>
where
E: Executor<EM, Z> + HasObservers<State = S>,
EM: UsesState<State = S> + EventFirer,
for<'a> E::Observers: Deserialize<'a>,
Z: ExecutionProcessor<E::Observers, State = S> + EvaluatorObservers<E::Observers>,
{
match event {
Event::NewTestcase {
input,
client_config: _,
exit_kind: _,
corpus_size: _,
observers_buf: _, // Useless as we are converting between types
time: _,
executions: _,
} => {
#[cfg(feature = "std")]
println!("Received new Testcase to convert from {_client_id}");
let Some(converter) = self.converter_back.as_mut() else {
return Ok(());
};
let _res = fuzzer.evaluate_input_with_observers::<E, EM>(
state,
executor,
manager,
converter.convert(input)?,
false,
)?;
#[cfg(feature = "std")]
if let Some(item) = _res.1 {
println!("Added received Testcase as item #{item}");
}
Ok(())
}
Event::CustomBuf { tag, buf } => {
for handler in &mut self.custom_buf_handlers {
if handler(state, &tag, &buf)? == CustomBufEventResult::Handled {
break;
}
}
Ok(())
}
_ => Err(Error::unknown(format!(
"Received illegal message that message should not have arrived: {:?}.",
event.name()
))),
}
}
/// Handle arriving events in the client
#[allow(clippy::unused_self)]
pub fn process<E, EM, Z>(
&mut self,
fuzzer: &mut Z,
state: &mut S,
executor: &mut E,
manager: &mut EM,
) -> Result<usize, Error>
where
E: Executor<EM, Z> + HasObservers<State = S>,
EM: UsesState<State = S> + EventFirer,
for<'a> E::Observers: Deserialize<'a>,
Z: ExecutionProcessor<E::Observers, State = S> + EvaluatorObservers<E::Observers>,
{
// TODO: Get around local event copy by moving handle_in_client
let self_id = self.llmp.sender.id;
let mut count = 0;
while let Some((client_id, tag, _flags, msg)) = self.llmp.recv_buf_with_flags()? {
assert!(
tag != _LLMP_TAG_EVENT_TO_BROKER,
"EVENT_TO_BROKER parcel should not have arrived in the client!"
);
if client_id == self_id {
continue;
}
#[cfg(not(feature = "llmp_compression"))]
let event_bytes = msg;
#[cfg(feature = "llmp_compression")]
let compressed;
#[cfg(feature = "llmp_compression")]
let event_bytes = if _flags & LLMP_FLAG_COMPRESSED == LLMP_FLAG_COMPRESSED {
compressed = self.compressor.decompress(msg)?;
&compressed
} else {
msg
};
let event: Event<DI> = postcard::from_bytes(event_bytes)?;
self.handle_in_client(fuzzer, executor, state, manager, client_id, event)?;
count += 1;
}
Ok(count)
}
}
impl<IC, ICB, DI, S, SP> UsesState for LlmpEventConverter<IC, ICB, DI, S, SP>
where
S: UsesInput,
SP: ShMemProvider,
IC: InputConverter<From = S::Input, To = DI>,
ICB: InputConverter<From = DI, To = S::Input>,
DI: Input,
{
type State = S;
}
impl<IC, ICB, DI, S, SP> EventFirer for LlmpEventConverter<IC, ICB, DI, S, SP>
where
S: UsesInput,
SP: ShMemProvider,
IC: InputConverter<From = S::Input, To = DI>,
ICB: InputConverter<From = DI, To = S::Input>,
DI: Input,
{
#[cfg(feature = "llmp_compression")]
fn fire(
&mut self,
_state: &mut Self::State,
event: Event<<Self::State as UsesInput>::Input>,
) -> Result<(), Error> {
if self.converter.is_none() {
return Ok(());
}
// Filter out non interestign events and convert `NewTestcase`
let converted_event = match event {
Event::NewTestcase {
input,
client_config,
exit_kind,
corpus_size,
observers_buf,
time,
executions,
} => Event::NewTestcase {
input: self.converter.as_mut().unwrap().convert(input)?,
client_config,
exit_kind,
corpus_size,
observers_buf,
time,
executions,
},
Event::CustomBuf { buf, tag } => Event::CustomBuf { buf, tag },
_ => {
return Ok(());
}
};
let serialized = postcard::to_allocvec(&converted_event)?;
let flags: Flags = LLMP_FLAG_INITIALIZED;
match self.compressor.compress(&serialized)? {
Some(comp_buf) => {
self.llmp.send_buf_with_flags(
LLMP_TAG_EVENT_TO_BOTH,
flags | LLMP_FLAG_COMPRESSED,
&comp_buf,
)?;
}
None => {
self.llmp.send_buf(LLMP_TAG_EVENT_TO_BOTH, &serialized)?;
}
}
Ok(())
}
#[cfg(not(feature = "llmp_compression"))]
fn fire(
&mut self,
_state: &mut Self::State,
event: Event<<Self::State as UsesInput>::Input>,
) -> Result<(), Error> {
if self.converter.is_none() {
return Ok(());
}
// Filter out non interestign events and convert `NewTestcase`
let converted_event = match event {
Event::NewTestcase {
input,
client_config,
exit_kind,
corpus_size,
observers_buf,
time,
executions,
} => Event::NewTestcase {
input: self.converter.as_mut().unwrap().convert(input)?,
client_config,
exit_kind,
corpus_size,
observers_buf,
time,
executions,
},
Event::CustomBuf { buf, tag } => Event::CustomBuf { buf, tag },
_ => {
return Ok(());
}
};
let serialized = postcard::to_allocvec(&converted_event)?;
self.llmp.send_buf(LLMP_TAG_EVENT_TO_BOTH, &serialized)?;
Ok(())
}
}
#[cfg(test)] #[cfg(test)]
#[cfg(feature = "std")] #[cfg(feature = "std")]
mod tests { mod tests {

View File

@ -15,10 +15,11 @@ pub use generalized::*;
#[cfg(feature = "nautilus")] #[cfg(feature = "nautilus")]
pub mod nautilus; pub mod nautilus;
use alloc::{ use alloc::{
boxed::Box,
string::{String, ToString}, string::{String, ToString},
vec::Vec, vec::Vec,
}; };
use core::{clone::Clone, fmt::Debug}; use core::{clone::Clone, fmt::Debug, marker::PhantomData};
#[cfg(feature = "std")] #[cfg(feature = "std")]
use std::{fs::File, hash::Hash, io::Read, path::Path}; use std::{fs::File, hash::Hash, io::Read, path::Path};
@ -79,6 +80,25 @@ pub trait Input: Clone + Serialize + serde::de::DeserializeOwned + Debug {
fn wrapped_as_testcase(&mut self) {} fn wrapped_as_testcase(&mut self) {}
} }
/// Convert between two input types with a state
pub trait InputConverter: Debug {
/// Source type
type From: Input;
/// Destination type
type To: Input;
/// Convert the src type to the dest
fn convert(&mut self, input: Self::From) -> Result<Self::To, Error>;
}
/// `None` type to satisfy the type infearence in an `Option`
#[macro_export]
macro_rules! none_input_converter {
() => {
None::<$crate::inputs::ClosureInputConverter<_, _>>
};
}
/// An input for tests, mainly. There is no real use much else. /// An input for tests, mainly. There is no real use much else.
#[derive(Copy, Clone, Serialize, Deserialize, Debug, Hash)] #[derive(Copy, Clone, Serialize, Deserialize, Debug, Hash)]
pub struct NopInput {} pub struct NopInput {}
@ -116,3 +136,74 @@ pub trait UsesInput {
/// Type which will be used throughout this state. /// Type which will be used throughout this state.
type Input: Input; type Input: Input;
} }
#[derive(Debug)]
/// Basic `InputConverter` with just one type that is not converting
pub struct NopInputConverter<I> {
phantom: PhantomData<I>,
}
impl<I> Default for NopInputConverter<I> {
fn default() -> Self {
Self {
phantom: PhantomData,
}
}
}
impl<I> InputConverter for NopInputConverter<I>
where
I: Input,
{
type From = I;
type To = I;
fn convert(&mut self, input: Self::From) -> Result<Self::To, Error> {
Ok(input)
}
}
/// `InputConverter` that uses a closure to convert
pub struct ClosureInputConverter<F, T>
where
F: Input,
T: Input,
{
convert_cb: Box<dyn FnMut(F) -> Result<T, Error>>,
}
impl<F, T> Debug for ClosureInputConverter<F, T>
where
F: Input,
T: Input,
{
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("ClosureInputConverter")
.finish_non_exhaustive()
}
}
impl<F, T> ClosureInputConverter<F, T>
where
F: Input,
T: Input,
{
/// Create a new converter using two closures, use None to forbid the conversion or the conversion back
#[must_use]
pub fn new(convert_cb: Box<dyn FnMut(F) -> Result<T, Error>>) -> Self {
Self { convert_cb }
}
}
impl<F, T> InputConverter for ClosureInputConverter<F, T>
where
F: Input,
T: Input,
{
type From = F;
type To = T;
fn convert(&mut self, input: Self::From) -> Result<Self::To, Error> {
(self.convert_cb)(input)
}
}

View File

@ -15,7 +15,12 @@ use grammartec::{
}; };
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::{bolts::HasLen, generators::nautilus::NautilusContext, inputs::Input}; use crate::{
bolts::HasLen,
generators::nautilus::NautilusContext,
inputs::{BytesInput, Input, InputConverter},
Error,
};
/// An [`Input`] implementation for `Nautilus` grammar. /// An [`Input`] implementation for `Nautilus` grammar.
#[derive(Serialize, Deserialize, Clone, Debug)] #[derive(Serialize, Deserialize, Clone, Debug)]
@ -104,3 +109,28 @@ impl Hash for NautilusInput {
self.tree().sizes.hash(state); self.tree().sizes.hash(state);
} }
} }
/// `InputConverter` to convert from `NautilusInput` to `BytesInput`
#[derive(Debug)]
pub struct NautilusToBytesInputConverter<'a> {
ctx: &'a NautilusContext,
}
impl<'a> NautilusToBytesInputConverter<'a> {
#[must_use]
/// Create a new `NautilusToBytesInputConverter` from a context
pub fn new(ctx: &'a NautilusContext) -> Self {
Self { ctx }
}
}
impl<'a> InputConverter for NautilusToBytesInputConverter<'a> {
type From = NautilusInput;
type To = BytesInput;
fn convert(&mut self, input: Self::From) -> Result<Self::To, Error> {
let mut bytes = vec![];
input.unparse(self.ctx, &mut bytes);
Ok(BytesInput::new(bytes))
}
}

View File

@ -884,7 +884,7 @@ where
{ {
/// Creates a new [`MapObserver`] /// Creates a new [`MapObserver`]
/// ///
/// # Safety /// # Note
/// Will get a pointer to the map and dereference it at any point in time. /// Will get a pointer to the map and dereference it at any point in time.
/// The map must not move in memory! /// The map must not move in memory!
#[must_use] #[must_use]

View File

@ -10,11 +10,14 @@ use std::{
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::{ use crate::{
corpus::CorpusId, bolts::{current_time, shmem::ShMemProvider},
fuzzer::Evaluator, corpus::{Corpus, CorpusId},
inputs::{Input, UsesInput}, events::{llmp::LlmpEventConverter, Event, EventConfig, EventFirer},
executors::{Executor, ExitKind, HasObservers},
fuzzer::{Evaluator, EvaluatorObservers, ExecutionProcessor},
inputs::{Input, InputConverter, UsesInput},
stages::Stage, stages::Stage,
state::{HasClientPerfMonitor, HasCorpus, HasMetadata, HasRand, UsesState}, state::{HasClientPerfMonitor, HasCorpus, HasExecutions, HasMetadata, HasRand, UsesState},
Error, Error,
}; };
@ -186,3 +189,129 @@ where
} }
} }
} }
/// Metadata used to store information about the last sent testcase with `SyncFromBrokerStage`
#[derive(Serialize, Deserialize, Debug)]
pub struct SyncFromBrokerMetadata {
/// The `CorpusId` of the last sent testcase
pub last_id: Option<CorpusId>,
}
crate::impl_serdeany!(SyncFromBrokerMetadata);
impl SyncFromBrokerMetadata {
/// Create a new [`struct@SyncFromBrokerMetadata`]
#[must_use]
pub fn new(last_id: Option<CorpusId>) -> Self {
Self { last_id }
}
}
/// A stage that loads testcases from disk to sync with other fuzzers such as AFL++
#[derive(Debug)]
pub struct SyncFromBrokerStage<IC, ICB, DI, S, SP>
where
SP: ShMemProvider + 'static,
S: UsesInput,
IC: InputConverter<From = S::Input, To = DI>,
ICB: InputConverter<From = DI, To = S::Input>,
DI: Input,
{
client: LlmpEventConverter<IC, ICB, DI, S, SP>,
}
impl<IC, ICB, DI, S, SP> UsesState for SyncFromBrokerStage<IC, ICB, DI, S, SP>
where
SP: ShMemProvider + 'static,
S: UsesInput,
IC: InputConverter<From = S::Input, To = DI>,
ICB: InputConverter<From = DI, To = S::Input>,
DI: Input,
{
type State = S;
}
impl<E, EM, IC, ICB, DI, S, SP, Z> Stage<E, EM, Z> for SyncFromBrokerStage<IC, ICB, DI, S, SP>
where
EM: UsesState<State = S> + EventFirer,
S: UsesInput + HasClientPerfMonitor + HasExecutions + HasCorpus + HasRand + HasMetadata,
SP: ShMemProvider,
E: HasObservers<State = S> + Executor<EM, Z>,
for<'a> E::Observers: Deserialize<'a>,
Z: EvaluatorObservers<E::Observers, State = S> + ExecutionProcessor<E::Observers, State = S>,
IC: InputConverter<From = S::Input, To = DI>,
ICB: InputConverter<From = DI, To = S::Input>,
DI: Input,
{
#[inline]
fn perform(
&mut self,
fuzzer: &mut Z,
executor: &mut E,
state: &mut Z::State,
manager: &mut EM,
_corpus_idx: CorpusId,
) -> Result<(), Error> {
if self.client.can_convert() {
let last_id = state
.metadata()
.get::<SyncFromBrokerMetadata>()
.and_then(|m| m.last_id);
let mut cur_id =
last_id.map_or_else(|| state.corpus().first(), |id| state.corpus().next(id));
while let Some(id) = cur_id {
let input = state.corpus().get(id)?.borrow_mut().load_input()?.clone();
self.client.fire(
state,
Event::NewTestcase {
input,
observers_buf: None,
exit_kind: ExitKind::Ok,
corpus_size: 0, // TODO choose if sending 0 or the actual real value
client_config: EventConfig::AlwaysUnique,
time: current_time(),
executions: 0,
},
)?;
cur_id = state.corpus().next(id);
}
let last = state.corpus().last();
if last_id.is_none() {
state
.metadata_mut()
.insert(SyncFromBrokerMetadata::new(last));
} else {
state
.metadata_mut()
.get_mut::<SyncFromBrokerMetadata>()
.unwrap()
.last_id = last;
}
}
self.client.process(fuzzer, state, executor, manager)?;
#[cfg(feature = "introspection")]
state.introspection_monitor_mut().finish_stage();
Ok(())
}
}
impl<IC, ICB, DI, S, SP> SyncFromBrokerStage<IC, ICB, DI, S, SP>
where
SP: ShMemProvider + 'static,
S: UsesInput,
IC: InputConverter<From = S::Input, To = DI>,
ICB: InputConverter<From = DI, To = S::Input>,
DI: Input,
{
/// Creates a new [`SyncFromBrokerStage`]
#[must_use]
pub fn new(client: LlmpEventConverter<IC, ICB, DI, S, SP>) -> Self {
Self { client }
}
}

View File

@ -8,7 +8,7 @@ use which::which;
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 = "d15b4d47e4f4ee3e3311e57183c2b7c1a12456f2"; const QEMU_REVISION: &str = "f6a2e732e8e225ebb8d1a9399561af7330af31b3";
fn build_dep_check(tools: &[&str]) { fn build_dep_check(tools: &[&str]) {
for tool in tools { for tool in tools {