diff --git a/.gitignore b/.gitignore index dd2abc5..32f4b16 100644 --- a/.gitignore +++ b/.gitignore @@ -13,4 +13,7 @@ target/ # MSVC Windows builds of rustc generate these, which store debugging information *.pdb -core \ No newline at end of file +core + +# Profiler files generated during coverage +*.profraw \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 0d6cc84..5791b40 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,9 +4,9 @@ version = 4 [[package]] name = "afl" -version = "0.17.0" +version = "0.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2027b09e36bf321ace7af43a23df3d46dae645b855be337b0b6582c79ad53769" +checksum = "62656735672273d859b4e3dcd2d3c6dbe2f4decee62e3c206aad2363b1a2f9e2" dependencies = [ "home", "libc", @@ -135,9 +135,9 @@ checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" [[package]] name = "clap" -version = "4.5.54" +version = "4.5.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6e6ff9dcd79cff5cd969a17a545d79e84ab086e444102a591e288a8aa3ce394" +checksum = "6899ea499e3fb9305a65d5ebf6e3d2248c5fab291f300ad0a704fbe142eae31a" dependencies = [ "clap_builder", "clap_derive", @@ -145,9 +145,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.54" +version = "4.5.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa42cf4d2b7a41bc8f663a7cab4031ebafa1bf3875705bfaf8466dc60ab52c00" +checksum = "7b12c8b680195a62a8364d16b8447b01b6c2c8f9aaf68bee653be34d4245e238" dependencies = [ "anstream", "anstyle", @@ -157,9 +157,9 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.49" +version = "4.5.55" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a0b5487afeab2deb2ff4e03a807ad1a03ac532ff5a2cee5d86884440c7f7671" +checksum = "a92793da1a46a5f2a02a6f4c46c6496b28c43638adea8306fcb0caa1634f24e5" dependencies = [ "heck", "proc-macro2", @@ -169,9 +169,9 @@ dependencies = [ [[package]] name = "clap_lex" -version = "0.7.6" +version = "0.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1d728cc89cf3aee9ff92b05e62b19ee65a02b5702cff7d5a377e32c6ae29d8d" +checksum = "c3e64b0cc0439b12df2fa678eae89a1c56a529fd067a9115f7827f1fffd22b32" [[package]] name = "colorchoice" @@ -406,9 +406,9 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" -version = "0.2.179" +version = "0.2.180" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5a2d376baa530d1238d133232d15e239abad80d05838b4b59354e5268af431f" +checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc" [[package]] name = "litemap" @@ -469,18 +469,18 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.105" +version = "1.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "535d180e0ecab6268a3e718bb9fd44db66bbbc256257165fc699dadf70d16fe7" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.43" +version = "1.0.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc74d9a594b72ae6656596548f56f667211f8a97b3d4c3d467150794690dc40a" +checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4" dependencies = [ "proc-macro2", ] @@ -513,9 +513,9 @@ dependencies = [ [[package]] name = "rand_core" -version = "0.9.3" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" +checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" dependencies = [ "getrandom", ] @@ -633,18 +633,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.17" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "2.0.17" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" dependencies = [ "proc-macro2", "quote", @@ -731,9 +731,9 @@ dependencies = [ [[package]] name = "wasip2" -version = "1.0.1+wasi-0.2.4" +version = "1.0.2+wasi-0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7" +checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5" dependencies = [ "wit-bindgen", ] @@ -755,9 +755,9 @@ dependencies = [ [[package]] name = "wit-bindgen" -version = "0.46.0" +version = "0.51.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" +checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" [[package]] name = "writeable" @@ -796,18 +796,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.32" +version = "0.8.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fabae64378cb18147bb18bca364e63bdbe72a0ffe4adf0addfec8aa166b2c56" +checksum = "57cf3aa6855b23711ee9852dfc97dfaa51c45feaba5b645d0c777414d494a961" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.32" +version = "0.8.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9c2d862265a8bb4471d87e033e730f536e2a285cc7cb05dbce09a2a97075f90" +checksum = "8a616990af1a287837c4fe6596ad77ef57948f787e46ce28e166facc0cc1cb75" dependencies = [ "proc-macro2", "quote", @@ -889,6 +889,6 @@ dependencies = [ [[package]] name = "zmij" -version = "1.0.12" +version = "1.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fc5a66a20078bf1251bde995aa2fdcc4b800c70b5d92dd2c62abc5c60f679f8" +checksum = "3ff05f8caa9038894637571ae6b9e29466c1f4f829d26c9b28f869a29cbe3445" diff --git a/Cargo.toml b/Cargo.toml index 50dadb7..b7c6ca6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,7 +23,7 @@ cli = [ "cargo_metadata", "twox-hash", ] -coverage = ["fork", "libc"] +coverage = [] [dependencies] afl = { version = "0.17.0", default-features = false, optional = true } diff --git a/README.md b/README.md index 7bea087..c365f4f 100644 --- a/README.md +++ b/README.md @@ -60,7 +60,7 @@ ziggy = { version = "1.4.0", default-features = false } Then use the `fuzz!` macro inside your `main` to create a harness. -```rust +```rust ignore fn main() { ziggy::fuzz!(|data: &[u8]| { println!("{data:?}"); diff --git a/src/bin/cargo-ziggy/coverage.rs b/src/bin/cargo-ziggy/coverage.rs index 165113e..4211c39 100644 --- a/src/bin/cargo-ziggy/coverage.rs +++ b/src/bin/cargo-ziggy/coverage.rs @@ -31,15 +31,12 @@ impl Cover { Cover::clean_old_cov()?; } - let mut shared_corpus = PathBuf::new(); - - shared_corpus.push( + let input_path = PathBuf::from( self.input .display() .to_string() .replace("{ziggy_output}", &self.ziggy_output.display().to_string()) - .replace("{target_name}", &self.target) - .as_str(), + .replace("{target_name}", &self.target), ); // Get the absolute path for the coverage directory to ensure .profraw files @@ -49,13 +46,24 @@ impl Cover { .join("target/coverage/debug/deps"); let profile_file = coverage_target_dir.join("coverage-%p-%m.profraw"); - let _ = process::Command::new(format!("./target/coverage/debug/{}", &self.target)) - .arg(format!("{}", shared_corpus.display())) - .env("LLVM_PROFILE_FILE", profile_file.display().to_string()) - .spawn() - .unwrap() - .wait_with_output() - .unwrap(); + let coverage_corpus = match input_path.is_dir() { + true => fs::read_dir(input_path) + .unwrap() + .flatten() + .map(|e| e.path()) + .collect(), + false => vec![input_path], + }; + + for file in coverage_corpus { + let _ = process::Command::new(format!("./target/coverage/debug/{}", &self.target)) + .arg(file.display().to_string()) + .env("LLVM_PROFILE_FILE", profile_file.display().to_string()) + .spawn() + .unwrap() + .wait_with_output() + .unwrap(); + } let source_or_workspace_root = match &self.source { Some(s) => s.display().to_string(), diff --git a/src/bin/cargo-ziggy/main.rs b/src/bin/cargo-ziggy/main.rs index 735f842..7d66fa1 100644 --- a/src/bin/cargo-ziggy/main.rs +++ b/src/bin/cargo-ziggy/main.rs @@ -200,10 +200,6 @@ pub struct Run { #[clap(value_name = "TARGET", default_value = DEFAULT_UNMODIFIED_TARGET)] target: String, - /// Maximum length of input - #[clap(short = 'G', long = "maxlength", default_value_t = 1048576)] - max_length: u64, - /// Input directories and/or files to run #[clap(short, long, value_name = "DIR", default_value = DEFAULT_CORPUS_DIR)] inputs: Vec, @@ -225,6 +221,10 @@ pub struct Run { /// Activate these features on the target #[clap(short = 'F', long, num_args = 0..)] features: Vec, + + /// Stop the run after the first crash is encountered + #[clap(short = 'x', long)] + stop_on_crash: bool, } #[derive(Args, Clone)] diff --git a/src/bin/cargo-ziggy/run.rs b/src/bin/cargo-ziggy/run.rs index 135b5cb..d09e5f4 100644 --- a/src/bin/cargo-ziggy/run.rs +++ b/src/bin/cargo-ziggy/run.rs @@ -67,14 +67,26 @@ impl Run { } } - let run_args: Vec = self + let input_files: Vec = self .inputs .iter() - .map(|x| { - x.display() + .flat_map(|x| { + let canonical_name = x + .display() .to_string() .replace("{ziggy_output}", &self.ziggy_output.display().to_string()) - .replace("{target_name}", &target) + .replace("{target_name}", &target); + // For each directory we read, we get all files in that directory + let path = PathBuf::from(canonical_name); + match path.is_dir() { + true => fs::read_dir(path) + .expect("could not read directory") + .filter_map(|entry| entry.ok()) + .map(|entry| entry.path()) + .filter(|path| path.is_file()) + .collect::>(), + false => vec![path], + } }) .collect(); @@ -83,21 +95,26 @@ impl Run { false => format!("./target/runner/debug/{}", target), }; - let res = process::Command::new(runner_path) - .args(run_args) - .env("RUST_BACKTRACE", "full") - .spawn() - .context("⚠️ couldn't spawn the runner process")? - .wait() - .context("⚠️ couldn't wait for the runner process")?; + for file in input_files { + let res = process::Command::new(&runner_path) + .arg(file) + .env("RUST_BACKTRACE", "full") + .spawn() + .context("⚠️ couldn't spawn the runner process")? + .wait() + .context("⚠️ couldn't wait for the runner process")?; - if !res.success() { - if let Some(signal) = res.signal() { - println!("⚠️ input terminated with signal {:?}!", signal); - } else if let Some(exit_code) = res.code() { - println!("⚠️ input terminated with code {:?}!", exit_code); - } else { - println!("⚠️ input terminated but we do not know why!"); + if !res.success() { + if let Some(signal) = res.signal() { + println!("⚠️ input terminated with signal {:?}!", signal); + } else if let Some(exit_code) = res.code() { + println!("⚠️ input terminated with code {:?}!", exit_code); + } else { + println!("⚠️ input terminated but we do not know why!"); + } + if self.stop_on_crash { + return Ok(()); + } } } diff --git a/src/lib.rs b/src/lib.rs index b9d7ac1..39e03c3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,115 +1,29 @@ #![doc = include_str!("../README.md")] #[cfg(feature = "afl")] pub use afl::fuzz as afl_fuzz; -#[cfg(feature = "coverage")] -pub use fork; #[cfg(feature = "honggfuzz")] pub use honggfuzz::fuzz as honggfuzz_fuzz; // This is our inner harness handler function for the runner. // We open the input file and feed the data to the harness closure. #[doc(hidden)] -#[cfg(not(feature = "coverage"))] -pub fn read_file_and_fuzz(mut closure: F, file: String) +pub fn run_file(mut closure: F) where F: FnMut(&[u8]), { - use std::{fs::File, io::Read}; - println!("Now running file {file}"); + use std::{env, fs::File, io::Read}; + let file_name: String = env::args().nth(1).expect("pass in a file name as argument"); + println!("Now running {file_name}"); let mut buffer: Vec = Vec::new(); - match File::open(file) { - Ok(mut f) => { - match f.read_to_end(&mut buffer) { - Ok(_) => { - closure(buffer.as_slice()); - } - Err(e) => { - println!("Could not get data from file: {e}"); - } - }; - } - Err(e) => { - println!("Error opening file: {e}"); - } - }; -} - -// This is our special coverage harness runner. -// We open the input file and feed the data to the harness closure. -// The difference with the runner is that we catch any kind of panic. -#[cfg(feature = "coverage")] -pub fn read_file_and_fuzz(mut closure: F, file: String) -where - F: FnMut(&[u8]), -{ - use std::{fs::File, io::Read, process::exit}; - println!("Now running file {file} for coverage"); - let mut buffer: Vec = Vec::new(); - match File::open(file) { - Ok(mut f) => { - match f.read_to_end(&mut buffer) { - Ok(_) => { - use crate::fork::{fork, Fork}; - - match fork() { - Ok(Fork::Parent(child)) => { - println!( - "Continuing execution in parent process, new child has pid: {}", - child - ); - unsafe { - let mut status = 0i32; - let _ = libc::waitpid(child, &mut status, 0); - } - println!("Child is done, moving on"); - } - Ok(Fork::Child) => { - closure(buffer.as_slice()); - exit(0); - } - Err(_) => println!("Fork failed"), - } - } - Err(e) => { - println!("Could not get data from file: {e}"); - } - }; - } - Err(e) => { - println!("Error opening file: {e}"); - } - }; -} - -// This is our middle harness handler macro for the runner and for coverage. -// We read input files and directories from the command line and run the inner harness `fuzz`. -#[doc(hidden)] -#[macro_export] -macro_rules! read_args_and_fuzz { - ( |$buf:ident| $body:block ) => { - use std::{env, fs}; - let args: Vec = env::args().collect(); - for path in &args[1..] { - if let Ok(metadata) = fs::metadata(&path) { - let files = match metadata.is_dir() { - true => fs::read_dir(&path) - .unwrap() - .map(|x| x.unwrap().path()) - .filter(|x| x.is_file()) - .map(|x| x.to_str().unwrap().to_string()) - .collect::>(), - false => vec![path.to_string()], - }; - - for file in files { - $crate::read_file_and_fuzz(|$buf| $body, file); - } - println!("Finished reading all files"); - } else { - println!("Could not read metadata for {path}"); - } - } - }; + let mut file = File::open(file_name).unwrap_or_else(|e| { + eprintln!("Could not open file: {e}"); + std::process::exit(1); + }); + file.read_to_end(&mut buffer).unwrap_or_else(|e| { + eprintln!("Could not read file: {e}"); + std::process::exit(1); + }); + closure(buffer.as_slice()); } /// Fuzz a closure-like block of code by passing an object of arbitrary type. @@ -135,13 +49,13 @@ macro_rules! read_args_and_fuzz { #[macro_export] macro_rules! inner_fuzz { (|$buf:ident| $body:block) => { - $crate::read_args_and_fuzz!(|$buf| $body); + $crate::run_file(|$buf| $body); }; (|$buf:ident: &[u8]| $body:block) => { - $crate::read_args_and_fuzz!(|$buf| $body); + $crate::run_file(|$buf| $body); }; (|$buf:ident: $dty: ty| $body:block) => { - $crate::read_args_and_fuzz!(|$buf| { + $crate::run_file(|$buf| { let $buf: $dty = { let mut data = ::arbitrary::Unstructured::new($buf); if let Ok(d) = ::arbitrary::Arbitrary::arbitrary(&mut data).map_err(|_| "") {