From 331ff66dd10e873ab91ba486204d197baa18b9e1 Mon Sep 17 00:00:00 2001 From: Valentin Huber Date: Fri, 31 May 2024 09:43:42 +0000 Subject: [PATCH] merging upstream again --- Cargo.toml | 2 +- fuzzers/coreutils_differential/.gitignore | 1 + fuzzers/coreutils_differential/Cargo.toml | 29 ++ fuzzers/coreutils_differential/Makefile.toml | 160 +++++++++ fuzzers/coreutils_differential/README.md | 25 ++ fuzzers/coreutils_differential/coverage.c | 52 +++ .../get_guard_num/Cargo.toml | 13 + .../get_guard_num/src/lib.rs | 69 ++++ .../setup_guard_redirection/Cargo.toml | 18 + .../setup_guard_redirection/src/lib.rs | 159 +++++++++ fuzzers/coreutils_differential/src/base64.rs | 297 ++++++++++++++++ .../src/generic/executor.rs | 99 ++++++ .../coreutils_differential/src/generic/mod.rs | 3 + .../src/generic/shmem.rs | 62 ++++ .../src/generic/stdio.rs | 148 ++++++++ fuzzers/coreutils_differential/src/main.rs | 317 ++++++++++++++++++ 16 files changed, 1453 insertions(+), 1 deletion(-) create mode 100644 fuzzers/coreutils_differential/.gitignore create mode 100644 fuzzers/coreutils_differential/Cargo.toml create mode 100644 fuzzers/coreutils_differential/Makefile.toml create mode 100644 fuzzers/coreutils_differential/README.md create mode 100644 fuzzers/coreutils_differential/coverage.c create mode 100644 fuzzers/coreutils_differential/get_guard_num/Cargo.toml create mode 100644 fuzzers/coreutils_differential/get_guard_num/src/lib.rs create mode 100644 fuzzers/coreutils_differential/setup_guard_redirection/Cargo.toml create mode 100644 fuzzers/coreutils_differential/setup_guard_redirection/src/lib.rs create mode 100644 fuzzers/coreutils_differential/src/base64.rs create mode 100644 fuzzers/coreutils_differential/src/generic/executor.rs create mode 100644 fuzzers/coreutils_differential/src/generic/mod.rs create mode 100644 fuzzers/coreutils_differential/src/generic/shmem.rs create mode 100644 fuzzers/coreutils_differential/src/generic/stdio.rs create mode 100644 fuzzers/coreutils_differential/src/main.rs diff --git a/Cargo.toml b/Cargo.toml index 0c49a0b6df1..7294147d3f6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,7 +19,7 @@ members = [ "utils/build_and_test_fuzzers", "utils/deexit", "utils/libafl_benches", - "utils/gramatron/construct_automata", + "utils/gramatron/construct_automata", "fuzzers/coreutils_report_unguided", ] default-members = [ "libafl", diff --git a/fuzzers/coreutils_differential/.gitignore b/fuzzers/coreutils_differential/.gitignore new file mode 100644 index 00000000000..2bf7cfee263 --- /dev/null +++ b/fuzzers/coreutils_differential/.gitignore @@ -0,0 +1 @@ +crashes* \ No newline at end of file diff --git a/fuzzers/coreutils_differential/Cargo.toml b/fuzzers/coreutils_differential/Cargo.toml new file mode 100644 index 00000000000..fa1013ac90e --- /dev/null +++ b/fuzzers/coreutils_differential/Cargo.toml @@ -0,0 +1,29 @@ +[package] +name = "coreutils_differential" +version = "0.1.0" +authors = ["Valentin Huber +#include + +// #define LOG_COV 1 + +static uint32_t *guard_stop = 0; +static uint32_t *guard_start = 0; + +void __sanitizer_cov_trace_pc_guard_init(uint32_t *start, uint32_t *stop) { + if (start == stop) { +#ifdef LOG_COV + fprintf(stderr, "Skipping initialization"); +#endif + return; + }; + +#ifdef LOG_COV + fprintf(stderr, "Initializing called with start %p and stop %p\n", start, + stop); +#endif + guard_start = start; + guard_stop = stop; + + for (uint32_t *x = start; x < stop; x++) + *x = 0; + +#ifdef LOG_COV + fprintf(stderr, "Done with initialization\n"); +#endif +} + +void __sanitizer_cov_trace_pc_guard(uint32_t *guard) { + // *guard += 1; + *guard = 1; +#ifdef LOG_COV + fprintf(stderr, "Updated guard %p to %u\n", guard, *guard); +#endif +} + +__attribute__((visibility("default"))) size_t get_guard_count() { +#ifdef LOG_COV + fprintf(stderr, "Returned guard count %zu\n", guard_stop - guard_start); +#endif + return guard_stop - guard_start; +} + +__attribute__((visibility("default"))) uint32_t *get_guard_values() { +#ifdef LOG_COV + fprintf(stderr, "Returned guard values %p\n", guard_start); +#endif + return guard_start; +} \ No newline at end of file diff --git a/fuzzers/coreutils_differential/get_guard_num/Cargo.toml b/fuzzers/coreutils_differential/get_guard_num/Cargo.toml new file mode 100644 index 00000000000..9ac85794645 --- /dev/null +++ b/fuzzers/coreutils_differential/get_guard_num/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "get_guard_num" +version = "0.1.0" +authors = ["Valentin Huber (name: &CStr, search_global: bool) -> T { + assert_eq!( + size_of::<*mut c_void>(), + size_of::(), + "T must be the same size as a pointer." + ); + + let handle = if search_global { + RTLD_DEFAULT + } else { + RTLD_NEXT + }; + + let symbol_pointer: *mut c_void = dlsym(handle, name.as_ptr()); + if symbol_pointer.is_null() { + panic!( + "Got a NULL pointer, could not load symbol {:#?}: {:#?}", + name, + dlerror() + ); + } + transmute_copy(&symbol_pointer) +} + +pub type LibcStartMainFunc = fn( + unsafe extern "C" fn(i32, *const *const u8, *const *const u8) -> i32, + i32, + *const *const char, + extern "C" fn(i32, *const *const u8, *const *const u8) -> i32, + extern "C" fn(), + unsafe extern "C" fn(), + *mut c_void, +) -> i32; + +#[no_mangle] +#[allow(clippy::similar_names)] +pub unsafe extern "C" fn __libc_start_main( + _main: unsafe extern "C" fn(i32, *const *const u8, *const *const u8) -> i32, + argc: i32, + argv: *mut *const char, + init: extern "C" fn(i32, *const *const u8, *const *const u8) -> i32, + fini: extern "C" fn(), + rtld_fini: unsafe extern "C" fn(), + stack_end: *mut c_void, +) -> i32 { + let orig_libc_start_main: LibcStartMainFunc = get_symbol(c"__libc_start_main", false); + orig_libc_start_main(main_hook, argc, argv, init, fini, rtld_fini, stack_end) +} + +#[no_mangle] +pub unsafe extern "C" fn main_hook( + _argc: i32, + _argv: *const *const u8, + _env: *const *const u8, +) -> i32 { + let get_guard_count: fn() -> usize = get_symbol(c"get_guard_count", true); + let guard_count = get_guard_count(); + println!("{}", guard_count); + 0 +} diff --git a/fuzzers/coreutils_differential/setup_guard_redirection/Cargo.toml b/fuzzers/coreutils_differential/setup_guard_redirection/Cargo.toml new file mode 100644 index 00000000000..4a36c89a59c --- /dev/null +++ b/fuzzers/coreutils_differential/setup_guard_redirection/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "setup_guard_redirection" +version = "0.1.0" +authors = ["Valentin Huber (name: &CStr, search_global: bool) -> T { + assert_eq!( + size_of::<*mut c_void>(), + size_of::(), + "T must be the same size as a pointer." + ); + + let handle = if search_global { + RTLD_DEFAULT + } else { + RTLD_NEXT + }; + + let symbol_pointer: *mut c_void = dlsym(handle, name.as_ptr()); + if symbol_pointer.is_null() { + panic!( + "Got a NULL pointer, could not load symbol {:#?}: {:#?}", + name, + dlerror() + ); + } + transmute_copy(&symbol_pointer) +} + +pub type LibcStartMainFunc = fn( + unsafe extern "C" fn(i32, *const *const u8, *const *const u8) -> i32, + i32, + *const *const char, + extern "C" fn(i32, *const *const u8, *const *const u8) -> i32, + extern "C" fn(), + unsafe fn(), + *mut c_void, +) -> i32; + +static mut SHMEM_DESCRIPTION: Option = None; +static mut RTLD_FINI: Option = None; + +unsafe fn extract_shmem_description(argc: &mut i32, argv: *mut *const char) { + assert!(*argc > 1, "Did not receive shared memory description"); + let last_arg = argv.offset((*argc - 1).try_into().unwrap()); + assert!( + !last_arg.is_null(), + "Passed a string of length 0 to the wrapper, expecting the shared memory description" + ); + + let shmem_description_string = CStr::from_ptr(*last_arg as *const i8) + .to_str() + .expect("Could not parse shared memory description to string"); + + SHMEM_DESCRIPTION = Some( + serde_json::from_str(shmem_description_string).unwrap_or_else(|e| { + panic!( + "Could not parse shared memory description to struct \"{:?}\" — {:?}", + shmem_description_string, e + ) + }), + ); + + // making the shmem passing transparent to the binary under test + *argc -= 1; + *last_arg = null(); +} + +fn log(s: T) { + OpenOptions::new() + .append(true) + .create(true) + .open("redirection.log") + .expect("Failed to open file") + .write_all( + format!( + "{}: {:?}\n", + SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap() + .as_millis(), + s + ) + .as_bytes(), + ) + .expect("Failed to write to file") +} + +#[no_mangle] +unsafe fn write_guards() { + let result = catch_unwind(AssertUnwindSafe(|| { + let shmem_description = SHMEM_DESCRIPTION.expect("No shared memory descriptor was saved"); + let mut shmem = MmapShMemProvider::default() + .shmem_from_description(shmem_description) + .expect("Could not acquire shared memory"); + let get_guard_count: fn() -> usize = get_symbol(c"get_guard_count", true); + let guard_count = get_guard_count(); + let shmem_len = shmem.len(); + if shmem_len != guard_count { + log(format!( + "Memory sizes don't match. shmem: {}, guards: {}", + shmem_len, guard_count + )); + } else { + let shmem_target = shmem.as_slice_mut(); + + let get_guard_values: fn() -> *const i32 = get_symbol(c"get_guard_values", true); + let guards = get_guard_values(); + let guard_slice = slice::from_raw_parts(guards, guard_count); + shmem_target + .copy_from_slice(&guard_slice.iter().map(|&x| x as u8).collect::>()); + } + })); + + if let Err(e) = result { + log(if let Some(msg) = e.downcast_ref::<&'static str>() { + msg.to_string() + } else if let Some(msg) = e.downcast_ref::() { + msg.clone() + } else { + "Panic occurred but the message is not a string.".to_string() + }) + } + + RTLD_FINI.expect("Did not previously store a reference to the original rtld_fini function")(); +} + +#[no_mangle] +#[allow(clippy::similar_names)] +pub unsafe extern "C" fn __libc_start_main( + main: unsafe extern "C" fn(i32, *const *const u8, *const *const u8) -> i32, + mut argc: i32, + argv: *mut *const char, + init: extern "C" fn(i32, *const *const u8, *const *const u8) -> i32, + fini: extern "C" fn(), + rtld_fini: extern "C" fn(), + stack_end: *mut c_void, +) -> i32 { + extract_shmem_description(&mut argc, argv); + RTLD_FINI = Some(rtld_fini); + let orig_libc_start_main: LibcStartMainFunc = get_symbol(c"__libc_start_main", false); + orig_libc_start_main(main, argc, argv, init, fini, write_guards, stack_end) +} diff --git a/fuzzers/coreutils_differential/src/base64.rs b/fuzzers/coreutils_differential/src/base64.rs new file mode 100644 index 00000000000..7f65e70e483 --- /dev/null +++ b/fuzzers/coreutils_differential/src/base64.rs @@ -0,0 +1,297 @@ +use core::fmt; +use std::{ + borrow::Cow, + ffi::{OsStr, OsString}, + fmt::{Display, Formatter}, + hash::{DefaultHasher, Hash, Hasher}, +}; + +use serde::{Deserialize, Serialize}; + +use libafl::{ + generators::Generator, + inputs::{HasMutatorBytes, Input}, + mutators::{havoc_mutations, MutationResult, Mutator}, + state::HasRand, + Error, SerdeAny, +}; + +use libafl_bolts::{ + prelude::Rand, + tuples::{tuple_list_type, Append}, + HasLen, Named, +}; + +use crate::generic::{executor::ExtractsToCommand, stdio::vec_string_mapper}; + +/// An [`Input`] implementation for coreutils' `base64` +#[derive(Serialize, Deserialize, Clone, Debug, Hash, SerdeAny)] +pub struct Base64Input { + pub input: Vec, + pub decode: bool, + pub ignore_garbage: bool, + pub wrap: Option, +} + +impl Display for Base64Input { + fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), fmt::Error> { + write!( + f, + "input: '{}'", + vec_string_mapper(&Some(self.input.clone())) + )?; + if self.decode { + write!(f, ", decode")?; + } + if self.ignore_garbage { + write!(f, ", ignore_garbage")?; + } + if let Some(wrap) = self.wrap { + write!(f, ", wrap: {}", wrap)?; + } + Ok(()) + } +} + +impl Input for Base64Input { + #[must_use] + fn generate_name(&self, _idx: usize) -> String { + let mut hasher = DefaultHasher::new(); + self.hash(&mut hasher); + format!("{:016x}", hasher.finish()) + } +} + +impl ExtractsToCommand for Base64Input { + #[must_use] + fn get_stdin(&self) -> &Vec { + &self.input + } + + #[must_use] + fn get_args<'a>(&self) -> Vec> { + let mut args = Vec::with_capacity(4); + if self.decode { + args.push(Cow::Borrowed(OsStr::new("-d"))) + } + if self.ignore_garbage { + args.push(Cow::Borrowed(OsStr::new("-i"))) + } + if let Some(w) = &self.wrap { + args.push(Cow::Borrowed(OsStr::new("-w"))); + args.push(Cow::Owned(OsString::from(w.to_string()))) + } + args + } +} + +impl HasMutatorBytes for Base64Input { + fn bytes(&self) -> &[u8] { + &self.input + } + + fn bytes_mut(&mut self) -> &mut [u8] { + &mut self.input + } + + fn resize(&mut self, new_len: usize, value: u8) { + self.input.resize(new_len, value) + } + + fn extend<'a, I: IntoIterator>(&mut self, iter: I) { + self.input.extend(iter) + } + + fn splice( + &mut self, + range: R, + replace_with: I, + ) -> libafl::prelude::alloc::vec::Splice<'_, I::IntoIter> + where + R: std::ops::RangeBounds, + I: IntoIterator, + { + self.input.splice(range, replace_with) + } + + fn drain(&mut self, range: R) -> libafl::prelude::alloc::vec::Drain<'_, u8> + where + R: std::ops::RangeBounds, + { + self.input.drain(range) + } +} + +impl HasLen for Base64Input { + fn len(&self) -> usize { + self.input.len() + } +} + +impl Base64Input { + #[must_use] + pub fn new(input: &[u8], decode: bool, ignore_garbage: bool, wrap: Option) -> Self { + Self { + input: Vec::from(input), + decode, + ignore_garbage, + wrap, + } + } +} + +pub struct Base64Generator { + input_size: u32, +} + +impl Base64Generator { + pub fn new(input_size: u32) -> Self { + Self { input_size } + } +} + +impl Generator for Base64Generator +where + S: HasRand, +{ + fn generate(&mut self, state: &mut S) -> Result { + let input = &generate_bytes(state, self.input_size); + + let rand = state.rand_mut(); + let decode = rand.coinflip(0.5); + let ignore_garbage = rand.coinflip(0.5); + let wrap = rand + .coinflip(0.5) + .then(|| rand.between(i8::MIN as usize, i8::MAX as usize) as i8); + Ok(Base64Input::new(input, decode, ignore_garbage, wrap)) + } +} + +pub struct Base64FlipDecodeMutator; +impl Mutator for Base64FlipDecodeMutator +where + S: HasRand, +{ + fn mutate(&mut self, _state: &mut S, input: &mut Base64Input) -> Result { + input.decode = !input.decode; + Ok(MutationResult::Mutated) + } +} + +impl Named for Base64FlipDecodeMutator { + fn name(&self) -> &Cow<'static, str> { + &Cow::Borrowed("Base64FlipDecodeMutator") + } +} +pub struct Base64FlipIgnoreGarbageMutator; +impl Mutator for Base64FlipIgnoreGarbageMutator +where + S: HasRand, +{ + fn mutate(&mut self, _state: &mut S, input: &mut Base64Input) -> Result { + input.ignore_garbage = !input.ignore_garbage; + Ok(MutationResult::Mutated) + } +} + +impl Named for Base64FlipIgnoreGarbageMutator { + fn name(&self) -> &Cow<'static, str> { + &Cow::Borrowed("Base64FlipIgnoreGarbageMutator") + } +} + +pub struct Base64FlipWrapMutator; +impl Mutator for Base64FlipWrapMutator +where + S: HasRand, +{ + fn mutate(&mut self, state: &mut S, input: &mut Base64Input) -> Result { + match &input.wrap { + None => { + input.wrap = + Some(state.rand_mut().between(i8::MIN as usize, i8::MAX as usize) as i8); + Ok(MutationResult::Mutated) + } + Some(_e) => { + input.wrap = None; + Ok(MutationResult::Mutated) + } + } + } +} + +impl Named for Base64FlipWrapMutator { + fn name(&self) -> &Cow<'static, str> { + &Cow::Borrowed("Base64FlipWrapMutator") + } +} + +pub struct Base64WrapContentMutator; + +impl Mutator for Base64WrapContentMutator +where + S: HasRand, +{ + fn mutate(&mut self, state: &mut S, input: &mut Base64Input) -> Result { + match input.wrap { + Some(_) => { + input.wrap = + Some(state.rand_mut().between(i8::MIN as usize, i8::MAX as usize) as i8); + Ok(MutationResult::Mutated) + } + None => Ok(MutationResult::Skipped), + } + } +} + +impl Named for Base64WrapContentMutator { + fn name(&self) -> &Cow<'static, str> { + &Cow::Borrowed("Base64WrapContentMutator") + } +} + +fn generate_bytes(state: &mut S, len: u32) -> Vec { + (0..len) + .map(|_e| state.rand_mut().below(u8::MAX as usize + 1) as u8) + .collect::>() +} + +pub fn base64_mutators() -> tuple_list_type!( + libafl::mutators::BitFlipMutator, + libafl::mutators::ByteFlipMutator, + libafl::mutators::ByteIncMutator, + libafl::mutators::ByteDecMutator, + libafl::mutators::ByteNegMutator, + libafl::mutators::ByteRandMutator, + libafl::mutators::ByteAddMutator, + libafl::mutators::WordAddMutator, + libafl::mutators::DwordAddMutator, + libafl::mutators::QwordAddMutator, + libafl::mutators::ByteInterestingMutator, + libafl::mutators::WordInterestingMutator, + libafl::mutators::DwordInterestingMutator, + libafl::mutators::BytesDeleteMutator, + libafl::mutators::BytesDeleteMutator, + libafl::mutators::BytesDeleteMutator, + libafl::mutators::BytesDeleteMutator, + libafl::mutators::BytesExpandMutator, + libafl::mutators::BytesInsertMutator, + libafl::mutators::BytesRandInsertMutator, + libafl::mutators::BytesSetMutator, + libafl::mutators::BytesRandSetMutator, + libafl::mutators::BytesCopyMutator, + libafl::mutators::BytesInsertCopyMutator, + libafl::mutators::BytesSwapMutator, + libafl::mutators::CrossoverInsertMutator, + libafl::mutators::CrossoverReplaceMutator, + Base64FlipDecodeMutator, + Base64FlipIgnoreGarbageMutator, + Base64FlipWrapMutator, + Base64WrapContentMutator +) { + havoc_mutations() + .append(Base64FlipDecodeMutator) + .append(Base64FlipIgnoreGarbageMutator) + .append(Base64FlipWrapMutator) + .append(Base64WrapContentMutator) +} diff --git a/fuzzers/coreutils_differential/src/generic/executor.rs b/fuzzers/coreutils_differential/src/generic/executor.rs new file mode 100644 index 00000000000..b32e3ad0744 --- /dev/null +++ b/fuzzers/coreutils_differential/src/generic/executor.rs @@ -0,0 +1,99 @@ +use std::{ + borrow::Cow, + ffi::OsStr, + fs::File, + io::Write, + marker::PhantomData, + process::{Child, Command, Stdio}, + time::Duration, +}; + +use libafl::{ + executors::{command::CommandConfigurator, CommandExecutor}, + state::State, + Error, +}; +use libafl_bolts::{shmem::ShMemDescription, tuples::MatchName}; +use serde::Serialize; + +// Create the executor for an in-process function with just one observer +#[derive(Debug)] +pub struct CoverageCommandExecutor { + shmem_coverage_description: String, + temp_file_stdin_path: String, + util: String, + phantom: PhantomData, +} + +impl CoverageCommandExecutor { + pub fn new( + shmem_coverage_description: &ShMemDescription, + observers: OT, + util: &str, + id: ID, + ) -> CommandExecutor> + where + S: State, + S::Input: ExtractsToCommand, + OT: MatchName, + ID: ToString, + { + let serialized_description = serde_json::to_string(&shmem_coverage_description) + .expect("Could not stringify shared memory description"); + + let configurator = Self { + shmem_coverage_description: serialized_description, + temp_file_stdin_path: format!("/dev/shm/temp{}", id.to_string()), + util: util.to_string(), + phantom: PhantomData, + }; + configurator.into_executor(observers) + } +} + +pub trait ExtractsToCommand: Serialize { + fn get_stdin(&self) -> &Vec; + fn get_args<'a>(&self) -> Vec>; +} + +impl CommandConfigurator for CoverageCommandExecutor +where + I: ExtractsToCommand, +{ + fn spawn_child(&mut self, input: &I) -> Result { + let mut command = Command::new(&self.util); + + command + .env( + "LD_PRELOAD", + "./target/release/libsetup_guard_redirection.so", + ) + .args(input.get_args()) + .arg(&self.shmem_coverage_description) + .stderr(Stdio::piped()) + .stdout(Stdio::piped()) + .stdin(pseudo_pipe(input.get_stdin(), &self.temp_file_stdin_path)?); + + let child = command.spawn().expect("failed to start process"); + Ok(child) + } + + fn exec_timeout(&self) -> Duration { + Duration::from_secs(2) + } +} + +/// Creates a [`File`] that can be used to write data to a [`Command`]'s `stdin`. +/// +/// The implementation relies on a temp file on disk. Consider using an in-memory file, e.g. by locating it in `/dev/shm/`. +/// +/// # Errors on +/// +/// This function will return an error if the underlying os functions error. +fn pseudo_pipe(data: &[u8], path: &str) -> Result { + File::create(path) + .map_err(|e| Error::os_error(e, "Could not create temp file"))? + .write_all(data) + .map_err(|e| Error::os_error(e, "Could not write data to temp file"))?; + File::open(path).map_err(|e| Error::os_error(e, "Could not open temp file again")) +} diff --git a/fuzzers/coreutils_differential/src/generic/mod.rs b/fuzzers/coreutils_differential/src/generic/mod.rs new file mode 100644 index 00000000000..815f9b87f56 --- /dev/null +++ b/fuzzers/coreutils_differential/src/generic/mod.rs @@ -0,0 +1,3 @@ +pub mod executor; +pub mod shmem; +pub mod stdio; diff --git a/fuzzers/coreutils_differential/src/generic/shmem.rs b/fuzzers/coreutils_differential/src/generic/shmem.rs new file mode 100644 index 00000000000..563e82748a8 --- /dev/null +++ b/fuzzers/coreutils_differential/src/generic/shmem.rs @@ -0,0 +1,62 @@ +use std::{io::Error as IOError, path::Path, process::Command}; + +use libafl::Error; +use libafl_bolts::shmem::{MmapShMem, MmapShMemProvider, ShMem, ShMemDescription, ShMemProvider}; +use libc::{fcntl, FD_CLOEXEC, F_GETFD, F_SETFD}; + +pub fn get_coverage_shmem_size(util: String) -> Result<(usize, String), Error> { + if !Path::new(&util).exists() { + return Err(Error::illegal_argument(format!("Util {util} not found"))); + } + + let shared = "./target/release/libget_guard_num.so"; + if !Path::new(shared).exists() { + return Err(Error::illegal_argument( + "Missing shared library to instrument binary to find number of edges. Check Makefile.toml for the appropriate target." + )); + } + + let guard_num_command_output = Command::new(&util) + .env("LD_PRELOAD", shared) + .output()? + .stdout; + let guard_num = String::from_utf8(guard_num_command_output)? + .trim() + .parse::()?; + + match guard_num { + 0 => Err(Error::illegal_state("Binary reported a guard count of 0")), + e => Ok((e, util)), + } +} + +fn make_shmem_persist(description: &ShMemDescription) -> Result<(), Error> { + let fd = description.id.as_str().parse().unwrap(); + let flags = unsafe { fcntl(fd, F_GETFD) }; + + if flags == -1 { + return Err(Error::os_error( + IOError::last_os_error(), + "Failed to retrieve FD flags", + )); + } + let result = unsafe { fcntl(fd, F_SETFD, flags & !FD_CLOEXEC) }; + if result == -1 { + return Err(Error::os_error( + IOError::last_os_error(), + "Failed to set FD flags", + )); + } + Ok(()) +} + +pub fn get_shmem(size: usize) -> Result<(MmapShMem, ShMemDescription), Error> { + let mut shmem_provider = MmapShMemProvider::default(); + let shmem = shmem_provider + .new_shmem(size) + .expect("Could not get the shared memory map"); + + let shmem_description = shmem.description(); + make_shmem_persist(&shmem_description)?; + Ok((shmem, shmem_description)) +} diff --git a/fuzzers/coreutils_differential/src/generic/stdio.rs b/fuzzers/coreutils_differential/src/generic/stdio.rs new file mode 100644 index 00000000000..1f1c7f9f70c --- /dev/null +++ b/fuzzers/coreutils_differential/src/generic/stdio.rs @@ -0,0 +1,148 @@ +use std::{borrow::Cow, fmt::Display}; + +use std::fmt::Write; + +use libafl::{ + events::EventFirer, + executors::ExitKind, + feedbacks::Feedback, + observers::{ObserversTuple, StdErrObserver, StdOutObserver}, + state::State, + Error, HasMetadata, SerdeAny, +}; +use libafl_bolts::{ + tuples::{Handle, MatchNameRef}, + Named, +}; + +use serde::{Deserialize, Serialize}; + +#[cfg(feature = "differential")] +use libafl_bolts::tuples::Handled; + +pub struct DiffStdIOMetadataPseudoFeedback { + name1: Cow<'static, str>, + name2: Cow<'static, str>, + stderr_observer1: Handle, + stderr_observer2: Handle, + stdout_observer1: Handle, + stdout_observer2: Handle, +} + +impl DiffStdIOMetadataPseudoFeedback { + #[cfg(feature = "differential")] + pub fn new( + name1: &str, + name2: &str, + stderr_observer1: &StdErrObserver, + stderr_observer2: &StdErrObserver, + stdout_observer1: &StdOutObserver, + stdout_observer2: &StdOutObserver, + ) -> Self { + Self { + name1: Cow::Owned(name1.to_string()), + name2: Cow::Owned(name2.to_string()), + stderr_observer1: stderr_observer1.handle(), + stderr_observer2: stderr_observer2.handle(), + stdout_observer1: stdout_observer1.handle(), + stdout_observer2: stdout_observer2.handle(), + } + } +} + +impl Feedback for DiffStdIOMetadataPseudoFeedback +where + S: State, + S::Input: Display, +{ + fn is_interesting( + &mut self, + _state: &mut S, + _manager: &mut EM, + _input: &S::Input, + _observers: &OT, + _exit_kind: &ExitKind, + ) -> Result + where + EM: EventFirer, + OT: ObserversTuple, + { + Ok(false) + } + + fn append_metadata( + &mut self, + _state: &mut S, + _manager: &mut EM, + observers: &OT, + testcase: &mut libafl::prelude::Testcase<::Input>, + ) -> Result<(), Error> + where + OT: ObserversTuple, + EM: EventFirer, + { + fn f<'a, T, OT>(handle: &Handle, observers: &'a OT) -> Result<&'a T, Error> + where + OT: MatchNameRef, + { + match observers.get(handle) { + None => Err(Error::illegal_argument(format!( + "DiffFeedback: observer {} not found", + handle.name() + ))), + Some(e) => Ok(e), + } + } + + let input = testcase.input().as_ref().map(|e| e.to_string()); + + testcase + .metadata_map_mut() + .insert(DiffStdIOMetadataPseudoFeedbackMetadata { + input, + name1: self.name1.to_string(), + name2: self.name2.to_string(), + stderr_observer1: vec_string_mapper(&f(&self.stderr_observer1, observers)?.stderr), + stderr_observer2: vec_string_mapper(&f(&self.stderr_observer2, observers)?.stderr), + stdout_observer1: vec_string_mapper(&f(&self.stdout_observer1, observers)?.stdout), + stdout_observer2: vec_string_mapper(&f(&self.stdout_observer2, observers)?.stdout), + }); + Ok(()) + } +} + +impl Named for DiffStdIOMetadataPseudoFeedback { + fn name(&self) -> &Cow<'static, str> { + &Cow::Borrowed("diff-stdio-metadata-pseudo-feedback") + } +} + +#[derive(Debug, SerdeAny, Serialize, Deserialize)] +struct DiffStdIOMetadataPseudoFeedbackMetadata { + input: Option, + name1: String, + name2: String, + stderr_observer1: String, + stderr_observer2: String, + stdout_observer1: String, + stdout_observer2: String, +} + +pub fn vec_string_mapper(v: &Option>) -> String { + v.as_ref() + .map(|v| { + std::str::from_utf8(v.as_ref()).map_or( + format!( + "utf8 error, lossy string: '{}', bytes: 0x{}", + String::from_utf8_lossy(v), + v.iter() + .fold(String::with_capacity(v.len() * 2), |mut w, byte| { + write!(w, "{:02x}", byte).unwrap(); + w + }) + ), + |s| s.to_string(), + ) + }) + .unwrap_or("Did not observe anything".to_string()) +} diff --git a/fuzzers/coreutils_differential/src/main.rs b/fuzzers/coreutils_differential/src/main.rs new file mode 100644 index 00000000000..172c3d424ed --- /dev/null +++ b/fuzzers/coreutils_differential/src/main.rs @@ -0,0 +1,317 @@ +mod base64; +mod generic; + +use std::path::PathBuf; + +use base64::{base64_mutators, Base64Generator}; + +use generic::{ + executor::CoverageCommandExecutor, + shmem::{get_coverage_shmem_size, get_shmem}, +}; +use libafl::{ + corpus::{InMemoryCorpus, OnDiskCorpus}, + events::{EventConfig, Launcher, LlmpRestartingEventManager}, + feedback_or_fast, + feedbacks::{CrashFeedback, DiffExitKindFeedback, MaxMapFeedback, TimeoutFeedback}, + mutators::StdScheduledMutator, + observers::{StdErrObserver, StdMapObserver, StdOutObserver, TimeObserver}, + schedulers::{powersched::PowerSchedule, StdScheduler, StdWeightedScheduler}, + stages::StdMutationalStage, + state::StdState, + Error, Fuzzer, StdFuzzer, +}; + +use libafl_bolts::{ + cli::parse_args, + core_affinity::CoreId, + current_nanos, + rands::StdRand, + shmem::{ShMemProvider, StdShMemProvider}, + tuples::tuple_list, + AsSliceMut, +}; + +#[cfg(feature = "differential")] +use { + generic::stdio::DiffStdIOMetadataPseudoFeedback, + libafl::{ + executors::DiffExecutor, + feedback_and, feedback_and_fast, feedback_or, + feedbacks::{differential::DiffResult, ConstFeedback, DiffFeedback, TimeFeedback}, + observers::MultiMapObserver, + }, + libafl_bolts::ownedref::OwnedMutSlice, +}; + +#[cfg(not(feature = "introspection"))] +use libafl::monitors::tui::{ui::TuiUI, TuiMonitor}; +#[cfg(feature = "introspection")] +use libafl::monitors::MultiMonitor; + +#[cfg(feature = "uutils")] +pub static UUTILS_PREFIX: &str = "./target/uutils_coreutils/target/release/"; +#[cfg(feature = "gnu")] +pub static GNU_PREFIX: &str = "./target/GNU_coreutils/src/"; + +pub fn main() { + let util = "base64"; + match fuzz(util) { + Ok(_) => (), + Err(Error::ShuttingDown) => { + println!("Orderly shutdown"); + } + Err(e) => { + println!("Error: {:#?}", e); + } + } +} + +fn fuzz(util: &str) -> Result<(), Error> { + let options = parse_args(); + + #[cfg(feature = "introspection")] + let monitor = MultiMonitor::new(|s| println!("{}", s)); + #[cfg(not(feature = "introspection"))] + let monitor = TuiMonitor::new(TuiUI::new("coreutils fuzzer".to_string(), true)); + + #[cfg(feature = "uutils")] + let (uutils_coverage_shmem_size, uutils_path) = + get_coverage_shmem_size(format!("{UUTILS_PREFIX}{util}"))?; + #[cfg(feature = "gnu")] + let (gnu_coverage_shmem_size, gnu_path) = + get_coverage_shmem_size(format!("{GNU_PREFIX}{util}"))?; + + let run_client = |state: Option<_>, + mut mgr: LlmpRestartingEventManager<_, _, _>, + core_id: CoreId| + -> Result<(), Error> { + #[cfg(feature = "uutils")] + let (mut uutils_coverage_shmem, uutils_coverage_shmem_description) = + get_shmem(uutils_coverage_shmem_size)?; + + #[cfg(feature = "gnu")] + let (mut gnu_coverage_shmem, gnu_coverage_shmem_description) = + get_shmem(gnu_coverage_shmem_size)?; + + #[cfg(feature = "differential")] + let combined_coverage_observer = + MultiMapObserver::differential("combined-coverage", unsafe { + vec![ + OwnedMutSlice::from_raw_parts_mut( + uutils_coverage_shmem.as_mut_ptr(), + uutils_coverage_shmem.len(), + ), + OwnedMutSlice::from_raw_parts_mut( + gnu_coverage_shmem.as_mut_ptr(), + gnu_coverage_shmem.len(), + ), + ] + }); + + #[cfg(feature = "uutils")] + let uutils_stdout_observer = StdOutObserver::new("uutils-stdout-observer"); + #[cfg(feature = "uutils")] + let uutils_stderr_observer = StdErrObserver::new("uutils-stderr-observer"); + #[cfg(feature = "uutils")] + let uutils_time_observer = TimeObserver::new("uutils-time-observer"); + #[cfg(feature = "uutils")] + let uutils_coverage_observer = unsafe { + StdMapObserver::new( + "uutils-coverage-observer", + uutils_coverage_shmem.as_slice_mut(), + ) + }; + + #[cfg(feature = "gnu")] + let gnu_stdout_observer = StdOutObserver::new("gnu-stdout-observer"); + #[cfg(feature = "gnu")] + let gnu_stderr_observer = StdErrObserver::new("gnu-stderr-observer"); + #[cfg(feature = "gnu")] + let gnu_time_observer = TimeObserver::new("gnu-time-observer"); + #[cfg(feature = "gnu")] + let gnu_coverage_observer = unsafe { + StdMapObserver::new("gnu-coverage-observer", gnu_coverage_shmem.as_slice_mut()) + }; + + #[cfg(feature = "differential")] + let (mut feedback, mut objective) = (|| -> Result<_, Error> { + let stdout_diff_feedback = DiffFeedback::new( + "stdout-eq-diff-feedback", + &uutils_stdout_observer, + &gnu_stdout_observer, + |o1, o2| { + if o1.stdout != o2.stdout { + DiffResult::Diff + } else { + DiffResult::Equal + } + }, + )?; + + let stderr_xor_feedback = DiffFeedback::new( + "stderr-eq-diff-feedback", + &uutils_stderr_observer, + &gnu_stderr_observer, + |o1, o2| { + if has_stderr(o1) != has_stderr(o2) { + DiffResult::Diff + } else { + DiffResult::Equal + } + }, + )?; + + let stderr_neither_feedback = DiffFeedback::new( + "stderr-neither-diff-feedback", + &uutils_stderr_observer, + &gnu_stderr_observer, + |o1, o2| { + if !has_stderr(o1) && !has_stderr(o2) { + DiffResult::Diff // trigger the feedback + } else { + DiffResult::Equal + } + }, + )?; + + let feedback = MaxMapFeedback::new(&combined_coverage_observer); + + // only add logger feedbacks if something was found + let objective = feedback_and!( + feedback_or_fast!( + // only test stdout equality if neither has a stderr + DiffExitKindFeedback::new(), + CrashFeedback::new(), + TimeoutFeedback::new(), + feedback_and_fast!(stderr_neither_feedback, stdout_diff_feedback), + stderr_xor_feedback + ), + feedback_or!( + DiffStdIOMetadataPseudoFeedback::new( + &uutils_path, + &gnu_path, + &uutils_stderr_observer, + &gnu_stderr_observer, + &uutils_stdout_observer, + &gnu_stdout_observer, + ), + TimeFeedback::new(&uutils_time_observer), + TimeFeedback::new(&gnu_time_observer), + ConstFeedback::new(true) // to ensure the whole block to be interesting + ) + ); + + Ok((feedback, objective)) + })()?; + + #[cfg(all(not(feature = "differential"), feature = "gnu"))] + let (mut feedback, mut objective) = { + let feedback = MaxMapFeedback::new(&gnu_coverage_observer); + let objective = feedback_or_fast!(CrashFeedback::new(), TimeoutFeedback::new()); + (feedback, objective) + }; + #[cfg(all(not(feature = "differential"), feature = "uutils"))] + let (mut feedback, mut objective) = { + let feedback = MaxMapFeedback::new(&uutils_coverage_observer); + let objective = feedback_or_fast!(CrashFeedback::new(), TimeoutFeedback::new()); + (feedback, objective) + }; + let mut state = state.unwrap_or_else(|| { + StdState::new( + StdRand::with_seed(current_nanos()), + InMemoryCorpus::new(), + OnDiskCorpus::new(PathBuf::from(&options.output)).unwrap(), + &mut feedback, + &mut objective, + ) + .unwrap() + }); + + let scheduler = StdWeightedScheduler::with_schedule( + &mut state, + #[cfg(feature = "differential")] + &combined_coverage_observer, + #[cfg(all(not(feature = "differential"), feature = "uutils"))] + &uutils_coverage_observer, + #[cfg(all(not(feature = "differential"), feature = "gnu"))] + &gnu_coverage_observer, + Some(PowerSchedule::FAST), + ); + // let scheduler = StdScheduler::new(); + + let mut fuzzer = StdFuzzer::new(scheduler, feedback, objective); + #[cfg(feature = "uutils")] + let uutils_executor = CoverageCommandExecutor::new( + &uutils_coverage_shmem_description, + tuple_list!( + uutils_coverage_observer, + uutils_stdout_observer, + uutils_stderr_observer, + uutils_time_observer + ), + &uutils_path, + format!("uutils-{:?}", core_id.0), + ); + + #[cfg(feature = "gnu")] + let gnu_executor = CoverageCommandExecutor::new( + &gnu_coverage_shmem_description, + tuple_list!( + gnu_coverage_observer, + gnu_stdout_observer, + gnu_stderr_observer, + gnu_time_observer + ), + &gnu_path, + format!("gnu-{:?}", core_id.0), + ); + + #[cfg(feature = "differential")] + let diff_executor = DiffExecutor::new( + uutils_executor, + gnu_executor, + tuple_list!(combined_coverage_observer), + ); + #[cfg(feature = "differential")] + let mut executor = diff_executor; + #[cfg(all(not(feature = "differential"), feature = "uutils"))] + let mut executor = uutils_executor; + #[cfg(all(not(feature = "differential"), feature = "gnu"))] + let mut executor = gnu_executor; + + if state.must_load_initial_inputs() { + state.generate_initial_inputs( + &mut fuzzer, + &mut executor, + &mut Base64Generator::new(2), + &mut mgr, + 8, + )? + } + + let mut stages = tuple_list!(StdMutationalStage::new(StdScheduledMutator::new( + base64_mutators() + ))); + + fuzzer.fuzz_loop(&mut stages, &mut executor, &mut state, &mut mgr) + }; + + let launcher_shmem_provider = StdShMemProvider::new()?; + + Launcher::builder() + .configuration(EventConfig::AlwaysUnique) + .shmem_provider(launcher_shmem_provider) + .monitor(monitor) + .run_client(run_client) + .cores(&options.cores) + .broker_port(options.broker_port) + .stdout_file(Some(&options.stdout)) + .remote_broker_addr(options.remote_broker_addr) + .build() + .launch() +} + +pub fn has_stderr(o: &StdErrObserver) -> bool { + o.stderr.as_ref().map_or(false, |e| !e.is_empty()) +}