From 499e42e71274b7ea1a540a03200a97a2bd54ae61 Mon Sep 17 00:00:00 2001 From: Louis Merlin Date: Mon, 9 Dec 2024 16:18:03 +0100 Subject: [PATCH 01/10] Add new mechanism for running inputs --- Cargo.lock | 10 ---- Cargo.toml | 2 - src/bin/cargo-ziggy/run.rs | 35 +++++++---- src/lib.rs | 120 ++++++------------------------------- 4 files changed, 42 insertions(+), 125 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8346b7e..b81e3e2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -213,15 +213,6 @@ version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" -[[package]] -name = "fork" -version = "0.1.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60e74d3423998a57e9d906e49252fb79eb4a04d5cdfe188fb1b7ff9fc076a8ed" -dependencies = [ - "libc", -] - [[package]] name = "form_urlencoded" version = "1.2.1" @@ -706,7 +697,6 @@ dependencies = [ "cargo_metadata", "clap", "console", - "fork", "glob", "honggfuzz", "libc", diff --git a/Cargo.toml b/Cargo.toml index 0f05d2e..3da9fda 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,7 +20,6 @@ anyhow = { version = "1.0.83", optional = true } cargo_metadata = { version = "0.18.1", optional = true } clap = { version = "4.5.4", features = ["cargo", "derive", "env"], optional = true } console = { version = "0.15.8", optional = true } -fork = { version = "0.1.23", optional = true } glob = { version = "0.3.1", optional = true } honggfuzz = { version = "0.5.56", optional = true } libc = { version = "0.2.153", optional = true } @@ -43,7 +42,6 @@ cli = [ "cargo_metadata", "twox-hash", ] -coverage = ["fork", "libc"] [lints.clippy] needless_doctest_main = "allow" diff --git a/src/bin/cargo-ziggy/run.rs b/src/bin/cargo-ziggy/run.rs index cfa1a33..2e475db 100644 --- a/src/bin/cargo-ziggy/run.rs +++ b/src/bin/cargo-ziggy/run.rs @@ -62,15 +62,28 @@ impl Run { } } - let run_args: Vec = self + let input_files: Vec = self .inputs .iter() .map(|x| { - x.display() + 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], + } }) + .flatten() .collect(); let runner_path = match self.asan { @@ -78,13 +91,15 @@ impl Run { false => format!("./target/runner/debug/{}", target), }; - 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 { + 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")?; + } Ok(()) } diff --git a/src/lib.rs b/src/lib.rs index 96cdd9f..835bf7a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,116 +1,30 @@ #![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(any(feature = "afl", feature = "honggfuzz", feature = "coverage")))] -pub fn read_file_and_fuzz(mut closure: F, file: String) -where - F: FnMut(&[u8]), -{ - use std::{fs::File, io::Read}; - println!("Now running file {file}"); - 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) +#[cfg(not(any(feature = "afl", feature = "honggfuzz")))] +pub fn run_file(mut closure: F) where F: FnMut(&[u8]), { - use std::{fs::File, io::Read, process::exit}; - println!("Now running file {file} for coverage"); + 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(_) => { - 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] -#[cfg(not(any(feature = "afl", feature = "honggfuzz")))] -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. @@ -137,13 +51,13 @@ macro_rules! read_args_and_fuzz { #[cfg(not(any(feature = "afl", feature = "honggfuzz")))] macro_rules! 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(|_| "") { From cb225190ae2f26776d231081bd8cea6ee0329fab Mon Sep 17 00:00:00 2001 From: Louis Merlin Date: Mon, 9 Dec 2024 16:23:22 +0100 Subject: [PATCH 02/10] Fix clippy --- src/bin/cargo-ziggy/run.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/bin/cargo-ziggy/run.rs b/src/bin/cargo-ziggy/run.rs index 2e475db..2b56791 100644 --- a/src/bin/cargo-ziggy/run.rs +++ b/src/bin/cargo-ziggy/run.rs @@ -65,7 +65,7 @@ impl Run { let input_files: Vec = self .inputs .iter() - .map(|x| { + .flat_map(|x| { let canonical_name = x .display() .to_string() @@ -83,7 +83,6 @@ impl Run { false => vec![path], } }) - .flatten() .collect(); let runner_path = match self.asan { From 878cebb95f7fee67868d20f0ceb1a6bd9d28b3ee Mon Sep 17 00:00:00 2001 From: Louis Merlin Date: Thu, 12 Dec 2024 07:45:40 +0100 Subject: [PATCH 03/10] Fix tests --- Cargo.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/Cargo.toml b/Cargo.toml index 3da9fda..6326a2d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -42,6 +42,7 @@ cli = [ "cargo_metadata", "twox-hash", ] +coverage = [] [lints.clippy] needless_doctest_main = "allow" From 73f36641723afdc0ef3f14e6fa74c0790b974782 Mon Sep 17 00:00:00 2001 From: Louis Merlin Date: Thu, 12 Dec 2024 08:00:56 +0100 Subject: [PATCH 04/10] Add ignore tag on README code --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 11f2ae7..4d04769 100644 --- a/README.md +++ b/README.md @@ -60,7 +60,7 @@ ziggy = { version = "1.2", 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:?}"); From 05dbf7dfed88403cac6216d4c8253edb9b0dc002 Mon Sep 17 00:00:00 2001 From: Louis Merlin Date: Mon, 16 Dec 2024 11:14:28 +0100 Subject: [PATCH 05/10] Add -x flag --- src/bin/cargo-ziggy/main.rs | 4 ++++ src/bin/cargo-ziggy/run.rs | 3 +++ 2 files changed, 7 insertions(+) diff --git a/src/bin/cargo-ziggy/main.rs b/src/bin/cargo-ziggy/main.rs index 436cadf..3fed8a8 100644 --- a/src/bin/cargo-ziggy/main.rs +++ b/src/bin/cargo-ziggy/main.rs @@ -217,6 +217,10 @@ pub struct Run { /// Build with ASAN (nightly only) #[clap(long = "asan", action)] asan: bool, + + /// 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 72c4257..cbb4fe4 100644 --- a/src/bin/cargo-ziggy/run.rs +++ b/src/bin/cargo-ziggy/run.rs @@ -108,6 +108,9 @@ impl Run { } else { println!("⚠️ input terminated but we do not know why!"); } + if self.stop_on_crash { + return Ok(()); + } } } From ac11b77f70247bbd3e3e545117be135f53869282 Mon Sep 17 00:00:00 2001 From: Louis Merlin Date: Mon, 16 Dec 2024 11:39:29 +0100 Subject: [PATCH 06/10] Fix fmt --- src/bin/cargo-ziggy/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bin/cargo-ziggy/main.rs b/src/bin/cargo-ziggy/main.rs index 3fed8a8..0cf7f1c 100644 --- a/src/bin/cargo-ziggy/main.rs +++ b/src/bin/cargo-ziggy/main.rs @@ -219,7 +219,7 @@ pub struct Run { asan: bool, /// Stop the run after the first crash is encountered - #[clap(short='x', long)] + #[clap(short = 'x', long)] stop_on_crash: bool, } From 93f7003243fba306b0920fa03a2f1d389f3ee322 Mon Sep 17 00:00:00 2001 From: Louis Merlin Date: Mon, 16 Dec 2024 11:41:49 +0100 Subject: [PATCH 07/10] Fix coverage with new runner --- src/bin/cargo-ziggy/coverage.rs | 38 ++++++++++++++++++++------------- 1 file changed, 23 insertions(+), 15 deletions(-) diff --git a/src/bin/cargo-ziggy/coverage.rs b/src/bin/cargo-ziggy/coverage.rs index 9a830af..0b10b0a 100644 --- a/src/bin/cargo-ziggy/coverage.rs +++ b/src/bin/cargo-ziggy/coverage.rs @@ -23,27 +23,35 @@ 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), ); - let _ = process::Command::new(format!("./target/coverage/debug/{}", &self.target)) - .arg(format!("{}", shared_corpus.display())) - .env( - "LLVM_PROFILE_FILE", - "target/coverage/debug/deps/coverage-%p-%m.profraw", - ) - .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", + "target/coverage/debug/deps/coverage-%p-%m.profraw", + ) + .spawn() + .unwrap() + .wait_with_output() + .unwrap(); + } let source_or_workspace_root = match &self.source { Some(s) => s.display().to_string(), From bbca978af237abe753dde778ce80853bd0215088 Mon Sep 17 00:00:00 2001 From: Louis Merlin Date: Mon, 16 Dec 2024 11:44:15 +0100 Subject: [PATCH 08/10] Add .profraw to gitignore --- .gitignore | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) 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 From 7e11b9848f81e41529f0e8ef2472e581f1956333 Mon Sep 17 00:00:00 2001 From: Louis Merlin Date: Mon, 16 Dec 2024 11:53:24 +0100 Subject: [PATCH 09/10] Remove unimplemented -G flag --- src/bin/cargo-ziggy/main.rs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/bin/cargo-ziggy/main.rs b/src/bin/cargo-ziggy/main.rs index 0cf7f1c..8d0d1bd 100644 --- a/src/bin/cargo-ziggy/main.rs +++ b/src/bin/cargo-ziggy/main.rs @@ -196,10 +196,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, From 8047febca98012caf46f4f141da65f0c31ea64ab Mon Sep 17 00:00:00 2001 From: Louis Merlin Date: Thu, 5 Feb 2026 11:55:29 +0100 Subject: [PATCH 10/10] Fix bug --- src/lib.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 0636b9a..39e03c3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -7,7 +7,6 @@ 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(any(feature = "afl", feature = "honggfuzz")))] pub fn run_file(mut closure: F) where F: FnMut(&[u8]),