Skip to content

Change the runner behavior #113

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 10 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,7 @@ target/
# MSVC Windows builds of rustc generate these, which store debugging information
*.pdb

core
core

# Profiler files generated during coverage
*.profraw
10 changes: 0 additions & 10 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 1 addition & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
Expand All @@ -43,7 +42,7 @@ cli = [
"cargo_metadata",
"twox-hash",
]
coverage = ["fork", "libc"]
coverage = []

[lints.clippy]
needless_doctest_main = "allow"
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:?}");
Expand Down
38 changes: 23 additions & 15 deletions src/bin/cargo-ziggy/coverage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
Expand Down
8 changes: 4 additions & 4 deletions src/bin/cargo-ziggy/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<PathBuf>,
Expand All @@ -217,6 +213,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)]
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why -x? 'S' or 'C' might be more intuitive maybe? also what about timeouts?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

-x kind of looks like a line crashing 😄 I'm open to changing it to -C.

ziggy run reacting to timeouts is a good idea, I'll create an issue 👍

stop_on_crash: bool,
}

#[derive(Args, Clone)]
Expand Down
53 changes: 35 additions & 18 deletions src/bin/cargo-ziggy/run.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,14 +63,26 @@ impl Run {
}
}

let run_args: Vec<String> = self
let input_files: Vec<PathBuf> = 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::<Vec<_>>(),
false => vec![path],
}
})
.collect();

Expand All @@ -79,21 +91,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(());
}
}
}

Expand Down
120 changes: 17 additions & 103 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -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<F>(mut closure: F, file: String)
where
F: FnMut(&[u8]),
{
use std::{fs::File, io::Read};
println!("Now running file {file}");
let mut buffer: Vec<u8> = 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<F>(mut closure: F, file: String)
#[cfg(not(any(feature = "afl", feature = "honggfuzz")))]
pub fn run_file<F>(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<u8> = 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<String> = 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::<Vec<String>>(),
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.
Expand All @@ -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(|_| "") {
Expand Down
Loading