From a123380a62d98e6eb463962cc1e3cd6d67fe454b Mon Sep 17 00:00:00 2001 From: Paul Grandperrin Date: Wed, 28 Apr 2021 22:37:24 +0200 Subject: [PATCH 1/2] WIP: redesign arg parsing with structopt --- Cargo.toml | 1 + src/bin/cargo-hfuzz.rs | 75 +++++++++++++++++++++++++++++++++++++++++- 2 files changed, 75 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 80d9b64..72b77d3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,6 +27,7 @@ maintenance = { status = "actively-developed" } [dependencies] arbitrary = "1" +structopt = "0.3" [dev-dependencies] rand = "0.8" diff --git a/src/bin/cargo-hfuzz.rs b/src/bin/cargo-hfuzz.rs index 6d76d85..c7892e0 100644 --- a/src/bin/cargo-hfuzz.rs +++ b/src/bin/cargo-hfuzz.rs @@ -11,6 +11,77 @@ 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)"); +use structopt::StructOpt; + +#[derive(Debug, StructOpt)] +#[structopt(name = "cargo-hfuzz", about = "Fuzz your Rust code with Google-developed Honggfuzz !")] +struct Opt { + #[structopt(subcommand)] + command: OptSub, +} + +#[derive(Debug, StructOpt)] +struct CommonOpts { + /// only build binary but don't execute it + #[structopt(long)] + only_build: bool, + + /// flags given to `rustc`, for example "-Z sanitizer=address" + #[structopt(long, env = "RUSTFLAGS")] + rustflags: Option, + + /// args given to `cargo build` + #[structopt(long, env = "HFUZZ_BUILD_ARGS")] + build_args: Option, + + /// path to working directory + #[structopt(short, long, default_value = "hfuzz_workspace", env = "HFUZZ_WORKSPACE")] + workspace: String, +} + +#[derive(Debug, StructOpt)] +enum OptSub { + /// build and run fuzzing + Fuzz { + #[structopt(flatten)] + common_opts: CommonOpts, + + /// path to fuzzer's input files (aka "corpus"), relative to `$HFUZZ_WORKSPACE/{TARGET}` + #[structopt(short, long, default_value = "input", env = "HFUZZ_INPUT")] + input: String, + + /// which binary target to fuzz + target: String, + + /// do no build with compiler instrumentation + #[structopt(long)] + no_instr: bool, + + /// args to target -- args to fuzzer + /// ( https://github.com/google/honggfuzz/blob/master/docs/USAGE.md ) + args: Vec, + }, + + Debug { + #[structopt(flatten)] + common_opts: CommonOpts, + + /// name or path to debugger, like `rust-gdb`, `gdb`, `/usr/bin/lldb-7`.. + #[structopt(short, long, default_value = "rust-lldb", env = "HFUZZ_DEBUGGER")] + debugger: String, + + /// which binary target to fuzz + target: String, + + /// path to crash file, typically like `hfuzz_workspace/[TARGET]/[..].fuzz` + crash_file: PathBuf, + + /// args to target + target_args: Vec, + }, + Clean, +} + #[derive(PartialEq)] enum BuildType { ReleaseInstrumented, @@ -252,7 +323,9 @@ fn hfuzz_clean(args: T) where T: std::iter::Iterator { } fn main() { - // TODO: maybe use `clap` crate + let opt = Opt::from_args(); + println!("{:?}", opt); + return; // WIP let mut args = env::args().skip(1); if args.next() != Some("hfuzz".to_string()) { From b8951b9af261f4e4b80897683fe1617a44a078b5 Mon Sep 17 00:00:00 2001 From: Paul Grandperrin Date: Wed, 28 Apr 2021 22:40:40 +0200 Subject: [PATCH 2/2] only require structopt for CLI --- Cargo.toml | 11 +- src/bin/cargo-hfuzz.rs | 377 +---------------------------------------- src/bin/hfuzz/mod.rs | 377 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 395 insertions(+), 370 deletions(-) create mode 100644 src/bin/hfuzz/mod.rs diff --git a/Cargo.toml b/Cargo.toml index 72b77d3..43cfb9f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,9 +25,13 @@ exclude = [ travis-ci = { repository = "rust-fuzz/honggfuzz-rs", branch = "master" } maintenance = { status = "actively-developed" } +[features] + +cli = ["structopt"] + [dependencies] arbitrary = "1" -structopt = "0.3" +structopt = { version = "0.3", optional = true } [dev-dependencies] rand = "0.8" @@ -38,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 \ No newline at end of file diff --git a/src/bin/cargo-hfuzz.rs b/src/bin/cargo-hfuzz.rs index c7892e0..0d0e770 100644 --- a/src/bin/cargo-hfuzz.rs +++ b/src/bin/cargo-hfuzz.rs @@ -1,377 +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)"); -use structopt::StructOpt; - -#[derive(Debug, StructOpt)] -#[structopt(name = "cargo-hfuzz", about = "Fuzz your Rust code with Google-developed Honggfuzz !")] -struct Opt { - #[structopt(subcommand)] - command: OptSub, -} - -#[derive(Debug, StructOpt)] -struct CommonOpts { - /// only build binary but don't execute it - #[structopt(long)] - only_build: bool, - - /// flags given to `rustc`, for example "-Z sanitizer=address" - #[structopt(long, env = "RUSTFLAGS")] - rustflags: Option, - - /// args given to `cargo build` - #[structopt(long, env = "HFUZZ_BUILD_ARGS")] - build_args: Option, - - /// path to working directory - #[structopt(short, long, default_value = "hfuzz_workspace", env = "HFUZZ_WORKSPACE")] - workspace: String, -} - -#[derive(Debug, StructOpt)] -enum OptSub { - /// build and run fuzzing - Fuzz { - #[structopt(flatten)] - common_opts: CommonOpts, - - /// path to fuzzer's input files (aka "corpus"), relative to `$HFUZZ_WORKSPACE/{TARGET}` - #[structopt(short, long, default_value = "input", env = "HFUZZ_INPUT")] - input: String, - - /// which binary target to fuzz - target: String, - - /// do no build with compiler instrumentation - #[structopt(long)] - no_instr: bool, - - /// args to target -- args to fuzzer - /// ( https://github.com/google/honggfuzz/blob/master/docs/USAGE.md ) - args: Vec, - }, - - Debug { - #[structopt(flatten)] - common_opts: CommonOpts, - - /// name or path to debugger, like `rust-gdb`, `gdb`, `/usr/bin/lldb-7`.. - #[structopt(short, long, default_value = "rust-lldb", env = "HFUZZ_DEBUGGER")] - debugger: String, - - /// which binary target to fuzz - target: String, - - /// path to crash file, typically like `hfuzz_workspace/[TARGET]/[..].fuzz` - crash_file: PathBuf, - - /// args to target - target_args: Vec, - }, - Clean, -} - -#[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 { - 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(mut args: T, crate_root: &Path, build_type: &BuildType) where T: std::iter::Iterator { - 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(args: T, crate_root: &Path, build_type: &BuildType) where T: std::iter::Iterator { - 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(args: T) where T: std::iter::Iterator { - 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() { - let opt = Opt::from_args(); - println!("{:?}", opt); - return; // WIP - - 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(); +} \ No newline at end of file diff --git a/src/bin/hfuzz/mod.rs b/src/bin/hfuzz/mod.rs new file mode 100644 index 0000000..aa16dea --- /dev/null +++ b/src/bin/hfuzz/mod.rs @@ -0,0 +1,377 @@ +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"; + + +use structopt::StructOpt; + +#[derive(Debug, StructOpt)] +#[structopt(name = "cargo-hfuzz", about = "Fuzz your Rust code with Google-developed Honggfuzz !")] +struct Opt { + #[structopt(subcommand)] + command: OptSub, +} + +#[derive(Debug, StructOpt)] +struct CommonOpts { + /// only build binary but don't execute it + #[structopt(long)] + only_build: bool, + + /// flags given to `rustc`, for example "-Z sanitizer=address" + #[structopt(long, env = "RUSTFLAGS")] + rustflags: Option, + + /// args given to `cargo build` + #[structopt(long, env = "HFUZZ_BUILD_ARGS")] + build_args: Option, + + /// path to working directory + #[structopt(short, long, default_value = "hfuzz_workspace", env = "HFUZZ_WORKSPACE")] + workspace: String, +} + +#[derive(Debug, StructOpt)] +enum OptSub { + /// build and run fuzzing + Fuzz { + #[structopt(flatten)] + common_opts: CommonOpts, + + /// path to fuzzer's input files (aka "corpus"), relative to `$HFUZZ_WORKSPACE/{TARGET}` + #[structopt(short, long, default_value = "input", env = "HFUZZ_INPUT")] + input: String, + + /// which binary target to fuzz + target: String, + + /// do no build with compiler instrumentation + #[structopt(long)] + no_instr: bool, + + /// args to target -- args to fuzzer + /// ( https://github.com/google/honggfuzz/blob/master/docs/USAGE.md ) + args: Vec, + }, + + Debug { + #[structopt(flatten)] + common_opts: CommonOpts, + + /// name or path to debugger, like `rust-gdb`, `gdb`, `/usr/bin/lldb-7`.. + #[structopt(short, long, default_value = "rust-lldb", env = "HFUZZ_DEBUGGER")] + debugger: String, + + /// which binary target to fuzz + target: String, + + /// path to crash file, typically like `hfuzz_workspace/[TARGET]/[..].fuzz` + crash_file: PathBuf, + + /// args to target + target_args: Vec, + }, + Clean, +} + +#[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 { + 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(mut args: T, crate_root: &Path, build_type: &BuildType) where T: std::iter::Iterator { + 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(args: T, crate_root: &Path, build_type: &BuildType) where T: std::iter::Iterator { + 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(args: T) where T: std::iter::Iterator { + 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)); + } +} + +pub fn main() { + let opt = Opt::from_args(); + println!("{:?}", opt); + return; + + 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); + } + } +} + +