handle libfuzzer fuzzing non-zero exits better (#381)

When running libfuzzer in 'fuzzing' mode, we expect the following on exit.

If the exit code is zero, crashing input isn't required.  This happens if the user specifies '-runs=N'

If the exit code is non-zero, then crashes are expected.  In practice, there are two causes to non-zero exits.
1. If the binary can't execute for some reason, like a missing prerequisite
2. If the binary _can_ execute, sometimes the sanitizers are put in such a bad place that they are unable to record the input that caused the crash.

This PR enables handling these two non-zero exit cases.

1. Optionally verify the libfuzzer target loads appropriately using `target_exe -help=1`.  This allows failing faster in the common issues, such a missing prerequisite library.
2. Optionally allow non-zero exits without crashes to be a warning, rather than a task failure.
This commit is contained in:
bmc-msft
2021-01-05 09:40:15 -05:00
committed by GitHub
parent 75d2ffd7f4
commit 37f06bb324
20 changed files with 240 additions and 21 deletions

View File

@ -182,6 +182,14 @@ Each event will be submitted via HTTP POST to the user provided URL.
"title": "Check Retry Count", "title": "Check Retry Count",
"type": "integer" "type": "integer"
}, },
"check_fuzzer_help": {
"title": "Check Fuzzer Help",
"type": "boolean"
},
"expect_crash_on_failure": {
"title": "Expect Crash On Failure",
"type": "boolean"
},
"rename_output": { "rename_output": {
"title": "Rename Output", "title": "Rename Output",
"type": "boolean" "type": "boolean"
@ -805,6 +813,14 @@ Each event will be submitted via HTTP POST to the user provided URL.
"title": "Check Retry Count", "title": "Check Retry Count",
"type": "integer" "type": "integer"
}, },
"check_fuzzer_help": {
"title": "Check Fuzzer Help",
"type": "boolean"
},
"expect_crash_on_failure": {
"title": "Expect Crash On Failure",
"type": "boolean"
},
"rename_output": { "rename_output": {
"title": "Rename Output", "title": "Rename Output",
"type": "boolean" "type": "boolean"

View File

@ -48,10 +48,14 @@ pub fn run(args: &clap::ArgMatches) -> Result<()> {
target_env.insert(k, v); target_env.insert(k, v);
} }
// this happens during setup, not during runtime
let check_fuzzer_help = true;
let config = Config { let config = Config {
target_exe, target_exe,
target_env, target_env,
target_options, target_options,
check_fuzzer_help,
input_queue: None, input_queue: None,
readonly_inputs: vec![], readonly_inputs: vec![],
coverage: SyncedDir { coverage: SyncedDir {

View File

@ -40,12 +40,16 @@ pub fn run(args: &clap::ArgMatches) -> Result<()> {
let target_timeout = value_t!(args, "target_timeout", u64).ok(); let target_timeout = value_t!(args, "target_timeout", u64).ok();
let check_retry_count = value_t!(args, "check_retry_count", u64)?; 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 { let config = Config {
target_exe, target_exe,
target_env, target_env,
target_options, target_options,
target_timeout, target_timeout,
check_retry_count, check_retry_count,
check_fuzzer_help,
input_queue: None, input_queue: None,
crashes: None, crashes: None,
reports: None, reports: None,

View File

@ -26,6 +26,12 @@ pub fn run(args: &clap::ArgMatches) -> Result<()> {
let inputs_dir = value_t!(args, "inputs_dir", String)?; let inputs_dir = value_t!(args, "inputs_dir", String)?;
let target_exe = value_t!(args, "target_exe", PathBuf)?; let target_exe = value_t!(args, "target_exe", PathBuf)?;
let target_options = args.values_of_lossy("target_options").unwrap_or_default(); 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(); let mut target_env = HashMap::new();
for opt in args.values_of_lossy("target_env").unwrap_or_default() { for opt in args.values_of_lossy("target_env").unwrap_or_default() {
let (k, v) = parse_key_value(opt)?; let (k, v) = parse_key_value(opt)?;
@ -56,6 +62,8 @@ pub fn run(args: &clap::ArgMatches) -> Result<()> {
target_options, target_options,
target_workers, target_workers,
ensemble_sync_delay, ensemble_sync_delay,
check_fuzzer_help,
expect_crash_on_failure,
common: CommonConfig { common: CommonConfig {
heartbeat_queue: None, heartbeat_queue: None,
instrumentation_key: None, instrumentation_key: None,
@ -104,4 +112,9 @@ pub fn args() -> App<'static, 'static> {
.takes_value(true) .takes_value(true)
.required(true), .required(true),
) )
.arg(
Arg::with_name("expect_crash_on_failure")
.takes_value(false)
.long("expect_crash_on_failure"),
)
} }

View File

@ -20,6 +20,9 @@ pub fn run(args: &clap::ArgMatches) -> Result<()> {
let unique_inputs = value_t!(args, "unique_inputs", String)?; let unique_inputs = value_t!(args, "unique_inputs", String)?;
let target_options = args.values_of_lossy("target_options").unwrap_or_default(); 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(); let mut target_env = HashMap::new();
for opt in args.values_of_lossy("target_env").unwrap_or_default() { for opt in args.values_of_lossy("target_env").unwrap_or_default() {
let (k, v) = parse_key_value(opt)?; let (k, v) = parse_key_value(opt)?;
@ -30,6 +33,7 @@ pub fn run(args: &clap::ArgMatches) -> Result<()> {
target_exe, target_exe,
target_env, target_env,
target_options, target_options,
check_fuzzer_help,
input_queue: None, input_queue: None,
inputs: vec![SyncedDir { inputs: vec![SyncedDir {
path: inputs.into(), path: inputs.into(),

View File

@ -30,14 +30,18 @@
//! //!
//! Versions in parentheses have been tested. //! Versions in parentheses have been tested.
use crate::tasks::coverage::{recorder::CoverageRecorder, total::TotalCoverage};
use crate::tasks::heartbeat::*; use crate::tasks::heartbeat::*;
use crate::tasks::{config::CommonConfig, generic::input_poller::*}; use crate::tasks::{config::CommonConfig, generic::input_poller::*};
use crate::tasks::{
coverage::{recorder::CoverageRecorder, total::TotalCoverage},
utils::default_bool_true,
};
use anyhow::Result; use anyhow::Result;
use async_trait::async_trait; use async_trait::async_trait;
use futures::stream::StreamExt; use futures::stream::StreamExt;
use onefuzz::{ 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 reqwest::Url;
use serde::Deserialize; use serde::Deserialize;
@ -61,6 +65,9 @@ pub struct Config {
pub readonly_inputs: Vec<SyncedDir>, pub readonly_inputs: Vec<SyncedDir>,
pub coverage: SyncedDir, pub coverage: SyncedDir,
#[serde(default = "default_bool_true")]
pub check_fuzzer_help: bool,
#[serde(flatten)] #[serde(flatten)]
pub common: CommonConfig, pub common: CommonConfig,
} }
@ -91,6 +98,16 @@ impl CoverageTask {
pub async fn run(&mut self) -> Result<()> { pub async fn run(&mut self) -> Result<()> {
info!("starting libFuzzer coverage task"); 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.config.coverage.init_pull().await?;
self.process().await self.process().await
} }

View File

@ -1,7 +1,11 @@
// Copyright (c) Microsoft Corporation. // Copyright (c) Microsoft Corporation.
// Licensed under the MIT License. // 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 anyhow::{Error, Result};
use futures::stream::StreamExt; use futures::stream::StreamExt;
use onefuzz::{ use onefuzz::{
@ -23,10 +27,6 @@ use std::{
}; };
use tokio::{fs, process::Command}; use tokio::{fs, process::Command};
fn default_bool_true() -> bool {
true
}
#[derive(Debug, Deserialize, Clone)] #[derive(Debug, Deserialize, Clone)]
pub struct GeneratorConfig { pub struct GeneratorConfig {
pub generator_exe: String, pub generator_exe: String,

View File

@ -1,7 +1,7 @@
// Copyright (c) Microsoft Corporation. // Copyright (c) Microsoft Corporation.
// Licensed under the MIT License. // 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 anyhow::Result;
use futures::{future::try_join_all, stream::StreamExt}; use futures::{future::try_join_all, stream::StreamExt};
use onefuzz::{ use onefuzz::{
@ -47,6 +47,12 @@ pub struct Config {
pub target_workers: Option<u64>, pub target_workers: Option<u64>,
pub ensemble_sync_delay: 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)] #[serde(flatten)]
pub common: CommonConfig, pub common: CommonConfig,
} }
@ -61,6 +67,15 @@ impl LibFuzzerFuzzTask {
} }
pub async fn start(&self) -> Result<()> { 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 workers = self.config.target_workers.unwrap_or_else(|| {
let cpus = num_cpus::get() as u64; let cpus = num_cpus::get() as u64;
u64::max(1, cpus - 1) u64::max(1, cpus - 1)
@ -166,14 +181,23 @@ impl LibFuzzerFuzzTask {
let files = list_files(crash_dir.path()).await?; let files = list_files(crash_dir.path()).await?;
// ignore libfuzzer exiting cleanly without crashing, which could happen via // If the target exits, crashes are required unless
// -runs=N // 1. Exited cleanly (happens with -runs=N)
if !exit_status.success && files.is_empty() { // 2. expect_crash_on_failure is disabled
if files.is_empty() && !exit_status.success {
if self.config.expect_crash_on_failure {
bail!( bail!(
"libfuzzer exited without generating crashes. status:{} stderr:{:?}", "libfuzzer exited without generating crashes. status:{} stderr:{:?}",
serde_json::to_string(&exit_status)?, serde_json::to_string(&exit_status)?,
libfuzzer_output.join("\n") 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 { for file in &files {

View File

@ -1,7 +1,11 @@
// Copyright (c) Microsoft Corporation. // Copyright (c) Microsoft Corporation.
// Licensed under the MIT License. // 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 anyhow::Result;
use onefuzz::{ use onefuzz::{
http::ResponseExt, http::ResponseExt,
@ -35,11 +39,23 @@ pub struct Config {
pub unique_inputs: SyncedDir, pub unique_inputs: SyncedDir,
pub preserve_existing_outputs: bool, pub preserve_existing_outputs: bool,
#[serde(default = "default_bool_true")]
pub check_fuzzer_help: bool,
#[serde(flatten)] #[serde(flatten)]
pub common: CommonConfig, pub common: CommonConfig,
} }
pub async fn spawn(config: Arc<Config>) -> Result<()> { 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?; config.unique_inputs.init().await?;
if let Some(url) = config.input_queue.clone() { if let Some(url) = config.input_queue.clone() {
loop { loop {

View File

@ -6,6 +6,7 @@ use crate::tasks::{
config::CommonConfig, config::CommonConfig,
generic::input_poller::{CallbackImpl, InputPoller, Processor}, generic::input_poller::{CallbackImpl, InputPoller, Processor},
heartbeat::*, heartbeat::*,
utils::default_bool_true,
}; };
use anyhow::Result; use anyhow::Result;
use async_trait::async_trait; use async_trait::async_trait;
@ -18,9 +19,6 @@ use std::{
}; };
use storage_queue::Message; use storage_queue::Message;
fn default_bool_true() -> bool {
true
}
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize)]
pub struct Config { pub struct Config {
pub target_exe: PathBuf, pub target_exe: PathBuf,

View File

@ -2,7 +2,9 @@
// Licensed under the MIT License. // Licensed under the MIT License.
use super::crash_report::*; 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 anyhow::Result;
use async_trait::async_trait; use async_trait::async_trait;
use onefuzz::{blob::BlobUrl, libfuzzer::LibFuzzer, sha256, syncdir::SyncedDir}; use onefuzz::{blob::BlobUrl, libfuzzer::LibFuzzer, sha256, syncdir::SyncedDir};
@ -27,6 +29,10 @@ pub struct Config {
pub reports: Option<SyncedDir>, pub reports: Option<SyncedDir>,
pub unique_reports: SyncedDir, pub unique_reports: SyncedDir,
pub no_repro: Option<SyncedDir>, pub no_repro: Option<SyncedDir>,
#[serde(default = "default_bool_true")]
pub check_fuzzer_help: bool,
#[serde(default)] #[serde(default)]
pub check_retry_count: u64, pub check_retry_count: u64,
@ -50,6 +56,15 @@ impl ReportTask {
} }
pub async fn run(&mut self) -> Result<()> { 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"); info!("Starting libFuzzer crash report task");
let mut processor = AsanProcessor::new(self.config.clone()).await?; let mut processor = AsanProcessor::new(self.config.clone()).await?;

View File

@ -78,3 +78,7 @@ pub fn parse_key_value(value: String) -> Result<(String, String)> {
Ok((value[..offset].to_string(), value[offset + 1..].to_string())) Ok((value[..offset].to_string(), value[offset + 1..].to_string()))
} }
pub fn default_bool_true() -> bool {
true
}

View File

@ -40,6 +40,37 @@ impl<'a> LibFuzzer<'a> {
} }
} }
pub async fn check_help(&self) -> Result<()> {
// 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( pub fn fuzz(
&self, &self,
fault_dir: impl AsRef<Path>, fault_dir: impl AsRef<Path>,

View File

@ -217,6 +217,38 @@ libfuzzer_linux = JobTemplate(
), ),
], ],
), ),
UserField(
name="check_fuzzer_help",
help="Verify fuzzer by checking if it supports -help=1",
type=UserFieldType.Bool,
default=True,
locations=[
UserFieldLocation(
op=UserFieldOperation.add,
path="/tasks/0/task/check_fuzzer_help",
),
UserFieldLocation(
op=UserFieldOperation.add,
path="/tasks/1/task/check_fuzzer_help",
),
UserFieldLocation(
op=UserFieldOperation.add,
path="/tasks/2/task/check_fuzzer_help",
),
],
),
UserField(
name="expect_crash_on_failure",
help="Require crashes upon non-zero exits from libfuzzer",
type=UserFieldType.Bool,
default=True,
locations=[
UserFieldLocation(
op=UserFieldOperation.add,
path="/tasks/0/task/expect_crash_on_failure",
),
],
),
UserField( UserField(
name="reboot_after_setup", name="reboot_after_setup",
help=REBOOT_HELP, help=REBOOT_HELP,

View File

@ -317,6 +317,20 @@ def build_task_config(
if TaskFeature.ensemble_sync_delay in definition.features: if TaskFeature.ensemble_sync_delay in definition.features:
config.ensemble_sync_delay = task_config.task.ensemble_sync_delay config.ensemble_sync_delay = task_config.task.ensemble_sync_delay
if TaskFeature.check_fuzzer_help in definition.features:
config.check_fuzzer_help = (
task_config.task.check_fuzzer_help
if task_config.task.check_fuzzer_help is not None
else True
)
if TaskFeature.expect_crash_on_failure in definition.features:
config.expect_crash_on_failure = (
task_config.task.expect_crash_on_failure
if task_config.task.expect_crash_on_failure is not None
else True
)
return config return config

View File

@ -63,6 +63,8 @@ TASK_DEFINITIONS = {
TaskFeature.target_options, TaskFeature.target_options,
TaskFeature.target_workers, TaskFeature.target_workers,
TaskFeature.ensemble_sync_delay, TaskFeature.ensemble_sync_delay,
TaskFeature.check_fuzzer_help,
TaskFeature.expect_crash_on_failure,
], ],
vm=VmDefinition(compare=Compare.AtLeast, value=1), vm=VmDefinition(compare=Compare.AtLeast, value=1),
containers=[ containers=[
@ -105,6 +107,7 @@ TASK_DEFINITIONS = {
TaskFeature.target_options, TaskFeature.target_options,
TaskFeature.target_timeout, TaskFeature.target_timeout,
TaskFeature.check_retry_count, TaskFeature.check_retry_count,
TaskFeature.check_fuzzer_help,
], ],
vm=VmDefinition(compare=Compare.AtLeast, value=1), vm=VmDefinition(compare=Compare.AtLeast, value=1),
containers=[ containers=[
@ -146,6 +149,7 @@ TASK_DEFINITIONS = {
TaskFeature.target_exe, TaskFeature.target_exe,
TaskFeature.target_env, TaskFeature.target_env,
TaskFeature.target_options, TaskFeature.target_options,
TaskFeature.check_fuzzer_help,
], ],
vm=VmDefinition(compare=Compare.Equal, value=1), vm=VmDefinition(compare=Compare.Equal, value=1),
containers=[ containers=[
@ -180,6 +184,7 @@ TASK_DEFINITIONS = {
TaskFeature.target_exe, TaskFeature.target_exe,
TaskFeature.target_env, TaskFeature.target_env,
TaskFeature.target_options, TaskFeature.target_options,
TaskFeature.check_fuzzer_help,
], ],
vm=VmDefinition(compare=Compare.Equal, value=1), vm=VmDefinition(compare=Compare.Equal, value=1),
containers=[ containers=[

View File

@ -777,6 +777,8 @@ class Tasks(Endpoint):
check_asan_log: bool = False, check_asan_log: bool = False,
check_debugger: bool = True, check_debugger: bool = True,
check_retry_count: Optional[int] = None, check_retry_count: Optional[int] = None,
check_fuzzer_help: Optional[bool] = None,
expect_crash_on_failure: Optional[bool] = None,
debug: Optional[List[enums.TaskDebugFlag]] = None, debug: Optional[List[enums.TaskDebugFlag]] = None,
duration: int = 24, duration: int = 24,
ensemble_sync_delay: Optional[int] = None, ensemble_sync_delay: Optional[int] = None,
@ -851,6 +853,8 @@ class Tasks(Endpoint):
check_asan_log=check_asan_log, check_asan_log=check_asan_log,
check_debugger=check_debugger, check_debugger=check_debugger,
check_retry_count=check_retry_count, check_retry_count=check_retry_count,
check_fuzzer_help=check_fuzzer_help,
expect_crash_on_failure=expect_crash_on_failure,
duration=duration, duration=duration,
ensemble_sync_delay=ensemble_sync_delay, ensemble_sync_delay=ensemble_sync_delay,
generator_exe=generator_exe, generator_exe=generator_exe,

View File

@ -48,6 +48,8 @@ class Libfuzzer(Command):
crash_report_timeout: Optional[int] = None, crash_report_timeout: Optional[int] = None,
debug: Optional[List[TaskDebugFlag]] = None, debug: Optional[List[TaskDebugFlag]] = None,
ensemble_sync_delay: Optional[int] = None, ensemble_sync_delay: Optional[int] = None,
check_fuzzer_help: bool = True,
expect_crash_on_failure: bool = True,
) -> None: ) -> None:
fuzzer_containers = [ fuzzer_containers = [
@ -76,6 +78,8 @@ class Libfuzzer(Command):
tags=tags, tags=tags,
debug=debug, debug=debug,
ensemble_sync_delay=ensemble_sync_delay, ensemble_sync_delay=ensemble_sync_delay,
check_fuzzer_help=check_fuzzer_help,
expect_crash_on_failure=expect_crash_on_failure,
) )
coverage_containers = [ coverage_containers = [
@ -98,6 +102,7 @@ class Libfuzzer(Command):
tags=tags, tags=tags,
prereq_tasks=[fuzzer_task.task_id], prereq_tasks=[fuzzer_task.task_id],
debug=debug, debug=debug,
check_fuzzer_help=check_fuzzer_help,
) )
report_containers = [ report_containers = [
@ -124,6 +129,7 @@ class Libfuzzer(Command):
prereq_tasks=[fuzzer_task.task_id], prereq_tasks=[fuzzer_task.task_id],
target_timeout=crash_report_timeout, target_timeout=crash_report_timeout,
check_retry_count=check_retry_count, check_retry_count=check_retry_count,
check_fuzzer_help=check_fuzzer_help,
debug=debug, debug=debug,
) )
@ -154,6 +160,8 @@ class Libfuzzer(Command):
notification_config: Optional[NotificationConfig] = None, notification_config: Optional[NotificationConfig] = None,
debug: Optional[List[TaskDebugFlag]] = None, debug: Optional[List[TaskDebugFlag]] = None,
ensemble_sync_delay: Optional[int] = None, ensemble_sync_delay: Optional[int] = None,
check_fuzzer_help: bool = True,
expect_crash_on_failure: bool = True,
) -> Optional[Job]: ) -> Optional[Job]:
""" """
Basic libfuzzer job Basic libfuzzer job
@ -229,6 +237,8 @@ class Libfuzzer(Command):
check_retry_count=check_retry_count, check_retry_count=check_retry_count,
debug=debug, debug=debug,
ensemble_sync_delay=ensemble_sync_delay, ensemble_sync_delay=ensemble_sync_delay,
check_fuzzer_help=check_fuzzer_help,
expect_crash_on_failure=expect_crash_on_failure,
) )
self.logger.info("done creating tasks") self.logger.info("done creating tasks")
@ -261,6 +271,7 @@ class Libfuzzer(Command):
notification_config: Optional[NotificationConfig] = None, notification_config: Optional[NotificationConfig] = None,
debug: Optional[List[TaskDebugFlag]] = None, debug: Optional[List[TaskDebugFlag]] = None,
preserve_existing_outputs: bool = False, preserve_existing_outputs: bool = False,
check_fuzzer_help: bool = True,
) -> Optional[Job]: ) -> Optional[Job]:
""" """
@ -348,6 +359,7 @@ class Libfuzzer(Command):
check_retry_count=check_retry_count, check_retry_count=check_retry_count,
debug=debug, debug=debug,
preserve_existing_outputs=preserve_existing_outputs, preserve_existing_outputs=preserve_existing_outputs,
check_fuzzer_help=check_fuzzer_help,
) )
self.logger.info("done creating tasks") self.logger.info("done creating tasks")

View File

@ -75,6 +75,8 @@ class TaskFeature(Enum):
check_retry_count = "check_retry_count" check_retry_count = "check_retry_count"
ensemble_sync_delay = "ensemble_sync_delay" ensemble_sync_delay = "ensemble_sync_delay"
preserve_existing_outputs = "preserve_existing_outputs" preserve_existing_outputs = "preserve_existing_outputs"
check_fuzzer_help = "check_fuzzer_help"
expect_crash_on_failure = "expect_crash_on_failure"
# Permissions for an Azure Blob Storage Container. # Permissions for an Azure Blob Storage Container.

View File

@ -116,6 +116,8 @@ class TaskDetails(BaseModel):
check_asan_log: Optional[bool] check_asan_log: Optional[bool]
check_debugger: Optional[bool] = Field(default=True) check_debugger: Optional[bool] = Field(default=True)
check_retry_count: Optional[int] check_retry_count: Optional[int]
check_fuzzer_help: Optional[bool]
expect_crash_on_failure: Optional[bool]
rename_output: Optional[bool] rename_output: Optional[bool]
supervisor_exe: Optional[str] supervisor_exe: Optional[str]
supervisor_env: Optional[Dict[str, str]] supervisor_env: Optional[Dict[str, str]]
@ -310,6 +312,8 @@ class TaskUnitConfig(BaseModel):
check_asan_log: Optional[bool] check_asan_log: Optional[bool]
check_debugger: Optional[bool] check_debugger: Optional[bool]
check_retry_count: Optional[int] check_retry_count: Optional[int]
check_fuzzer_help: Optional[bool]
expect_crash_on_failure: Optional[bool]
rename_output: Optional[bool] rename_output: Optional[bool]
generator_exe: Optional[str] generator_exe: Optional[str]
generator_env: Optional[Dict[str, str]] generator_env: Optional[Dict[str, str]]