Skip to content
This repository has been archived by the owner on Nov 1, 2023. It is now read-only.

handle libfuzzer fuzzing non-zero exits better #381

Merged
15 commits merged into from
Jan 5, 2021
Merged
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
16 changes: 16 additions & 0 deletions docs/webhook_events.md
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,14 @@ Each event will be submitted via HTTP POST to the user provided URL.
"title": "Check Retry Count",
"type": "integer"
},
"check_fuzzer_help": {
"title": "Check Fuzzer Help",
bmc-msft marked this conversation as resolved.
Show resolved Hide resolved
"type": "boolean"
},
"expect_crash_on_failure": {
"title": "Expect Crash On Failure",
"type": "boolean"
},
"rename_output": {
"title": "Rename Output",
"type": "boolean"
Expand Down Expand Up @@ -805,6 +813,14 @@ Each event will be submitted via HTTP POST to the user provided URL.
"title": "Check Retry Count",
"type": "integer"
},
"check_fuzzer_help": {
"title": "Check Fuzzer Help",
"type": "boolean"
},
"expect_crash_on_failure": {
"title": "Expect Crash On Failure",
"type": "boolean"
},
"rename_output": {
"title": "Rename Output",
"type": "boolean"
Expand Down
4 changes: 4 additions & 0 deletions src/agent/onefuzz-agent/src/debug/libfuzzer_coverage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,10 +48,14 @@ pub fn run(args: &clap::ArgMatches) -> Result<()> {
target_env.insert(k, v);
}

// this happens during setup, not during runtime
let check_fuzzer_help = true;

let config = Config {
target_exe,
target_env,
target_options,
check_fuzzer_help,
input_queue: None,
readonly_inputs: vec![],
coverage: SyncedDir {
Expand Down
4 changes: 4 additions & 0 deletions src/agent/onefuzz-agent/src/debug/libfuzzer_crash_report.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,12 +40,16 @@ pub fn run(args: &clap::ArgMatches) -> Result<()> {
let target_timeout = value_t!(args, "target_timeout", u64).ok();
let check_retry_count = value_t!(args, "check_retry_count", u64)?;

// this happens during setup, not during runtime
let check_fuzzer_help = true;

let config = Config {
target_exe,
target_env,
target_options,
target_timeout,
check_retry_count,
check_fuzzer_help,
input_queue: None,
crashes: None,
reports: None,
Expand Down
13 changes: 13 additions & 0 deletions src/agent/onefuzz-agent/src/debug/libfuzzer_fuzz.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,12 @@ pub fn run(args: &clap::ArgMatches) -> Result<()> {
let inputs_dir = value_t!(args, "inputs_dir", String)?;
let target_exe = value_t!(args, "target_exe", PathBuf)?;
let target_options = args.values_of_lossy("target_options").unwrap_or_default();

// this happens during setup, not during runtime
let check_fuzzer_help = true;

let expect_crash_on_failure = args.is_present("expect_crash_on_failure");

let mut target_env = HashMap::new();
for opt in args.values_of_lossy("target_env").unwrap_or_default() {
let (k, v) = parse_key_value(opt)?;
Expand Down Expand Up @@ -56,6 +62,8 @@ pub fn run(args: &clap::ArgMatches) -> Result<()> {
target_options,
target_workers,
ensemble_sync_delay,
check_fuzzer_help,
expect_crash_on_failure,
common: CommonConfig {
heartbeat_queue: None,
instrumentation_key: None,
Expand Down Expand Up @@ -104,4 +112,9 @@ pub fn args() -> App<'static, 'static> {
.takes_value(true)
.required(true),
)
.arg(
Arg::with_name("expect_crash_on_failure")
.takes_value(false)
.long("expect_crash_on_failure"),
)
}
4 changes: 4 additions & 0 deletions src/agent/onefuzz-agent/src/debug/libfuzzer_merge.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ pub fn run(args: &clap::ArgMatches) -> Result<()> {
let unique_inputs = value_t!(args, "unique_inputs", String)?;
let target_options = args.values_of_lossy("target_options").unwrap_or_default();

// this happens during setup, not during runtime
let check_fuzzer_help = true;

let mut target_env = HashMap::new();
for opt in args.values_of_lossy("target_env").unwrap_or_default() {
let (k, v) = parse_key_value(opt)?;
Expand All @@ -30,6 +33,7 @@ pub fn run(args: &clap::ArgMatches) -> Result<()> {
target_exe,
target_env,
target_options,
check_fuzzer_help,
input_queue: None,
inputs: vec![SyncedDir {
path: inputs.into(),
Expand Down
21 changes: 19 additions & 2 deletions src/agent/onefuzz-agent/src/tasks/coverage/libfuzzer_coverage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,14 +30,18 @@
//!
//! Versions in parentheses have been tested.

use crate::tasks::coverage::{recorder::CoverageRecorder, total::TotalCoverage};
use crate::tasks::heartbeat::*;
use crate::tasks::{config::CommonConfig, generic::input_poller::*};
use crate::tasks::{
coverage::{recorder::CoverageRecorder, total::TotalCoverage},
utils::default_bool_true,
};
use anyhow::Result;
use async_trait::async_trait;
use futures::stream::StreamExt;
use onefuzz::{
fs::list_files, syncdir::SyncedDir, telemetry::Event::coverage_data, telemetry::EventData,
fs::list_files, libfuzzer::LibFuzzer, syncdir::SyncedDir, telemetry::Event::coverage_data,
telemetry::EventData,
};
use reqwest::Url;
use serde::Deserialize;
Expand All @@ -61,6 +65,9 @@ pub struct Config {
pub readonly_inputs: Vec<SyncedDir>,
pub coverage: SyncedDir,

#[serde(default = "default_bool_true")]
pub check_fuzzer_help: bool,

#[serde(flatten)]
pub common: CommonConfig,
}
Expand Down Expand Up @@ -91,6 +98,16 @@ impl CoverageTask {

pub async fn run(&mut self) -> Result<()> {
info!("starting libFuzzer coverage task");

if self.config.check_fuzzer_help {
let target = LibFuzzer::new(
&self.config.target_exe,
&self.config.target_options,
&self.config.target_env,
);
target.check_help().await?;
}

self.config.coverage.init_pull().await?;
self.process().await
}
Expand Down
10 changes: 5 additions & 5 deletions src/agent/onefuzz-agent/src/tasks/fuzz/generator.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

use crate::tasks::{config::CommonConfig, heartbeat::*, utils};
use crate::tasks::{
config::CommonConfig,
heartbeat::*,
utils::{self, default_bool_true},
};
use anyhow::{Error, Result};
use futures::stream::StreamExt;
use onefuzz::{
Expand All @@ -23,10 +27,6 @@ use std::{
};
use tokio::{fs, process::Command};

fn default_bool_true() -> bool {
true
}

#[derive(Debug, Deserialize, Clone)]
pub struct GeneratorConfig {
pub generator_exe: String,
Expand Down
42 changes: 33 additions & 9 deletions src/agent/onefuzz-agent/src/tasks/fuzz/libfuzzer_fuzz.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

use crate::tasks::{config::CommonConfig, heartbeat::HeartbeatSender};
use crate::tasks::{config::CommonConfig, heartbeat::HeartbeatSender, utils::default_bool_true};
use anyhow::Result;
use futures::{future::try_join_all, stream::StreamExt};
use onefuzz::{
Expand Down Expand Up @@ -47,6 +47,12 @@ pub struct Config {
pub target_workers: Option<u64>,
pub ensemble_sync_delay: Option<u64>,

#[serde(default = "default_bool_true")]
pub check_fuzzer_help: bool,

#[serde(default = "default_bool_true")]
pub expect_crash_on_failure: bool,

#[serde(flatten)]
pub common: CommonConfig,
}
Expand All @@ -61,6 +67,15 @@ impl LibFuzzerFuzzTask {
}

pub async fn start(&self) -> Result<()> {
if self.config.check_fuzzer_help {
let target = LibFuzzer::new(
&self.config.target_exe,
&self.config.target_options,
&self.config.target_env,
);
target.check_help().await?;
}

let workers = self.config.target_workers.unwrap_or_else(|| {
let cpus = num_cpus::get() as u64;
u64::max(1, cpus - 1)
Expand Down Expand Up @@ -166,14 +181,23 @@ impl LibFuzzerFuzzTask {

let files = list_files(crash_dir.path()).await?;

// ignore libfuzzer exiting cleanly without crashing, which could happen via
// -runs=N
if !exit_status.success && files.is_empty() {
bail!(
"libfuzzer exited without generating crashes. status:{} stderr:{:?}",
serde_json::to_string(&exit_status)?,
libfuzzer_output.join("\n")
);
// If the target exits, crashes are required unless
// 1. Exited cleanly (happens with -runs=N)
// 2. expect_crash_on_failure is disabled
if files.is_empty() && !exit_status.success {
if self.config.expect_crash_on_failure {
bail!(
"libfuzzer exited without generating crashes. status:{} stderr:{:?}",
serde_json::to_string(&exit_status)?,
libfuzzer_output.join("\n")
);
} else {
warn!(
"libfuzzer exited without generating crashes, continuing. status:{} stderr:{:?}",
serde_json::to_string(&exit_status)?,
libfuzzer_output.join("\n")
);
}
}

for file in &files {
Expand Down
18 changes: 17 additions & 1 deletion src/agent/onefuzz-agent/src/tasks/merge/libfuzzer_merge.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

use crate::tasks::{config::CommonConfig, heartbeat::*, utils};
use crate::tasks::{
config::CommonConfig,
heartbeat::*,
utils::{self, default_bool_true},
};
use anyhow::Result;
use onefuzz::{
http::ResponseExt,
Expand Down Expand Up @@ -35,11 +39,23 @@ pub struct Config {
pub unique_inputs: SyncedDir,
pub preserve_existing_outputs: bool,

#[serde(default = "default_bool_true")]
pub check_fuzzer_help: bool,

#[serde(flatten)]
pub common: CommonConfig,
}

pub async fn spawn(config: Arc<Config>) -> Result<()> {
if config.check_fuzzer_help {
let target = LibFuzzer::new(
&config.target_exe,
&config.target_options,
&config.target_env,
);
target.check_help().await?;
}

config.unique_inputs.init().await?;
if let Some(url) = config.input_queue.clone() {
loop {
Expand Down
4 changes: 1 addition & 3 deletions src/agent/onefuzz-agent/src/tasks/report/generic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use crate::tasks::{
config::CommonConfig,
generic::input_poller::{CallbackImpl, InputPoller, Processor},
heartbeat::*,
utils::default_bool_true,
};
use anyhow::Result;
use async_trait::async_trait;
Expand All @@ -18,9 +19,6 @@ use std::{
};
use storage_queue::Message;

fn default_bool_true() -> bool {
true
}
#[derive(Debug, Deserialize)]
pub struct Config {
pub target_exe: PathBuf,
Expand Down
17 changes: 16 additions & 1 deletion src/agent/onefuzz-agent/src/tasks/report/libfuzzer_report.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@
// Licensed under the MIT License.

use super::crash_report::*;
use crate::tasks::{config::CommonConfig, generic::input_poller::*, heartbeat::*};
use crate::tasks::{
config::CommonConfig, generic::input_poller::*, heartbeat::*, utils::default_bool_true,
};
use anyhow::Result;
use async_trait::async_trait;
use onefuzz::{blob::BlobUrl, libfuzzer::LibFuzzer, sha256, syncdir::SyncedDir};
Expand All @@ -27,6 +29,10 @@ pub struct Config {
pub reports: Option<SyncedDir>,
pub unique_reports: SyncedDir,
pub no_repro: Option<SyncedDir>,

#[serde(default = "default_bool_true")]
pub check_fuzzer_help: bool,

#[serde(default)]
pub check_retry_count: u64,

Expand All @@ -50,6 +56,15 @@ impl ReportTask {
}

pub async fn run(&mut self) -> Result<()> {
if self.config.check_fuzzer_help {
let target = LibFuzzer::new(
&self.config.target_exe,
&self.config.target_options,
&self.config.target_env,
);
target.check_help().await?;
}

info!("Starting libFuzzer crash report task");
let mut processor = AsanProcessor::new(self.config.clone()).await?;

Expand Down
4 changes: 4 additions & 0 deletions src/agent/onefuzz-agent/src/tasks/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -78,3 +78,7 @@ pub fn parse_key_value(value: String) -> Result<(String, String)> {

Ok((value[..offset].to_string(), value[offset + 1..].to_string()))
}

pub fn default_bool_true() -> bool {
true
}
31 changes: 31 additions & 0 deletions src/agent/onefuzz/src/libfuzzer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,37 @@ impl<'a> LibFuzzer<'a> {
}
}

pub async fn check_help(&self) -> Result<()> {
bmc-msft marked this conversation as resolved.
Show resolved Hide resolved
// Verify -help=1 exits with a zero return code, which validates the
// libfuzzer works as we expect.
let mut cmd = Command::new(&self.exe);

cmd.kill_on_drop(true)
.env_remove("RUST_LOG")
.stdin(Stdio::null())
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.arg("-help=1");

let mut expand = Expand::new();
expand.target_exe(&self.exe).target_options(&self.options);

for (k, v) in self.env {
cmd.env(k, expand.evaluate_value(v)?);
}

// Pass custom option arguments.
for o in expand.evaluate(self.options)? {
cmd.arg(o);
}

let result = cmd.spawn()?.wait_with_output().await?;
if !result.status.success() {
bail!("fuzzer does not respond to '-help=1'. output:{:?}", result);
}
Ok(())
}

pub fn fuzz(
&self,
fault_dir: impl AsRef<Path>,
Expand Down
Loading