mirror of
https://github.com/microsoft/onefuzz.git
synced 2025-06-16 11:58:09 +00:00
Add dotnet coverage task (#2062)
* checkpoint * some more progress * more progress * More progress * Now it's time to test it * It works locally 🎉 * Attempting clean build * fmt * temporarily stub out macos * missed a few * please be the last one * . * . * . * noop change to unstuck actions * . * . * Fix setup script * Some fixes * It works except for a race condition -- use a directory watcher to fix it * It works end to end! * Execute the commands using tokio's structs and timeout mechanism * It works.... for real this time * Undo timer changes * Cleanup * 🧹 * Fix import * . * PR comments * Fix clippy * Clippy whyyy * Only check dotnet path once * fmt * Fix a couple more comments
This commit is contained in:
@ -33,6 +33,7 @@ The current task types available are:
|
|||||||
* generic_crash_report: use a built-in debugging tool (debugapi or ptrace based)
|
* generic_crash_report: use a built-in debugging tool (debugapi or ptrace based)
|
||||||
to rerun the crashing input, attempting to generate an informational report
|
to rerun the crashing input, attempting to generate an informational report
|
||||||
for each discovered crash
|
for each discovered crash
|
||||||
|
* dotnet_coverage: same as `coverage` but for dotnet
|
||||||
|
|
||||||
Each type of task has a unique set of configuration options available, these
|
Each type of task has a unique set of configuration options available, these
|
||||||
include:
|
include:
|
||||||
|
@ -565,7 +565,8 @@ If webhook is set to have Event Grid message format then the payload will look a
|
|||||||
"generic_merge",
|
"generic_merge",
|
||||||
"generic_generator",
|
"generic_generator",
|
||||||
"generic_crash_report",
|
"generic_crash_report",
|
||||||
"generic_regression"
|
"generic_regression",
|
||||||
|
"dotnet_coverage"
|
||||||
],
|
],
|
||||||
"title": "TaskType"
|
"title": "TaskType"
|
||||||
},
|
},
|
||||||
@ -1204,7 +1205,8 @@ If webhook is set to have Event Grid message format then the payload will look a
|
|||||||
"generic_merge",
|
"generic_merge",
|
||||||
"generic_generator",
|
"generic_generator",
|
||||||
"generic_crash_report",
|
"generic_crash_report",
|
||||||
"generic_regression"
|
"generic_regression",
|
||||||
|
"dotnet_coverage"
|
||||||
],
|
],
|
||||||
"title": "TaskType"
|
"title": "TaskType"
|
||||||
},
|
},
|
||||||
@ -2444,7 +2446,8 @@ If webhook is set to have Event Grid message format then the payload will look a
|
|||||||
"generic_merge",
|
"generic_merge",
|
||||||
"generic_generator",
|
"generic_generator",
|
||||||
"generic_crash_report",
|
"generic_crash_report",
|
||||||
"generic_regression"
|
"generic_regression",
|
||||||
|
"dotnet_coverage"
|
||||||
],
|
],
|
||||||
"title": "TaskType"
|
"title": "TaskType"
|
||||||
},
|
},
|
||||||
@ -3159,7 +3162,8 @@ If webhook is set to have Event Grid message format then the payload will look a
|
|||||||
"generic_merge",
|
"generic_merge",
|
||||||
"generic_generator",
|
"generic_generator",
|
||||||
"generic_crash_report",
|
"generic_crash_report",
|
||||||
"generic_regression"
|
"generic_regression",
|
||||||
|
"dotnet_coverage"
|
||||||
],
|
],
|
||||||
"title": "TaskType"
|
"title": "TaskType"
|
||||||
},
|
},
|
||||||
@ -3665,7 +3669,8 @@ If webhook is set to have Event Grid message format then the payload will look a
|
|||||||
"generic_merge",
|
"generic_merge",
|
||||||
"generic_generator",
|
"generic_generator",
|
||||||
"generic_crash_report",
|
"generic_crash_report",
|
||||||
"generic_regression"
|
"generic_regression",
|
||||||
|
"dotnet_coverage"
|
||||||
],
|
],
|
||||||
"title": "TaskType"
|
"title": "TaskType"
|
||||||
},
|
},
|
||||||
@ -4114,7 +4119,8 @@ If webhook is set to have Event Grid message format then the payload will look a
|
|||||||
"generic_merge",
|
"generic_merge",
|
||||||
"generic_generator",
|
"generic_generator",
|
||||||
"generic_crash_report",
|
"generic_crash_report",
|
||||||
"generic_regression"
|
"generic_regression",
|
||||||
|
"dotnet_coverage"
|
||||||
],
|
],
|
||||||
"title": "TaskType"
|
"title": "TaskType"
|
||||||
},
|
},
|
||||||
@ -4551,7 +4557,8 @@ If webhook is set to have Event Grid message format then the payload will look a
|
|||||||
"generic_merge",
|
"generic_merge",
|
||||||
"generic_generator",
|
"generic_generator",
|
||||||
"generic_crash_report",
|
"generic_crash_report",
|
||||||
"generic_regression"
|
"generic_regression",
|
||||||
|
"dotnet_coverage"
|
||||||
],
|
],
|
||||||
"title": "TaskType"
|
"title": "TaskType"
|
||||||
},
|
},
|
||||||
@ -4987,7 +4994,8 @@ If webhook is set to have Event Grid message format then the payload will look a
|
|||||||
"generic_merge",
|
"generic_merge",
|
||||||
"generic_generator",
|
"generic_generator",
|
||||||
"generic_crash_report",
|
"generic_crash_report",
|
||||||
"generic_regression"
|
"generic_regression",
|
||||||
|
"dotnet_coverage"
|
||||||
],
|
],
|
||||||
"title": "TaskType"
|
"title": "TaskType"
|
||||||
},
|
},
|
||||||
@ -6725,7 +6733,8 @@ If webhook is set to have Event Grid message format then the payload will look a
|
|||||||
"generic_merge",
|
"generic_merge",
|
||||||
"generic_generator",
|
"generic_generator",
|
||||||
"generic_crash_report",
|
"generic_crash_report",
|
||||||
"generic_regression"
|
"generic_regression",
|
||||||
|
"dotnet_coverage"
|
||||||
],
|
],
|
||||||
"title": "TaskType"
|
"title": "TaskType"
|
||||||
},
|
},
|
||||||
|
@ -70,7 +70,8 @@ public enum TaskType {
|
|||||||
GenericMerge,
|
GenericMerge,
|
||||||
GenericGenerator,
|
GenericGenerator,
|
||||||
GenericCrashReport,
|
GenericCrashReport,
|
||||||
GenericRegression
|
GenericRegression,
|
||||||
|
DotnetCoverage,
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum Os {
|
public enum Os {
|
||||||
|
@ -555,5 +555,42 @@ public static class Defs {
|
|||||||
ContainerPermission.Read | ContainerPermission.List
|
ContainerPermission.Read | ContainerPermission.List
|
||||||
),
|
),
|
||||||
})
|
})
|
||||||
} };
|
},
|
||||||
|
{ TaskType.DotnetCoverage ,
|
||||||
|
new TaskDefinition(
|
||||||
|
Features: new[] {
|
||||||
|
TaskFeature.TargetExe,
|
||||||
|
TaskFeature.TargetEnv,
|
||||||
|
TaskFeature.TargetOptions,
|
||||||
|
TaskFeature.TargetTimeout,
|
||||||
|
TaskFeature.CoverageFilter,
|
||||||
|
TaskFeature.TargetMustUseInput,
|
||||||
|
},
|
||||||
|
Vm: new VmDefinition(Compare: Compare.Equal, Value:1),
|
||||||
|
Containers: new [] {
|
||||||
|
new ContainerDefinition(
|
||||||
|
Type:ContainerType.Setup,
|
||||||
|
Compare: Compare.Equal,
|
||||||
|
Value:1,
|
||||||
|
Permissions: ContainerPermission.Read | ContainerPermission.List
|
||||||
|
),
|
||||||
|
new ContainerDefinition(
|
||||||
|
Type:ContainerType.ReadonlyInputs,
|
||||||
|
Compare: Compare.AtLeast,
|
||||||
|
Value:1,
|
||||||
|
Permissions: ContainerPermission.Read | ContainerPermission.List
|
||||||
|
),
|
||||||
|
new ContainerDefinition(
|
||||||
|
Type:ContainerType.Coverage,
|
||||||
|
Compare: Compare.Equal,
|
||||||
|
Value:1,
|
||||||
|
Permissions:
|
||||||
|
ContainerPermission.List |
|
||||||
|
ContainerPermission.Read |
|
||||||
|
ContainerPermission.Write
|
||||||
|
|
||||||
|
)},
|
||||||
|
MonitorQueue: ContainerType.ReadonlyInputs)
|
||||||
|
},
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
// Copyright (c) Microsoft Corporation.
|
// Copyright (c) Microsoft Corporation.
|
||||||
// Licensed under the MIT License.
|
// Licensed under the MIT License.
|
||||||
|
|
||||||
#[cfg(any(target_os = "linux", target_os = "windows"))]
|
|
||||||
use crate::local::coverage;
|
|
||||||
use crate::local::{
|
use crate::local::{
|
||||||
common::add_common_config, generic_analysis, generic_crash_report, generic_generator,
|
common::add_common_config, generic_analysis, generic_crash_report, generic_generator,
|
||||||
libfuzzer, libfuzzer_crash_report, libfuzzer_fuzz, libfuzzer_merge, libfuzzer_regression,
|
libfuzzer, libfuzzer_crash_report, libfuzzer_fuzz, libfuzzer_merge, libfuzzer_regression,
|
||||||
libfuzzer_test_input, radamsa, test_input, tui::TerminalUi,
|
libfuzzer_test_input, radamsa, test_input, tui::TerminalUi,
|
||||||
};
|
};
|
||||||
|
#[cfg(any(target_os = "linux", target_os = "windows"))]
|
||||||
|
use crate::local::{coverage, dotnet_coverage};
|
||||||
use anyhow::{Context, Result};
|
use anyhow::{Context, Result};
|
||||||
use clap::{App, Arg, SubCommand};
|
use clap::{App, Arg, SubCommand};
|
||||||
use crossterm::tty::IsTty;
|
use crossterm::tty::IsTty;
|
||||||
@ -23,6 +23,8 @@ enum Commands {
|
|||||||
Radamsa,
|
Radamsa,
|
||||||
#[cfg(any(target_os = "linux", target_os = "windows"))]
|
#[cfg(any(target_os = "linux", target_os = "windows"))]
|
||||||
Coverage,
|
Coverage,
|
||||||
|
#[cfg(any(target_os = "linux", target_os = "windows"))]
|
||||||
|
DotnetCoverage,
|
||||||
LibfuzzerFuzz,
|
LibfuzzerFuzz,
|
||||||
LibfuzzerMerge,
|
LibfuzzerMerge,
|
||||||
LibfuzzerCrashReport,
|
LibfuzzerCrashReport,
|
||||||
@ -59,6 +61,8 @@ pub async fn run(args: clap::ArgMatches<'static>) -> Result<()> {
|
|||||||
match command {
|
match command {
|
||||||
#[cfg(any(target_os = "linux", target_os = "windows"))]
|
#[cfg(any(target_os = "linux", target_os = "windows"))]
|
||||||
Commands::Coverage => coverage::run(&sub_args, event_sender).await,
|
Commands::Coverage => coverage::run(&sub_args, event_sender).await,
|
||||||
|
#[cfg(any(target_os = "linux", target_os = "windows"))]
|
||||||
|
Commands::DotnetCoverage => dotnet_coverage::run(&sub_args, event_sender).await,
|
||||||
Commands::Radamsa => radamsa::run(&sub_args, event_sender).await,
|
Commands::Radamsa => radamsa::run(&sub_args, event_sender).await,
|
||||||
Commands::LibfuzzerCrashReport => {
|
Commands::LibfuzzerCrashReport => {
|
||||||
libfuzzer_crash_report::run(&sub_args, event_sender).await
|
libfuzzer_crash_report::run(&sub_args, event_sender).await
|
||||||
@ -117,6 +121,8 @@ pub fn args(name: &str) -> App<'static, 'static> {
|
|||||||
let app = match subcommand {
|
let app = match subcommand {
|
||||||
#[cfg(any(target_os = "linux", target_os = "windows"))]
|
#[cfg(any(target_os = "linux", target_os = "windows"))]
|
||||||
Commands::Coverage => coverage::args(subcommand.into()),
|
Commands::Coverage => coverage::args(subcommand.into()),
|
||||||
|
#[cfg(any(target_os = "linux", target_os = "windows"))]
|
||||||
|
Commands::DotnetCoverage => dotnet_coverage::args(subcommand.into()),
|
||||||
Commands::Radamsa => radamsa::args(subcommand.into()),
|
Commands::Radamsa => radamsa::args(subcommand.into()),
|
||||||
Commands::LibfuzzerCrashReport => libfuzzer_crash_report::args(subcommand.into()),
|
Commands::LibfuzzerCrashReport => libfuzzer_crash_report::args(subcommand.into()),
|
||||||
Commands::LibfuzzerFuzz => libfuzzer_fuzz::args(subcommand.into()),
|
Commands::LibfuzzerFuzz => libfuzzer_fuzz::args(subcommand.into()),
|
||||||
|
@ -29,6 +29,8 @@ pub const CHECK_RETRY_COUNT: &str = "check_retry_count";
|
|||||||
pub const DISABLE_CHECK_QUEUE: &str = "disable_check_queue";
|
pub const DISABLE_CHECK_QUEUE: &str = "disable_check_queue";
|
||||||
pub const UNIQUE_REPORTS_DIR: &str = "unique_reports_dir";
|
pub const UNIQUE_REPORTS_DIR: &str = "unique_reports_dir";
|
||||||
pub const COVERAGE_DIR: &str = "coverage_dir";
|
pub const COVERAGE_DIR: &str = "coverage_dir";
|
||||||
|
#[cfg(any(target_os = "linux", target_os = "windows"))]
|
||||||
|
pub const DOTNET_COVERAGE_DIR: &str = "dotnet_coverage_dir";
|
||||||
pub const READONLY_INPUTS: &str = "readonly_inputs_dir";
|
pub const READONLY_INPUTS: &str = "readonly_inputs_dir";
|
||||||
pub const CHECK_ASAN_LOG: &str = "check_asan_log";
|
pub const CHECK_ASAN_LOG: &str = "check_asan_log";
|
||||||
pub const TOOLS_DIR: &str = "tools_dir";
|
pub const TOOLS_DIR: &str = "tools_dir";
|
||||||
|
124
src/agent/onefuzz-task/src/local/dotnet_coverage.rs
Normal file
124
src/agent/onefuzz-task/src/local/dotnet_coverage.rs
Normal file
@ -0,0 +1,124 @@
|
|||||||
|
// Copyright (c) Microsoft Corporation.
|
||||||
|
// Licensed under the MIT License.
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
local::common::{
|
||||||
|
build_local_context, get_cmd_arg, get_cmd_env, get_cmd_exe, get_synced_dir,
|
||||||
|
get_synced_dirs, CmdType, SyncCountDirMonitor, UiEvent, COVERAGE_DIR, INPUTS_DIR,
|
||||||
|
READONLY_INPUTS, TARGET_ENV, TARGET_EXE, TARGET_OPTIONS, TARGET_TIMEOUT,
|
||||||
|
},
|
||||||
|
tasks::{
|
||||||
|
config::CommonConfig,
|
||||||
|
coverage::dotnet::{Config, DotnetCoverageTask},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
use anyhow::Result;
|
||||||
|
use clap::{App, Arg, SubCommand};
|
||||||
|
use flume::Sender;
|
||||||
|
use storage_queue::QueueClient;
|
||||||
|
|
||||||
|
pub fn build_shared_args(local_job: bool) -> Vec<Arg<'static, 'static>> {
|
||||||
|
let mut args = vec![
|
||||||
|
Arg::with_name(TARGET_EXE)
|
||||||
|
.long(TARGET_EXE)
|
||||||
|
.takes_value(true)
|
||||||
|
.required(true),
|
||||||
|
Arg::with_name(TARGET_ENV)
|
||||||
|
.long(TARGET_ENV)
|
||||||
|
.takes_value(true)
|
||||||
|
.multiple(true),
|
||||||
|
Arg::with_name(TARGET_OPTIONS)
|
||||||
|
.long(TARGET_OPTIONS)
|
||||||
|
.default_value("{input}")
|
||||||
|
.takes_value(true)
|
||||||
|
.value_delimiter(" ")
|
||||||
|
.help("Use a quoted string with space separation to denote multiple arguments"),
|
||||||
|
Arg::with_name(TARGET_TIMEOUT)
|
||||||
|
.takes_value(true)
|
||||||
|
.long(TARGET_TIMEOUT),
|
||||||
|
Arg::with_name(COVERAGE_DIR)
|
||||||
|
.takes_value(true)
|
||||||
|
.required(!local_job)
|
||||||
|
.long(COVERAGE_DIR),
|
||||||
|
];
|
||||||
|
if local_job {
|
||||||
|
args.push(
|
||||||
|
Arg::with_name(INPUTS_DIR)
|
||||||
|
.long(INPUTS_DIR)
|
||||||
|
.takes_value(true)
|
||||||
|
.required(true),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
args.push(
|
||||||
|
Arg::with_name(READONLY_INPUTS)
|
||||||
|
.takes_value(true)
|
||||||
|
.required(true)
|
||||||
|
.long(READONLY_INPUTS)
|
||||||
|
.multiple(true),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
args
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn build_coverage_config(
|
||||||
|
args: &clap::ArgMatches<'_>,
|
||||||
|
local_job: bool,
|
||||||
|
input_queue: Option<QueueClient>,
|
||||||
|
common: CommonConfig,
|
||||||
|
event_sender: Option<Sender<UiEvent>>,
|
||||||
|
) -> Result<Config> {
|
||||||
|
let target_exe = get_cmd_exe(CmdType::Target, args)?.into();
|
||||||
|
let target_env = get_cmd_env(CmdType::Target, args)?;
|
||||||
|
let target_options = get_cmd_arg(CmdType::Target, args);
|
||||||
|
let target_timeout = value_t!(args, TARGET_TIMEOUT, u64).ok();
|
||||||
|
|
||||||
|
let readonly_inputs = if local_job {
|
||||||
|
info!("Took inputs_dir");
|
||||||
|
vec![
|
||||||
|
get_synced_dir(INPUTS_DIR, common.job_id, common.task_id, args)?
|
||||||
|
.monitor_count(&event_sender)?,
|
||||||
|
]
|
||||||
|
} else {
|
||||||
|
get_synced_dirs(READONLY_INPUTS, common.job_id, common.task_id, args)?
|
||||||
|
.into_iter()
|
||||||
|
.map(|sd| sd.monitor_count(&event_sender))
|
||||||
|
.collect::<Result<Vec<_>>>()?
|
||||||
|
};
|
||||||
|
|
||||||
|
let coverage = get_synced_dir(COVERAGE_DIR, common.job_id, common.task_id, args)?
|
||||||
|
.monitor_count(&event_sender)?;
|
||||||
|
|
||||||
|
let config = Config {
|
||||||
|
target_exe,
|
||||||
|
target_env,
|
||||||
|
target_options,
|
||||||
|
target_timeout,
|
||||||
|
input_queue,
|
||||||
|
readonly_inputs,
|
||||||
|
coverage,
|
||||||
|
common,
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(config)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn run(args: &clap::ArgMatches<'_>, event_sender: Option<Sender<UiEvent>>) -> Result<()> {
|
||||||
|
let context = build_local_context(args, true, event_sender.clone())?;
|
||||||
|
let config = build_coverage_config(
|
||||||
|
args,
|
||||||
|
false,
|
||||||
|
None,
|
||||||
|
context.common_config.clone(),
|
||||||
|
event_sender,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let mut task = DotnetCoverageTask::new(config);
|
||||||
|
task.run().await
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn args(name: &'static str) -> App<'static, 'static> {
|
||||||
|
SubCommand::with_name(name)
|
||||||
|
.about("execute a local-only coverage task")
|
||||||
|
.args(&build_shared_args(false))
|
||||||
|
}
|
@ -1,14 +1,6 @@
|
|||||||
// Copyright (c) Microsoft Corporation.
|
// Copyright (c) Microsoft Corporation.
|
||||||
// Licensed under the MIT License.
|
// Licensed under the MIT License.
|
||||||
|
|
||||||
#[cfg(any(target_os = "linux", target_os = "windows"))]
|
|
||||||
use crate::{
|
|
||||||
local::{
|
|
||||||
common::COVERAGE_DIR,
|
|
||||||
coverage::{build_coverage_config, build_shared_args as build_coverage_args},
|
|
||||||
},
|
|
||||||
tasks::coverage::generic::CoverageTask,
|
|
||||||
};
|
|
||||||
use crate::{
|
use crate::{
|
||||||
local::{
|
local::{
|
||||||
common::{
|
common::{
|
||||||
@ -28,6 +20,17 @@ use crate::{
|
|||||||
report::libfuzzer_report::ReportTask,
|
report::libfuzzer_report::ReportTask,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
#[cfg(any(target_os = "linux", target_os = "windows"))]
|
||||||
|
use crate::{
|
||||||
|
local::{
|
||||||
|
common::{COVERAGE_DIR, DOTNET_COVERAGE_DIR},
|
||||||
|
coverage,
|
||||||
|
coverage::build_shared_args as build_coverage_args,
|
||||||
|
dotnet_coverage,
|
||||||
|
},
|
||||||
|
tasks::coverage::dotnet::DotnetCoverageTask,
|
||||||
|
tasks::coverage::generic::CoverageTask,
|
||||||
|
};
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use clap::{App, SubCommand};
|
use clap::{App, SubCommand};
|
||||||
use flume::Sender;
|
use flume::Sender;
|
||||||
@ -75,11 +78,33 @@ pub async fn run(args: &clap::ArgMatches<'_>, event_sender: Option<Sender<UiEven
|
|||||||
task_handles.push(crash_report_input_monitor.handle);
|
task_handles.push(crash_report_input_monitor.handle);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(any(target_os = "linux", target_os = "windows"))]
|
||||||
|
if args.is_present(DOTNET_COVERAGE_DIR) {
|
||||||
|
let coverage_input_monitor =
|
||||||
|
DirectoryMonitorQueue::start_monitoring(crash_dir.clone()).await?;
|
||||||
|
let dotnet_coverage_config = dotnet_coverage::build_coverage_config(
|
||||||
|
args,
|
||||||
|
true,
|
||||||
|
Some(coverage_input_monitor.queue_client),
|
||||||
|
CommonConfig {
|
||||||
|
task_id: Uuid::new_v4(),
|
||||||
|
..context.common_config.clone()
|
||||||
|
},
|
||||||
|
event_sender.clone(),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let mut dotnet_coverage = DotnetCoverageTask::new(dotnet_coverage_config);
|
||||||
|
let dotnet_coverage_task = spawn(async move { dotnet_coverage.run().await });
|
||||||
|
|
||||||
|
task_handles.push(dotnet_coverage_task);
|
||||||
|
task_handles.push(coverage_input_monitor.handle);
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(any(target_os = "linux", target_os = "windows"))]
|
#[cfg(any(target_os = "linux", target_os = "windows"))]
|
||||||
if args.is_present(COVERAGE_DIR) {
|
if args.is_present(COVERAGE_DIR) {
|
||||||
let coverage_input_monitor =
|
let coverage_input_monitor =
|
||||||
DirectoryMonitorQueue::start_monitoring(crash_dir.clone()).await?;
|
DirectoryMonitorQueue::start_monitoring(crash_dir.clone()).await?;
|
||||||
let coverage_config = build_coverage_config(
|
let coverage_config = coverage::build_coverage_config(
|
||||||
args,
|
args,
|
||||||
true,
|
true,
|
||||||
Some(coverage_input_monitor.queue_client),
|
Some(coverage_input_monitor.queue_client),
|
||||||
|
@ -5,6 +5,8 @@ pub mod cmd;
|
|||||||
pub mod common;
|
pub mod common;
|
||||||
#[cfg(any(target_os = "linux", target_os = "windows"))]
|
#[cfg(any(target_os = "linux", target_os = "windows"))]
|
||||||
pub mod coverage;
|
pub mod coverage;
|
||||||
|
#[cfg(any(target_os = "linux", target_os = "windows"))]
|
||||||
|
pub mod dotnet_coverage;
|
||||||
pub mod generic_analysis;
|
pub mod generic_analysis;
|
||||||
pub mod generic_crash_report;
|
pub mod generic_crash_report;
|
||||||
pub mod generic_generator;
|
pub mod generic_generator;
|
||||||
|
@ -83,6 +83,10 @@ pub enum Config {
|
|||||||
#[serde(alias = "coverage")]
|
#[serde(alias = "coverage")]
|
||||||
Coverage(coverage::generic::Config),
|
Coverage(coverage::generic::Config),
|
||||||
|
|
||||||
|
#[cfg(any(target_os = "linux", target_os = "windows"))]
|
||||||
|
#[serde(alias = "dotnet_coverage")]
|
||||||
|
DotnetCoverage(coverage::dotnet::Config),
|
||||||
|
|
||||||
#[serde(alias = "libfuzzer_fuzz")]
|
#[serde(alias = "libfuzzer_fuzz")]
|
||||||
LibFuzzerFuzz(fuzz::libfuzzer_fuzz::Config),
|
LibFuzzerFuzz(fuzz::libfuzzer_fuzz::Config),
|
||||||
|
|
||||||
@ -130,6 +134,8 @@ impl Config {
|
|||||||
match self {
|
match self {
|
||||||
#[cfg(any(target_os = "linux", target_os = "windows"))]
|
#[cfg(any(target_os = "linux", target_os = "windows"))]
|
||||||
Config::Coverage(c) => &mut c.common,
|
Config::Coverage(c) => &mut c.common,
|
||||||
|
#[cfg(any(target_os = "linux", target_os = "windows"))]
|
||||||
|
Config::DotnetCoverage(c) => &mut c.common,
|
||||||
Config::LibFuzzerFuzz(c) => &mut c.common,
|
Config::LibFuzzerFuzz(c) => &mut c.common,
|
||||||
Config::LibFuzzerMerge(c) => &mut c.common,
|
Config::LibFuzzerMerge(c) => &mut c.common,
|
||||||
Config::LibFuzzerReport(c) => &mut c.common,
|
Config::LibFuzzerReport(c) => &mut c.common,
|
||||||
@ -147,6 +153,8 @@ impl Config {
|
|||||||
match self {
|
match self {
|
||||||
#[cfg(any(target_os = "linux", target_os = "windows"))]
|
#[cfg(any(target_os = "linux", target_os = "windows"))]
|
||||||
Config::Coverage(c) => &c.common,
|
Config::Coverage(c) => &c.common,
|
||||||
|
#[cfg(any(target_os = "linux", target_os = "windows"))]
|
||||||
|
Config::DotnetCoverage(c) => &c.common,
|
||||||
Config::LibFuzzerFuzz(c) => &c.common,
|
Config::LibFuzzerFuzz(c) => &c.common,
|
||||||
Config::LibFuzzerMerge(c) => &c.common,
|
Config::LibFuzzerMerge(c) => &c.common,
|
||||||
Config::LibFuzzerReport(c) => &c.common,
|
Config::LibFuzzerReport(c) => &c.common,
|
||||||
@ -164,6 +172,8 @@ impl Config {
|
|||||||
let event_type = match self {
|
let event_type = match self {
|
||||||
#[cfg(any(target_os = "linux", target_os = "windows"))]
|
#[cfg(any(target_os = "linux", target_os = "windows"))]
|
||||||
Config::Coverage(_) => "coverage",
|
Config::Coverage(_) => "coverage",
|
||||||
|
#[cfg(any(target_os = "linux", target_os = "windows"))]
|
||||||
|
Config::DotnetCoverage(_) => "dotnet_coverage",
|
||||||
Config::LibFuzzerFuzz(_) => "libfuzzer_fuzz",
|
Config::LibFuzzerFuzz(_) => "libfuzzer_fuzz",
|
||||||
Config::LibFuzzerMerge(_) => "libfuzzer_merge",
|
Config::LibFuzzerMerge(_) => "libfuzzer_merge",
|
||||||
Config::LibFuzzerReport(_) => "libfuzzer_crash_report",
|
Config::LibFuzzerReport(_) => "libfuzzer_crash_report",
|
||||||
@ -208,6 +218,12 @@ impl Config {
|
|||||||
match self {
|
match self {
|
||||||
#[cfg(any(target_os = "linux", target_os = "windows"))]
|
#[cfg(any(target_os = "linux", target_os = "windows"))]
|
||||||
Config::Coverage(config) => coverage::generic::CoverageTask::new(config).run().await,
|
Config::Coverage(config) => coverage::generic::CoverageTask::new(config).run().await,
|
||||||
|
#[cfg(any(target_os = "linux", target_os = "windows"))]
|
||||||
|
Config::DotnetCoverage(config) => {
|
||||||
|
coverage::dotnet::DotnetCoverageTask::new(config)
|
||||||
|
.run()
|
||||||
|
.await
|
||||||
|
}
|
||||||
Config::LibFuzzerFuzz(config) => {
|
Config::LibFuzzerFuzz(config) => {
|
||||||
fuzz::libfuzzer_fuzz::LibFuzzerFuzzTask::new(config)?
|
fuzz::libfuzzer_fuzz::LibFuzzerFuzzTask::new(config)?
|
||||||
.run()
|
.run()
|
||||||
|
421
src/agent/onefuzz-task/src/tasks/coverage/dotnet.rs
Normal file
421
src/agent/onefuzz-task/src/tasks/coverage/dotnet.rs
Normal file
@ -0,0 +1,421 @@
|
|||||||
|
// Copyright (c) Microsoft Corporation.
|
||||||
|
// Licensed under the MIT License.
|
||||||
|
|
||||||
|
use anyhow::{Context, Result};
|
||||||
|
use async_trait::async_trait;
|
||||||
|
use onefuzz::{
|
||||||
|
expand::{Expand, PlaceHolder},
|
||||||
|
monitor::DirectoryMonitor,
|
||||||
|
syncdir::SyncedDir,
|
||||||
|
};
|
||||||
|
use reqwest::Url;
|
||||||
|
use std::{
|
||||||
|
collections::HashMap,
|
||||||
|
path::{Path, PathBuf},
|
||||||
|
process::Stdio,
|
||||||
|
time::Duration,
|
||||||
|
};
|
||||||
|
use std::{env, process::ExitStatus};
|
||||||
|
use storage_queue::{Message, QueueClient};
|
||||||
|
use tokio::{fs, process::Command, time::timeout};
|
||||||
|
use tokio_stream::wrappers::ReadDirStream;
|
||||||
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
use crate::tasks::{
|
||||||
|
config::CommonConfig,
|
||||||
|
coverage::COBERTURA_COVERAGE_FILE,
|
||||||
|
generic::input_poller::{CallbackImpl, InputPoller, Processor},
|
||||||
|
heartbeat::{HeartbeatSender, TaskHeartbeatClient},
|
||||||
|
};
|
||||||
|
|
||||||
|
const MAX_COVERAGE_RECORDING_ATTEMPTS: usize = 2;
|
||||||
|
const DEFAULT_TARGET_TIMEOUT: Duration = Duration::from_secs(5);
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
pub struct Config {
|
||||||
|
pub target_exe: PathBuf,
|
||||||
|
pub target_env: HashMap<String, String>,
|
||||||
|
pub target_options: Vec<String>,
|
||||||
|
pub target_timeout: Option<u64>,
|
||||||
|
|
||||||
|
pub input_queue: Option<QueueClient>,
|
||||||
|
pub readonly_inputs: Vec<SyncedDir>,
|
||||||
|
pub coverage: SyncedDir,
|
||||||
|
|
||||||
|
#[serde(flatten)]
|
||||||
|
pub common: CommonConfig,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Config {
|
||||||
|
pub fn timeout(&self) -> Duration {
|
||||||
|
self.target_timeout
|
||||||
|
.map(Duration::from_secs)
|
||||||
|
.unwrap_or(DEFAULT_TARGET_TIMEOUT)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct DotnetCoverageTask {
|
||||||
|
config: Config,
|
||||||
|
poller: InputPoller<Message>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DotnetCoverageTask {
|
||||||
|
pub fn new(config: Config) -> Self {
|
||||||
|
let poller = InputPoller::new("dotnet_coverage");
|
||||||
|
Self { config, poller }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn run(&mut self) -> Result<()> {
|
||||||
|
info!("starting dotnet_coverage task");
|
||||||
|
self.config.coverage.init_pull().await?;
|
||||||
|
|
||||||
|
let dotnet_path = dotnet_path()?;
|
||||||
|
let dotnet_coverage_path = dotnet_coverage_path()?;
|
||||||
|
|
||||||
|
let heartbeat = self.config.common.init_heartbeat(None).await?;
|
||||||
|
let mut context = TaskContext::new(
|
||||||
|
&self.config,
|
||||||
|
heartbeat,
|
||||||
|
dotnet_path,
|
||||||
|
dotnet_coverage_path.clone(),
|
||||||
|
);
|
||||||
|
|
||||||
|
if !context.uses_input() {
|
||||||
|
bail!("input is not specified on the command line or arguments for the target");
|
||||||
|
}
|
||||||
|
|
||||||
|
context.heartbeat.alive();
|
||||||
|
|
||||||
|
let coverage_local_path = self.config.coverage.local_path.canonicalize()?;
|
||||||
|
let intermediate_files_path = intermediate_coverage_files_path(&coverage_local_path)?;
|
||||||
|
fs::create_dir_all(&intermediate_files_path).await?;
|
||||||
|
let timeout = self.config.timeout();
|
||||||
|
let coverage_dir = self.config.coverage.clone();
|
||||||
|
let dotnet_coverage_path = dotnet_coverage_path;
|
||||||
|
|
||||||
|
tokio::spawn(async move {
|
||||||
|
if let Err(e) = start_directory_monitor(
|
||||||
|
&intermediate_files_path,
|
||||||
|
&coverage_local_path,
|
||||||
|
timeout,
|
||||||
|
coverage_dir,
|
||||||
|
&dotnet_coverage_path,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
error!("Directory monitor failed: {}", e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
for dir in &self.config.readonly_inputs {
|
||||||
|
debug!("recording coverage for {}", dir.local_path.display());
|
||||||
|
|
||||||
|
dir.init_pull().await?;
|
||||||
|
let dir_count = context.record_corpus(&dir.local_path).await?;
|
||||||
|
|
||||||
|
info!(
|
||||||
|
"recorded coverage for {} inputs from {}",
|
||||||
|
dir_count,
|
||||||
|
dir.local_path.display()
|
||||||
|
);
|
||||||
|
|
||||||
|
context.heartbeat.alive();
|
||||||
|
}
|
||||||
|
|
||||||
|
context.heartbeat.alive();
|
||||||
|
|
||||||
|
if let Some(queue) = &self.config.input_queue {
|
||||||
|
info!("polling queue for new coverage inputs");
|
||||||
|
|
||||||
|
let callback = CallbackImpl::new(queue.clone(), context)?;
|
||||||
|
self.poller.run(callback).await?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn start_directory_monitor(
|
||||||
|
intermediate_files_path: &PathBuf,
|
||||||
|
coverage_local_path: &Path,
|
||||||
|
timeout: Duration,
|
||||||
|
coverage_dir: SyncedDir,
|
||||||
|
dotnet_coverage_path: &Path,
|
||||||
|
) -> Result<()> {
|
||||||
|
info!(
|
||||||
|
"Starting dotnet coverage intermediate file directory monitor on {}",
|
||||||
|
intermediate_files_path.to_string_lossy()
|
||||||
|
);
|
||||||
|
let mut monitor = DirectoryMonitor::new(intermediate_files_path).await?;
|
||||||
|
debug!("Started directory monitor, waiting for files");
|
||||||
|
while (monitor.next_file().await?).is_some() {
|
||||||
|
debug!("Found intermediate coverage file");
|
||||||
|
save_and_sync_coverage(
|
||||||
|
coverage_local_path,
|
||||||
|
timeout,
|
||||||
|
coverage_dir.clone(),
|
||||||
|
dotnet_coverage_path,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
info!("Updated and synced coverage");
|
||||||
|
}
|
||||||
|
info!("Shut down directory monitor");
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
struct TaskContext<'a> {
|
||||||
|
config: &'a Config,
|
||||||
|
heartbeat: Option<TaskHeartbeatClient>,
|
||||||
|
dotnet_path: PathBuf,
|
||||||
|
dotnet_coverage_path: PathBuf,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> TaskContext<'a> {
|
||||||
|
pub fn new(
|
||||||
|
config: &'a Config,
|
||||||
|
heartbeat: Option<TaskHeartbeatClient>,
|
||||||
|
dotnet_path: PathBuf,
|
||||||
|
dotnet_coverage_path: PathBuf,
|
||||||
|
) -> Self {
|
||||||
|
Self {
|
||||||
|
config,
|
||||||
|
heartbeat,
|
||||||
|
dotnet_path,
|
||||||
|
dotnet_coverage_path,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
async fn record_corpus(&mut self, dir: &Path) -> Result<usize> {
|
||||||
|
use futures::stream::StreamExt;
|
||||||
|
|
||||||
|
let mut corpus = fs::read_dir(dir)
|
||||||
|
.await
|
||||||
|
.map(ReadDirStream::new)
|
||||||
|
.with_context(|| format!("unable to read corpus directory: {}", dir.display()))?;
|
||||||
|
|
||||||
|
let mut count = 0;
|
||||||
|
|
||||||
|
while let Some(entry) = corpus.next().await {
|
||||||
|
match entry {
|
||||||
|
Ok(entry) => {
|
||||||
|
if entry.file_type().await?.is_file() {
|
||||||
|
self.record_input(&entry.path()).await?;
|
||||||
|
count += 1;
|
||||||
|
} else {
|
||||||
|
warn!("skipping non-file dir entry: {}", entry.path().display());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
error!("{:?}", err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(count)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn record_input(&mut self, input: &Path) -> Result<()> {
|
||||||
|
debug!("recording coverage for {}", input.display());
|
||||||
|
let attempts = MAX_COVERAGE_RECORDING_ATTEMPTS;
|
||||||
|
|
||||||
|
for attempt in 1..=attempts {
|
||||||
|
let result = self.try_record_input(input).await;
|
||||||
|
|
||||||
|
if let Err(err) = &result {
|
||||||
|
// Recording failed, check if we can retry.
|
||||||
|
if attempt < attempts {
|
||||||
|
// We will retry, but warn to capture the error if we succeed.
|
||||||
|
warn!(
|
||||||
|
"error recording coverage for input = {}: {:?}",
|
||||||
|
input.display(),
|
||||||
|
err
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
// Final attempt, do not retry.
|
||||||
|
return result.with_context(|| {
|
||||||
|
format_err!(
|
||||||
|
"failed to record coverage for input = {} after {} attempts",
|
||||||
|
input.display(),
|
||||||
|
attempts
|
||||||
|
)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// We successfully recorded the coverage for `input`, so stop.
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn try_record_input(&self, input: &Path) -> Result<()> {
|
||||||
|
let mut cmd = self.command_for_input(input).await?;
|
||||||
|
let timeout = self.config.timeout();
|
||||||
|
spawn_with_timeout(&mut cmd, timeout).await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn command_for_input(&self, input: &Path) -> Result<Command> {
|
||||||
|
let expand = Expand::new()
|
||||||
|
.machine_id()
|
||||||
|
.await?
|
||||||
|
.input_path(input)
|
||||||
|
.job_id(&self.config.common.job_id)
|
||||||
|
.setup_dir(&self.config.common.setup_dir)
|
||||||
|
.target_exe(&self.config.target_exe)
|
||||||
|
.target_options(&self.config.target_options)
|
||||||
|
.task_id(&self.config.common.task_id);
|
||||||
|
|
||||||
|
let dotnet_coverage_path = &self.dotnet_coverage_path;
|
||||||
|
let dotnet_path = &self.dotnet_path;
|
||||||
|
let id = Uuid::new_v4();
|
||||||
|
let output_file_path =
|
||||||
|
intermediate_coverage_files_path(self.config.coverage.local_path.as_path())?
|
||||||
|
.join(format!("{}.cobertura.xml", id));
|
||||||
|
|
||||||
|
let target_options = expand.evaluate(&self.config.target_options)?;
|
||||||
|
|
||||||
|
let mut cmd = Command::new(dotnet_coverage_path);
|
||||||
|
cmd.arg("collect")
|
||||||
|
.args(["--output-format", "cobertura"])
|
||||||
|
.args(["-o", &output_file_path.to_string_lossy()])
|
||||||
|
.arg(format!(
|
||||||
|
"{} {} -- {}",
|
||||||
|
dotnet_path.to_string_lossy(),
|
||||||
|
self.config.target_exe.canonicalize()?.to_string_lossy(),
|
||||||
|
target_options.join(" ")
|
||||||
|
));
|
||||||
|
|
||||||
|
info!("{:?}", &cmd);
|
||||||
|
|
||||||
|
for (k, v) in &self.config.target_env {
|
||||||
|
cmd.env(k, expand.evaluate_value(v)?);
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd.env_remove("RUST_LOG");
|
||||||
|
cmd.stdin(Stdio::null());
|
||||||
|
cmd.stdout(Stdio::piped());
|
||||||
|
cmd.stderr(Stdio::piped());
|
||||||
|
|
||||||
|
Ok(cmd)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn uses_input(&self) -> bool {
|
||||||
|
let input = PlaceHolder::Input.get_string();
|
||||||
|
|
||||||
|
for entry in &self.config.target_options {
|
||||||
|
if entry.contains(&input) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (k, v) in &self.config.target_env {
|
||||||
|
if k == &input || v.contains(&input) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn save_and_sync_coverage(
|
||||||
|
coverage_local_path: &Path,
|
||||||
|
timeout: Duration,
|
||||||
|
coverage_dir: SyncedDir,
|
||||||
|
dotnet_coverage_path: &Path,
|
||||||
|
) -> Result<()> {
|
||||||
|
info!("Saving and syncing coverage");
|
||||||
|
let mut cmd = command_for_merge(coverage_local_path, dotnet_coverage_path).await?;
|
||||||
|
spawn_with_timeout(&mut cmd, timeout).await?;
|
||||||
|
info!("Pushing coverage");
|
||||||
|
coverage_dir.sync_push().await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn command_for_merge(
|
||||||
|
coverage_local_path: &Path,
|
||||||
|
dotnet_coverage_path: &Path,
|
||||||
|
) -> Result<Command> {
|
||||||
|
let output_file = working_dir(coverage_local_path)?.join(COBERTURA_COVERAGE_FILE);
|
||||||
|
|
||||||
|
let mut cmd = Command::new(dotnet_coverage_path);
|
||||||
|
cmd.arg("merge")
|
||||||
|
.args(["--output-format", "cobertura"])
|
||||||
|
.args(["-o", &output_file.to_string_lossy()])
|
||||||
|
.arg("-r")
|
||||||
|
.arg("--remove-input-files")
|
||||||
|
.arg("*.cobertura.xml")
|
||||||
|
.arg(COBERTURA_COVERAGE_FILE); // This lets us 'fold' any new coverage into the existing coverage file.
|
||||||
|
|
||||||
|
cmd.current_dir(working_dir(coverage_local_path)?);
|
||||||
|
|
||||||
|
info!("{:?}", &cmd);
|
||||||
|
info!("From: {:?}", working_dir(coverage_local_path)?);
|
||||||
|
|
||||||
|
cmd.env_remove("RUST_LOG");
|
||||||
|
cmd.stdin(Stdio::null());
|
||||||
|
cmd.stdout(Stdio::piped());
|
||||||
|
cmd.stderr(Stdio::piped());
|
||||||
|
|
||||||
|
Ok(cmd)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn working_dir(coverage_local_path: &Path) -> Result<PathBuf> {
|
||||||
|
Ok(coverage_local_path.canonicalize()?)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn intermediate_coverage_files_path(coverage_local_path: &Path) -> Result<PathBuf> {
|
||||||
|
Ok(working_dir(coverage_local_path)?.join("intermediate-coverage-files"))
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn spawn_with_timeout(
|
||||||
|
cmd: &mut Command,
|
||||||
|
timeout_after: Duration,
|
||||||
|
) -> Result<ExitStatus, std::io::Error> {
|
||||||
|
cmd.kill_on_drop(true);
|
||||||
|
timeout(timeout_after, cmd.spawn()?.wait()).await?
|
||||||
|
}
|
||||||
|
|
||||||
|
fn dotnet_coverage_path() -> Result<PathBuf> {
|
||||||
|
let tools_dir = env::var("ONEFUZZ_TOOLS")?;
|
||||||
|
#[cfg(target_os = "windows")]
|
||||||
|
let dotnet_coverage_executable = "dotnet-coverage.exe";
|
||||||
|
#[cfg(not(target_os = "windows"))]
|
||||||
|
let dotnet_coverage_executable = "dotnet-coverage";
|
||||||
|
let dotnet_coverage = Path::new(&tools_dir).join(dotnet_coverage_executable);
|
||||||
|
|
||||||
|
Ok(dotnet_coverage)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn dotnet_path() -> Result<PathBuf> {
|
||||||
|
let dotnet_root_dir = env::var("DOTNET_ROOT")?;
|
||||||
|
#[cfg(target_os = "windows")]
|
||||||
|
let dotnet_executable = "dotnet.exe";
|
||||||
|
#[cfg(not(target_os = "windows"))]
|
||||||
|
let dotnet_executable = "dotnet";
|
||||||
|
let dotnet = Path::new(&dotnet_root_dir).join(dotnet_executable); // The dotnet executable
|
||||||
|
|
||||||
|
Ok(dotnet)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl<'a> Processor for TaskContext<'a> {
|
||||||
|
async fn process(&mut self, _url: Option<Url>, input: &Path) -> Result<()> {
|
||||||
|
self.heartbeat.alive();
|
||||||
|
|
||||||
|
self.record_input(input).await?;
|
||||||
|
let coverage_local_path = self.config.coverage.local_path.canonicalize()?;
|
||||||
|
|
||||||
|
save_and_sync_coverage(
|
||||||
|
coverage_local_path.as_path(),
|
||||||
|
self.config.timeout(),
|
||||||
|
self.config.coverage.clone(),
|
||||||
|
&self.dotnet_coverage_path,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
@ -28,10 +28,11 @@ use crate::tasks::config::CommonConfig;
|
|||||||
use crate::tasks::generic::input_poller::{CallbackImpl, InputPoller, Processor};
|
use crate::tasks::generic::input_poller::{CallbackImpl, InputPoller, Processor};
|
||||||
use crate::tasks::heartbeat::{HeartbeatSender, TaskHeartbeatClient};
|
use crate::tasks::heartbeat::{HeartbeatSender, TaskHeartbeatClient};
|
||||||
|
|
||||||
|
use super::COBERTURA_COVERAGE_FILE;
|
||||||
|
|
||||||
const MAX_COVERAGE_RECORDING_ATTEMPTS: usize = 2;
|
const MAX_COVERAGE_RECORDING_ATTEMPTS: usize = 2;
|
||||||
const COVERAGE_FILE: &str = "coverage.json";
|
const COVERAGE_FILE: &str = "coverage.json";
|
||||||
const SOURCE_COVERAGE_FILE: &str = "source-coverage.json";
|
const SOURCE_COVERAGE_FILE: &str = "source-coverage.json";
|
||||||
const COBERTURA_COVERAGE_FILE: &str = "cobertura-coverage.xml";
|
|
||||||
const MODULE_CACHE_FILE: &str = "module-cache.json";
|
const MODULE_CACHE_FILE: &str = "module-cache.json";
|
||||||
|
|
||||||
const DEFAULT_TARGET_TIMEOUT: Duration = Duration::from_secs(5);
|
const DEFAULT_TARGET_TIMEOUT: Duration = Duration::from_secs(5);
|
||||||
|
@ -1,4 +1,7 @@
|
|||||||
// Copyright (c) Microsoft Corporation.
|
// Copyright (c) Microsoft Corporation.
|
||||||
// Licensed under the MIT License.
|
// Licensed under the MIT License.
|
||||||
|
|
||||||
|
const COBERTURA_COVERAGE_FILE: &str = "cobertura-coverage.xml";
|
||||||
|
|
||||||
|
pub mod dotnet;
|
||||||
pub mod generic;
|
pub mod generic;
|
||||||
|
@ -558,4 +558,40 @@ TASK_DEFINITIONS = {
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
TaskType.dotnet_coverage: TaskDefinition(
|
||||||
|
features=[
|
||||||
|
TaskFeature.target_exe,
|
||||||
|
TaskFeature.target_env,
|
||||||
|
TaskFeature.target_options,
|
||||||
|
TaskFeature.target_timeout,
|
||||||
|
TaskFeature.coverage_filter,
|
||||||
|
TaskFeature.target_must_use_input,
|
||||||
|
],
|
||||||
|
vm=VmDefinition(compare=Compare.Equal, value=1),
|
||||||
|
containers=[
|
||||||
|
ContainerDefinition(
|
||||||
|
type=ContainerType.setup,
|
||||||
|
compare=Compare.Equal,
|
||||||
|
value=1,
|
||||||
|
permissions=[ContainerPermission.Read, ContainerPermission.List],
|
||||||
|
),
|
||||||
|
ContainerDefinition(
|
||||||
|
type=ContainerType.readonly_inputs,
|
||||||
|
compare=Compare.AtLeast,
|
||||||
|
value=1,
|
||||||
|
permissions=[ContainerPermission.Read, ContainerPermission.List],
|
||||||
|
),
|
||||||
|
ContainerDefinition(
|
||||||
|
type=ContainerType.coverage,
|
||||||
|
compare=Compare.Equal,
|
||||||
|
value=1,
|
||||||
|
permissions=[
|
||||||
|
ContainerPermission.List,
|
||||||
|
ContainerPermission.Read,
|
||||||
|
ContainerPermission.Write,
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
monitor_queue=ContainerType.readonly_inputs,
|
||||||
|
),
|
||||||
}
|
}
|
||||||
|
@ -165,6 +165,8 @@ class TaskType(Enum):
|
|||||||
generic_crash_report = "generic_crash_report"
|
generic_crash_report = "generic_crash_report"
|
||||||
generic_regression = "generic_regression"
|
generic_regression = "generic_regression"
|
||||||
|
|
||||||
|
dotnet_coverage = "dotnet_coverage"
|
||||||
|
|
||||||
|
|
||||||
class VmState(Enum):
|
class VmState(Enum):
|
||||||
init = "init"
|
init = "init"
|
||||||
|
@ -14,6 +14,7 @@ MANAGED_SETUP="/onefuzz/bin/managed.sh"
|
|||||||
SCALESET_SETUP="/onefuzz/bin/scaleset-setup.sh"
|
SCALESET_SETUP="/onefuzz/bin/scaleset-setup.sh"
|
||||||
DOTNET_VERSION="6.0.300"
|
DOTNET_VERSION="6.0.300"
|
||||||
export DOTNET_ROOT=/onefuzz/tools/dotnet
|
export DOTNET_ROOT=/onefuzz/tools/dotnet
|
||||||
|
export DOTNET_CLI_HOME="$DOTNET_ROOT"
|
||||||
export ONEFUZZ_ROOT=/onefuzz
|
export ONEFUZZ_ROOT=/onefuzz
|
||||||
export LLVM_SYMBOLIZER_PATH=/onefuzz/bin/llvm-symbolizer
|
export LLVM_SYMBOLIZER_PATH=/onefuzz/bin/llvm-symbolizer
|
||||||
|
|
||||||
@ -128,23 +129,24 @@ if type apt > /dev/null 2> /dev/null; then
|
|||||||
|
|
||||||
# Install dotnet
|
# Install dotnet
|
||||||
until sudo apt install -y curl libicu-dev; do
|
until sudo apt install -y curl libicu-dev; do
|
||||||
echo "apt failed, sleeping 10s then retrying"
|
logger "apt failed, sleeping 10s then retrying"
|
||||||
sleep 10
|
sleep 10
|
||||||
done
|
done
|
||||||
|
|
||||||
echo "downloading dotnet install"
|
logger "downloading dotnet install"
|
||||||
curl --retry 10 -sSL https://dot.net/v1/dotnet-install.sh -o dotnet-install.sh > /dev/null
|
curl --retry 10 -sSL https://dot.net/v1/dotnet-install.sh -o dotnet-install.sh 2>&1 | logger -s -i -t 'onefuzz-curl-dotnet-install'
|
||||||
chmod +x dotnet-install.sh
|
chmod +x dotnet-install.sh
|
||||||
|
|
||||||
echo "running dotnet install"
|
logger "running dotnet install"
|
||||||
. ./dotnet-install.sh --version "$DOTNET_VERSION" --install-dir "$DOTNET_ROOT" 2>&1 | logger -s -i -t 'onefuzz-dotnet-setup'
|
/bin/bash ./dotnet-install.sh --version "$DOTNET_VERSION" --install-dir "$DOTNET_ROOT" 2>&1 | logger -s -i -t 'onefuzz-dotnet-setup'
|
||||||
rm dotnet-install.sh
|
rm dotnet-install.sh
|
||||||
|
|
||||||
echo "install dotnet tools"
|
logger "install dotnet tools"
|
||||||
pushd "$DOTNET_ROOT"
|
pushd "$DOTNET_ROOT"
|
||||||
./dotnet tool install dotnet-dump --tool-path /onefuzz/tools
|
ls -lah 2>&1 | logger -s -i -t 'onefuzz-dotnet-tools'
|
||||||
./dotnet tool install dotnet-coverage --tool-path /onefuzz/tools
|
"$DOTNET_ROOT"/dotnet tool install dotnet-dump --version 6.0.328102 --tool-path /onefuzz/tools 2>&1 | logger -s -i -t 'onefuzz-dotnet-tools'
|
||||||
./dotnet tool install dotnet-sos --tool-path /onefuzz/tools
|
"$DOTNET_ROOT"/dotnet tool install dotnet-coverage --version 17.3.6 --tool-path /onefuzz/tools 2>&1 | logger -s -i -t 'onefuzz-dotnet-tools'
|
||||||
|
"$DOTNET_ROOT"/dotnet tool install dotnet-sos --version 6.0.328102 --tool-path /onefuzz/tools 2>&1 | logger -s -i -t 'onefuzz-dotnet-tools'
|
||||||
popd
|
popd
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
@ -181,9 +181,9 @@ function Install-Dotnet([string]$Version, [string]$InstallDir, [string]$ToolsDir
|
|||||||
log "Installing dotnet: done"
|
log "Installing dotnet: done"
|
||||||
log "Installing dotnet tools to ${ToolsDir}"
|
log "Installing dotnet tools to ${ToolsDir}"
|
||||||
Push-Location $InstallDir
|
Push-Location $InstallDir
|
||||||
./dotnet.exe tool install dotnet-dump --tool-path $ToolsDir
|
./dotnet.exe tool install dotnet-dump --version 6.0.328102 --tool-path $ToolsDir
|
||||||
./dotnet.exe tool install dotnet-coverage --tool-path $ToolsDir
|
./dotnet.exe tool install dotnet-coverage --version 17.3.6 --tool-path $ToolsDir
|
||||||
./dotnet.exe tool install dotnet-sos --tool-path $ToolsDir
|
./dotnet.exe tool install dotnet-sos --version 6.0.328102 --tool-path $ToolsDir
|
||||||
Pop-Location
|
Pop-Location
|
||||||
log "Installing dotnet tools: done"
|
log "Installing dotnet tools: done"
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user