From 39abb04e3a83f089526211c3f0fa0b8888a63988 Mon Sep 17 00:00:00 2001 From: Christoph Gehrke Date: Mon, 30 Mar 2026 14:51:57 +0200 Subject: [PATCH 1/3] feat: add timeout for `cargo ziggy run` Additionally, copy timeouts into dedicated `timeout` dir within ziggy's output dir. --- Cargo.lock | 44 ++++++++ Cargo.toml | 2 + src/bin/cargo-ziggy/fuzz.rs | 212 ++++++++++++++++++++++++------------ src/bin/cargo-ziggy/main.rs | 18 ++- src/bin/cargo-ziggy/run.rs | 110 +++++++++++++++---- 5 files changed, 295 insertions(+), 91 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 11a79e3..dc90b42 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -106,6 +106,12 @@ version = "3.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" +[[package]] +name = "bytes" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" + [[package]] name = "camino" version = "1.2.2" @@ -579,6 +585,17 @@ dependencies = [ "libc", ] +[[package]] +name = "mio" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc" +dependencies = [ + "libc", + "wasi", + "windows-sys", +] + [[package]] name = "once_cell" version = "1.21.4" @@ -597,6 +614,12 @@ version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" +[[package]] +name = "pin-project-lite" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" + [[package]] name = "portable-atomic" version = "1.13.1" @@ -915,6 +938,20 @@ dependencies = [ "zerovec", ] +[[package]] +name = "tokio" +version = "1.50.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27ad5e34374e03cfffefc301becb44e9dc3c17584f414349ebe29ed26661822d" +dependencies = [ + "bytes", + "libc", + "mio", + "pin-project-lite", + "signal-hook-registry", + "windows-sys", +] + [[package]] name = "twox-hash" version = "2.1.2" @@ -989,6 +1026,12 @@ dependencies = [ "memchr", ] +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + [[package]] name = "wasip2" version = "1.0.2+wasi-0.2.9" @@ -1328,6 +1371,7 @@ dependencies = [ "target-triple", "tempfile", "time-humanize", + "tokio", "twox-hash", ] diff --git a/Cargo.toml b/Cargo.toml index c5519f5..f34c22c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -30,6 +30,7 @@ cli = [ "target-triple", "tempfile", "time-humanize", + "tokio", "twox-hash", ] coverage = [] @@ -51,6 +52,7 @@ strip-ansi-escapes = { version = "0.2.1", optional = true } target-triple = { version = "1.0.0", optional = true } tempfile = { version = "3.27.0", optional = true } time-humanize = { version = "0.1.3", optional = true } +tokio = { version = "1.50.0", features = ["process", "rt", "time"], optional = true } twox-hash = { version = "2.1.2", optional = true } [lints.clippy] diff --git a/src/bin/cargo-ziggy/fuzz.rs b/src/bin/cargo-ziggy/fuzz.rs index 7cb740b..0e6d549 100644 --- a/src/bin/cargo-ziggy/fuzz.rs +++ b/src/bin/cargo-ziggy/fuzz.rs @@ -31,6 +31,9 @@ use strip_ansi_escapes::strip_str; /// ``` /// The `all_afl_corpora` directory corresponds to the `output/target_name/afl/**/queue/` directories. impl Fuzz { + const HFUZZ_NO_CRASH: [&str; 3] = ["README.txt", "HONGGFUZZ.REPORT.TXT", "input"]; + const HFUZZ_TIMEOUT_PREFIX: &str = "SIGVTALRM"; + pub fn corpus(&self) -> String { self.corpus .display() @@ -111,7 +114,10 @@ impl Fuzz { let crash_dir = format!("{}/crashes/{time}", self.output_target()); let crash_path = Path::new(&crash_dir); + let timeouts_dir = format!("{}/timeouts/{time}", self.output_target()); + let timeouts_path = Path::new(&timeouts_dir); fs::create_dir_all(crash_path)?; + fs::create_dir_all(timeouts_path)?; fs::create_dir_all(format!("{}/logs", self.output_target()))?; fs::create_dir_all(format!("{}/queue", self.output_target()))?; @@ -173,7 +179,7 @@ impl Fuzz { let target = self.target.clone(); let main_corpus = self.corpus(); let output_target = self.output_target(); - let mut crashes = (String::new(), String::new()); + let mut stats = Stats::default(); common.shutdown_deferred(); // handle termination signals gracefully loop { @@ -182,12 +188,13 @@ impl Fuzz { if common.is_terminated() { eprintln!("\rShutting down..."); - let res = ( + let res = [ stop_fuzzers(&processes), self.sync_corpora(sync_after).map(|_| ()), self.sync_crashes(crash_path), - ); - return res.0.and(res.1).and(res.2); + self.sync_timeouts(timeouts_path), + ]; + return res.into_iter().fold(Ok(()), std::result::Result::and); } let coverage_status = match ( @@ -203,7 +210,7 @@ impl Fuzz { (false, _, _) => String::from("disabled"), }; - let current_crashes = self.print_stats(common, &coverage_status); + let current_stats = self.print_stats(common, &coverage_status); if coverage_status.as_str() == "starting" { *coverage_now_running.lock().unwrap() = true; @@ -285,10 +292,13 @@ impl Fuzz { } // Copy crash files from AFL++ and Honggfuzz's outputs - if current_crashes != crashes { - crashes = current_crashes; + if current_stats.crashes != stats.crashes { self.sync_crashes(crash_path)?; } + if current_stats.timeouts != stats.timeouts { + self.sync_timeouts(timeouts_path)?; + } + stats = current_stats; // Sync corpus dirs if last_sync_time.elapsed() > Duration::from_mins(self.corpus_sync_interval) { @@ -308,30 +318,54 @@ impl Fuzz { /// Copy crashes from AFL++ or Honggfuzz's outputs into `target_dir` fn sync_crashes(&self, target_dir: &Path) -> Result<(), anyhow::Error> { - let crash_dirs = glob(&format!("{}/afl/*/crashes", self.output_target())) + // afl + for dir in glob(&format!("{}/afl/*/crashes", self.output_target())) .map_err(|_| anyhow!("Failed to read crashes glob pattern"))? .flatten() - .chain(std::iter::once(PathBuf::from(format!( + { + copy_from_dir(dir, target_dir, |_| true)?; + } + + // honggfuzz + copy_from_dir( + PathBuf::from(format!( "{}/honggfuzz/{}", self.output_target(), self.target - )))); - - for crash_dir in crash_dirs { - if let Ok(crashes) = fs::read_dir(crash_dir) { - for crash_input in crashes.flatten() { - let file_name = crash_input.file_name(); - let to_path = target_dir.join(&file_name); - if ["README.txt", "HONGGFUZZ.REPORT.TXT", "input"] - .iter() - .all(|name| name != &file_name) - && !to_path.exists() - { - fs::copy(crash_input.path(), to_path)?; - } - } - } + )), + target_dir, + |file_name| { + let no_timeout = !BStr(file_name).starts_with(Self::HFUZZ_TIMEOUT_PREFIX); + no_timeout && Self::HFUZZ_NO_CRASH.iter().all(|name| *name != file_name) + }, + )?; + + Ok(()) + } + + fn sync_timeouts(&self, target_dir: &Path) -> Result<(), anyhow::Error> { + // afl + for dir in glob(&format!("{}/afl/*/hangs", self.output_target())) + .map_err(|_| anyhow!("Failed to read timeouts glob pattern"))? + .flatten() + { + copy_from_dir(dir, target_dir, |_| true)?; } + + // honggfuzz + copy_from_dir( + PathBuf::from(format!( + "{}/honggfuzz/{}", + self.output_target(), + self.target + )), + target_dir, + |file_name| { + let is_timeout = BStr(file_name).starts_with(Self::HFUZZ_TIMEOUT_PREFIX); + is_timeout && Self::HFUZZ_NO_CRASH.iter().all(|name| *name != file_name) + }, + )?; + Ok(()) } @@ -601,6 +635,7 @@ impl Fuzz { .env("AFL_IMPORT_FIRST", "1") .env(final_sync, "1") .env("AFL_IGNORE_SEED_PROBLEMS", "1") + .env("AFL_PIZZA_MODE", "-1") .stdout(log_destination()) .stderr(log_destination()) .spawn()?, @@ -610,21 +645,6 @@ impl Fuzz { } if honggfuzz_jobs > 0 { - let dictionary_option = match &self.dictionary { - Some(d) => format!("-w{}", &d.display().to_string()), - None => String::new(), - }; - - let timeout_option = match self.timeout { - Some(t) => format!("-t{t}"), - None => String::new(), - }; - - let memory_option = match &self.memory_limit { - Some(m) => format!("--rlimit_as{m}"), - None => String::new(), - }; - self.check_bin_target( super::target_dir() .join("honggfuzz") @@ -634,6 +654,29 @@ impl Fuzz { .as_std_path(), )?; + let run_args = { + let mut run_args = String::new(); + run_args.push_str(&format!(" --input={}", self.corpus())); + run_args.push_str(&format!(" -o{}/honggfuzz/corpus", self.output_target())); + run_args.push_str(&format!(" -n{honggfuzz_jobs}")); + run_args.push_str(&format!(" -F{}", self.max_length)); + run_args.push_str(&format!(" --dynamic_input={}/queue", self.output_target())); + run_args.push_str(" --tmout_sigvtalrm"); + if let Some(t) = self.timeout { + run_args.push_str(&format!(" -t{t}")); + } + if let Some(d) = &self.dictionary { + run_args.push_str(&format!(" -w{}", d.display())); + } + if let Some(m) = &self.memory_limit { + run_args.push_str(&format!(" --rlimit_as={m}")); + } + + run_args + }; + + let log = File::create(format!("{}/logs/honggfuzz.log", self.output_target()))?; + // The `script` invocation is a trick to get the correct TTY output for honggfuzz fuzzer_handles.push( process::Command::new("script") @@ -650,25 +693,10 @@ impl Fuzz { "HFUZZ_WORKSPACE", format!("{}/honggfuzz", self.output_target()), ) - .env( - "HFUZZ_RUN_ARGS", - format!( - "--input={} -o{}/honggfuzz/corpus -n{honggfuzz_jobs} -F{} --dynamic_input={}/queue {timeout_option} {dictionary_option} {memory_option}", - self.corpus(), - self.output_target(), - self.max_length, - self.output_target(), - ), - ) + .env("HFUZZ_RUN_ARGS", &run_args) .stdin(std::process::Stdio::null()) - .stderr(File::create(format!( - "{}/logs/honggfuzz.log", - self.output_target() - ))?) - .stdout(File::create(format!( - "{}/logs/honggfuzz.log", - self.output_target() - ))?) + .stderr(log.try_clone()?) + .stdout(log) .spawn()?, ); eprintln!( @@ -795,7 +823,7 @@ impl Fuzz { Ok(()) } - pub fn print_stats(&self, common: &Common, cov_worker_status: &str) -> (String, String) { + pub fn print_stats(&self, common: &Common, cov_worker_status: &str) -> Stats { let fuzzer_name = format!(" {} ", self.target); let reset = "\x1b[0m"; @@ -871,8 +899,8 @@ impl Fuzz { let mut hf_threads = String::new(); let mut hf_speed = String::new(); let mut hf_coverage = String::new(); - let mut hf_crashes = String::new(); - let mut hf_timeouts = String::new(); + let mut hf_crashes: usize = 0; + let mut hf_timeouts: usize = 0; let mut hf_new_finds = String::new(); if !self.honggfuzz() { @@ -914,9 +942,17 @@ impl Fuzz { .unwrap_or_default(), ); } else if let Some(crashes) = line.strip_prefix("Crashes : ") { - hf_crashes = String::from(crashes.split(' ').next().unwrap_or_default()); + hf_crashes = crashes + .split(' ') + .next() + .and_then(|n| n.parse().ok()) + .unwrap_or_default(); } else if let Some(timeouts) = line.strip_prefix("Timeouts : ") { - hf_timeouts = String::from(timeouts.split(' ').next().unwrap_or_default()); + hf_timeouts = timeouts + .split(' ') + .next() + .and_then(|n| n.parse().ok()) + .unwrap_or_default(); } else if let Some(new_finds) = line.strip_prefix("Cov Update : ") { hf_new_finds = String::from(new_finds.trim()); hf_new_finds = String::from( @@ -952,6 +988,9 @@ impl Fuzz { total_run_time = String::from("..."); } + // honggfuzz counts timeouts as crashes, we fix this + hf_crashes = hf_crashes.saturating_sub(hf_timeouts); + // Fourth step: Print stats let mut screen = String::new(); // We start by clearing the screen @@ -992,17 +1031,20 @@ impl Fuzz { screen += &format!( "│ {gray}threads :{reset} {hf_threads:17.17} │ {gray}coverage :{reset} {hf_coverage:17.17} │\n" ); - if hf_crashes == "0" { + if hf_crashes == 0 { screen += &format!( - "│{gray}average speed :{reset} {hf_speed:17.17} │ {gray}crashes saved :{reset} {hf_crashes:17.17} │\n" + "│{gray}average speed :{reset} {hf_speed:17.17} │ {gray}crashes saved :{reset} {:17.17} │\n", + hf_crashes.to_string(), ); } else { screen += &format!( - "│{gray}average speed :{reset} {hf_speed:17.17} │ {gray}crashes saved :{reset} {red}{hf_crashes:17.17}{reset} │\n" + "│{gray}average speed :{reset} {hf_speed:17.17} │ {gray}crashes saved :{reset} {red}{:17.17}{reset} │\n", + hf_crashes.to_string(), ); } screen += &format!( - "│ {gray}total execs :{reset} {hf_total_execs:17.17} │{gray}timeouts saved :{reset} {hf_timeouts:17.17} │\n" + "│ {gray}total execs :{reset} {hf_total_execs:17.17} │{gray}timeouts saved :{reset} {:17.17} │\n", + hf_timeouts.to_string(), ); screen += &format!( "│ │ {gray}no find for :{reset} {hf_new_finds:17.17} │\n" @@ -1019,7 +1061,10 @@ impl Fuzz { screen += "└──────────────────────────────────────────────────────────────────────┘\n"; } eprintln!("{screen}"); - (afl_crashes, hf_crashes) + Stats { + crashes: (afl_crashes, hf_crashes), + timeouts: (afl_timeouts, hf_timeouts), + } } } @@ -1076,3 +1121,34 @@ pub fn stop_fuzzers(processes: &[process::Child]) -> Result<(), Error> { } Ok(()) } + +fn copy_from_dir( + crash_dir: PathBuf, + target_dir: &Path, + filter: impl Fn(&std::ffi::OsStr) -> bool, +) -> Result<(), anyhow::Error> { + if let Ok(crashes) = fs::read_dir(crash_dir) { + for crash_input in crashes.flatten() { + let file_name = crash_input.file_name(); + let to_path = target_dir.join(&file_name); + if filter(&file_name) && !to_path.exists() { + fs::copy(crash_input.path(), to_path)?; + } + } + } + Ok(()) +} + +#[derive(Debug, Default)] +pub struct Stats { + crashes: (String, usize), + timeouts: (String, usize), +} + +struct BStr<'a>(&'a std::ffi::OsStr); + +impl BStr<'_> { + fn starts_with(&self, pat: &str) -> bool { + pat.len() <= self.0.len() && &self.0.as_encoded_bytes()[..pat.len()] == pat.as_bytes() + } +} diff --git a/src/bin/cargo-ziggy/main.rs b/src/bin/cargo-ziggy/main.rs index 0c42a00..5af6458 100644 --- a/src/bin/cargo-ziggy/main.rs +++ b/src/bin/cargo-ziggy/main.rs @@ -15,6 +15,7 @@ use clap::{Args, Parser, Subcommand, ValueEnum}; use std::{ fs, path::PathBuf, + sync::OnceLock, sync::{Arc, atomic::AtomicBool}, }; @@ -228,6 +229,10 @@ pub struct Run { #[clap(short = 'F', long, num_args = 0..)] features: Vec, + /// Timeout for a single run + #[clap(short, long, value_name = "SECS")] + timeout: Option, + /// Stop the run after the first crash is encountered #[clap(short = 'x', long)] stop_on_crash: bool, @@ -382,6 +387,7 @@ pub struct Common { terminate: Arc, sigs_done: Option<()>, pub cargo_path: PathBuf, + runtime: OnceLock, } impl Common { @@ -392,6 +398,7 @@ impl Common { cargo_path: std::env::var("CARGO") .unwrap_or_else(|_| String::from("cargo")) .into(), + runtime: OnceLock::new(), } } fn is_terminated(&self) -> bool { @@ -429,6 +436,15 @@ impl Common { cmd.stdin(std::process::Stdio::null()); cmd } + + fn async_runtime(&self) -> &tokio::runtime::Runtime { + self.runtime.get_or_init(|| { + tokio::runtime::Builder::new_current_thread() + .enable_all() + .build() + .expect("Failed building tokio runtime") + }) + } } fn main() -> Result<(), anyhow::Error> { @@ -440,7 +456,7 @@ fn main() -> Result<(), anyhow::Error> { match command { Ziggy::Build(args) => args.build().context("Failed to build the fuzzers"), Ziggy::Fuzz(mut args) => args.fuzz(&common).context("Failure running fuzzers"), - Ziggy::Run(mut args) => args.run().context("Failure running inputs"), + Ziggy::Run(mut args) => args.run(&common).context("Failure running inputs"), Ziggy::Minimize(mut args) => args.minimize().context("Failure running minimization"), Ziggy::Cover(mut args) => args .generate_coverage() diff --git a/src/bin/cargo-ziggy/run.rs b/src/bin/cargo-ziggy/run.rs index 2aa3c74..d905a73 100644 --- a/src/bin/cargo-ziggy/run.rs +++ b/src/bin/cargo-ziggy/run.rs @@ -1,4 +1,4 @@ -use crate::{Run, find_target}; +use crate::{Common, Run, find_target}; use anyhow::{Context, Result, bail}; use console::style; use std::{ @@ -6,13 +6,11 @@ use std::{ env, fs, os::unix::process::ExitStatusExt, path::{Path, PathBuf}, - process, }; impl Run { // Run inputs - pub fn run(&mut self) -> Result<(), anyhow::Error> { - let cargo = env::var("CARGO").unwrap_or_else(|_| String::from("cargo")); + pub fn run(&mut self, common: &Common) -> Result<(), anyhow::Error> { let target = find_target(&self.target)?; let target_dir = format!("--target-dir={}", super::target_dir().join("runner")); @@ -37,7 +35,8 @@ impl Run { eprintln!(" {} runner", style("Building").red().bold()); // We run the compilation command - let run = process::Command::new(&cargo) + let run = common + .cargo() .args(args) .env("RUSTFLAGS", rust_flags) .env("RUSTDOCFLAGS", rust_doc_flags) @@ -95,25 +94,35 @@ impl Run { super::target_dir().join(format!("runner/debug/{target}")) }; + let runner = Runner::new( + common.async_runtime(), + runner_path.as_std_path(), + self.timeout + .map(|s| tokio::time::Duration::from_secs(u64::from(s))), + ); + 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!"); + match runner.run(&file) { + Status::Ok(status) => { + if !status.success() { + if let Some(signal) = status.signal() { + println!("⚠️ input terminated with signal {signal:?}!"); + } else if let Some(exit_code) = status.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(()); + } + } } - if self.stop_on_crash { - return Ok(()); + Status::Err(e) => return Err(e), + Status::Timeout => { + println!("⚠️ input timed out!"); + if self.stop_on_crash { + return Ok(()); + } } } } @@ -138,3 +147,60 @@ fn collect_dirs_recursively( } Ok(()) } + +struct Runner<'a> { + rt: &'a tokio::runtime::Runtime, + path: &'a Path, + timeout: Option, +} + +impl<'a> Runner<'a> { + fn new( + rt: &'a tokio::runtime::Runtime, + path: &'a Path, + timeout: Option, + ) -> Self { + Self { rt, path, timeout } + } + + fn run(&self, seed: &Path) -> Status { + self.rt.block_on(async { + let mut child = match tokio::process::Command::new(self.path) + .arg(seed) + .env("RUST_BACKTRACE", "full") + .spawn() + .context("⚠️ couldn't spawn the runner process") + { + Ok(child) => child, + Err(e) => return e.into(), + }; + let res = if let Some(duration) = self.timeout { + if let Ok(res) = tokio::time::timeout(duration, child.wait()).await { + res + } else { + let _ = child.start_kill(); + return Status::Timeout; + } + } else { + child.wait().await + } + .context("⚠️ couldn't wait for the runner process"); + match res { + Ok(status) => Status::Ok(status), + Err(e) => e.into(), + } + }) + } +} + +enum Status { + Timeout, + Ok(std::process::ExitStatus), + Err(anyhow::Error), +} + +impl From for Status { + fn from(err: anyhow::Error) -> Self { + Self::Err(err) + } +} From 15aabb286ce7a4230cab419755b6483154677f61 Mon Sep 17 00:00:00 2001 From: Christoph Gehrke Date: Wed, 1 Apr 2026 10:57:14 +0200 Subject: [PATCH 2/3] chore: update doc and readme --- .github/workflows/ci.yml | 2 +- README.md | 12 +++++++----- src/lib.rs | 32 +++++++++++--------------------- 3 files changed, 19 insertions(+), 27 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4f55c73..e8c723a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,4 +1,4 @@ -name: Cargo Build & Test +name: ci on: push: diff --git a/README.md b/README.md index f34dc3d..7e14203 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,9 @@ # `ziggy` +[![Build status](https://github.com/srlabs/ziggy/actions/workflows/ci.yml/badge.svg)](https://github.com/srlabs/ziggy/actions/workflows/ci.yml) +[![Crates.io](https://img.shields.io/crates/v/ziggy.svg)](https://crates.io/crates/ziggy) +[![Docs.rs](https://img.shields.io/docsrs/ziggy)](https://docs.rs/ziggy) + `ziggy` is a fuzzer manager for Rust projects which is built to: - launch different fuzzers in parallel with a shared corpus @@ -43,7 +47,8 @@ Commands: cover Generate code coverage information using the existing corpus plot Plot AFL++ data using afl-plot add-seeds Add seeds to the running AFL++ fuzzers - triage Triage crashes found with casr - currently only works for AFL++ + triage Triage crashes found with CASR - currently only works for AFL++ + clean Remove generated artifacts from the target directory help Print this message or the help of the given subcommand(s) Options: @@ -76,6 +81,7 @@ After you've launched your fuzzer, you'll find a couple of items in the `output` - the `corpus` directory containing the full corpus - the `crashes` directory containing any crashes detected by the fuzzers +- the `timeouts` directory containing any timeouts/hangs detected by the fuzzers - the `logs` directory containing fuzzer log files - the `afl` directory containing AFL++'s output - the `honggfuzz` directory containing Honggfuzz's output @@ -94,10 +100,6 @@ CARGO_HOME=.cargo cargo ziggy cover This will clone every dependency into a `.cargo` directory and this directory will be included in the generated coverage. -## `ziggy` logs - -If you want to see `ziggy`'s internal logs, you can set `RUST_LOG=INFO`. - ## Trophy case [CVE-2026-24116](https://www.cve.org/CVERecord?id=CVE-2026-24116) was found in wasmtime by differential fuzzing with wasmi diff --git a/src/lib.rs b/src/lib.rs index 60db2e7..bf6586a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,8 +1,4 @@ #![doc = include_str!("../README.md")] -#[cfg(feature = "afl")] -pub use afl::fuzz as afl_fuzz; -#[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. @@ -26,9 +22,9 @@ where closure(buffer.as_slice()); } -/// Fuzz a closure-like block of code by passing an object of arbitrary type. +/// Fuzz a closure-like block of code by passing a slice or an object of arbitrary type. /// -/// It can handle different types of arguments for the harness closure, including Arbitrary. +/// It can handle different types of arguments for the harness closure, including [`Arbitrary`](https://docs.rs/arbitrary/latest/arbitrary/trait.Arbitrary.html). /// /// See [our examples](https://github.com/srlabs/ziggy/tree/main/examples). /// @@ -47,6 +43,7 @@ where /// # } /// ``` #[macro_export] +#[doc(hidden)] macro_rules! inner_fuzz { (|$buf:ident| $body:block) => { $crate::run_file(|$buf| $body); @@ -69,14 +66,13 @@ macro_rules! inner_fuzz { }; } -/// We need this wrapper -#[macro_export] #[cfg(not(any(feature = "afl", feature = "honggfuzz")))] -macro_rules! fuzz { - ( $($x:tt)* ) => { - $crate::inner_fuzz!($($x)*); - } -} +#[doc(inline)] +pub use inner_fuzz as fuzz; + +#[cfg(feature = "afl")] +#[doc(hidden)] +pub use afl::fuzz as afl_fuzz; #[macro_export] #[cfg(feature = "afl")] @@ -91,12 +87,6 @@ macro_rules! fuzz { }; } -#[macro_export] #[cfg(all(feature = "honggfuzz", not(feature = "afl")))] -macro_rules! fuzz { - ( $($x:tt)* ) => { - loop { - $crate::honggfuzz_fuzz!($($x)*); - } - }; -} +#[doc(inline)] +pub use honggfuzz::fuzz; From b8a2aa75dac060fd6b5f23400677f843628a3322 Mon Sep 17 00:00:00 2001 From: Christoph Gehrke Date: Tue, 7 Apr 2026 16:38:29 +0200 Subject: [PATCH 3/3] chore(examples): upgrade to 2024 edition --- examples/arbitrary/Cargo.toml | 4 ++-- examples/asan/Cargo.toml | 2 +- examples/url/Cargo.toml | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/examples/arbitrary/Cargo.toml b/examples/arbitrary/Cargo.toml index 67ab3c1..56b4b75 100644 --- a/examples/arbitrary/Cargo.toml +++ b/examples/arbitrary/Cargo.toml @@ -1,9 +1,9 @@ [package] name = "arbitrary-fuzz" version = "0.1.0" -edition = "2021" +edition = "2024" publish = false [dependencies] +arbitrary = { version = "1", features = ["derive"] } ziggy = { path = "../../", default-features = false } -arbitrary = { version = "1", features= ["derive"] } diff --git a/examples/asan/Cargo.toml b/examples/asan/Cargo.toml index 8c175a7..92bd236 100644 --- a/examples/asan/Cargo.toml +++ b/examples/asan/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "asan-fuzz" version = "0.1.0" -edition = "2021" +edition = "2024" [dependencies] ziggy = { path = "../../", default-features = false } diff --git a/examples/url/Cargo.toml b/examples/url/Cargo.toml index b19ce17..4fa5ffc 100644 --- a/examples/url/Cargo.toml +++ b/examples/url/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "url-fuzz" version = "0.1.0" -edition = "2021" +edition = "2024" publish = false [dependencies] @@ -9,4 +9,4 @@ url = "2.5.0" ziggy = { path = "../../", default-features = false } [features] -fuzzing = [] \ No newline at end of file +fuzzing = []