Skip to content

Commit

Permalink
add a single-shot crash report utility (microsoft#703)
Browse files Browse the repository at this point in the history
Adds `test-input` and `test-input-libfuzzer`, which print the CrashTestResult in json form.

While many of the existing tasks make sense running in a managed loop, crash report generation is something that having a single one-off is useful.

Example:
```
$ onefuzz-agent local test-input /tmp/fuzz.exe /tmp/crash.txt
{
  "crash_report": {
    "input_sha256": "a35b3ce1038750e9175a6dcd3f64c8d4e85720affb12cc11f5d0b6889274d06e",
    "executable": "/tmp/fuzz.exe",
    "crash_type": "SIGABRT",
    "crash_site": "0x7f0d9d4ad18b in gsignal+0xcb (/usr/lib/x86_64-linux-gnu/libc-2.31.so+0x4618b)",
    "call_stack": [
      "#0 0x7f0d9d4ad18b in gsignal+0xcb (/usr/lib/x86_64-linux-gnu/libc-2.31.so+0x4618b)",
      "#1 0x7f0d9d48c859 in abort+0x12b (/usr/lib/x86_64-linux-gnu/libc-2.31.so+0x25859)",
      "#2 0x7f0d9d4f73ee in <unknown> (/usr/lib/x86_64-linux-gnu/libc-2.31.so+0x903ee)",
      "#3 0x7f0d9d599b4a in __fortify_fail+0x2a (/usr/lib/x86_64-linux-gnu/libc-2.31.so+0x132b4a)",
      "#4 0x7f0d9d5983e6 in __chk_fail+0x16 (/usr/lib/x86_64-linux-gnu/libc-2.31.so+0x1313e6)",
      "#5 0x7f0d9d597e09 in __strncpy_chk+0x19 (/usr/lib/x86_64-linux-gnu/libc-2.31.so+0x130e09)",
      "#6 0x400a54 in from_file+0xa4 (/tmp/fuzz.exe+0xa54)",
      "#7 0x7f0d9d48e0b3 in __libc_start_main+0xf3 (/usr/lib/x86_64-linux-gnu/libc-2.31.so+0x270b3)",
      "#8 0x40077a in _start+0x2a (/tmp/fuzz.exe+0x77a)"
    ],
    "call_stack_sha256": "6906234fb235690cc2843a1a55f49ff68b424e54bec55f9b8258415d97b3e638",
    "task_id": "00000000-0000-0000-0000-000000000000",
    "job_id": "00000000-0000-0000-0000-000000000000"
  }
}
$
```
  • Loading branch information
bmc-msft authored Mar 22, 2021
1 parent cf6c4e5 commit 7be4f3b
Show file tree
Hide file tree
Showing 15 changed files with 207 additions and 27 deletions.
10 changes: 9 additions & 1 deletion src/agent/onefuzz-agent/src/local/cmd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use clap::{App, SubCommand};
use crate::local::{
common::add_common_config, generic_analysis, generic_crash_report, generic_generator,
libfuzzer, libfuzzer_coverage, libfuzzer_crash_report, libfuzzer_fuzz, libfuzzer_merge,
radamsa,
libfuzzer_test_input, radamsa, test_input,
};

const RADAMSA: &str = "radamsa";
Expand All @@ -16,9 +16,11 @@ const LIBFUZZER_FUZZ: &str = "libfuzzer-fuzz";
const LIBFUZZER_CRASH_REPORT: &str = "libfuzzer-crash-report";
const LIBFUZZER_COVERAGE: &str = "libfuzzer-coverage";
const LIBFUZZER_MERGE: &str = "libfuzzer-merge";
const LIBFUZZER_TEST_INPUT: &str = "libfuzzer-test-input";
const GENERIC_CRASH_REPORT: &str = "generic-crash-report";
const GENERIC_GENERATOR: &str = "generic-generator";
const GENERIC_ANALYSIS: &str = "generic-analysis";
const GENERIC_TEST_INPUT: &str = "generic-test-input";

pub async fn run(args: &clap::ArgMatches<'_>) -> Result<()> {
match args.subcommand() {
Expand All @@ -31,6 +33,8 @@ pub async fn run(args: &clap::ArgMatches<'_>) -> Result<()> {
(GENERIC_ANALYSIS, Some(sub)) => generic_analysis::run(sub).await,
(GENERIC_CRASH_REPORT, Some(sub)) => generic_crash_report::run(sub).await,
(GENERIC_GENERATOR, Some(sub)) => generic_generator::run(sub).await,
(GENERIC_TEST_INPUT, Some(sub)) => test_input::run(sub).await,
(LIBFUZZER_TEST_INPUT, Some(sub)) => libfuzzer_test_input::run(sub).await,
_ => {
anyhow::bail!("missing subcommand\nUSAGE: {}", args.usage());
}
Expand All @@ -57,4 +61,8 @@ pub fn args(name: &str) -> App<'static, 'static> {
GENERIC_GENERATOR,
)))
.subcommand(add_common_config(generic_analysis::args(GENERIC_ANALYSIS)))
.subcommand(add_common_config(test_input::args(GENERIC_TEST_INPUT)))
.subcommand(add_common_config(libfuzzer_test_input::args(
LIBFUZZER_TEST_INPUT,
)))
}
15 changes: 13 additions & 2 deletions src/agent/onefuzz-agent/src/local/common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ pub const CHECK_ASAN_LOG: &str = "check_asan_log";
pub const TOOLS_DIR: &str = "tools_dir";
pub const RENAME_OUTPUT: &str = "rename_output";
pub const CHECK_FUZZER_HELP: &str = "check_fuzzer_help";
pub const DISABLE_CHECK_DEBUGGER: &str = "disable_check_debugger";

pub const TARGET_EXE: &str = "target_exe";
pub const TARGET_ENV: &str = "target_env";
Expand Down Expand Up @@ -176,9 +177,19 @@ pub fn get_synced_dir(
})
}

pub fn build_common_config(args: &ArgMatches<'_>) -> Result<CommonConfig> {
// NOTE: generate_task_id is intended to change the default behavior for local
// fuzzing tasks from generating random task id to using UUID::nil(). This
// enables making the one-shot crash report generation, which isn't really a task,
// consistent across multiple runs.
pub fn build_common_config(args: &ArgMatches<'_>, generate_task_id: bool) -> Result<CommonConfig> {
let job_id = get_uuid("job_id", args).unwrap_or_else(|_| Uuid::nil());
let task_id = get_uuid("task_id", args).unwrap_or_else(|_| Uuid::new_v4());
let task_id = get_uuid("task_id", args).unwrap_or_else(|_| {
if generate_task_id {
Uuid::new_v4()
} else {
Uuid::nil()
}
});
let instance_id = get_uuid("instance_id", args).unwrap_or_else(|_| Uuid::nil());

let setup_dir = if args.is_present(SETUP_DIR) {
Expand Down
2 changes: 1 addition & 1 deletion src/agent/onefuzz-agent/src/local/generic_analysis.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ pub fn build_analysis_config(
}

pub async fn run(args: &clap::ArgMatches<'_>) -> Result<()> {
let common = build_common_config(args)?;
let common = build_common_config(args, true)?;
let config = build_analysis_config(args, None, common)?;
run_analysis(config).await
}
Expand Down
13 changes: 7 additions & 6 deletions src/agent/onefuzz-agent/src/local/generic_crash_report.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@
use crate::{
local::common::{
build_common_config, get_cmd_arg, get_cmd_env, get_cmd_exe, get_synced_dir, CmdType,
CHECK_ASAN_LOG, CHECK_RETRY_COUNT, CRASHES_DIR, DISABLE_CHECK_QUEUE, NO_REPRO_DIR,
REPORTS_DIR, TARGET_ENV, TARGET_EXE, TARGET_OPTIONS, TARGET_TIMEOUT, UNIQUE_REPORTS_DIR,
CHECK_ASAN_LOG, CHECK_RETRY_COUNT, CRASHES_DIR, DISABLE_CHECK_DEBUGGER,
DISABLE_CHECK_QUEUE, NO_REPRO_DIR, REPORTS_DIR, TARGET_ENV, TARGET_EXE, TARGET_OPTIONS,
TARGET_TIMEOUT, UNIQUE_REPORTS_DIR,
},
tasks::{
config::CommonConfig,
Expand Down Expand Up @@ -46,7 +47,7 @@ pub fn build_report_config(
let check_retry_count = value_t!(args, CHECK_RETRY_COUNT, u64)?;
let check_queue = !args.is_present(DISABLE_CHECK_QUEUE);
let check_asan_log = args.is_present(CHECK_ASAN_LOG);
let check_debugger = !args.is_present("disable_check_debugger");
let check_debugger = !args.is_present(DISABLE_CHECK_DEBUGGER);

let config = Config {
target_exe,
Expand All @@ -70,7 +71,7 @@ pub fn build_report_config(
}

pub async fn run(args: &clap::ArgMatches<'_>) -> Result<()> {
let common = build_common_config(args)?;
let common = build_common_config(args, true)?;
let config = build_report_config(args, None, common)?;
ReportTask::new(config).managed_run().await
}
Expand Down Expand Up @@ -121,9 +122,9 @@ pub fn build_shared_args() -> Vec<Arg<'static, 'static>> {
Arg::with_name(CHECK_ASAN_LOG)
.takes_value(false)
.long(CHECK_ASAN_LOG),
Arg::with_name("disable_check_debugger")
Arg::with_name(DISABLE_CHECK_DEBUGGER)
.takes_value(false)
.long("disable_check_debugger"),
.long(DISABLE_CHECK_DEBUGGER),
]
}

Expand Down
14 changes: 7 additions & 7 deletions src/agent/onefuzz-agent/src/local/generic_generator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@
use crate::{
local::common::{
build_common_config, get_cmd_arg, get_cmd_env, get_cmd_exe, get_synced_dir,
get_synced_dirs, CmdType, CHECK_ASAN_LOG, CHECK_RETRY_COUNT, CRASHES_DIR, GENERATOR_ENV,
GENERATOR_EXE, GENERATOR_OPTIONS, READONLY_INPUTS, RENAME_OUTPUT, TARGET_ENV, TARGET_EXE,
TARGET_OPTIONS, TARGET_TIMEOUT, TOOLS_DIR,
get_synced_dirs, CmdType, CHECK_ASAN_LOG, CHECK_RETRY_COUNT, CRASHES_DIR,
DISABLE_CHECK_DEBUGGER, GENERATOR_ENV, GENERATOR_EXE, GENERATOR_OPTIONS, READONLY_INPUTS,
RENAME_OUTPUT, TARGET_ENV, TARGET_EXE, TARGET_OPTIONS, TARGET_TIMEOUT, TOOLS_DIR,
},
tasks::{
config::CommonConfig,
Expand All @@ -29,7 +29,7 @@ pub fn build_fuzz_config(args: &clap::ArgMatches<'_>, common: CommonConfig) -> R

let rename_output = args.is_present(RENAME_OUTPUT);
let check_asan_log = args.is_present(CHECK_ASAN_LOG);
let check_debugger = !args.is_present("disable_check_debugger");
let check_debugger = !args.is_present(DISABLE_CHECK_DEBUGGER);
let check_retry_count = value_t!(args, CHECK_RETRY_COUNT, u64)?;
let target_timeout = Some(value_t!(args, TARGET_TIMEOUT, u64)?);

Expand Down Expand Up @@ -60,7 +60,7 @@ pub fn build_fuzz_config(args: &clap::ArgMatches<'_>, common: CommonConfig) -> R
}

pub async fn run(args: &clap::ArgMatches<'_>) -> Result<()> {
let common = build_common_config(args)?;
let common = build_common_config(args, true)?;
let config = build_fuzz_config(args, common)?;
GeneratorTask::new(config).run().await
}
Expand Down Expand Up @@ -120,9 +120,9 @@ pub fn build_shared_args() -> Vec<Arg<'static, 'static>> {
.takes_value(true)
.long(TARGET_TIMEOUT)
.default_value("30"),
Arg::with_name("disable_check_debugger")
Arg::with_name(DISABLE_CHECK_DEBUGGER)
.takes_value(false)
.long("disable_check_debugger"),
.long(DISABLE_CHECK_DEBUGGER),
]
}

Expand Down
2 changes: 1 addition & 1 deletion src/agent/onefuzz-agent/src/local/libfuzzer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ use tokio::task::spawn;
use uuid::Uuid;

pub async fn run(args: &clap::ArgMatches<'_>) -> Result<()> {
let common = build_common_config(args)?;
let common = build_common_config(args, true)?;
let fuzz_config = build_fuzz_config(args, common.clone())?;
let crash_dir = fuzz_config
.crashes
Expand Down
2 changes: 1 addition & 1 deletion src/agent/onefuzz-agent/src/local/libfuzzer_coverage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ pub fn build_coverage_config(
}

pub async fn run(args: &clap::ArgMatches<'_>) -> Result<()> {
let common = build_common_config(args)?;
let common = build_common_config(args, true)?;
let config = build_coverage_config(args, false, None, common)?;

let mut task = CoverageTask::new(config);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ pub fn build_report_config(
}

pub async fn run(args: &clap::ArgMatches<'_>) -> Result<()> {
let common = build_common_config(args)?;
let common = build_common_config(args, true)?;
let config = build_report_config(args, None, common)?;
ReportTask::new(config).managed_run().await
}
Expand Down
2 changes: 1 addition & 1 deletion src/agent/onefuzz-agent/src/local/libfuzzer_fuzz.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ pub fn build_fuzz_config(args: &clap::ArgMatches<'_>, common: CommonConfig) -> R
}

pub async fn run(args: &clap::ArgMatches<'_>) -> Result<()> {
let common = build_common_config(args)?;
let common = build_common_config(args, true)?;
let config = build_fuzz_config(args, common)?;
LibFuzzerFuzzTask::new(config)?.run().await
}
Expand Down
2 changes: 1 addition & 1 deletion src/agent/onefuzz-agent/src/local/libfuzzer_merge.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ pub fn build_merge_config(
}

pub async fn run(args: &clap::ArgMatches<'_>) -> Result<()> {
let common = build_common_config(args)?;
let common = build_common_config(args, true)?;
let config = build_merge_config(args, None, common)?;
spawn(std::sync::Arc::new(config)).await
}
Expand Down
72 changes: 72 additions & 0 deletions src/agent/onefuzz-agent/src/local/libfuzzer_test_input.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

use crate::{
local::common::{
build_common_config, get_cmd_arg, get_cmd_env, CmdType, CHECK_RETRY_COUNT, TARGET_ENV,
TARGET_EXE, TARGET_OPTIONS, TARGET_TIMEOUT,
},
tasks::report::libfuzzer_report::{test_input, TestInputArgs},
};
use anyhow::Result;
use clap::{App, Arg, SubCommand};
use std::path::PathBuf;

pub async fn run(args: &clap::ArgMatches<'_>) -> Result<()> {
let common = build_common_config(args, true)?;

let target_exe = value_t!(args, TARGET_EXE, PathBuf)?;
let target_env = get_cmd_env(CmdType::Target, args)?;
let target_options = get_cmd_arg(CmdType::Target, args);
let input = value_t!(args, "input", PathBuf)?;
let target_timeout = value_t!(args, TARGET_TIMEOUT, u64).ok();
let check_retry_count = value_t!(args, CHECK_RETRY_COUNT, u64)?;

let config = TestInputArgs {
target_exe: &target_exe.as_path(),
target_env: &target_env,
target_options: &target_options,
input_url: None,
input: input.as_path(),
job_id: common.job_id,
task_id: common.task_id,
target_timeout,
check_retry_count,
setup_dir: &common.setup_dir,
minimized_stack_depth: None,
};

let result = test_input(config).await?;
println!("{}", serde_json::to_string_pretty(&result)?);
Ok(())
}

pub fn build_shared_args() -> Vec<Arg<'static, 'static>> {
vec![
Arg::with_name(TARGET_EXE).takes_value(true).required(true),
Arg::with_name("input").takes_value(true).required(true),
Arg::with_name(TARGET_ENV)
.long(TARGET_ENV)
.takes_value(true)
.multiple(true),
Arg::with_name(TARGET_OPTIONS)
.default_value("{input}")
.long(TARGET_OPTIONS)
.takes_value(true)
.value_delimiter(" ")
.help("Use a quoted string with space separation to denote multiple arguments"),
Arg::with_name(TARGET_TIMEOUT)
.takes_value(true)
.long(TARGET_TIMEOUT),
Arg::with_name(CHECK_RETRY_COUNT)
.takes_value(true)
.long(CHECK_RETRY_COUNT)
.default_value("0"),
]
}

pub fn args(name: &'static str) -> App<'static, 'static> {
SubCommand::with_name(name)
.about("test a libfuzzer application with a specific input")
.args(&build_shared_args())
}
2 changes: 2 additions & 0 deletions src/agent/onefuzz-agent/src/local/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,6 @@ pub mod libfuzzer_coverage;
pub mod libfuzzer_crash_report;
pub mod libfuzzer_fuzz;
pub mod libfuzzer_merge;
pub mod libfuzzer_test_input;
pub mod radamsa;
pub mod test_input;
2 changes: 1 addition & 1 deletion src/agent/onefuzz-agent/src/local/radamsa.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ use tokio::task::spawn;
use uuid::Uuid;

pub async fn run(args: &clap::ArgMatches<'_>) -> Result<()> {
let common = build_common_config(args)?;
let common = build_common_config(args, true)?;
let fuzz_config = build_fuzz_config(args, common.clone())?;
let crash_dir = fuzz_config
.crashes
Expand Down
82 changes: 82 additions & 0 deletions src/agent/onefuzz-agent/src/local/test_input.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

use crate::{
local::common::{
build_common_config, get_cmd_arg, get_cmd_env, CmdType, CHECK_ASAN_LOG, CHECK_RETRY_COUNT,
DISABLE_CHECK_DEBUGGER, TARGET_ENV, TARGET_EXE, TARGET_OPTIONS, TARGET_TIMEOUT,
},
tasks::report::generic::{test_input, TestInputArgs},
};
use anyhow::Result;
use clap::{App, Arg, SubCommand};
use std::path::PathBuf;

pub async fn run(args: &clap::ArgMatches<'_>) -> Result<()> {
let common = build_common_config(args, false)?;

let target_exe = value_t!(args, TARGET_EXE, PathBuf)?;
let target_env = get_cmd_env(CmdType::Target, args)?;
let target_options = get_cmd_arg(CmdType::Target, args);
let input = value_t!(args, "input", PathBuf)?;
let target_timeout = value_t!(args, TARGET_TIMEOUT, u64).ok();
let check_retry_count = value_t!(args, CHECK_RETRY_COUNT, u64)?;
let check_asan_log = args.is_present(CHECK_ASAN_LOG);
let check_debugger = !args.is_present(DISABLE_CHECK_DEBUGGER);

let config = TestInputArgs {
target_exe: &target_exe.as_path(),
target_env: &target_env,
target_options: &target_options,
input_url: None,
input: input.as_path(),
job_id: common.job_id,
task_id: common.task_id,
target_timeout,
check_retry_count,
setup_dir: &common.setup_dir,
minimized_stack_depth: None,
check_asan_log,
check_debugger,
};

let result = test_input(config).await?;
println!("{}", serde_json::to_string_pretty(&result)?);
Ok(())
}

pub fn build_shared_args() -> Vec<Arg<'static, 'static>> {
vec![
Arg::with_name(TARGET_EXE).takes_value(true).required(true),
Arg::with_name("input").takes_value(true).required(true),
Arg::with_name(TARGET_ENV)
.long(TARGET_ENV)
.takes_value(true)
.multiple(true),
Arg::with_name(TARGET_OPTIONS)
.default_value("{input}")
.long(TARGET_OPTIONS)
.takes_value(true)
.value_delimiter(" ")
.help("Use a quoted string with space separation to denote multiple arguments"),
Arg::with_name(TARGET_TIMEOUT)
.takes_value(true)
.long(TARGET_TIMEOUT),
Arg::with_name(CHECK_RETRY_COUNT)
.takes_value(true)
.long(CHECK_RETRY_COUNT)
.default_value("0"),
Arg::with_name(CHECK_ASAN_LOG)
.takes_value(false)
.long(CHECK_ASAN_LOG),
Arg::with_name(DISABLE_CHECK_DEBUGGER)
.takes_value(false)
.long("disable_check_debugger"),
]
}

pub fn args(name: &'static str) -> App<'static, 'static> {
SubCommand::with_name(name)
.about("test an application with a specific input")
.args(&build_shared_args())
}
Loading

0 comments on commit 7be4f3b

Please sign in to comment.