diff --git a/Cargo.lock b/Cargo.lock index f4e793220..02ff5a5af 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7,6 +7,7 @@ name = "afl" version = "0.12.8" dependencies = [ "arbitrary", + "assert_cmd", "clap", "fs_extra", "lazy_static", @@ -25,6 +26,20 @@ dependencies = [ "derive_arbitrary", ] +[[package]] +name = "assert_cmd" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93ae1ddd39efd67689deb1979d80bad3bf7f2b09c6e6117c8d1f2443b5e2f83e" +dependencies = [ + "bstr", + "doc-comment", + "predicates", + "predicates-core", + "predicates-tree", + "wait-timeout", +] + [[package]] name = "atty" version = "0.2.14" @@ -36,18 +51,23 @@ dependencies = [ "winapi", ] -[[package]] -name = "autocfg" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" - [[package]] name = "bitflags" version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +[[package]] +name = "bstr" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba3569f383e8f1598449f1a423e72e99569137b47740b1da11ef19af3d5c3223" +dependencies = [ + "lazy_static", + "memchr", + "regex-automata", +] + [[package]] name = "cfg-if" version = "1.0.0" @@ -56,25 +76,23 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "clap" -version = "3.2.22" +version = "4.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86447ad904c7fb335a790c9d7fe3d0d971dc523b8ccd1561a520de9a85302750" +checksum = "30607dd93c420c6f1f80b544be522a0238a7db35e6a12968d28910983fee0df0" dependencies = [ "atty", "bitflags", "clap_lex", - "indexmap", "once_cell", "strsim", "termcolor", - "textwrap", ] [[package]] name = "clap_lex" -version = "0.2.4" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2850f2f5a82cbf437dd5af4d49848fbdfc27c157c3d010345776f952765261c5" +checksum = "0d4198f73e42b4936b35b5bb248d81d2b595ecb170da0bac7655c54eedfa8da8" dependencies = [ "os_str_bytes", ] @@ -90,6 +108,12 @@ dependencies = [ "syn", ] +[[package]] +name = "difflib" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6184e33543162437515c2e2b48714794e37845ec9851711914eec9d308f6ebe8" + [[package]] name = "dirs" version = "4.0.0" @@ -110,6 +134,18 @@ dependencies = [ "winapi", ] +[[package]] +name = "doc-comment" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" + +[[package]] +name = "either" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797" + [[package]] name = "fastrand" version = "1.8.0" @@ -136,12 +172,6 @@ dependencies = [ "wasi", ] -[[package]] -name = "hashbrown" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" - [[package]] name = "hermit-abi" version = "0.1.19" @@ -152,22 +182,21 @@ dependencies = [ ] [[package]] -name = "indexmap" -version = "1.9.1" +name = "instant" +version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10a35a97730320ffe8e2d410b5d3b69279b98d2c14bdb8b70ea89ecf7888d41e" +checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" dependencies = [ - "autocfg", - "hashbrown", + "cfg-if", ] [[package]] -name = "instant" -version = "0.1.12" +name = "itertools" +version = "0.10.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" dependencies = [ - "cfg-if", + "either", ] [[package]] @@ -182,6 +211,12 @@ version = "0.2.134" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "329c933548736bc49fd575ee68c89e8be4d260064184389a5b77517cddd99ffb" +[[package]] +name = "memchr" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" + [[package]] name = "once_cell" version = "1.13.1" @@ -194,6 +229,33 @@ version = "6.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ff7415e9ae3fff1225851df9e0d9e4e5479f947619774677a63572e55e80eff" +[[package]] +name = "predicates" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5aab5be6e4732b473071984b3164dbbfb7a3674d30ea5ff44410b6bcd960c3c" +dependencies = [ + "difflib", + "itertools", + "predicates-core", +] + +[[package]] +name = "predicates-core" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da1c2388b1513e1b605fcec39a95e0a9e8ef088f71443ef37099fa9ae6673fcb" + +[[package]] +name = "predicates-tree" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d86de6de25020a36c6d3643a86d9a6a9f552107c0559c60ea03551b5e16c032" +dependencies = [ + "predicates-core", + "termtree", +] + [[package]] name = "proc-macro2" version = "1.0.43" @@ -232,6 +294,12 @@ dependencies = [ "thiserror", ] +[[package]] +name = "regex-automata" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" + [[package]] name = "remove_dir_all" version = "0.5.3" @@ -297,10 +365,10 @@ dependencies = [ ] [[package]] -name = "textwrap" -version = "0.15.1" +name = "termtree" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "949517c0cf1bf4ee812e2e07e08ab448e3ae0d23472aee8a06c985f0c8815b16" +checksum = "507e9898683b6c43a9aa55b64259b721b52ba226e0f3779137e50ad114a4c90b" [[package]] name = "thiserror" @@ -328,6 +396,15 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c4f5b37a154999a8f3f98cc23a628d850e154479cd94decf3414696e12e31aaf" +[[package]] +name = "wait-timeout" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f200f5b12eb75f8c1ed65abd4b2db8a6e1b138a20de009dacee265a2498f3f6" +dependencies = [ + "libc", +] + [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" diff --git a/Cargo.toml b/Cargo.toml index 91ac999c8..87832386e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,7 +17,7 @@ tempfile = "3.3" xdg = "2.4" [dependencies] -clap = { version = "3.2", features = ["cargo"] } +clap = { version = "4.0", features = ["cargo"] } lazy_static = { version = "1.4.0", optional = true } libc = "0.2.134" rustc_version = "0.4" @@ -25,6 +25,7 @@ xdg = "2.4" [dev-dependencies] arbitrary = { version = "1", features = ["derive"] } +assert_cmd = "2.0" tempfile = "3.3" [features] diff --git a/src/bin/cargo-afl.rs b/src/bin/cargo-afl.rs index 50ea6ec1d..f3e63fe0f 100644 --- a/src/bin/cargo-afl.rs +++ b/src/bin/cargo-afl.rs @@ -1,7 +1,7 @@ use clap::crate_version; use std::env; -use std::ffi::OsStr; +use std::ffi::{OsStr, OsString}; use std::io; use std::process::{self, Command, ExitStatus, Stdio}; use std::sync::{Arc, Condvar, Mutex}; @@ -30,59 +30,55 @@ fn main() { match afl_matches.subcommand() { Some(("analyze", sub_matches)) => { let args = sub_matches - .values_of_os("afl-analyze args") + .get_many::("afl-analyze args") .unwrap_or_default(); run_afl(args, "afl-analyze", None); } Some(("cmin", sub_matches)) => { let args = sub_matches - .values_of_os("afl-cmin args") + .get_many::("afl-cmin args") .unwrap_or_default(); run_afl(args, "afl-cmin", None); } Some(("fuzz", sub_matches)) => { let args = sub_matches - .values_of_os("afl-fuzz args") + .get_many::("afl-fuzz args") .unwrap_or_default(); - let timeout = sub_matches.value_of("max_total_time").map(|_| { - sub_matches - .value_of_t::("max_total_time") - .unwrap_or_else(|e| e.exit()) - }); + let timeout = sub_matches.get_one::("max_total_time").copied(); run_afl(args, "afl-fuzz", timeout); } Some(("gotcpu", sub_matches)) => { let args = sub_matches - .values_of_os("afl-gotcpu args") + .get_many::("afl-gotcpu args") .unwrap_or_default(); run_afl(args, "afl-gotcpu", None); } Some(("plot", sub_matches)) => { let args = sub_matches - .values_of_os("afl-plot args") + .get_many::("afl-plot args") .unwrap_or_default(); run_afl(args, "afl-plot", None); } Some(("showmap", sub_matches)) => { let args = sub_matches - .values_of_os("afl-showmap args") + .get_many::("afl-showmap args") .unwrap_or_default(); run_afl(args, "afl-showmap", None); } Some(("tmin", sub_matches)) => { let args = sub_matches - .values_of_os("afl-tmin args") + .get_many::("afl-tmin args") .unwrap_or_default(); run_afl(args, "afl-tmin", None); } Some(("whatsup", sub_matches)) => { let args = sub_matches - .values_of_os("afl-whatsup args") + .get_many::("afl-whatsup args") .unwrap_or_default(); run_afl(args, "afl-whatsup", None); } Some((subcommand, sub_matches)) => { - let args = sub_matches.values_of_os("").unwrap_or_default(); + let args = sub_matches.get_many::("").unwrap_or_default(); run_cargo(subcommand, args); } // unreachable due to SubcommandRequiredElseHelp on "afl" subcommand @@ -91,138 +87,134 @@ fn main() { } #[allow(clippy::too_many_lines)] -fn clap_app() -> clap::App<'static> { - use clap::{ - App, - AppSettings::{ - AllowExternalSubcommands, AllowHyphenValues, AllowInvalidUtf8ForExternalSubcommands, - DisableHelpFlag, DisableHelpSubcommand, DisableVersionFlag, SubcommandRequiredElseHelp, - }, - Arg, - }; +fn clap_app() -> clap::Command { + use clap::{value_parser, Arg, Command}; - App::new("cargo afl") - .bin_name("cargo") - .setting(SubcommandRequiredElseHelp) + Command::new("cargo afl") + .display_name("cargo") + .subcommand_required(true) + .arg_required_else_help(true) .subcommand( - App::new("afl") + Command::new("afl") .version(crate_version!()) - .setting(SubcommandRequiredElseHelp) - .setting(AllowExternalSubcommands) - .setting(AllowInvalidUtf8ForExternalSubcommands) + .subcommand_required(true) + .arg_required_else_help(true) + .allow_external_subcommands(true) + .external_subcommand_value_parser(value_parser!(OsString)) .override_usage("cargo afl [SUBCOMMAND or Cargo SUBCOMMAND]") .after_help( "In addition to the subcommands above, Cargo subcommands are also \ supported (see `cargo help` for a list of all Cargo subcommands).", ) .subcommand( - App::new("analyze") + Command::new("analyze") .about("Invoke afl-analyze") - .setting(AllowHyphenValues) - .setting(DisableHelpSubcommand) - .setting(DisableHelpFlag) - .setting(DisableVersionFlag) + .allow_hyphen_values(true) + .disable_help_subcommand(true) + .disable_help_flag(true) + .disable_version_flag(true) .arg( Arg::new("afl-analyze args") - .allow_invalid_utf8(true) - .multiple_values(true), + .value_parser(value_parser!(OsString)) + .num_args(0..), ), ) .subcommand( - App::new("cmin") + Command::new("cmin") .about("Invoke afl-cmin") - .setting(AllowHyphenValues) - .setting(DisableHelpSubcommand) - .setting(DisableHelpFlag) - .setting(DisableVersionFlag) + .allow_hyphen_values(true) + .disable_help_subcommand(true) + .disable_help_flag(true) + .disable_version_flag(true) .arg( Arg::new("afl-cmin args") - .allow_invalid_utf8(true) - .multiple_values(true), + .value_parser(value_parser!(OsString)) + .num_args(0..), ), ) .subcommand( - App::new("fuzz") + Command::new("fuzz") .about("Invoke afl-fuzz") - .setting(AllowHyphenValues) - .setting(DisableHelpSubcommand) - .setting(DisableHelpFlag) - .setting(DisableVersionFlag) + .allow_hyphen_values(true) + .disable_help_subcommand(true) + .disable_help_flag(true) + .disable_version_flag(true) .arg( Arg::new("max_total_time") .long("max_total_time") - .takes_value(true) + .num_args(1) + .value_parser(value_parser!(u64)) .help("Maximum amount of time to run the fuzzer"), ) .arg( Arg::new("afl-fuzz args") - .allow_invalid_utf8(true) - .multiple_values(true), + .value_parser(value_parser!(OsString)) + .num_args(0..), ), ) .subcommand( - App::new("gotcpu") + Command::new("gotcpu") .about("Invoke afl-gotcpu") - .setting(AllowHyphenValues) - .setting(DisableHelpSubcommand) - .setting(DisableHelpFlag) - .setting(DisableVersionFlag) + .allow_hyphen_values(true) + .disable_help_subcommand(true) + .disable_help_flag(true) + .disable_version_flag(true) .arg( Arg::new("afl-gotcpu args") - .allow_invalid_utf8(true) - .multiple_values(true), + .value_parser(value_parser!(OsString)) + .num_args(0..), ), ) .subcommand( - App::new("plot") + Command::new("plot") .about("Invoke afl-plot") - .setting(AllowHyphenValues) - .setting(DisableHelpSubcommand) - .setting(DisableHelpFlag) - .setting(DisableVersionFlag) + .allow_hyphen_values(true) + .disable_help_subcommand(true) + .disable_help_flag(true) + .disable_version_flag(true) .arg( Arg::new("afl-plot args") - .allow_invalid_utf8(true) - .multiple_values(true), + .value_parser(value_parser!(OsString)) + .num_args(0..), ), ) .subcommand( - App::new("showmap") + Command::new("showmap") .about("Invoke afl-showmap") - .setting(AllowHyphenValues) - .setting(DisableHelpSubcommand) - .setting(DisableHelpFlag) - .setting(DisableVersionFlag) + .allow_hyphen_values(true) + .disable_help_subcommand(true) + .disable_help_flag(true) + .disable_version_flag(true) .arg( Arg::new("afl-showmap args") - .allow_invalid_utf8(true) - .multiple_values(true), + .value_parser(value_parser!(OsString)) + .num_args(0..), ), ) .subcommand( - App::new("tmin") + Command::new("tmin") .about("Invoke afl-tmin") - .setting(AllowHyphenValues) - .setting(DisableHelpSubcommand) - .setting(DisableHelpFlag) - .setting(DisableVersionFlag) + .allow_hyphen_values(true) + .disable_help_subcommand(true) + .disable_help_flag(true) + .disable_version_flag(true) .arg( Arg::new("afl-tmin args") - .allow_invalid_utf8(true) - .multiple_values(true), + .value_parser(value_parser!(OsString)) + .num_args(0..), ), ) .subcommand( - App::new("whatsup") + Command::new("whatsup") .about("Invoke afl-whatsup") - .setting(AllowHyphenValues) - .setting(DisableHelpSubcommand) - .setting(DisableHelpFlag) - .setting(DisableVersionFlag) + .allow_hyphen_values(true) + .disable_help_subcommand(true) + .disable_help_flag(true) + .disable_version_flag(true) .arg( Arg::new("afl-whatsup args") - .allow_invalid_utf8(true) - .multiple_values(true), + .value_parser(value_parser!(OsString)) + .num_args(0..), ), ), ) @@ -438,12 +430,148 @@ fn is_nightly() -> bool { .success() } -#[cfg(test)] +#[cfg(all(test, unix))] mod tests { use super::*; + use assert_cmd::Command; + use std::os::unix::ffi::OsStringExt; #[test] fn test_app() { clap_app().debug_assert(); } + + #[test] + fn display_name() { + assert!( + String::from_utf8(cargo_afl(&["-V"]).output().unwrap().stdout) + .unwrap() + .starts_with("cargo-afl") + ); + } + + #[test] + fn afl_required_else_help() { + assert_eq!( + String::from_utf8(command().arg("--help").output().unwrap().stdout).unwrap(), + String::from_utf8(command().output().unwrap().stderr).unwrap() + ); + } + + #[test] + fn subcommand_required_else_help() { + assert_eq!( + String::from_utf8(cargo_afl(&["--help"]).output().unwrap().stdout).unwrap(), + String::from_utf8(cargo_afl::<&OsStr>(&[]).output().unwrap().stderr).unwrap() + ); + } + + #[test] + fn external_subcommands_allow_invalid_utf8() { + let _arg_matches = clap_app() + .try_get_matches_from(&[ + OsStr::new("cargo"), + OsStr::new("afl"), + OsStr::new("test"), + &invalid_utf8(), + ]) + .unwrap(); + } + + const SUBCOMMANDS: &[&str] = &[ + "analyze", "cmin", "fuzz", "gotcpu", "plot", "showmap", "tmin", "whatsup", + ]; + + #[test] + fn subcommands_allow_invalid_utf8() { + for &subcommand in SUBCOMMANDS.iter() { + let _arg_matches = clap_app() + .try_get_matches_from(&[ + OsStr::new("cargo"), + OsStr::new("afl"), + OsStr::new(subcommand), + &invalid_utf8(), + ]) + .unwrap(); + } + } + + #[test] + fn subcommands_allow_hyphen_values() { + for &subcommand in SUBCOMMANDS.iter() { + let _arg_matches = clap_app() + .try_get_matches_from(&["cargo", "afl", subcommand, "-i", "--input"]) + .unwrap(); + } + } + + #[test] + fn subcommands_help_subcommand_disabled() { + assert!( + String::from_utf8(cargo_afl(&["help"]).output().unwrap().stdout) + .unwrap() + .starts_with("Usage:") + ); + + for &subcommand in SUBCOMMANDS.iter() { + assert!( + !String::from_utf8(cargo_afl(&[subcommand, "help"]).output().unwrap().stdout) + .unwrap() + .starts_with("Usage:") + ); + } + } + + #[test] + fn subcommands_help_flag_disabled() { + assert!( + String::from_utf8(cargo_afl(&["--help"]).output().unwrap().stdout) + .unwrap() + .starts_with("Usage:") + ); + + for &subcommand in SUBCOMMANDS.iter() { + assert!(!String::from_utf8( + cargo_afl(&[subcommand, "--help"]).output().unwrap().stdout + ) + .unwrap() + .starts_with("Usage:")); + } + } + + #[test] + fn subcommands_version_flag_disabled() { + assert!( + String::from_utf8(cargo_afl(&["-V"]).output().unwrap().stdout) + .unwrap() + .starts_with("cargo-afl") + ); + + for &subcommand in SUBCOMMANDS.iter() { + assert!( + !String::from_utf8(cargo_afl(&[subcommand, "-V"]).output().unwrap().stdout) + .unwrap() + .starts_with("cargo-afl") + ); + } + } + + fn cargo_afl>(args: &[T]) -> Command { + let mut command = command(); + command.arg("afl").args(args); + command + } + + fn command() -> Command { + Command::cargo_bin("cargo-afl").unwrap() + } + + fn invalid_utf8() -> OsString { + OsString::from_vec(vec![0xfe]) + } + + #[test] + fn invalid_utf8_is_invalid() { + assert!(String::from_utf8(invalid_utf8().into_vec()).is_err()); + } }