Adding extra container to tasks (#2847)

* adding extra container to tasks

* setup expand

* build fix

* generate docs

* build fix

* build fix

* build fix

* format

* format

* build fix

* fix extra container references

* format

* Update "Needs Triage" label to the one we use. (#2845)

* Report extension errors (#2846)

Old failure message:
```
failed to launch extension
```

New failure message:

```
failed to launch extension(s): Errors for extension 'CustomScriptExtension':
:Error: ProvisioningState/failed/3 (Provisioning failed) - Failed to download all specified files. Exiting. Error Message: The remote server returned an error: (400) Bad Request.
```

* Sematically validate notification configs (#2850)

* Add new command

* Update remaining jinja templates and references to use scriban

* Add ado template validation

* Validate ado and github templates

* Remove unnecessary function

* Update src/ApiService/ApiService/OneFuzzTypes/Model.cs

Co-authored-by: Cheick Keita <kcheick@gmail.com>

---------

Co-authored-by: Cheick Keita <kcheick@gmail.com>

* adding extra container to integration tests

* adding doc

* update tests

* format

* build and clippy fix

* Update src/agent/onefuzz-task/src/tasks/report/generic.rs

Co-authored-by: Teo Voinea <58236992+tevoinea@users.noreply.github.com>

---------

Co-authored-by: Marc Greisen <mgreisen@microsoft.com>
Co-authored-by: George Pollard <gpollard@microsoft.com>
Co-authored-by: Teo Voinea <58236992+tevoinea@users.noreply.github.com>
This commit is contained in:
Cheick Keita
2023-02-23 11:08:01 -08:00
committed by GitHub
parent dfb0db87c1
commit b84896802c
47 changed files with 367 additions and 27 deletions

View File

@ -26,6 +26,7 @@ The following values are replaced with the specific values at runtime.
* `{crashes_container}`: Container name for the `crashes` container * `{crashes_container}`: Container name for the `crashes` container
* `{microsoft_telemetry_key}`: Application Insights key used for collecting [non-attributable telemetry](telemetry.md) to improve OneFuzz. * `{microsoft_telemetry_key}`: Application Insights key used for collecting [non-attributable telemetry](telemetry.md) to improve OneFuzz.
* `{instance_telemetry_key}`: Application Insights key used for private, instance-owned telemetry and logging (See [OneFuzz Telemetry](telemetry.md). * `{instance_telemetry_key}`: Application Insights key used for private, instance-owned telemetry and logging (See [OneFuzz Telemetry](telemetry.md).
* `{extra}`: Path to the optionally provided `extra` directory
## Example ## Example

View File

@ -151,7 +151,8 @@ If webhook is set to have Event Grid message format then the payload will look a
"unique_inputs", "unique_inputs",
"unique_reports", "unique_reports",
"regression_reports", "regression_reports",
"logs" "logs",
"extra"
], ],
"title": "ContainerType" "title": "ContainerType"
}, },
@ -2001,7 +2002,8 @@ If webhook is set to have Event Grid message format then the payload will look a
"unique_inputs", "unique_inputs",
"unique_reports", "unique_reports",
"regression_reports", "regression_reports",
"logs" "logs",
"extra"
], ],
"title": "ContainerType" "title": "ContainerType"
}, },
@ -2927,7 +2929,8 @@ If webhook is set to have Event Grid message format then the payload will look a
"unique_inputs", "unique_inputs",
"unique_reports", "unique_reports",
"regression_reports", "regression_reports",
"logs" "logs",
"extra"
], ],
"title": "ContainerType" "title": "ContainerType"
}, },
@ -3408,7 +3411,8 @@ If webhook is set to have Event Grid message format then the payload will look a
"unique_inputs", "unique_inputs",
"unique_reports", "unique_reports",
"regression_reports", "regression_reports",
"logs" "logs",
"extra"
], ],
"title": "ContainerType" "title": "ContainerType"
}, },
@ -3932,7 +3936,8 @@ If webhook is set to have Event Grid message format then the payload will look a
"unique_inputs", "unique_inputs",
"unique_reports", "unique_reports",
"regression_reports", "regression_reports",
"logs" "logs",
"extra"
], ],
"title": "ContainerType" "title": "ContainerType"
}, },
@ -4380,7 +4385,8 @@ If webhook is set to have Event Grid message format then the payload will look a
"unique_inputs", "unique_inputs",
"unique_reports", "unique_reports",
"regression_reports", "regression_reports",
"logs" "logs",
"extra"
], ],
"title": "ContainerType" "title": "ContainerType"
}, },
@ -4855,7 +4861,8 @@ If webhook is set to have Event Grid message format then the payload will look a
"unique_inputs", "unique_inputs",
"unique_reports", "unique_reports",
"regression_reports", "regression_reports",
"logs" "logs",
"extra"
], ],
"title": "ContainerType" "title": "ContainerType"
}, },
@ -5460,7 +5467,8 @@ If webhook is set to have Event Grid message format then the payload will look a
"unique_inputs", "unique_inputs",
"unique_reports", "unique_reports",
"regression_reports", "regression_reports",
"logs" "logs",
"extra"
], ],
"title": "ContainerType" "title": "ContainerType"
}, },

View File

@ -98,7 +98,8 @@ public enum ContainerType {
UniqueInputs, UniqueInputs,
UniqueReports, UniqueReports,
RegressionReports, RegressionReports,
Logs Logs,
Extra
} }

View File

@ -893,6 +893,7 @@ public record TaskDefinition(
public record WorkSet( public record WorkSet(
bool Reboot, bool Reboot,
Uri SetupUrl, Uri SetupUrl,
Uri? ExtraUrl,
bool Script, bool Script,
List<WorkUnit> WorkUnits List<WorkUnit> WorkUnits
); );
@ -1020,6 +1021,7 @@ public record TaskUnitConfig(
public IContainerDef? UniqueInputs { get; set; } public IContainerDef? UniqueInputs { get; set; }
public IContainerDef? UniqueReports { get; set; } public IContainerDef? UniqueReports { get; set; }
public IContainerDef? RegressionReports { get; set; } public IContainerDef? RegressionReports { get; set; }
public IContainerDef? Extra { get; set; }
} }

View File

@ -140,6 +140,9 @@ public class Config : IConfig {
case ContainerType.RegressionReports: case ContainerType.RegressionReports:
config.RegressionReports = def; config.RegressionReports = def;
break; break;
case ContainerType.Extra:
config.Extra = def;
break;
} }
} }

View File

@ -40,7 +40,14 @@ public static class Defs {
ContainerPermission.List | ContainerPermission.List |
ContainerPermission.Read | ContainerPermission.Read |
ContainerPermission.Write ContainerPermission.Write
)}, ),
new ContainerDefinition(
Type:ContainerType.Extra,
Compare: Compare.AtMost,
Value:1,
Permissions: ContainerPermission.Read | ContainerPermission.List
),
},
MonitorQueue: ContainerType.ReadonlyInputs) MonitorQueue: ContainerType.ReadonlyInputs)
}, },
{ {
@ -83,6 +90,12 @@ public static class Defs {
Value:1, Value:1,
Permissions: ContainerPermission.Read | ContainerPermission.List Permissions: ContainerPermission.Read | ContainerPermission.List
), ),
new ContainerDefinition(
Type:ContainerType.Extra,
Compare: Compare.AtMost,
Value:1,
Permissions: ContainerPermission.Read | ContainerPermission.List
),
}, },
MonitorQueue: ContainerType.ReadonlyInputs) MonitorQueue: ContainerType.ReadonlyInputs)
}, },
@ -135,6 +148,12 @@ public static class Defs {
Value:1, Value:1,
Permissions: ContainerPermission.Read | ContainerPermission.List Permissions: ContainerPermission.Read | ContainerPermission.List
), ),
new ContainerDefinition(
Type:ContainerType.Extra,
Compare: Compare.AtMost,
Value:1,
Permissions: ContainerPermission.Read | ContainerPermission.List
),
}, },
MonitorQueue: ContainerType.Crashes MonitorQueue: ContainerType.Crashes
) )
@ -185,6 +204,12 @@ public static class Defs {
Value:1, Value:1,
Permissions: ContainerPermission.Read | ContainerPermission.List Permissions: ContainerPermission.Read | ContainerPermission.List
), ),
new ContainerDefinition(
Type:ContainerType.Extra,
Compare: Compare.AtMost,
Value:1,
Permissions: ContainerPermission.Read | ContainerPermission.List
),
} }
)}, )},
{ TaskType.GenericAnalysis , { TaskType.GenericAnalysis ,
@ -222,6 +247,12 @@ public static class Defs {
Value:1, Value:1,
Permissions: ContainerPermission.Read | ContainerPermission.List Permissions: ContainerPermission.Read | ContainerPermission.List
), ),
new ContainerDefinition(
Type:ContainerType.Extra,
Compare: Compare.AtMost,
Value:1,
Permissions: ContainerPermission.Read | ContainerPermission.List
),
}, },
MonitorQueue: ContainerType.Crashes) MonitorQueue: ContainerType.Crashes)
}, },
@ -264,6 +295,12 @@ public static class Defs {
Value: 0, Value: 0,
Permissions: ContainerPermission.Read | ContainerPermission.List Permissions: ContainerPermission.Read | ContainerPermission.List
), ),
new ContainerDefinition(
Type:ContainerType.Extra,
Compare: Compare.AtMost,
Value:1,
Permissions: ContainerPermission.Read | ContainerPermission.List
),
} }
)}, )},
{ {
@ -309,6 +346,12 @@ public static class Defs {
Value: 1, Value: 1,
Permissions: ContainerPermission.Write Permissions: ContainerPermission.Write
), ),
new ContainerDefinition(
Type:ContainerType.Extra,
Compare: Compare.AtMost,
Value:1,
Permissions: ContainerPermission.Read | ContainerPermission.List
),
}, },
MonitorQueue: ContainerType.Crashes MonitorQueue: ContainerType.Crashes
) )
@ -343,7 +386,16 @@ public static class Defs {
ContainerPermission.List | ContainerPermission.List |
ContainerPermission.Read | ContainerPermission.Read |
ContainerPermission.Write ContainerPermission.Write
)}, ),
new ContainerDefinition(
Type:ContainerType.Extra,
Compare: Compare.AtMost,
Value:1,
Permissions: ContainerPermission.Read | ContainerPermission.List
),
},
MonitorQueue: ContainerType.ReadonlyInputs MonitorQueue: ContainerType.ReadonlyInputs
)}, )},
{ {
@ -377,6 +429,12 @@ public static class Defs {
Value: 0, Value: 0,
Permissions: ContainerPermission.Read | ContainerPermission.List Permissions: ContainerPermission.Read | ContainerPermission.List
), ),
new ContainerDefinition(
Type:ContainerType.Extra,
Compare: Compare.AtMost,
Value:1,
Permissions: ContainerPermission.Read | ContainerPermission.List
),
} }
) )
}, },
@ -445,6 +503,12 @@ public static class Defs {
Value: 1, Value: 1,
Permissions: ContainerPermission.Write | ContainerPermission.Read | ContainerPermission.List Permissions: ContainerPermission.Write | ContainerPermission.Read | ContainerPermission.List
), ),
new ContainerDefinition(
Type:ContainerType.Extra,
Compare: Compare.AtMost,
Value:1,
Permissions: ContainerPermission.Read | ContainerPermission.List
),
} }
) )
}, },
@ -486,6 +550,12 @@ public static class Defs {
Value:1, Value:1,
Permissions: ContainerPermission.Write| ContainerPermission.List Permissions: ContainerPermission.Write| ContainerPermission.List
), ),
new ContainerDefinition(
Type:ContainerType.Extra,
Compare: Compare.AtMost,
Value:1,
Permissions: ContainerPermission.Read | ContainerPermission.List
),
} }
) )
}, },
@ -532,6 +602,12 @@ public static class Defs {
Value:1, Value:1,
Permissions: ContainerPermission.Read | ContainerPermission.List Permissions: ContainerPermission.Read | ContainerPermission.List
), ),
new ContainerDefinition(
Type:ContainerType.Extra,
Compare: Compare.AtMost,
Value:1,
Permissions: ContainerPermission.Read | ContainerPermission.List
),
} }
) )
}, },
@ -579,6 +655,12 @@ public static class Defs {
Value:1, Value:1,
Permissions: ContainerPermission.Write Permissions: ContainerPermission.Write
), ),
new ContainerDefinition(
Type:ContainerType.Extra,
Compare: Compare.AtMost,
Value:1,
Permissions: ContainerPermission.Read | ContainerPermission.List
),
}, },
MonitorQueue: ContainerType.Crashes MonitorQueue: ContainerType.Crashes
) )
@ -640,6 +722,12 @@ public static class Defs {
Value:1, Value:1,
Permissions: ContainerPermission.Read | ContainerPermission.List Permissions: ContainerPermission.Read | ContainerPermission.List
), ),
new ContainerDefinition(
Type:ContainerType.Extra,
Compare: Compare.AtMost,
Value:1,
Permissions: ContainerPermission.Read | ContainerPermission.List
),
}) })
}, },
{ {
@ -699,6 +787,12 @@ public static class Defs {
Permissions: Permissions:
ContainerPermission.Read | ContainerPermission.List ContainerPermission.Read | ContainerPermission.List
), ),
new ContainerDefinition(
Type:ContainerType.Extra,
Compare: Compare.AtMost,
Value:1,
Permissions: ContainerPermission.Read | ContainerPermission.List
),
}) })
}, },
}; };

View File

@ -104,10 +104,12 @@ public class Scheduler : IScheduler {
if (bucketConfig is not null) { if (bucketConfig is not null) {
var setupUrl = await _containers.GetContainerSasUrl(bucketConfig.setupContainer, StorageType.Corpus, BlobContainerSasPermissions.Read | BlobContainerSasPermissions.List); var setupUrl = await _containers.GetContainerSasUrl(bucketConfig.setupContainer, StorageType.Corpus, BlobContainerSasPermissions.Read | BlobContainerSasPermissions.List);
var extraUrl = bucketConfig.extraContainer != null ? await _containers.GetContainerSasUrl(bucketConfig.extraContainer, StorageType.Corpus, BlobContainerSasPermissions.Read | BlobContainerSasPermissions.List) : null;
var workSet = new WorkSet( var workSet = new WorkSet(
Reboot: bucketConfig.reboot, Reboot: bucketConfig.reboot,
Script: bucketConfig.setupScript is not null, Script: bucketConfig.setupScript is not null,
SetupUrl: setupUrl, SetupUrl: setupUrl,
ExtraUrl: extraUrl,
WorkUnits: workUnits WorkUnits: workUnits
); );
@ -118,7 +120,7 @@ public class Scheduler : IScheduler {
} }
sealed record BucketConfig(long count, bool reboot, Container setupContainer, string? setupScript, Pool pool); sealed record BucketConfig(long count, bool reboot, Container setupContainer, Container? extraContainer, string? setupScript, Pool pool);
sealed record PoolKey( sealed record PoolKey(
PoolName? poolName = null, PoolName? poolName = null,
@ -172,6 +174,8 @@ public class Scheduler : IScheduler {
} }
var setupContainer = task.Config.Containers?.FirstOrDefault(c => c.Type == ContainerType.Setup) ?? throw new Exception($"task missing setup container: task_type = {task.Config.Task.Type}"); var setupContainer = task.Config.Containers?.FirstOrDefault(c => c.Type == ContainerType.Setup) ?? throw new Exception($"task missing setup container: task_type = {task.Config.Task.Type}");
var extraContainer = task.Config.Containers?.FirstOrDefault(c => c.Type == ContainerType.Extra);
string? setupScript = null; string? setupScript = null;
if (task.Os == Os.Windows) { if (task.Os == Os.Windows) {
if (await _containers.BlobExists(setupContainer.Name, "setup.ps1", StorageType.Corpus)) { if (await _containers.BlobExists(setupContainer.Name, "setup.ps1", StorageType.Corpus)) {
@ -209,6 +213,7 @@ public class Scheduler : IScheduler {
count, count,
reboot, reboot,
setupContainer.Name, setupContainer.Name,
extraContainer?.Name,
setupScript, setupScript,
pool with { ETag = default, TimeStamp = default }); pool with { ETag = default, TimeStamp = default });

View File

@ -129,7 +129,7 @@ pub fn find_coverage_sites(
// Apply allowlists per block, to account for inlining. The `location` values // Apply allowlists per block, to account for inlining. The `location` values
// here describe the top of the inline-inclusive call stack. // here describe the top of the inline-inclusive call stack.
if !allowlist.source_files.is_allowed(&path) { if !allowlist.source_files.is_allowed(path) {
continue; continue;
} }

View File

@ -60,6 +60,7 @@ impl Fixture {
WorkSet { WorkSet {
reboot: false, reboot: false,
setup_url: self.setup_url(), setup_url: self.setup_url(),
extra_url: None,
script: false, script: false,
work_units: vec![self.work_unit()], work_units: vec![self.work_unit()],
} }

View File

@ -143,6 +143,9 @@ pub struct RunWorkerOpt {
#[clap(long)] #[clap(long)]
script: bool, script: bool,
#[clap(long)]
extra_url: Option<Url>,
} }
fn debug_run_worker(opt: RunWorkerOpt) -> Result<()> { fn debug_run_worker(opt: RunWorkerOpt) -> Result<()> {
@ -164,6 +167,7 @@ fn debug_run_worker(opt: RunWorkerOpt) -> Result<()> {
let work_set = WorkSet { let work_set = WorkSet {
reboot: false, reboot: false,
setup_url: BlobContainerUrl::new(opt.setup_url)?, setup_url: BlobContainerUrl::new(opt.setup_url)?,
extra_url: opt.extra_url.map(BlobContainerUrl::new).transpose()?,
script: opt.script, script: opt.script,
work_units: vec![work_unit], work_units: vec![work_unit],
}; };
@ -188,8 +192,9 @@ async fn run_worker(mut work_set: WorkSet) -> Result<Vec<WorkerEvent>> {
let mut events = vec![]; let mut events = vec![];
let work_unit = work_set.work_units.pop().unwrap(); let work_unit = work_set.work_units.pop().unwrap();
let setup_dir = work_set.setup_dir()?; let setup_dir = work_set.setup_dir()?;
let extra_dir = work_set.extra_dir()?;
let mut worker = Worker::new(&setup_dir, work_unit); let mut worker = Worker::new(&setup_dir, extra_dir, work_unit);
while !worker.is_done() { while !worker.is_done() {
worker = worker worker = worker
.update( .update(

View File

@ -250,8 +250,9 @@ impl State<Ready> {
pub async fn run(self) -> Result<State<Busy>> { pub async fn run(self) -> Result<State<Busy>> {
let mut workers = vec![]; let mut workers = vec![];
let setup_dir = &self.ctx.work_set.setup_dir()?; let setup_dir = &self.ctx.work_set.setup_dir()?;
let extra_dir = self.ctx.work_set.extra_dir()?;
for work in self.ctx.work_set.work_units { for work in self.ctx.work_set.work_units {
let worker = Some(Worker::new(setup_dir, work)); let worker = Some(Worker::new(setup_dir, extra_dir.clone(), work));
workers.push(worker); workers.push(worker);
} }

View File

@ -43,6 +43,20 @@ pub struct SetupRunner {
impl SetupRunner { impl SetupRunner {
pub async fn run(&self, work_set: &WorkSet) -> Result<SetupOutput> { pub async fn run(&self, work_set: &WorkSet) -> Result<SetupOutput> {
if let (Some(extra_url), Some(extra_dir)) = (&work_set.extra_url, work_set.extra_dir()?) {
info!("downloading extra container");
// `azcopy sync` requires the local dir to exist.
fs::create_dir_all(&extra_dir).await.with_context(|| {
format!("unable to create extra container: {}", extra_dir.display())
})?;
az_copy::sync(extra_url.to_string(), &extra_dir, false).await?;
debug!(
"synced extra container from {} to {}",
extra_url,
extra_dir.display(),
);
}
info!("running setup for work set"); info!("running setup for work set");
work_set.save_context(self.machine_id).await?; work_set.save_context(self.machine_id).await?;
// Download the setup container. // Download the setup container.

View File

@ -22,6 +22,7 @@ pub type TaskId = Uuid;
pub struct WorkSet { pub struct WorkSet {
pub reboot: bool, pub reboot: bool,
pub setup_url: BlobContainerUrl, pub setup_url: BlobContainerUrl,
pub extra_url: Option<BlobContainerUrl>,
pub script: bool, pub script: bool,
pub work_units: Vec<WorkUnit>, pub work_units: Vec<WorkUnit>,
} }
@ -92,6 +93,21 @@ impl WorkSet {
.join("blob-containers") .join("blob-containers")
.join(setup_dir)) .join(setup_dir))
} }
pub fn extra_dir(&self) -> Result<Option<PathBuf>> {
if let Some(extra_url) = &self.extra_url {
let extra_dir = extra_url
.account()
.ok_or_else(|| anyhow!("Invalid container Url"))?;
Ok(Some(
onefuzz::fs::onefuzz_root()?
.join("blob-containers")
.join(extra_dir),
))
} else {
Ok(None)
}
}
} }
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] #[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]

View File

@ -45,9 +45,14 @@ pub enum Worker {
} }
impl Worker { impl Worker {
pub fn new(setup_dir: impl AsRef<Path>, work: WorkUnit) -> Self { pub fn new(
setup_dir: impl AsRef<Path>,
extra_dir: Option<impl AsRef<Path>>,
work: WorkUnit,
) -> Self {
let ctx = Ready { let ctx = Ready {
setup_dir: PathBuf::from(setup_dir.as_ref()), setup_dir: PathBuf::from(setup_dir.as_ref()),
extra_dir: extra_dir.map(|dir| PathBuf::from(dir.as_ref())),
}; };
let state = State { ctx, work }; let state = State { ctx, work };
state.into() state.into()
@ -98,6 +103,7 @@ impl Worker {
#[derive(Debug)] #[derive(Debug)]
pub struct Ready { pub struct Ready {
setup_dir: PathBuf, setup_dir: PathBuf,
extra_dir: Option<PathBuf>,
} }
#[derive(Debug)] #[derive(Debug)]
@ -130,7 +136,9 @@ impl<C: Context> State<C> {
impl State<Ready> { impl State<Ready> {
pub async fn run(self, runner: &mut dyn IWorkerRunner) -> Result<State<Running>> { pub async fn run(self, runner: &mut dyn IWorkerRunner) -> Result<State<Running>> {
let child = runner.run(&self.ctx.setup_dir, &self.work).await?; let child = runner
.run(&self.ctx.setup_dir, self.ctx.extra_dir, &self.work)
.await?;
let state = State { let state = State {
ctx: Running { child }, ctx: Running { child },
@ -189,7 +197,12 @@ impl_from_state_for_worker!(Done);
#[async_trait] #[async_trait]
pub trait IWorkerRunner: Downcast { pub trait IWorkerRunner: Downcast {
async fn run(&self, setup_dir: &Path, work: &WorkUnit) -> Result<Box<dyn IWorkerChild>>; async fn run(
&self,
setup_dir: &Path,
extra_dir: Option<PathBuf>,
work: &WorkUnit,
) -> Result<Box<dyn IWorkerChild>>;
} }
impl_downcast!(IWorkerRunner); impl_downcast!(IWorkerRunner);
@ -214,7 +227,12 @@ impl WorkerRunner {
#[async_trait] #[async_trait]
impl IWorkerRunner for WorkerRunner { impl IWorkerRunner for WorkerRunner {
async fn run(&self, setup_dir: &Path, work: &WorkUnit) -> Result<Box<dyn IWorkerChild>> { async fn run(
&self,
setup_dir: &Path,
extra_dir: Option<PathBuf>,
work: &WorkUnit,
) -> Result<Box<dyn IWorkerChild>> {
let working_dir = work.working_dir(self.machine_identity.machine_id)?; let working_dir = work.working_dir(self.machine_identity.machine_id)?;
debug!("worker working dir = {}", working_dir.display()); debug!("worker working dir = {}", working_dir.display());
@ -260,6 +278,11 @@ impl IWorkerRunner for WorkerRunner {
cmd.arg("managed"); cmd.arg("managed");
cmd.arg("config.json"); cmd.arg("config.json");
cmd.arg(setup_dir); cmd.arg(setup_dir);
if let Some(extra_dir) = extra_dir {
cmd.arg("--extra_dir");
cmd.arg(extra_dir);
}
cmd.stderr(Stdio::piped()); cmd.stderr(Stdio::piped());
cmd.stdout(Stdio::piped()); cmd.stdout(Stdio::piped());

View File

@ -10,7 +10,12 @@ pub struct WorkerRunnerDouble {
#[async_trait] #[async_trait]
impl IWorkerRunner for WorkerRunnerDouble { impl IWorkerRunner for WorkerRunnerDouble {
async fn run(&self, _setup_dir: &Path, _work: &WorkUnit) -> Result<Box<dyn IWorkerChild>> { async fn run(
&self,
_setup_dir: &Path,
_extra_dir: Option<PathBuf>,
_work: &WorkUnit,
) -> Result<Box<dyn IWorkerChild>> {
Ok(Box::new(self.child.clone())) Ok(Box::new(self.child.clone()))
} }
} }

View File

@ -55,7 +55,12 @@ struct RunnerDouble {
#[async_trait] #[async_trait]
impl IWorkerRunner for RunnerDouble { impl IWorkerRunner for RunnerDouble {
async fn run(&self, _setup_dir: &Path, _work: &WorkUnit) -> Result<Box<dyn IWorkerChild>> { async fn run(
&self,
_setup_dir: &Path,
_extra_dir: Option<PathBuf>,
_work: &WorkUnit,
) -> Result<Box<dyn IWorkerChild>> {
Ok(Box::new(self.child.clone())) Ok(Box::new(self.child.clone()))
} }
} }
@ -66,6 +71,7 @@ async fn test_ready_run() {
let state = State { let state = State {
ctx: Ready { ctx: Ready {
setup_dir: PathBuf::default(), setup_dir: PathBuf::default(),
extra_dir: None,
}, },
work: Fixture.work(), work: Fixture.work(),
}; };
@ -148,6 +154,7 @@ async fn test_worker_ready_update() {
let state = State { let state = State {
ctx: Ready { ctx: Ready {
setup_dir: PathBuf::default(), setup_dir: PathBuf::default(),
extra_dir: None,
}, },
work: Fixture.work(), work: Fixture.work(),
}; };

View File

@ -22,6 +22,7 @@ use crate::tasks::config::CommonConfig;
use crate::tasks::utils::parse_key_value; use crate::tasks::utils::parse_key_value;
pub const SETUP_DIR: &str = "setup_dir"; pub const SETUP_DIR: &str = "setup_dir";
pub const EXTRA_DIR: &str = "extra_dir";
pub const INPUTS_DIR: &str = "inputs_dir"; pub const INPUTS_DIR: &str = "inputs_dir";
pub const CRASHES_DIR: &str = "crashes_dir"; pub const CRASHES_DIR: &str = "crashes_dir";
pub const TARGET_WORKERS: &str = "target_workers"; pub const TARGET_WORKERS: &str = "target_workers";
@ -237,6 +238,7 @@ pub async fn build_local_context(
}); });
let instance_id = get_uuid("instance_id", args).unwrap_or_default(); let instance_id = get_uuid("instance_id", args).unwrap_or_default();
let extra_dir = args.get_one::<PathBuf>(EXTRA_DIR).cloned();
let setup_dir = if let Some(setup_dir) = args.get_one::<PathBuf>(SETUP_DIR) { let setup_dir = if let Some(setup_dir) = args.get_one::<PathBuf>(SETUP_DIR) {
setup_dir.clone() setup_dir.clone()
} else if let Some(target_exe) = args.get_one::<String>(TARGET_EXE) { } else if let Some(target_exe) = args.get_one::<String>(TARGET_EXE) {
@ -253,6 +255,7 @@ pub async fn build_local_context(
task_id, task_id,
instance_id, instance_id,
setup_dir, setup_dir,
extra_dir,
machine_identity: MachineIdentity { machine_identity: MachineIdentity {
machine_id: Uuid::nil(), machine_id: Uuid::nil(),
machine_name: "local".to_string(), machine_name: "local".to_string(),

View File

@ -29,6 +29,7 @@ pub async fn run(args: &clap::ArgMatches, event_sender: Option<Sender<UiEvent>>)
.get_one::<u64>(CHECK_RETRY_COUNT) .get_one::<u64>(CHECK_RETRY_COUNT)
.copied() .copied()
.expect("has a default value"); .expect("has a default value");
let extra_dir = context.common_config.extra_dir.as_deref();
let config = TestInputArgs { let config = TestInputArgs {
target_exe: target_exe.as_path(), target_exe: target_exe.as_path(),
@ -41,6 +42,7 @@ pub async fn run(args: &clap::ArgMatches, event_sender: Option<Sender<UiEvent>>)
target_timeout, target_timeout,
check_retry_count, check_retry_count,
setup_dir: &context.common_config.setup_dir, setup_dir: &context.common_config.setup_dir,
extra_dir,
minimized_stack_depth: None, minimized_stack_depth: None,
machine_identity: context.common_config.machine_identity, machine_identity: context.common_config.machine_identity,
}; };

View File

@ -44,6 +44,7 @@ pub async fn run(args: &clap::ArgMatches, event_sender: Option<Sender<UiEvent>>)
target_timeout, target_timeout,
check_retry_count, check_retry_count,
setup_dir: &context.common_config.setup_dir, setup_dir: &context.common_config.setup_dir,
extra_dir: context.common_config.extra_dir.as_deref(),
minimized_stack_depth: None, minimized_stack_depth: None,
check_asan_log, check_asan_log,
check_debugger, check_debugger,

View File

@ -24,7 +24,8 @@ pub async fn run(args: &clap::ArgMatches) -> Result<()> {
.get_one::<PathBuf>("setup_dir") .get_one::<PathBuf>("setup_dir")
.expect("marked as required"); .expect("marked as required");
let config = Config::from_file(config_path, setup_dir)?; let extra_dir = args.get_one::<PathBuf>("extra_dir").map(|f| f.as_path());
let config = Config::from_file(config_path, setup_dir, extra_dir)?;
init_telemetry(config.common()).await; init_telemetry(config.common()).await;
@ -138,4 +139,9 @@ pub fn args(name: &'static str) -> Command {
.required(true) .required(true)
.value_parser(value_parser!(PathBuf)), .value_parser(value_parser!(PathBuf)),
) )
.arg(
Arg::new("extra_dir")
.required(false)
.value_parser(value_parser!(PathBuf)),
)
} }

View File

@ -209,6 +209,9 @@ pub async fn run_tool(
.output_dir(&config.analysis.local_path) .output_dir(&config.analysis.local_path)
.tools_dir(&config.tools.local_path) .tools_dir(&config.tools.local_path)
.setup_dir(&config.common.setup_dir) .setup_dir(&config.common.setup_dir)
.set_optional_ref(&config.common.extra_dir, |expand, extra_dir| {
expand.extra_dir(extra_dir)
})
.job_id(&config.common.job_id) .job_id(&config.common.job_id)
.task_id(&config.common.task_id) .task_id(&config.common.task_id)
.set_optional_ref(&config.common.microsoft_telemetry_key, |tester, key| { .set_optional_ref(&config.common.microsoft_telemetry_key, |tester, key| {

View File

@ -55,6 +55,9 @@ pub struct CommonConfig {
#[serde(default)] #[serde(default)]
pub setup_dir: PathBuf, pub setup_dir: PathBuf,
#[serde(default)]
pub extra_dir: Option<PathBuf>,
/// Lower bound on available system memory. If the available memory drops /// Lower bound on available system memory. If the available memory drops
/// below the limit, the task will exit with an error. This is a fail-fast /// below the limit, the task will exit with an error. This is a fail-fast
/// mechanism to support debugging. /// mechanism to support debugging.
@ -139,13 +142,15 @@ pub enum Config {
} }
impl Config { impl Config {
pub fn from_file(path: &Path, setup_dir: &Path) -> Result<Self> { pub fn from_file(path: &Path, setup_dir: &Path, extra_dir: Option<&Path>) -> Result<Self> {
let json = std::fs::read_to_string(path)?; let json = std::fs::read_to_string(path)?;
let json_config: serde_json::Value = serde_json::from_str(&json)?; let json_config: serde_json::Value = serde_json::from_str(&json)?;
// override the setup_dir in the config file with the parameter value if specified // override the setup_dir in the config file with the parameter value if specified
let mut config: Self = serde_json::from_value(json_config)?; let mut config: Self = serde_json::from_value(json_config)?;
config.common_mut().setup_dir = setup_dir.to_owned(); config.common_mut().setup_dir = setup_dir.to_owned();
config.common_mut().extra_dir = extra_dir.map(|x| x.to_owned());
Ok(config) Ok(config)
} }

View File

@ -299,6 +299,9 @@ impl<'a> TaskContext<'a> {
.input_path(input) .input_path(input)
.job_id(&self.config.common.job_id) .job_id(&self.config.common.job_id)
.setup_dir(&self.config.common.setup_dir) .setup_dir(&self.config.common.setup_dir)
.set_optional_ref(&self.config.common.extra_dir, |expand, extra_dir| {
expand.extra_dir(extra_dir)
})
.target_exe(&target_exe) .target_exe(&target_exe)
.target_options(&self.config.target_options) .target_options(&self.config.target_options)
.task_id(&self.config.common.task_id); .task_id(&self.config.common.task_id);

View File

@ -299,6 +299,9 @@ impl<'a> TaskContext<'a> {
.input_path(input) .input_path(input)
.job_id(&self.config.common.job_id) .job_id(&self.config.common.job_id)
.setup_dir(&self.config.common.setup_dir) .setup_dir(&self.config.common.setup_dir)
.set_optional_ref(&self.config.common.extra_dir, |expand, extra_dir| {
expand.extra_dir(extra_dir)
})
.target_exe(&target_exe) .target_exe(&target_exe)
.target_options(&self.config.target_options) .target_options(&self.config.target_options)
.task_id(&self.config.common.task_id); .task_id(&self.config.common.task_id);

View File

@ -99,6 +99,7 @@ impl GeneratorTask {
let tester = Tester::new( let tester = Tester::new(
&self.config.common.setup_dir, &self.config.common.setup_dir,
self.config.common.extra_dir.as_deref(),
&target_exe, &target_exe,
&self.config.target_options, &self.config.target_options,
&self.config.target_env, &self.config.target_env,
@ -168,6 +169,9 @@ impl GeneratorTask {
.machine_id() .machine_id()
.await? .await?
.setup_dir(&self.config.common.setup_dir) .setup_dir(&self.config.common.setup_dir)
.set_optional_ref(&self.config.common.extra_dir, |expand, extra_dir| {
expand.extra_dir(extra_dir)
})
.generated_inputs(&output_dir) .generated_inputs(&output_dir)
.input_corpus(&corpus_dir) .input_corpus(&corpus_dir)
.generator_exe(&self.config.generator_exe) .generator_exe(&self.config.generator_exe)
@ -298,6 +302,7 @@ mod tests {
microsoft_telemetry_key: Default::default(), microsoft_telemetry_key: Default::default(),
logs: Default::default(), logs: Default::default(),
setup_dir: Default::default(), setup_dir: Default::default(),
extra_dir: Default::default(),
min_available_memory_mb: Default::default(), min_available_memory_mb: Default::default(),
machine_identity: onefuzz::machine_id::MachineIdentity { machine_identity: onefuzz::machine_id::MachineIdentity {
machine_id: uuid::Uuid::new_v4(), machine_id: uuid::Uuid::new_v4(),

View File

@ -86,6 +86,7 @@ impl common::LibFuzzerType for LibFuzzerDotnet {
options, options,
env, env,
&config.common.setup_dir, &config.common.setup_dir,
config.common.extra_dir.as_ref(),
config.common.machine_identity.clone(), config.common.machine_identity.clone(),
)) ))
} }

View File

@ -28,6 +28,7 @@ impl common::LibFuzzerType for GenericLibFuzzer {
config.target_options.clone(), config.target_options.clone(),
config.target_env.clone(), config.target_env.clone(),
&config.common.setup_dir, &config.common.setup_dir,
config.common.extra_dir.as_ref(),
config.common.machine_identity.clone(), config.common.machine_identity.clone(),
)) ))
} }

View File

@ -214,6 +214,9 @@ async fn start_supervisor(
.input_corpus(&inputs.local_path) .input_corpus(&inputs.local_path)
.reports_dir(reports_dir) .reports_dir(reports_dir)
.setup_dir(&config.common.setup_dir) .setup_dir(&config.common.setup_dir)
.set_optional_ref(&config.common.extra_dir, |expand, extra_dir| {
expand.extra_dir(extra_dir)
})
.job_id(&config.common.job_id) .job_id(&config.common.job_id)
.task_id(&config.common.task_id) .task_id(&config.common.task_id)
.set_optional_ref(&config.tools, |expand, tools| { .set_optional_ref(&config.tools, |expand, tools| {
@ -391,6 +394,7 @@ mod tests {
microsoft_telemetry_key: Default::default(), microsoft_telemetry_key: Default::default(),
logs: Default::default(), logs: Default::default(),
setup_dir: Default::default(), setup_dir: Default::default(),
extra_dir: Default::default(),
min_available_memory_mb: Default::default(), min_available_memory_mb: Default::default(),
machine_identity: MachineIdentity { machine_identity: MachineIdentity {
machine_id: uuid::Uuid::new_v4(), machine_id: uuid::Uuid::new_v4(),

View File

@ -141,6 +141,9 @@ async fn merge(config: &Config, output_dir: impl AsRef<Path>) -> Result<()> {
.generated_inputs(output_dir) .generated_inputs(output_dir)
.target_exe(&target_exe) .target_exe(&target_exe)
.setup_dir(&config.common.setup_dir) .setup_dir(&config.common.setup_dir)
.set_optional_ref(&config.common.extra_dir, |expand, extra_dir| {
expand.extra_dir(extra_dir)
})
.tools_dir(&config.tools.local_path) .tools_dir(&config.tools.local_path)
.job_id(&config.common.job_id) .job_id(&config.common.job_id)
.task_id(&config.common.task_id) .task_id(&config.common.task_id)

View File

@ -46,6 +46,7 @@ pub async fn spawn(config: Arc<Config>) -> Result<()> {
config.target_options.clone(), config.target_options.clone(),
config.target_env.clone(), config.target_env.clone(),
&config.common.setup_dir, &config.common.setup_dir,
config.common.extra_dir.clone(),
config.common.machine_identity.clone(), config.common.machine_identity.clone(),
); );
fuzzer.verify(config.check_fuzzer_help, None).await?; fuzzer.verify(config.check_fuzzer_help, None).await?;
@ -160,6 +161,7 @@ pub async fn merge_inputs(
config.target_options.clone(), config.target_options.clone(),
config.target_env.clone(), config.target_env.clone(),
&config.common.setup_dir, &config.common.setup_dir,
config.common.extra_dir.clone(),
config.common.machine_identity.clone(), config.common.machine_identity.clone(),
); );
merger merger

View File

@ -60,6 +60,7 @@ impl RegressionHandler for GenericRegressionTask {
try_resolve_setup_relative_path(&self.config.common.setup_dir, &self.config.target_exe) try_resolve_setup_relative_path(&self.config.common.setup_dir, &self.config.target_exe)
.await?; .await?;
let extra_dir = self.config.common.extra_dir.as_deref();
let args = generic::TestInputArgs { let args = generic::TestInputArgs {
input_url: Some(input_url), input_url: Some(input_url),
input: &input, input: &input,
@ -67,6 +68,7 @@ impl RegressionHandler for GenericRegressionTask {
target_options: &self.config.target_options, target_options: &self.config.target_options,
target_env: &self.config.target_env, target_env: &self.config.target_env,
setup_dir: &self.config.common.setup_dir, setup_dir: &self.config.common.setup_dir,
extra_dir,
task_id: self.config.common.task_id, task_id: self.config.common.task_id,
job_id: self.config.common.job_id, job_id: self.config.common.job_id,
target_timeout: self.config.target_timeout, target_timeout: self.config.target_timeout,

View File

@ -66,6 +66,7 @@ impl RegressionHandler for LibFuzzerRegressionTask {
target_options: &self.config.target_options, target_options: &self.config.target_options,
target_env: &self.config.target_env, target_env: &self.config.target_env,
setup_dir: &self.config.common.setup_dir, setup_dir: &self.config.common.setup_dir,
extra_dir: self.config.common.extra_dir.as_deref(),
task_id: self.config.common.task_id, task_id: self.config.common.task_id,
job_id: self.config.common.job_id, job_id: self.config.common.job_id,
target_timeout: self.config.target_timeout, target_timeout: self.config.target_timeout,

View File

@ -182,7 +182,10 @@ impl AsanProcessor {
let expand = Expand::new(&self.config.common.machine_identity) let expand = Expand::new(&self.config.common.machine_identity)
.input_path(input) .input_path(input)
.setup_dir(&self.config.common.setup_dir); .setup_dir(&self.config.common.setup_dir)
.set_optional_ref(&self.config.common.extra_dir, |expand, extra_dir| {
expand.extra_dir(extra_dir)
});
let expanded_args = expand.evaluate(&args)?; let expanded_args = expand.evaluate(&args)?;
let env = { let env = {

View File

@ -113,6 +113,7 @@ pub struct TestInputArgs<'a> {
pub target_options: &'a [String], pub target_options: &'a [String],
pub target_env: &'a HashMap<String, String>, pub target_env: &'a HashMap<String, String>,
pub setup_dir: &'a Path, pub setup_dir: &'a Path,
pub extra_dir: Option<&'a Path>,
pub task_id: Uuid, pub task_id: Uuid,
pub job_id: Uuid, pub job_id: Uuid,
pub target_timeout: Option<u64>, pub target_timeout: Option<u64>,
@ -124,8 +125,10 @@ pub struct TestInputArgs<'a> {
} }
pub async fn test_input(args: TestInputArgs<'_>) -> Result<CrashTestResult> { pub async fn test_input(args: TestInputArgs<'_>) -> Result<CrashTestResult> {
let extra_dir = args.extra_dir;
let tester = Tester::new( let tester = Tester::new(
args.setup_dir, args.setup_dir,
extra_dir,
args.target_exe, args.target_exe,
args.target_options, args.target_options,
args.target_env, args.target_env,
@ -201,6 +204,7 @@ impl<'a> GenericReportProcessor<'a> {
try_resolve_setup_relative_path(&self.config.common.setup_dir, &self.config.target_exe) try_resolve_setup_relative_path(&self.config.common.setup_dir, &self.config.target_exe)
.await?; .await?;
let extra_dir = self.config.common.extra_dir.as_deref();
let args = TestInputArgs { let args = TestInputArgs {
input_url, input_url,
input, input,
@ -208,6 +212,7 @@ impl<'a> GenericReportProcessor<'a> {
target_options: &self.config.target_options, target_options: &self.config.target_options,
target_env: &self.config.target_env, target_env: &self.config.target_env,
setup_dir: &self.config.common.setup_dir, setup_dir: &self.config.common.setup_dir,
extra_dir,
task_id: self.config.common.task_id, task_id: self.config.common.task_id,
job_id: self.config.common.job_id, job_id: self.config.common.job_id,
target_timeout: self.config.target_timeout, target_timeout: self.config.target_timeout,

View File

@ -76,6 +76,7 @@ impl ReportTask {
self.config.target_options.clone(), self.config.target_options.clone(),
self.config.target_env.clone(), self.config.target_env.clone(),
&self.config.common.setup_dir, &self.config.common.setup_dir,
self.config.common.extra_dir.clone(),
self.config.common.machine_identity.clone(), self.config.common.machine_identity.clone(),
); );
fuzzer.verify(self.config.check_fuzzer_help, None).await fuzzer.verify(self.config.check_fuzzer_help, None).await
@ -118,6 +119,7 @@ pub struct TestInputArgs<'a> {
pub target_options: &'a [String], pub target_options: &'a [String],
pub target_env: &'a HashMap<String, String>, pub target_env: &'a HashMap<String, String>,
pub setup_dir: &'a Path, pub setup_dir: &'a Path,
pub extra_dir: Option<&'a Path>,
pub task_id: uuid::Uuid, pub task_id: uuid::Uuid,
pub job_id: uuid::Uuid, pub job_id: uuid::Uuid,
pub target_timeout: Option<u64>, pub target_timeout: Option<u64>,
@ -132,6 +134,7 @@ pub async fn test_input(args: TestInputArgs<'_>) -> Result<CrashTestResult> {
args.target_options.to_vec(), args.target_options.to_vec(),
args.target_env.clone(), args.target_env.clone(),
args.setup_dir, args.setup_dir,
args.extra_dir.map(PathBuf::from),
args.machine_identity, args.machine_identity,
); );
@ -215,6 +218,7 @@ impl AsanProcessor {
target_options: &self.config.target_options, target_options: &self.config.target_options,
target_env: &self.config.target_env, target_env: &self.config.target_env,
setup_dir: &self.config.common.setup_dir, setup_dir: &self.config.common.setup_dir,
extra_dir: self.config.common.extra_dir.as_deref(),
task_id: self.config.common.task_id, task_id: self.config.common.task_id,
job_id: self.config.common.job_id, job_id: self.config.common.job_id,
target_timeout: self.config.target_timeout, target_timeout: self.config.target_timeout,

View File

@ -55,6 +55,7 @@ async fn main() -> Result<()> {
let env = Default::default(); let env = Default::default();
let tester = Tester::new( let tester = Tester::new(
&setup_dir, &setup_dir,
None,
&opt.exe, &opt.exe,
&target_options, &target_options,
&env, &env,

View File

@ -41,6 +41,7 @@ pub enum PlaceHolder {
SupervisorExe, SupervisorExe,
SupervisorOptions, SupervisorOptions,
SetupDir, SetupDir,
ExtraDir,
ReportsDir, ReportsDir,
JobId, JobId,
TaskId, TaskId,
@ -74,6 +75,7 @@ impl PlaceHolder {
Self::SupervisorExe => "{supervisor_exe}", Self::SupervisorExe => "{supervisor_exe}",
Self::SupervisorOptions => "{supervisor_options}", Self::SupervisorOptions => "{supervisor_options}",
Self::SetupDir => "{setup_dir}", Self::SetupDir => "{setup_dir}",
Self::ExtraDir => "{extra_dir}",
Self::ReportsDir => "{reports_dir}", Self::ReportsDir => "{reports_dir}",
Self::JobId => "{job_id}", Self::JobId => "{job_id}",
Self::TaskId => "{task_id}", Self::TaskId => "{task_id}",
@ -317,6 +319,12 @@ impl<'a> Expand<'a> {
self.set_value(PlaceHolder::SetupDir, ExpandedValue::Path(path)) self.set_value(PlaceHolder::SetupDir, ExpandedValue::Path(path))
} }
pub fn extra_dir(self, arg: impl AsRef<Path>) -> Self {
let arg = arg.as_ref();
let path = String::from(arg.to_string_lossy());
self.set_value(PlaceHolder::ExtraDir, ExpandedValue::Path(path))
}
pub fn coverage_dir(self, arg: impl AsRef<Path>) -> Self { pub fn coverage_dir(self, arg: impl AsRef<Path>) -> Self {
let arg = arg.as_ref(); let arg = arg.as_ref();
let path = String::from(arg.to_string_lossy()); let path = String::from(arg.to_string_lossy());

View File

@ -27,6 +27,7 @@ const CRASH_SITE_UNAVAILABLE: &str = "<crash site unavailable>";
pub struct Tester<'a> { pub struct Tester<'a> {
setup_dir: &'a Path, setup_dir: &'a Path,
extra_dir: Option<&'a Path>,
exe_path: &'a Path, exe_path: &'a Path,
arguments: &'a [String], arguments: &'a [String],
environ: &'a HashMap<String, String>, environ: &'a HashMap<String, String>,
@ -56,6 +57,7 @@ pub struct TestResult {
impl<'a> Tester<'a> { impl<'a> Tester<'a> {
pub fn new( pub fn new(
setup_dir: &'a Path, setup_dir: &'a Path,
extra_dir: Option<&'a Path>,
exe_path: &'a Path, exe_path: &'a Path,
arguments: &'a [String], arguments: &'a [String],
environ: &'a HashMap<String, String>, environ: &'a HashMap<String, String>,
@ -63,6 +65,7 @@ impl<'a> Tester<'a> {
) -> Self { ) -> Self {
Self { Self {
setup_dir, setup_dir,
extra_dir,
exe_path, exe_path,
arguments, arguments,
environ, environ,
@ -298,7 +301,10 @@ impl<'a> Tester<'a> {
.input_path(input_file) .input_path(input_file)
.target_exe(self.exe_path) .target_exe(self.exe_path)
.target_options(self.arguments) .target_options(self.arguments)
.setup_dir(self.setup_dir); .setup_dir(self.setup_dir)
.set_optional(self.extra_dir.as_ref(), |expand, extra_dir| {
expand.extra_dir(extra_dir)
});
let argv = expand.evaluate(self.arguments)?; let argv = expand.evaluate(self.arguments)?;
let mut env: HashMap<String, String> = HashMap::new(); let mut env: HashMap<String, String> = HashMap::new();

View File

@ -37,6 +37,7 @@ pub struct LibFuzzerMergeOutput {
pub struct LibFuzzer { pub struct LibFuzzer {
setup_dir: PathBuf, setup_dir: PathBuf,
extra_dir: Option<PathBuf>,
exe: PathBuf, exe: PathBuf,
options: Vec<String>, options: Vec<String>,
env: HashMap<String, String>, env: HashMap<String, String>,
@ -49,6 +50,7 @@ impl LibFuzzer {
options: Vec<String>, options: Vec<String>,
env: HashMap<String, String>, env: HashMap<String, String>,
setup_dir: impl Into<PathBuf>, setup_dir: impl Into<PathBuf>,
extra_dir: Option<impl Into<PathBuf>>,
machine_identity: MachineIdentity, machine_identity: MachineIdentity,
) -> Self { ) -> Self {
Self { Self {
@ -56,6 +58,7 @@ impl LibFuzzer {
options, options,
env, env,
setup_dir: setup_dir.into(), setup_dir: setup_dir.into(),
extra_dir: extra_dir.map(|x| x.into()),
machine_identity, machine_identity,
} }
} }
@ -108,6 +111,9 @@ impl LibFuzzer {
.target_exe(&self.exe) .target_exe(&self.exe)
.target_options(&self.options) .target_options(&self.options)
.setup_dir(&self.setup_dir) .setup_dir(&self.setup_dir)
.set_optional(self.extra_dir.as_ref(), |expand, extra_dir| {
expand.extra_dir(extra_dir)
})
.set_optional(corpus_dir, |e, corpus_dir| e.input_corpus(corpus_dir)) .set_optional(corpus_dir, |e, corpus_dir| e.input_corpus(corpus_dir))
.set_optional(fault_dir, |e, fault_dir| e.crashes(fault_dir)); .set_optional(fault_dir, |e, fault_dir| e.crashes(fault_dir));
@ -314,6 +320,7 @@ impl LibFuzzer {
let mut tester = Tester::new( let mut tester = Tester::new(
&self.setup_dir, &self.setup_dir,
self.extra_dir.as_deref(),
&self.exe, &self.exe,
&options, &options,
&self.env, &self.env,
@ -443,6 +450,7 @@ mod tests {
options.clone(), options.clone(),
env.clone(), env.clone(),
temp_setup_dir.path(), temp_setup_dir.path(),
Option::<PathBuf>::None,
MachineIdentity { MachineIdentity {
machine_id: uuid::Uuid::new_v4(), machine_id: uuid::Uuid::new_v4(),
machine_name: "test-input".into(), machine_name: "test-input".into(),
@ -476,6 +484,7 @@ mod tests {
options.clone(), options.clone(),
env.clone(), env.clone(),
temp_setup_dir.path(), temp_setup_dir.path(),
Option::<PathBuf>::None,
MachineIdentity { MachineIdentity {
machine_id: uuid::Uuid::new_v4(), machine_id: uuid::Uuid::new_v4(),
machine_name: "test-input".into(), machine_name: "test-input".into(),

View File

@ -53,6 +53,7 @@ class AFL(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,
extra_container: Optional[Container] = None,
) -> Optional[Job]: ) -> Optional[Job]:
""" """
Basic AFL job Basic AFL job
@ -93,6 +94,7 @@ class AFL(Command):
ContainerType.reports, ContainerType.reports,
ContainerType.unique_reports, ContainerType.unique_reports,
) )
if existing_inputs: if existing_inputs:
self.onefuzz.containers.get(existing_inputs) self.onefuzz.containers.get(existing_inputs)
helper.containers[ContainerType.inputs] = existing_inputs helper.containers[ContainerType.inputs] = existing_inputs
@ -133,6 +135,11 @@ class AFL(Command):
(ContainerType.inputs, helper.containers[ContainerType.inputs]), (ContainerType.inputs, helper.containers[ContainerType.inputs]),
] ]
if extra_container is not None:
containers.append(
(ContainerType.extra, helper.containers[ContainerType.extra])
)
self.logger.info("creating afl fuzz task") self.logger.info("creating afl fuzz task")
fuzzer_task = self.onefuzz.tasks.create( fuzzer_task = self.onefuzz.tasks.create(
helper.job.job_id, helper.job.job_id,
@ -166,6 +173,11 @@ class AFL(Command):
), ),
] ]
if extra_container is not None:
report_containers.append(
(ContainerType.extra, helper.containers[ContainerType.extra])
)
self.logger.info("creating generic_crash_report task") self.logger.info("creating generic_crash_report task")
self.onefuzz.tasks.create( self.onefuzz.tasks.create(
helper.job.job_id, helper.job.job_id,

View File

@ -74,6 +74,7 @@ class Libfuzzer(Command):
analyzer_options: Optional[List[str]] = None, analyzer_options: Optional[List[str]] = None,
analyzer_env: Optional[Dict[str, str]] = None, analyzer_env: Optional[Dict[str, str]] = None,
tools: Optional[Container] = None, tools: Optional[Container] = None,
extra_container: Optional[Container] = None,
) -> None: ) -> None:
target_options = target_options or [] target_options = target_options or []
@ -331,6 +332,7 @@ class Libfuzzer(Command):
analyzer_options: Optional[List[str]] = None, analyzer_options: Optional[List[str]] = None,
analyzer_env: Optional[Dict[str, str]] = None, analyzer_env: Optional[Dict[str, str]] = None,
tools: Optional[Container] = None, tools: Optional[Container] = None,
extra_container: Optional[Container] = None,
) -> Optional[Job]: ) -> Optional[Job]:
""" """
Basic libfuzzer job Basic libfuzzer job
@ -413,9 +415,14 @@ class Libfuzzer(Command):
else: else:
source_allowlist_blob_name = None source_allowlist_blob_name = None
containers = helper.containers
if extra_container is not None:
containers[ContainerType.extra] = extra_container
self._create_tasks( self._create_tasks(
job=helper.job, job=helper.job,
containers=helper.containers, containers=containers,
pool_name=pool_name, pool_name=pool_name,
target_exe=target_exe_blob_name, target_exe=target_exe_blob_name,
vm_count=vm_count, vm_count=vm_count,
@ -474,6 +481,7 @@ class Libfuzzer(Command):
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, check_fuzzer_help: bool = True,
extra_container: Optional[Container] = None,
) -> Optional[Job]: ) -> Optional[Job]:
""" """
libfuzzer merge task libfuzzer merge task
@ -510,6 +518,7 @@ class Libfuzzer(Command):
helper.define_containers( helper.define_containers(
ContainerType.setup, ContainerType.setup,
) )
if inputs: if inputs:
helper.define_containers(ContainerType.inputs) helper.define_containers(ContainerType.inputs)
@ -535,6 +544,9 @@ class Libfuzzer(Command):
), ),
] ]
if extra_container is not None:
merge_containers.append((ContainerType.extra, extra_container))
if inputs: if inputs:
merge_containers.append( merge_containers.append(
(ContainerType.inputs, helper.containers[ContainerType.inputs]) (ContainerType.inputs, helper.containers[ContainerType.inputs])
@ -598,6 +610,7 @@ class Libfuzzer(Command):
colocate_secondary_tasks: bool = True, colocate_secondary_tasks: bool = True,
expect_crash_on_failure: bool = False, expect_crash_on_failure: bool = False,
notification_config: Optional[NotificationConfig] = None, notification_config: Optional[NotificationConfig] = None,
extra_container: Optional[Container] = None,
) -> Optional[Job]: ) -> Optional[Job]:
pool = self.onefuzz.pools.get(pool_name) pool = self.onefuzz.pools.get(pool_name)
@ -673,6 +686,9 @@ class Libfuzzer(Command):
(ContainerType.tools, fuzzer_tools_container), (ContainerType.tools, fuzzer_tools_container),
] ]
if extra_container is not None:
fuzzer_containers.append((ContainerType.extra, extra_container))
helper.create_containers() helper.create_containers()
helper.setup_notifications(notification_config) helper.setup_notifications(notification_config)
@ -728,6 +744,9 @@ class Libfuzzer(Command):
(ContainerType.tools, fuzzer_tools_container), (ContainerType.tools, fuzzer_tools_container),
] ]
if extra_container is not None:
coverage_containers.append((ContainerType.extra, extra_container))
self.logger.info("creating `dotnet_coverage` task") self.logger.info("creating `dotnet_coverage` task")
self.onefuzz.tasks.create( self.onefuzz.tasks.create(
helper.job.job_id, helper.job.job_id,
@ -756,6 +775,9 @@ class Libfuzzer(Command):
(ContainerType.tools, fuzzer_tools_container), (ContainerType.tools, fuzzer_tools_container),
] ]
if extra_container is not None:
report_containers.append((ContainerType.extra, extra_container))
self.logger.info("creating `dotnet_crash_report` task") self.logger.info("creating `dotnet_crash_report` task")
self.onefuzz.tasks.create( self.onefuzz.tasks.create(
helper.job.job_id, helper.job.job_id,
@ -808,6 +830,7 @@ class Libfuzzer(Command):
crash_report_timeout: Optional[int] = 1, crash_report_timeout: Optional[int] = 1,
check_retry_count: Optional[int] = 300, check_retry_count: Optional[int] = 300,
check_fuzzer_help: bool = True, check_fuzzer_help: bool = True,
extra_container: Optional[Container] = None,
) -> Optional[Job]: ) -> Optional[Job]:
""" """
libfuzzer tasks, wrapped via qemu-user (PREVIEW FEATURE) libfuzzer tasks, wrapped via qemu-user (PREVIEW FEATURE)
@ -866,6 +889,9 @@ class Libfuzzer(Command):
(ContainerType.inputs, helper.containers[ContainerType.inputs]), (ContainerType.inputs, helper.containers[ContainerType.inputs]),
] ]
if extra_container is not None:
fuzzer_containers.append((ContainerType.extra, extra_container))
helper.create_containers() helper.create_containers()
target_exe_blob_name = helper.setup_relative_blob_name(target_exe, None) target_exe_blob_name = helper.setup_relative_blob_name(target_exe, None)
@ -959,6 +985,9 @@ class Libfuzzer(Command):
(ContainerType.no_repro, helper.containers[ContainerType.no_repro]), (ContainerType.no_repro, helper.containers[ContainerType.no_repro]),
] ]
if extra_container is not None:
report_containers.append((ContainerType.extra, extra_container))
self.logger.info("creating libfuzzer_crash_report task") self.logger.info("creating libfuzzer_crash_report task")
self.onefuzz.tasks.create( self.onefuzz.tasks.create(
helper.job.job_id, helper.job.job_id,

View File

@ -11,7 +11,7 @@ from typing import Dict, List, Optional, Tuple
from onefuzztypes.enums import OS, ContainerType, TaskDebugFlag from onefuzztypes.enums import OS, ContainerType, TaskDebugFlag
from onefuzztypes.models import NotificationConfig from onefuzztypes.models import NotificationConfig
from onefuzztypes.primitives import File, PoolName from onefuzztypes.primitives import Container, File, PoolName
from onefuzz.api import Command from onefuzz.api import Command
from onefuzz.backend import container_file_path from onefuzz.backend import container_file_path
@ -119,6 +119,7 @@ class OssFuzz(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,
extra_container: Optional[Container] = None,
) -> None: ) -> None:
""" """
OssFuzz style libfuzzer jobs OssFuzz style libfuzzer jobs
@ -212,6 +213,10 @@ class OssFuzz(Command):
ContainerType.no_repro, ContainerType.no_repro,
ContainerType.coverage, ContainerType.coverage,
) )
if extra_container is not None:
helper.containers[ContainerType.extra] = extra_container
helper.create_containers() helper.create_containers()
helper.setup_notifications(notification_config) helper.setup_notifications(notification_config)

View File

@ -50,6 +50,7 @@ class Radamsa(Command):
debug: Optional[List[TaskDebugFlag]] = None, debug: Optional[List[TaskDebugFlag]] = None,
ensemble_sync_delay: Optional[int] = None, ensemble_sync_delay: Optional[int] = None,
target_timeout: Optional[int] = None, target_timeout: Optional[int] = None,
extra_container: Optional[Container] = None,
) -> Optional[Job]: ) -> Optional[Job]:
""" """
Basic radamsa job Basic radamsa job
@ -90,6 +91,7 @@ class Radamsa(Command):
ContainerType.no_repro, ContainerType.no_repro,
ContainerType.analysis, ContainerType.analysis,
) )
if existing_inputs: if existing_inputs:
self.onefuzz.containers.get(existing_inputs) self.onefuzz.containers.get(existing_inputs)
helper.containers[ContainerType.readonly_inputs] = existing_inputs helper.containers[ContainerType.readonly_inputs] = existing_inputs
@ -155,6 +157,9 @@ class Radamsa(Command):
), ),
] ]
if extra_container is not None:
containers.append((ContainerType.extra, extra_container))
fuzzer_task = self.onefuzz.tasks.create( fuzzer_task = self.onefuzz.tasks.create(
helper.job.job_id, helper.job.job_id,
TaskType.generic_generator, TaskType.generic_generator,
@ -188,6 +193,9 @@ class Radamsa(Command):
(ContainerType.no_repro, helper.containers[ContainerType.no_repro]), (ContainerType.no_repro, helper.containers[ContainerType.no_repro]),
] ]
if extra_container is not None:
report_containers.append((ContainerType.extra, extra_container))
self.logger.info("creating generic_crash_report task") self.logger.info("creating generic_crash_report task")
self.onefuzz.tasks.create( self.onefuzz.tasks.create(
helper.job.job_id, helper.job.job_id,
@ -231,6 +239,9 @@ class Radamsa(Command):
(ContainerType.crashes, helper.containers[ContainerType.crashes]), (ContainerType.crashes, helper.containers[ContainerType.crashes]),
] ]
if extra_container is not None:
analysis_containers.append((ContainerType.extra, extra_container))
self.onefuzz.tasks.create( self.onefuzz.tasks.create(
helper.job.job_id, helper.job.job_id,
TaskType.generic_analysis, TaskType.generic_analysis,

View File

@ -56,6 +56,7 @@ class Regression(Command):
check_fuzzer_help: bool = True, check_fuzzer_help: bool = True,
delete_input_container: bool = True, delete_input_container: bool = True,
check_regressions: bool = False, check_regressions: bool = False,
extra_container: Optional[Container] = None,
) -> None: ) -> None:
""" """
generic regression task generic regression task
@ -89,6 +90,7 @@ class Regression(Command):
check_fuzzer_help=check_fuzzer_help, check_fuzzer_help=check_fuzzer_help,
delete_input_container=delete_input_container, delete_input_container=delete_input_container,
check_regressions=check_regressions, check_regressions=check_regressions,
extra_container=extra_container,
) )
def libfuzzer( def libfuzzer(
@ -115,6 +117,7 @@ class Regression(Command):
check_fuzzer_help: bool = True, check_fuzzer_help: bool = True,
delete_input_container: bool = True, delete_input_container: bool = True,
check_regressions: bool = False, check_regressions: bool = False,
extra_container: Optional[Container] = None,
) -> None: ) -> None:
""" """
libfuzzer regression task libfuzzer regression task
@ -148,6 +151,7 @@ class Regression(Command):
check_fuzzer_help=check_fuzzer_help, check_fuzzer_help=check_fuzzer_help,
delete_input_container=delete_input_container, delete_input_container=delete_input_container,
check_regressions=check_regressions, check_regressions=check_regressions,
extra_container=extra_container,
) )
def _create_job( def _create_job(
@ -175,6 +179,7 @@ class Regression(Command):
check_fuzzer_help: bool = True, check_fuzzer_help: bool = True,
delete_input_container: bool = True, delete_input_container: bool = True,
check_regressions: bool = False, check_regressions: bool = False,
extra_container: Optional[Container] = None,
) -> None: ) -> None:
if dryrun: if dryrun:
return None return None
@ -216,6 +221,9 @@ class Regression(Command):
), ),
] ]
if extra_container:
containers.append((ContainerType.extra, extra_container))
if crashes: if crashes:
helper.containers[ helper.containers[
ContainerType.readonly_inputs ContainerType.readonly_inputs

View File

@ -109,6 +109,7 @@ TARGETS: Dict[str, Integration] = {
}, },
reboot_after_setup=True, reboot_after_setup=True,
inject_fake_regression=True, inject_fake_regression=True,
fuzzing_target_options=["--test:{extra}"],
), ),
"linux-libfuzzer-with-options": Integration( "linux-libfuzzer-with-options": Integration(
template=TemplateType.libfuzzer, template=TemplateType.libfuzzer,
@ -180,6 +181,7 @@ TARGETS: Dict[str, Integration] = {
os=OS.linux, os=OS.linux,
target_exe="fuzz_target_1", target_exe="fuzz_target_1",
wait_for_files={ContainerType.unique_reports: 1, ContainerType.coverage: 1}, wait_for_files={ContainerType.unique_reports: 1, ContainerType.coverage: 1},
fuzzing_target_options=["--test:{extra}"],
), ),
"linux-trivial-crash": Integration( "linux-trivial-crash": Integration(
template=TemplateType.radamsa, template=TemplateType.radamsa,
@ -209,6 +211,7 @@ TARGETS: Dict[str, Integration] = {
ContainerType.coverage: 1, ContainerType.coverage: 1,
}, },
inject_fake_regression=True, inject_fake_regression=True,
fuzzing_target_options=["--test:{extra}"],
), ),
"windows-libfuzzer-linked-library": Integration( "windows-libfuzzer-linked-library": Integration(
template=TemplateType.libfuzzer, template=TemplateType.libfuzzer,
@ -575,6 +578,8 @@ class TestOnefuzz:
job: Optional[Job] = None job: Optional[Job] = None
if config.template == TemplateType.libfuzzer: if config.template == TemplateType.libfuzzer:
# building the extra container to test this variable substitution
extra = self.of.containers.create("extra")
job = self.of.template.libfuzzer.basic( job = self.of.template.libfuzzer.basic(
self.project, self.project,
target, target,
@ -588,6 +593,7 @@ class TestOnefuzz:
reboot_after_setup=config.reboot_after_setup or False, reboot_after_setup=config.reboot_after_setup or False,
target_options=config.target_options, target_options=config.target_options,
fuzzing_target_options=config.fuzzing_target_options, fuzzing_target_options=config.fuzzing_target_options,
extra_container=Container(extra.name),
) )
elif config.template == TemplateType.libfuzzer_dotnet: elif config.template == TemplateType.libfuzzer_dotnet:
if setup is None: if setup is None:

View File

@ -228,6 +228,7 @@ class ContainerType(Enum):
unique_reports = "unique_reports" unique_reports = "unique_reports"
regression_reports = "regression_reports" regression_reports = "regression_reports"
logs = "logs" logs = "logs"
extra = "extra"
@classmethod @classmethod
def reset_defaults(cls) -> List["ContainerType"]: def reset_defaults(cls) -> List["ContainerType"]:

View File

@ -408,6 +408,7 @@ class TaskUnitConfig(BaseModel):
unique_inputs: CONTAINER_DEF unique_inputs: CONTAINER_DEF
unique_reports: CONTAINER_DEF unique_reports: CONTAINER_DEF
regression_reports: CONTAINER_DEF regression_reports: CONTAINER_DEF
extra: CONTAINER_DEF
class Forward(BaseModel): class Forward(BaseModel):