Make LibAFL-fuzz build on MacOS (#2549)

* Make LibAFL-fuzz build on MacOS

* Works on MacOS

* Update AFL++

* libafl-fuzz: fix CI cmplog (#2548)

* undo

* clippy

* clippy

---------

Co-authored-by: Aarnav <aarnavbos@gmail.com>
This commit is contained in:
Dominik Maier 2024-09-24 03:25:20 +02:00 committed by GitHub
parent 967449e3cb
commit 691fd1f8cb
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 140 additions and 62 deletions

4
fuzzers/others/libafl-fuzz/.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
test/out-cmplog
test/out-instr
test/output-cmplog/
test/output/

View File

@ -12,7 +12,7 @@ FUZZER = '${CARGO_TARGET_DIR}/${PROFILE_DIR}/${FUZZER_NAME}'
LLVM_CONFIG = { value = "llvm-config-18", condition = { env_not_set = [
"LLVM_CONFIG",
] } }
AFL_VERSION = "598a3c6b5e24bd33e84b914e145810d39f88adf6"
AFL_VERSION = "8b35dd49be5f846e945f6d6a9414623d195a99cb"
AFL_DIR = { value = "${PROJECT_DIR}/AFLplusplus" }
AFL_CC_PATH = { value = "${AFL_DIR}/afl-clang-fast" }
CC = { value = "clang" }

View File

@ -0,0 +1,2 @@
group_imports = "StdExternalCrate"
imports_granularity = "Crate"

View File

@ -4,7 +4,7 @@ use std::{
fmt::Display,
fs::{File, OpenOptions},
io::{BufRead, BufReader, Write},
path::PathBuf,
path::{Path, PathBuf},
process,
};
@ -444,7 +444,7 @@ where
}
}
fn create_plot_data_file(fuzzer_dir: &PathBuf) -> Result<(), Error> {
fn create_plot_data_file(fuzzer_dir: &Path) -> Result<(), Error> {
let path = fuzzer_dir.join("plot_data");
if path.exists() {
// check if it contains any data
@ -458,10 +458,10 @@ where
Ok(())
}
fn create_fuzzer_stats_file(fuzzer_dir: &PathBuf) -> Result<(), Error> {
fn create_fuzzer_stats_file(fuzzer_dir: &Path) -> Result<(), Error> {
let path = fuzzer_dir.join("fuzzer_stats");
if !path.exists() {
OpenOptions::new().append(true).create(true).open(path)?;
_ = OpenOptions::new().append(true).create(true).open(path)?;
}
Ok(())
}
@ -470,7 +470,7 @@ where
let tmp_file = self.fuzzer_dir.join(".fuzzer_stats_tmp");
let stats_file = self.fuzzer_dir.join("fuzzer_stats");
std::fs::write(&tmp_file, stats.to_string())?;
std::fs::copy(&tmp_file, &stats_file)?;
_ = std::fs::copy(&tmp_file, &stats_file)?;
std::fs::remove_file(tmp_file)?;
Ok(())
}

View File

@ -2,7 +2,7 @@ use std::{
borrow::Cow,
fs::File,
io::{self, BufRead, BufReader},
path::{Path, PathBuf},
path::Path,
};
use libafl::{
@ -39,10 +39,12 @@ pub fn generate_base_filename(state: &mut LibaflFuzzState) -> String {
name
}
// The function needs to be compatible with CustomFilepathToTestcaseFeedback
#[allow(clippy::unnecessary_wraps)]
pub fn set_corpus_filepath(
state: &mut LibaflFuzzState,
testcase: &mut Testcase<BytesInput>,
_fuzzer_dir: &PathBuf,
_fuzzer_dir: &Path,
) -> Result<(), Error> {
let mut name = generate_base_filename(state);
if testcase.hit_feedbacks().contains(&Cow::Borrowed("edges")) {
@ -53,10 +55,12 @@ pub fn set_corpus_filepath(
Ok(())
}
// The function needs to be compatible with CustomFilepathToTestcaseFeedback
#[allow(clippy::unnecessary_wraps)]
pub fn set_solution_filepath(
state: &mut LibaflFuzzState,
testcase: &mut Testcase<BytesInput>,
output_dir: &PathBuf,
output_dir: &Path,
) -> Result<(), Error> {
// sig:0SIGNAL
// TODO: verify if 0 time if objective found during seed loading
@ -137,7 +141,7 @@ pub fn check_autoresume(fuzzer_dir: &Path, auto_resume: bool) -> Result<Flock<Fi
Ok(file)
}
pub fn create_dir_if_not_exists(path: &PathBuf) -> std::io::Result<()> {
pub fn create_dir_if_not_exists(path: &Path) -> io::Result<()> {
if path.is_file() {
return Err(io::Error::new(
// TODO: change this to ErrorKind::NotADirectory
@ -158,8 +162,8 @@ pub fn create_dir_if_not_exists(path: &PathBuf) -> std::io::Result<()> {
}
}
pub fn remove_main_node_file(output_dir: &PathBuf) -> Result<(), Error> {
for entry in std::fs::read_dir(output_dir)?.filter_map(std::result::Result::ok) {
pub fn remove_main_node_file(output_dir: &Path) -> Result<(), Error> {
for entry in std::fs::read_dir(output_dir)?.filter_map(Result::ok) {
let path = entry.path();
if path.is_dir() && path.join("is_main_node").exists() {
std::fs::remove_file(path.join("is_main_node"))?;

View File

@ -38,8 +38,7 @@ pub fn parse_envs(opt: &mut Opt) -> Result<(), Error> {
opt.no_autodict = parse_bool(&res)?;
}
if let Ok(res) = std::env::var("AFL_MAP_SIZE") {
let map_size = res.parse()?;
validate_map_size(map_size)?;
let map_size = validate_map_size(res.parse()?)?;
opt.map_size = Some(map_size);
};
if let Ok(res) = std::env::var("AFL_IGNORE_TIMEOUT") {
@ -131,7 +130,7 @@ fn parse_target_env(s: &str) -> Result<Option<HashMap<String, String>>, Error> {
let env_regex = regex::Regex::new(r"([^\s=]+)\s*=\s*([^\s]+)").unwrap();
let mut target_env = HashMap::new();
for vars in env_regex.captures_iter(s) {
target_env.insert(
_ = target_env.insert(
vars.get(1)
.ok_or(Error::illegal_argument("invalid AFL_TARGET_ENV format"))?
.as_str()

View File

@ -1,6 +1,6 @@
use std::{
fs::File,
os::{linux::fs::MetadataExt, unix::fs::PermissionsExt},
os::unix::fs::PermissionsExt,
path::{Path, PathBuf},
};
@ -54,7 +54,7 @@ pub fn check_binary(opt: &mut Opt, shmem_env_var: &str) -> Result<(), Error> {
let metadata = bin_path.metadata()?;
// AFL++ does not follow symlinks, BUT we do.
let is_reg = bin_path.is_file();
let bin_size = metadata.st_size();
let bin_size = metadata.len();
let is_executable = metadata.permissions().mode() & 0o111 != 0;
if !is_reg || !is_executable || bin_size < 4 {
return Err(Error::illegal_argument(format!(
@ -84,6 +84,7 @@ pub fn check_binary(opt: &mut Opt, shmem_env_var: &str) -> Result<(), Error> {
}
// check if the binary is an ELF file
#[cfg(target_os = "linux")]
if mmap[0..4] != [0x7f, 0x45, 0x4c, 0x46] {
return Err(Error::illegal_argument(format!(
"Program '{}' is not an ELF binary",
@ -91,7 +92,7 @@ pub fn check_binary(opt: &mut Opt, shmem_env_var: &str) -> Result<(), Error> {
)));
}
#[cfg(all(target_os = "macos", not(target_arch = "arm")))]
#[cfg(target_vendor = "apple")]
{
if (mmap[0] != 0xCF || mmap[1] != 0xFA || mmap[2] != 0xED)
&& (mmap[0] != 0xCA || mmap[1] != 0xFE || mmap[2] != 0xBA)
@ -186,13 +187,18 @@ fn find_executable_in_path<P: AsRef<Path>>(executable: &P) -> Option<PathBuf> {
}
pub fn find_afl_binary(filename: &str, same_dir_as: Option<PathBuf>) -> Result<PathBuf, Error> {
let is_library =
filename.contains('.') && filename.ends_with(".so") || filename.ends_with(".dylib");
let permission = if is_library {
S_IRUSR // user can read
let extension = Path::new(filename).extension();
let is_library = if let Some(extension) = extension {
extension.eq_ignore_ascii_case("so") || extension.eq_ignore_ascii_case("dylib")
} else {
S_IXUSR // user can exec
false
};
#[allow(clippy::useless_conversion)] // u16 on MacOS, u32 on Linux
let permission = if is_library {
u32::from(S_IRUSR) // user can read
} else {
u32::from(S_IXUSR) // user can exec
};
// First we check if it is present in AFL_PATH
@ -229,7 +235,7 @@ pub fn find_afl_binary(filename: &str, same_dir_as: Option<PathBuf>) -> Result<P
Err(Error::unknown(format!("cannot find {filename}")))
}
fn check_file_found(file: &PathBuf, perm: u32) -> bool {
fn check_file_found(file: &Path, perm: u32) -> bool {
if !file.exists() {
return false;
}

View File

@ -2,7 +2,7 @@ use std::{
borrow::Cow,
fmt::{Debug, Formatter},
marker::PhantomData,
path::PathBuf,
path::{Path, PathBuf},
};
use libafl::{
@ -26,7 +26,7 @@ pub struct CustomFilepathToTestcaseFeedback<F, I, S>
where
I: Input,
S: State<Input = I>,
F: FnMut(&mut S, &mut Testcase<I>, &PathBuf) -> Result<(), Error>,
F: FnMut(&mut S, &mut Testcase<I>, &Path) -> Result<(), Error>,
{
/// Closure that returns the filename.
func: F,
@ -39,7 +39,7 @@ impl<F, I, S> CustomFilepathToTestcaseFeedback<F, I, S>
where
I: Input,
S: State<Input = I>,
F: FnMut(&mut S, &mut Testcase<I>, &PathBuf) -> Result<(), Error>,
F: FnMut(&mut S, &mut Testcase<I>, &Path) -> Result<(), Error>,
{
/// Create a new [`CustomFilepathToTestcaseFeedback`].
pub fn new(func: F, out_dir: PathBuf) -> Self {
@ -56,7 +56,7 @@ impl<F, I, S, T> FeedbackFactory<CustomFilepathToTestcaseFeedback<F, I, S>, T>
where
I: Input,
S: State<Input = I>,
F: FnMut(&mut S, &mut Testcase<I>, &PathBuf) -> Result<(), Error> + Clone,
F: FnMut(&mut S, &mut Testcase<I>, &Path) -> Result<(), Error> + Clone,
{
fn create_feedback(&self, _ctx: &T) -> CustomFilepathToTestcaseFeedback<F, I, S> {
Self {
@ -71,7 +71,7 @@ impl<F, I, S> Named for CustomFilepathToTestcaseFeedback<F, I, S>
where
I: Input,
S: State<Input = I>,
F: FnMut(&mut S, &mut Testcase<I>, &PathBuf) -> Result<(), Error>,
F: FnMut(&mut S, &mut Testcase<I>, &Path) -> Result<(), Error>,
{
fn name(&self) -> &Cow<'static, str> {
static NAME: Cow<'static, str> = Cow::Borrowed("CustomFilepathToTestcaseFeedback");
@ -83,7 +83,7 @@ impl<F, I, S> Debug for CustomFilepathToTestcaseFeedback<F, I, S>
where
I: Input,
S: State<Input = I>,
F: FnMut(&mut S, &mut Testcase<I>, &PathBuf) -> Result<(), Error>,
F: FnMut(&mut S, &mut Testcase<I>, &Path) -> Result<(), Error>,
{
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.debug_struct("CustomFilepathToTestcaseFeedback")
@ -94,7 +94,7 @@ where
impl<F, I, S> Feedback<S> for CustomFilepathToTestcaseFeedback<F, I, S>
where
S: State<Input = I>,
F: FnMut(&mut S, &mut Testcase<S::Input>, &PathBuf) -> Result<(), Error>,
F: FnMut(&mut S, &mut Testcase<S::Input>, &Path) -> Result<(), Error>,
I: Input,
{
#[allow(clippy::wrong_self_convention)]

View File

@ -102,7 +102,7 @@ where
if self.should_run() {
self.record.push_back(input.clone());
if self.record.len() == self.record_size {
self.record.pop_front();
drop(self.record.pop_front());
}
}
Ok(false)

View File

@ -11,7 +11,7 @@ use crate::Opt;
/// A wrapper feedback used to determine actions for initial seeds.
/// Handles `AFL_EXIT_ON_SEED_ISSUES`, `AFL_IGNORE_SEED_ISSUES` & default afl-fuzz behavior
/// then, essentially becomes benign
#[allow(clippy::module_name_repetitions)]
#[allow(clippy::module_name_repetitions, clippy::struct_excessive_bools)]
#[derive(Debug)]
pub struct SeedFeedback<A, S>
where

View File

@ -1,4 +1,9 @@
use std::{borrow::Cow, marker::PhantomData, path::PathBuf, time::Duration};
use std::{
borrow::Cow,
marker::PhantomData,
path::{Path, PathBuf},
time::Duration,
};
use libafl::{
corpus::{CachedOnDiskCorpus, Corpus, OnDiskCorpus},
@ -33,7 +38,7 @@ use libafl_bolts::{
fs::get_unique_std_input_file,
ownedref::OwnedRefMut,
rands::StdRand,
shmem::{ShMem, ShMemProvider, StdShMemProvider},
shmem::{ShMem, ShMemProvider, UnixShMemProvider},
tuples::{tuple_list, Handled, Merge},
AsSliceMut,
};
@ -66,7 +71,7 @@ pub fn run_client<EMH, SP>(
LibaflFuzzState,
SP,
>,
fuzzer_dir: &PathBuf,
fuzzer_dir: &Path,
core_id: CoreId,
opt: &Opt,
is_main_node: bool,
@ -76,7 +81,7 @@ where
SP: ShMemProvider,
{
// Create the shared memory map for comms with the forkserver
let mut shmem_provider = StdShMemProvider::new().unwrap();
let mut shmem_provider = UnixShMemProvider::new().unwrap();
let mut shmem = shmem_provider
.new_shmem(opt.map_size.unwrap_or(AFL_DEFAULT_MAP_SIZE))
.unwrap();
@ -109,7 +114,7 @@ where
// Create a AFLStatsStage;
let afl_stats_stage = AflStatsStage::new(
opt,
fuzzer_dir.clone(),
fuzzer_dir.to_path_buf(),
&edges_observer,
user_token_count,
!opt.no_autodict,
@ -130,7 +135,7 @@ where
feedback_or!(
map_feedback,
TimeFeedback::new(&time_observer),
CustomFilepathToTestcaseFeedback::new(set_corpus_filepath, fuzzer_dir.clone())
CustomFilepathToTestcaseFeedback::new(set_corpus_filepath, fuzzer_dir.to_path_buf())
),
opt,
);
@ -153,7 +158,7 @@ where
),
MaxMapFeedback::with_name("edges_objective", &edges_observer)
),
CustomFilepathToTestcaseFeedback::new(set_solution_filepath, fuzzer_dir.clone()),
CustomFilepathToTestcaseFeedback::new(set_solution_filepath, fuzzer_dir.to_path_buf()),
PersitentRecordFeedback::new(opt.persistent_record),
);
@ -163,7 +168,7 @@ where
StdRand::with_seed(opt.rng_seed.unwrap_or(current_nanos())),
// TODO: configure testcache size
CachedOnDiskCorpus::<BytesInput>::new(fuzzer_dir.join("queue"), 1000).unwrap(),
OnDiskCorpus::<BytesInput>::new(fuzzer_dir.clone()).unwrap(),
OnDiskCorpus::<BytesInput>::new(fuzzer_dir).unwrap(),
&mut feedback,
&mut objective,
)
@ -234,19 +239,19 @@ where
}
// Create the base Executor
let mut executor = base_executor(opt, &mut shmem_provider, fuzzer_dir)?;
let mut executor_builder = base_executor_builder(opt, &mut shmem_provider, fuzzer_dir);
// Set a custom exit code to be interpreted as a Crash if configured.
if let Some(crash_exitcode) = opt.crash_exitcode {
executor = executor.crash_exitcode(crash_exitcode);
executor_builder = executor_builder.crash_exitcode(crash_exitcode);
}
// Enable autodict if configured
if !opt.no_autodict {
executor = executor.autotokens(&mut tokens);
executor_builder = executor_builder.autotokens(&mut tokens);
};
// Finalize and build our Executor
let mut executor = executor
let mut executor = executor_builder
.build(tuple_list!(time_observer, edges_observer))
.unwrap();
@ -270,7 +275,7 @@ where
)))?
.to_string();
filename = format!("id:{id:0>6},time:0,execs:0,orig:{filename}");
let cpy_res = std::fs::copy(&path, queue_dir.join(filename));
let cpy_res = std::fs::copy(path, queue_dir.join(filename));
match cpy_res {
Err(e) if e.kind() == std::io::ErrorKind::InvalidInput => {
println!("skipping {} since it is not a regular file", path.display());
@ -354,7 +359,7 @@ where
// Create the CmpLog executor.
// Cmplog has 25% execution overhead so we give it double the timeout
let cmplog_executor = base_executor(opt, &mut shmem_provider, fuzzer_dir)?
let cmplog_executor = base_executor_builder(opt, &mut shmem_provider, fuzzer_dir)
.timeout(Duration::from_millis(opt.hang_timeout * 2))
.program(cmplog_executable_path)
.build(tuple_list!(cmplog_observer))
@ -422,11 +427,11 @@ where
// TODO: serialize state when exiting.
}
fn base_executor<'a>(
fn base_executor_builder<'a>(
opt: &'a Opt,
shmem_provider: &'a mut StdShMemProvider,
fuzzer_dir: &PathBuf,
) -> Result<ForkserverExecutorBuilder<'a, StdShMemProvider>, Error> {
shmem_provider: &'a mut UnixShMemProvider,
fuzzer_dir: &Path,
) -> ForkserverExecutorBuilder<'a, UnixShMemProvider> {
let mut executor = ForkserverExecutor::builder()
.program(opt.executable.clone())
.coverage_map_size(opt.map_size.unwrap_or(AFL_DEFAULT_MAP_SIZE))
@ -469,7 +474,7 @@ fn base_executor<'a>(
.expect("to find afl-qemu-trace"),
);
}
Ok(executor)
executor
}
pub fn fuzzer_target_mode(opt: &Opt) -> Cow<'static, str> {

View File

@ -1,12 +1,69 @@
#![forbid(unexpected_cfgs)]
#![allow(incomplete_features)]
#![warn(clippy::cargo)]
#![allow(ambiguous_glob_reexports)]
#![deny(clippy::cargo_common_metadata)]
#![deny(rustdoc::broken_intra_doc_links)]
#![deny(clippy::all)]
#![deny(clippy::pedantic)]
#![allow(clippy::unsafe_derive_deserialize)]
#![allow(clippy::ptr_arg)]
#![allow(clippy::unnecessary_wraps)]
#![allow(clippy::module_name_repetitions)]
#![allow(clippy::similar_names)]
#![allow(clippy::too_many_lines)]
#![allow(clippy::struct_excessive_bools)]
#![allow(clippy::case_sensitive_file_extension_comparisons)]
#![allow(
clippy::unreadable_literal,
clippy::type_repetition_in_bounds,
clippy::missing_errors_doc,
clippy::cast_possible_truncation,
clippy::used_underscore_binding,
clippy::ptr_as_ptr,
clippy::missing_panics_doc,
clippy::missing_docs_in_private_items,
clippy::module_name_repetitions,
clippy::ptr_cast_constness,
clippy::unsafe_derive_deserialize,
clippy::similar_names,
clippy::too_many_lines,
clippy::into_iter_without_iter, // broken
)]
#![cfg_attr(not(test), warn(
missing_debug_implementations,
//missing_docs,
trivial_casts,
trivial_numeric_casts,
unused_extern_crates,
unused_import_braces,
unused_qualifications,
//unused_results
))]
#![cfg_attr(
test,
deny(
missing_debug_implementations,
trivial_casts,
trivial_numeric_casts,
unused_extern_crates,
unused_import_braces,
unused_qualifications,
unused_must_use,
//unused_results
)
)]
#![cfg_attr(
test,
deny(
bad_style,
dead_code,
improper_ctypes,
non_shorthand_field_patterns,
no_mangle_generic_items,
overflowing_literals,
path_statements,
patterns_in_fns_without_body,
unconditional_recursion,
unused,
unused_allocation,
unused_comparisons,
unused_parens,
while_true
)
)]
use std::{collections::HashMap, path::PathBuf, time::Duration};
mod afl_stats;
@ -256,7 +313,7 @@ struct Opt {
non_instrumented_mode: bool,
}
#[allow(dead_code)]
#[allow(dead_code, clippy::struct_excessive_bools)]
#[derive(Debug, Clone)]
pub struct CmplogOpts {
file_size: CmplogFileSize,
@ -285,6 +342,7 @@ impl From<&str> for CmplogFileSize {
}
}
#[allow(clippy::unnecessary_wraps)] // we need to be compatible with Clap's value_parser
fn parse_cmplog_args(s: &str) -> Result<CmplogOpts, String> {
Ok(CmplogOpts {
file_size: s.into(),