Skip to content
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

WIP: redesign arg parsing with structopt #52

Closed
wants to merge 2 commits into from
Closed
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
10 changes: 10 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,13 @@ exclude = [
travis-ci = { repository = "rust-fuzz/honggfuzz-rs", branch = "master" }
maintenance = { status = "actively-developed" }

[features]

cli = ["structopt"]

[dependencies]
arbitrary = "1"
structopt = { version = "0.3", optional = true }

[dev-dependencies]
rand = "0.8"
Expand All @@ -37,3 +42,8 @@ lazy_static = "1.4"

[target.'cfg(fuzzing_debug)'.dependencies]
memmap = "0.7"

[[bin]]
name = "cargo-hfuzz"
# requires the `cli` feature, but if it is forgotten, we want to fail compilation instead of silently skipping this binary
# we use conditionnal compilation to throw the error
Comment on lines +48 to +49
Copy link
Contributor

Choose a reason for hiding this comment

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

Not sure I would want to go this way, I'd prefer to split it into two crates and avoid the end user chores.

Copy link
Member Author

Choose a reason for hiding this comment

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

IDK, maintaining two crates, their synchronization, and making their doc reference each other in a non confusing way seems difficult too.

It would make sense to name the binary crate hfuzz though..

But the feature flag is not so bad, the user will try cargo install honggfuzz, will get a clear error stating that the flag is missing, and try again with cargo install honggfuzz --features=cli.

In the docs, the later command will be directly shown.

304 changes: 8 additions & 296 deletions src/bin/cargo-hfuzz.rs
Original file line number Diff line number Diff line change
@@ -1,304 +1,16 @@
use std::fs;
use std::env;
use std::process::{self, Command};
use std::os::unix::process::CommandExt;
use std::path::{Path, PathBuf};

const VERSION: &str = env!("CARGO_PKG_VERSION");
const HONGGFUZZ_TARGET: &str = "hfuzz_target";
const HONGGFUZZ_WORKSPACE: &str = "hfuzz_workspace";

#[cfg(target_family="windows")]
compile_error!("honggfuzz-rs does not currently support Windows but works well under WSL (Windows Subsystem for Linux)");

#[derive(PartialEq)]
enum BuildType {
ReleaseInstrumented,
ReleaseNotInstrumented,
ProfileWithGrcov,
Debug
}

// TODO: maybe use `rustc_version` crate
fn target_triple() -> String {
let output = Command::new("rustc").args(&["-v", "-V"]).output().unwrap();
let stdout = String::from_utf8(output.stdout).unwrap();
let triple = stdout.lines().filter(|l|{l.starts_with("host: ")}).next().unwrap().get(6..).unwrap();
triple.into()
}

fn find_crate_root() -> Option<PathBuf> {
let mut path = env::current_dir().unwrap();

while !path.join("Cargo.toml").is_file() {
// move to parent path
path = match path.parent() {
Some(parent) => parent.into(),
None => return None, // early return
};
}

Some(path)
}

fn debugger_command(target: &str) -> Command {
let debugger = env::var("HFUZZ_DEBUGGER").unwrap_or_else(|_| "rust-lldb".into());
let honggfuzz_target = env::var("CARGO_TARGET_DIR").unwrap_or_else(|_| HONGGFUZZ_TARGET.into());

let mut cmd = Command::new(&debugger);

match Path::new(&debugger).file_name().map(|f| f.to_string_lossy().contains("lldb")) {
Some(true) => {
cmd.args(&["-o", "b rust_panic", "-o", "r", "-o", "bt", "-f", &format!("{}/{}/debug/{}", &honggfuzz_target, target_triple(), target), "--"]);
}
_ => {
cmd.args(&["-ex", "b rust_panic", "-ex", "r", "-ex", "bt", "--args", &format!("{}/{}/debug/{}", &honggfuzz_target, target_triple(), target)]);
}
};

cmd
}

fn hfuzz_version() {
println!("cargo-hfuzz {}", VERSION);
}

fn hfuzz_run<T>(mut args: T, crate_root: &Path, build_type: &BuildType) where T: std::iter::Iterator<Item=String> {
let target = args.next().unwrap_or_else(||{
eprintln!("please specify the name of the target like this \"cargo hfuzz run[-debug|-no-instr] TARGET [ ARGS ... ]\"");
process::exit(1);
});

let honggfuzz_target = env::var("CARGO_TARGET_DIR").unwrap_or_else(|_| HONGGFUZZ_TARGET.into());
let honggfuzz_workspace = env::var("HFUZZ_WORKSPACE").unwrap_or_else(|_| HONGGFUZZ_WORKSPACE.into());
let honggfuzz_input = env::var("HFUZZ_INPUT").unwrap_or_else(|_| format!("{}/{}/input", honggfuzz_workspace, target));

hfuzz_build(vec!["--bin".to_string(), target.clone()].into_iter(), crate_root, build_type);

match *build_type {
BuildType::Debug => {
let crash_filename = args.next().unwrap_or_else(||{
eprintln!("please specify the crash filename like this \"cargo hfuzz run-debug TARGET CRASH_FILENAME [ ARGS ... ]\"");
process::exit(1);
});

let status = debugger_command(&target)
.args(args)
.env("CARGO_HONGGFUZZ_CRASH_FILENAME", crash_filename)
.env("RUST_BACKTRACE", env::var("RUST_BACKTRACE").unwrap_or_else(|_| "1".into()))
.status()
.unwrap();
if !status.success() {
process::exit(status.code().unwrap_or(1));
}
}
_ => {
// add some flags to sanitizers to make them work with Rust code
let asan_options = env::var("ASAN_OPTIONS").unwrap_or_default();
let asan_options = format!("detect_odr_violation=0:{}", asan_options);

let tsan_options = env::var("TSAN_OPTIONS").unwrap_or_default();
let tsan_options = format!("report_signal_unsafe=0:{}", tsan_options);

// get user-defined args for honggfuzz
let hfuzz_run_args = env::var("HFUZZ_RUN_ARGS").unwrap_or_default();
// FIXME: we split by whitespace without respecting escaping or quotes
let hfuzz_run_args = hfuzz_run_args.split_whitespace();

fs::create_dir_all(&format!("{}/{}/input", &honggfuzz_workspace, target)).unwrap_or_else(|_| {
println!("error: failed to create \"{}/{}/input\"", &honggfuzz_workspace, target);
});

let command = format!("{}/honggfuzz", &honggfuzz_target);
Command::new(&command) // exec honggfuzz replacing current process
.args(&["-W", &format!("{}/{}", &honggfuzz_workspace, target), "-f", &honggfuzz_input, "-P"])
.args(hfuzz_run_args) // allows user-specified arguments to be given to honggfuzz
.args(&["--", &format!("{}/{}/release/{}", &honggfuzz_target, target_triple(), target)])
.args(args)
.env("ASAN_OPTIONS", asan_options)
.env("TSAN_OPTIONS", tsan_options)
.exec();

// code flow will only reach here if honggfuzz failed to execute
eprintln!("cannot execute {}, try to execute \"cargo hfuzz build\" from fuzzed project directory", &command);
process::exit(1);
}
}
}

fn hfuzz_build<T>(args: T, crate_root: &Path, build_type: &BuildType) where T: std::iter::Iterator<Item=String> {
let honggfuzz_target = env::var("CARGO_TARGET_DIR").unwrap_or_else(|_| HONGGFUZZ_TARGET.into());

// HACK: temporary fix, see https://github.com/rust-lang/rust/issues/53945#issuecomment-426824324
let use_gold_linker: bool = match Command::new("which") // check if the gold linker is available
.args(&["ld.gold"])
.status() {
Err(_) => false,
Ok(status) => {
match status.code() {
Some(0) => true,
_ => false
}
}
};

let mut rustflags = "\
--cfg fuzzing \
-C debug-assertions \
-C overflow_checks \
".to_string();

let mut cargo_incremental = "1";
match *build_type {
BuildType::Debug => {
rustflags.push_str("\
--cfg fuzzing_debug \
-C opt-level=0 \
-C debuginfo=2 \
");
}

BuildType::ProfileWithGrcov => {
rustflags.push_str("\
--cfg fuzzing_debug \
-Zprofile \
-Cpanic=abort \
-C opt-level=0 \
-C debuginfo=2 \
-Ccodegen-units=1 \
-Cinline-threshold=0 \
-Clink-dead-code \
");
//-Coverflow-checks=off \
cargo_incremental = "0";
}

_ => {
rustflags.push_str("\
-C opt-level=3 \
-C target-cpu=native \
-C debuginfo=0 \
");

if *build_type == BuildType::ReleaseInstrumented {
rustflags.push_str("\
-C passes=sancov \
-C llvm-args=-sanitizer-coverage-level=4 \
-C llvm-args=-sanitizer-coverage-trace-pc-guard \
-C llvm-args=-sanitizer-coverage-trace-divs \
");

// trace-compares doesn't work on macOS without a sanitizer
if cfg!(not(target_os="macos")) {
rustflags.push_str("\
-C llvm-args=-sanitizer-coverage-trace-compares \
");
}

// HACK: temporary fix, see https://github.com/rust-lang/rust/issues/53945#issuecomment-426824324
if use_gold_linker {
rustflags.push_str("-Clink-arg=-fuse-ld=gold ");
}
}
}
}

// add user provided flags
rustflags.push_str(&env::var("RUSTFLAGS").unwrap_or_default());

// get user-defined args for building
let hfuzz_build_args = env::var("HFUZZ_BUILD_ARGS").unwrap_or_default();
// FIXME: we split by whitespace without respecting escaping or quotes
let hfuzz_build_args = hfuzz_build_args.split_whitespace();

let cargo_bin = env::var("CARGO").unwrap();
let mut command = Command::new(cargo_bin);
command.args(&["build", "--target", &target_triple()]) // HACK to avoid building build scripts with rustflags
.args(args)
.args(hfuzz_build_args) // allows user-specified arguments to be given to cargo build
.env("RUSTFLAGS", rustflags)
.env("CARGO_INCREMENTAL", cargo_incremental)
.env("CARGO_TARGET_DIR", &honggfuzz_target) // change target_dir to not clash with regular builds
.env("CRATE_ROOT", &crate_root);

if *build_type == BuildType::ProfileWithGrcov {
command.env("CARGO_HONGGFUZZ_BUILD_VERSION", VERSION) // used by build.rs to check that versions are in sync
.env("CARGO_HONGGFUZZ_TARGET_DIR", &honggfuzz_target); // env variable to be read by build.rs script
} // to place honggfuzz executable at a known location
else if *build_type != BuildType::Debug {
command.arg("--release")
.env("CARGO_HONGGFUZZ_BUILD_VERSION", VERSION) // used by build.rs to check that versions are in sync
.env("CARGO_HONGGFUZZ_TARGET_DIR", &honggfuzz_target); // env variable to be read by build.rs script
} // to place honggfuzz executable at a known location

let status = command.status().unwrap();
if !status.success() {
process::exit(status.code().unwrap_or(1));
}
}

fn hfuzz_clean<T>(args: T) where T: std::iter::Iterator<Item=String> {
let honggfuzz_target = env::var("CARGO_TARGET_DIR").unwrap_or_else(|_| HONGGFUZZ_TARGET.into());
let cargo_bin = env::var("CARGO").unwrap();
let status = Command::new(cargo_bin)
.args(&["clean"])
.args(args)
.env("CARGO_TARGET_DIR", &honggfuzz_target) // change target_dir to not clash with regular builds
.status()
.unwrap();
if !status.success() {
process::exit(status.code().unwrap_or(1));
#[cfg(feature = "cli")]
mod hfuzz;
#[cfg(not(feature = "cli"))]
mod hfuzz {
pub fn main() {
compile_error!("to build the `cargo-hfuzz` command-line tool, enable the `cli` feature with `--features=cli`");
}
}

fn main() {
// TODO: maybe use `clap` crate

let mut args = env::args().skip(1);
if args.next() != Some("hfuzz".to_string()) {
eprintln!("please launch as a cargo subcommand: \"cargo hfuzz ...\"");
process::exit(1);
}

// change to crate root to have the same behavior as cargo build/run
let crate_root = find_crate_root().unwrap_or_else(|| {
eprintln!("error: could not find `Cargo.toml` in current directory or any parent directory");
process::exit(1);
});
env::set_current_dir(&crate_root).unwrap();

match args.next() {
Some(ref s) if s == "build" => {
hfuzz_build(args, &crate_root, &BuildType::ReleaseInstrumented);
}
Some(ref s) if s == "build-no-instr" => {
hfuzz_build(args, &crate_root, &BuildType::ReleaseNotInstrumented);
}
Some(ref s) if s == "build-debug" => {
hfuzz_build(args, &crate_root, &BuildType::Debug);
}
Some(ref s) if s == "build-grcov" => {
hfuzz_build(args, &crate_root, &BuildType::ProfileWithGrcov);
}
Some(ref s) if s == "run" => {
hfuzz_run(args, &crate_root, &BuildType::ReleaseInstrumented);
}
Some(ref s) if s == "run-no-instr" => {
hfuzz_run(args, &crate_root, &BuildType::ReleaseNotInstrumented);
}

Some(ref s) if s == "run-debug" => {
hfuzz_run(args, &crate_root, &BuildType::Debug);
}
Some(ref s) if s == "clean" => {
hfuzz_clean(args);
}
Some(ref s) if s == "version" => {
hfuzz_version();
}
_ => {
eprintln!("possible commands are: run, run-no-instr, run-debug, build, build-no-instr, build-grcov, build-debug, clean, version");
process::exit(1);
}
}
}
hfuzz::main();
}
Loading