diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 2dd85d7c92..99e9bddd32 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -79,16 +79,24 @@ jobs:
key: ${{env.ACTIONS_CACHE_KEY_DATE}} # additional key for cache-busting
workspaces: src/agent
- name: Linux Prereqs
- if: runner.os == 'Linux' && steps.cache-agent-artifacts.outputs.cache-hit != 'true'
+ if: runner.os == 'Linux'
run: |
sudo apt-get -y update
- sudo apt-get -y install libssl-dev libunwind-dev build-essential pkg-config
+ sudo apt-get -y install libssl-dev libunwind-dev build-essential pkg-config clang
+ - name: Clone onefuzz-samples
+ run: git clone https://github.com/microsoft/onefuzz-samples
+ - name: Prepare for agent integration tests
+ shell: bash
+ working-directory: ./onefuzz-samples/examples/simple-libfuzzer
+ run: |
+ make
+ mkdir -p ../../../src/agent/onefuzz-task/tests/targets/simple
+ cp fuzz.exe ../../../src/agent/onefuzz-task/tests/targets/simple/fuzz.exe
+ cp *.pdb ../../../src/agent/onefuzz-task/tests/targets/simple/ 2>/dev/null || :
- name: Install Rust Prereqs
- if: steps.rust-build-cache.outputs.cache-hit != 'true' && steps.cache-agent-artifacts.outputs.cache-hit != 'true'
shell: bash
run: src/ci/rust-prereqs.sh
- run: src/ci/agent.sh
- if: steps.cache-agent-artifacts.outputs.cache-hit != 'true'
shell: bash
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v3
diff --git a/CHANGELOG.md b/CHANGELOG.md
index be4779ad77..f02721fa44 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -7,6 +7,32 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
+## 8.8.0
+
+### Added
+
+* Agent: Added Mariner Linux support for agent VMs [#3306](https://github.com/microsoft/onefuzz/pull/3306)
+* Service: Added support for custom ado fields that mark work items as duplicate [#3467](https://github.com/microsoft/onefuzz/pull/3467)
+* Service: Permanently store OneFuzz job result data - # crashing input, # regression crashing input, etc. - in Azure storage [#3380](https://github.com/microsoft/onefuzz/pull/3380), [#3439](https://github.com/microsoft/onefuzz/pull/3439)
+* Service: Added validation for Iteration/AreaPath on notifications when a job is submitted with a notification config and for `onefuzz debug notification test_template` [#3386](https://github.com/microsoft/onefuzz/pull/3386)
+
+### Changed
+
+* Agent: Updated libfuzzer-fuzz basic template to include required args and make it match cli [#3429](https://github.com/microsoft/onefuzz/pull/3429)
+* Agent: Downgraded some debug logs from warn to debug [#3450](https://github.com/microsoft/onefuzz/pull/3450)
+* CLI: Removed CLI commands from the local fuzzing tasks as they can now be described via yaml template [#3428](https://github.com/microsoft/onefuzz/pull/3428)
+* Service: AutoScale table entries are now deleted on VMSS shutdown [#3455](https://github.com/microsoft/onefuzz/pull/3455)
+
+### Fixed
+
+* Agent: Fixed local path generation [#3432](https://github.com/microsoft/onefuzz/pull/3432), [#3460](https://github.com/microsoft/onefuzz/pull/3460)
+
+## 8.7.1
+
+### Fixed
+
+* Service: Removed deprecated Azure retention policy setting that was causing scaleset deployment errors [#3452](https://github.com/microsoft/onefuzz/pull/3452)
+
## 8.7.0
### Added
diff --git a/CURRENT_VERSION b/CURRENT_VERSION
index c0bcaebe8f..cfc27b4fab 100644
--- a/CURRENT_VERSION
+++ b/CURRENT_VERSION
@@ -1 +1 @@
-8.7.0
\ No newline at end of file
+8.8.0
\ No newline at end of file
diff --git a/README.md b/README.md
index 010148dd3a..486dae6c15 100644
--- a/README.md
+++ b/README.md
@@ -1,5 +1,20 @@
#
+# IMPORTANT NOTICE
+
+**_Since September 2020 when OneFuzz was first open sourced, we’ve been on a journey to create a best-in-class orchestrator for running fuzzers, driving security and quality into our products._**
+
+
+**_Initially launched by a small group in MSR, OneFuzz has now become a significant internal platform within Microsoft. As such, we are regretfully archiving the project to focus our attention on becoming a more deeply integrated service within the company. Unfortunately, we aren’t a large enough team to live in both the open-source world and the internal Microsoft world with its own unique set of requirements._**
+
+**_Our current plan is to archive the project in the next few months. That means we’ll still be making updates for a little while. Of course, even after it’s archived, you’ll still be able to fork it and make the changes you need. Once we’ve decided on a specific date for archiving, we’ll update this readme._**
+
+**_Thanks for taking the journey with us._**
+
+**_The OneFuzz team._**
+
+---
+
[![Onefuzz build status](https://github.com/microsoft/onefuzz/workflows/Build/badge.svg?branch=main)](https://github.com/microsoft/onefuzz/actions/workflows/ci.yml?query=branch%3Amain)
## A self-hosted Fuzzing-As-A-Service platform
diff --git a/contrib/onefuzz-job-azure-devops-pipeline/ado-work-items.json b/contrib/onefuzz-job-azure-devops-pipeline/ado-work-items.json
index eb89fc019d..034d97cf15 100644
--- a/contrib/onefuzz-job-azure-devops-pipeline/ado-work-items.json
+++ b/contrib/onefuzz-job-azure-devops-pipeline/ado-work-items.json
@@ -13,6 +13,10 @@
"System.AreaPath": "OneFuzz-Ado-Integration",
"System.Title": "{{report.task_id}}"
},
+ "ado_duplicate_fields": {
+ "System.Reason": "My custom value that means a work item is a duplicate",
+ "Custom.Work.Item.Field": "My custom value that means a work item is a duplicate"
+ },
"on_duplicate": {
"increment": [],
"comment": "DUP {{report.input_sha256}}
Repro Command:
{{ repro_cmd }}
",
diff --git a/docs/notifications/ado.md b/docs/notifications/ado.md
index 131986afba..09dd5b9072 100644
--- a/docs/notifications/ado.md
+++ b/docs/notifications/ado.md
@@ -51,6 +51,13 @@ clickable, make it a link.
"System.Title": "{{ report.crash_site }} - {{ report.executable }}",
"Microsoft.VSTS.TCM.ReproSteps": "This is my call stack: {{ for item in report.call_stack }} - {{ item }}
{{ end }}
"
},
+ "ado_duplicate_fields": {
+ "System.Reason": "My custom value that means a work item is a duplicate",
+ "Custom.Work.Item.Field": "My custom value that means a work item is a duplicate"
+ // note: the fields and values below are checked by default and don't need to be specified
+ // "System.Reason": "Duplicate"
+ // "Microsoft.VSTS.Common.ResolvedReason": "Duplicate"
+ },
"comment": "This is my comment. {{ report.input_sha256 }} {{ input_url }}
{{ repro_cmd }}
",
"unique_fields": ["System.Title", "System.AreaPath"],
"on_duplicate": {
diff --git a/src/ApiService/ApiService/OneFuzzTypes/Model.cs b/src/ApiService/ApiService/OneFuzzTypes/Model.cs
index b839f52ddc..424669899a 100644
--- a/src/ApiService/ApiService/OneFuzzTypes/Model.cs
+++ b/src/ApiService/ApiService/OneFuzzTypes/Model.cs
@@ -689,6 +689,7 @@ public record AdoTemplate(
List UniqueFields,
Dictionary AdoFields,
ADODuplicateTemplate OnDuplicate,
+ Dictionary? AdoDuplicateFields = null,
string? Comment = null
) : NotificationTemplate {
public async Task Validate() {
@@ -704,8 +705,9 @@ public record RenderedAdoTemplate(
List UniqueFields,
Dictionary AdoFields,
ADODuplicateTemplate OnDuplicate,
+ Dictionary? AdoDuplicateFields = null,
string? Comment = null
- ) : AdoTemplate(BaseUrl, AuthToken, Project, Type, UniqueFields, AdoFields, OnDuplicate, Comment);
+ ) : AdoTemplate(BaseUrl, AuthToken, Project, Type, UniqueFields, AdoFields, OnDuplicate, AdoDuplicateFields, Comment);
public record TeamsTemplate(SecretData Url) : NotificationTemplate {
public Task Validate() {
diff --git a/src/ApiService/ApiService/onefuzzlib/notifications/Ado.cs b/src/ApiService/ApiService/onefuzzlib/notifications/Ado.cs
index e05bb9bc24..3780bc1b2b 100644
--- a/src/ApiService/ApiService/onefuzzlib/notifications/Ado.cs
+++ b/src/ApiService/ApiService/onefuzzlib/notifications/Ado.cs
@@ -291,6 +291,7 @@ public static RenderedAdoTemplate RenderAdoTemplate(ILogger logTracer, Renderer
original.UniqueFields,
adoFields,
onDuplicate,
+ original.AdoDuplicateFields,
original.Comment != null ? Render(renderer, original.Comment, instanceUrl, logTracer) : null
);
}
@@ -535,7 +536,7 @@ public async Async.Task Process(IList<(string, string)> notificationInfo) {
_logTracer.AddTags(new List<(string, string)> { ("MatchingWorkItemIds", $"{workItem.Id}") });
_logTracer.LogInformation("Found matching work item");
}
- if (IsADODuplicateWorkItem(workItem)) {
+ if (IsADODuplicateWorkItem(workItem, _config.AdoDuplicateFields)) {
continue;
}
@@ -575,13 +576,17 @@ public async Async.Task Process(IList<(string, string)> notificationInfo) {
}
}
- private static bool IsADODuplicateWorkItem(WorkItem wi) {
+ private static bool IsADODuplicateWorkItem(WorkItem wi, Dictionary? duplicateFields) {
// A work item could have System.State == Resolve && System.Reason == Duplicate
// OR it could have System.State == Closed && System.Reason == Duplicate
// I haven't found any other combinations where System.Reason could be duplicate but just to be safe
// we're explicitly _not_ checking the state of the work item to determine if it's duplicate
return wi.Fields.ContainsKey("System.Reason") && string.Equals(wi.Fields["System.Reason"].ToString(), "Duplicate", StringComparison.OrdinalIgnoreCase)
|| wi.Fields.ContainsKey("Microsoft.VSTS.Common.ResolvedReason") && string.Equals(wi.Fields["Microsoft.VSTS.Common.ResolvedReason"].ToString(), "Duplicate", StringComparison.OrdinalIgnoreCase)
+ || duplicateFields?.Any(fieldPair => {
+ var (field, value) = fieldPair;
+ return wi.Fields.ContainsKey(field) && string.Equals(wi.Fields[field].ToString(), value, StringComparison.OrdinalIgnoreCase);
+ }) == true
// Alternatively, the work item can also specify a 'relation' to another work item.
// This is typically used to create parent/child relationships between work items but can also
// Be used to mark duplicates so we should check this as well.
diff --git a/src/ApiService/IntegrationTests/JinjaToScribanMigrationTests.cs b/src/ApiService/IntegrationTests/JinjaToScribanMigrationTests.cs
index 0ae3b11cb5..4033a05369 100644
--- a/src/ApiService/IntegrationTests/JinjaToScribanMigrationTests.cs
+++ b/src/ApiService/IntegrationTests/JinjaToScribanMigrationTests.cs
@@ -111,6 +111,7 @@ public async Async.Task OptionalFieldsAreSupported() {
},
"{{ if org }} blah {{ end }}"
),
+ null,
"{{ if org }} blah {{ end }}"
);
@@ -137,6 +138,7 @@ public async Async.Task All_ADO_Fields_Are_Migrated() {
},
"{% if org %} comment {% endif %}"
),
+ null,
"{% if org %} comment {% endif %}"
);
diff --git a/src/ApiService/Tests/OrmModelsTest.cs b/src/ApiService/Tests/OrmModelsTest.cs
index 1aa7d2d163..956d0c30c5 100644
--- a/src/ApiService/Tests/OrmModelsTest.cs
+++ b/src/ApiService/Tests/OrmModelsTest.cs
@@ -232,6 +232,7 @@ from authToken in Arb.Generate>()
from str in Arb.Generate()
from fields in Arb.Generate>()
from adoFields in Arb.Generate>()
+ from adoDuplicateFields in Arb.Generate>()
from dupeTemplate in Arb.Generate()
select new AdoTemplate(
baseUrl,
@@ -241,6 +242,7 @@ from dupeTemplate in Arb.Generate()
fields,
adoFields,
dupeTemplate,
+ adoDuplicateFields,
str.Get));
public static Arbitrary ArbTeamsTemplate()
diff --git a/src/agent/Cargo.lock b/src/agent/Cargo.lock
index 254684be97..f395e93408 100644
--- a/src/agent/Cargo.lock
+++ b/src/agent/Cargo.lock
@@ -14,7 +14,7 @@ version = "0.20.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f4fa78e18c64fce05e902adecd7a5eed15a5e0a3439f7b0e169f0252214865e3"
dependencies = [
- "gimli",
+ "gimli 0.27.3",
]
[[package]]
@@ -60,16 +60,15 @@ dependencies = [
[[package]]
name = "anstream"
-version = "0.3.2"
+version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0ca84f3628370c59db74ee214b3263d58f9aadd9b4fe7e711fd87dc452b7f163"
+checksum = "b1f58811cfac344940f1a400b6e6231ce35171f614f26439e80f8c1465c5cc0c"
dependencies = [
"anstyle",
"anstyle-parse",
"anstyle-query",
"anstyle-wincon",
"colorchoice",
- "is-terminal",
"utf8parse",
]
@@ -99,9 +98,9 @@ dependencies = [
[[package]]
name = "anstyle-wincon"
-version = "1.0.2"
+version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c677ab05e09154296dd37acecd46420c17b9713e8366facafa8fc0885167cf4c"
+checksum = "58f54d10c6dfa51283a066ceab3ec1ab78d13fae00aa49243a45e4571fb79dfd"
dependencies = [
"anstyle",
"windows-sys 0.48.0",
@@ -200,9 +199,9 @@ checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
[[package]]
name = "azure_core"
-version = "0.13.0"
+version = "0.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "86b0f0eea648347e40f5f7f7e6bfea4553bcefad0fbf52044ea339e5ce3aba61"
+checksum = "2331555a3618a32516c6172a63e9fec4af0edb43c6fcfeb5303a0716fc34498b"
dependencies = [
"async-trait",
"base64 0.21.2",
@@ -214,7 +213,7 @@ dependencies = [
"log",
"paste",
"pin-project",
- "quick-xml 0.29.0",
+ "quick-xml 0.30.0 (registry+https://github.com/rust-lang/crates.io-index)",
"rand 0.8.5",
"reqwest",
"rustc_version",
@@ -227,9 +226,9 @@ dependencies = [
[[package]]
name = "azure_storage"
-version = "0.13.0"
+version = "0.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "32d9cfa13ed9acb51cd663e04f343bd550a92b455add96c90de387a9a6bc4dbc"
+checksum = "16565073e533053f4e29e6b139de2af758e984108a1cddbb1a432387e7f4474d"
dependencies = [
"RustyXML",
"async-trait",
@@ -250,9 +249,9 @@ dependencies = [
[[package]]
name = "azure_storage_blobs"
-version = "0.13.1"
+version = "0.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "57cb0fe58af32a3fb49e560613cb1e4937f9f13161a2c1caf1bba0224435f2af"
+checksum = "0900e63940d1ba51039efda3d8cf658157a1c75449081a6e18069d2588809329"
dependencies = [
"RustyXML",
"azure_core",
@@ -397,9 +396,9 @@ checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610"
[[package]]
name = "bytes"
-version = "1.4.0"
+version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be"
+checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223"
dependencies = [
"serde",
]
@@ -465,33 +464,31 @@ dependencies = [
[[package]]
name = "clap"
-version = "4.3.21"
+version = "4.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c27cdf28c0f604ba3f512b0c9a409f8de8513e4816705deb0498b627e7c3a3fd"
+checksum = "6a13b88d2c62ff462f88e4a121f17a82c1af05693a2f192b5c38d14de73c19f6"
dependencies = [
"clap_builder",
"clap_derive",
- "once_cell",
]
[[package]]
name = "clap_builder"
-version = "4.3.21"
+version = "4.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "08a9f1ab5e9f01a9b81f202e8562eb9a10de70abf9eaeac1be465c28b75aa4aa"
+checksum = "2bb9faaa7c2ef94b2743a21f5a29e6f0010dff4caa69ac8e9d6cf8b6fa74da08"
dependencies = [
"anstream",
"anstyle",
"clap_lex",
- "once_cell",
"strsim",
]
[[package]]
name = "clap_derive"
-version = "4.3.12"
+version = "4.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "54a9bb5758fc5dfe728d1019941681eccaf0cf8a4189b692a0ee2f2ecf90a050"
+checksum = "0862016ff20d69b84ef8247369fabf5c008a7417002411897d40ee1f4532b873"
dependencies = [
"heck",
"proc-macro2 1.0.66",
@@ -510,7 +507,9 @@ name = "cobertura"
version = "0.1.0"
dependencies = [
"anyhow",
- "quick-xml 0.30.0",
+ "async-trait",
+ "quick-xml 0.30.0 (git+https://github.com/tafia/quick-xml/)",
+ "tokio",
]
[[package]]
@@ -580,6 +579,7 @@ dependencies = [
"symbolic",
"tempfile",
"thiserror",
+ "tokio",
]
[[package]]
@@ -749,7 +749,7 @@ dependencies = [
"anyhow",
"clap",
"elsa",
- "gimli",
+ "gimli 0.28.0",
"goblin",
"iced-x86",
"log",
@@ -863,7 +863,7 @@ dependencies = [
"regex",
"thiserror",
"windows",
- "winreg 0.50.0",
+ "winreg 0.51.0",
]
[[package]]
@@ -883,9 +883,9 @@ dependencies = [
[[package]]
name = "elsa"
-version = "1.8.1"
+version = "1.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b5e0aca8dce8856e420195bd13b6a64de3334235ccc9214e824b86b12bf26283"
+checksum = "714f766f3556b44e7e4776ad133fcc3445a489517c25c704ace411bb14790194"
dependencies = [
"stable_deref_trait",
]
@@ -968,6 +968,12 @@ version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7"
+[[package]]
+name = "fallible-iterator"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649"
+
[[package]]
name = "fastrand"
version = "1.9.0"
@@ -1257,8 +1263,18 @@ version = "0.27.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6c80984affa11d98d1b88b66ac8853f143217b399d3c74116778ff8fdb4ed2e"
dependencies = [
- "fallible-iterator",
- "indexmap 1.9.3",
+ "fallible-iterator 0.2.0",
+ "stable_deref_trait",
+]
+
+[[package]]
+name = "gimli"
+version = "0.28.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6fb8d784f27acf97159b40fc4db5ecd8aa23b9ad5ef69cdd136d3bc80665f0c0"
+dependencies = [
+ "fallible-iterator 0.3.0",
+ "indexmap 2.0.0",
"stable_deref_trait",
]
@@ -2035,18 +2051,19 @@ dependencies = [
[[package]]
name = "notify"
-version = "6.0.1"
+version = "6.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5738a2795d57ea20abec2d6d76c6081186709c0024187cd5977265eda6598b51"
+checksum = "6205bd8bb1e454ad2e27422015fb5e4f2bcc7e08fa8f27058670d208324a4d2d"
dependencies = [
- "bitflags 1.3.2",
+ "bitflags 2.3.3",
"filetime",
"inotify",
"kqueue",
"libc",
+ "log",
"mio 0.8.8",
"walkdir",
- "windows-sys 0.45.0",
+ "windows-sys 0.48.0",
]
[[package]]
@@ -2151,7 +2168,7 @@ dependencies = [
"urlparse",
"uuid",
"windows",
- "winreg 0.50.0",
+ "winreg 0.51.0",
]
[[package]]
@@ -2193,7 +2210,7 @@ dependencies = [
"coverage",
"debuggable-module",
"pretty_assertions",
- "quick-xml 0.30.0",
+ "quick-xml 0.30.0 (registry+https://github.com/rust-lang/crates.io-index)",
"serde",
"serde_json",
]
@@ -2403,7 +2420,7 @@ version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "82040a392923abe6279c00ab4aff62d5250d1c8555dc780e4b02783a7aa74863"
dependencies = [
- "fallible-iterator",
+ "fallible-iterator 0.2.0",
"scroll",
"uuid",
]
@@ -2436,9 +2453,9 @@ checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94"
[[package]]
name = "pete"
-version = "0.10.0"
+version = "0.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "229eb6b3cb0d3d075727c614687ab08384cac3b75fa100e1e08b30d7bee39d00"
+checksum = "0f09c1c1ad40df294ff8643fe88a3dc64fff3293b6bc0ed9f71aff71f7086cbd"
dependencies = [
"libc",
"memoffset 0.8.0",
@@ -2589,9 +2606,9 @@ dependencies = [
[[package]]
name = "quick-xml"
-version = "0.29.0"
+version = "0.30.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "81b9228215d82c7b61490fec1de287136b5de6f5700f6e58ea9ad61a7964ca51"
+checksum = "eff6510e86862b57b210fd8cbe8ed3f0d7d600b9c2863cd4549a2e033c66e956"
dependencies = [
"memchr",
"serde",
@@ -2600,11 +2617,10 @@ dependencies = [
[[package]]
name = "quick-xml"
version = "0.30.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "eff6510e86862b57b210fd8cbe8ed3f0d7d600b9c2863cd4549a2e033c66e956"
+source = "git+https://github.com/tafia/quick-xml/#efb2dc00eba4b0502b10fb481225b13dab41447e"
dependencies = [
"memchr",
- "serde",
+ "tokio",
]
[[package]]
@@ -3278,7 +3294,7 @@ dependencies = [
"flume",
"num_cpus",
"queue-file",
- "quick-xml 0.30.0",
+ "quick-xml 0.30.0 (registry+https://github.com/rust-lang/crates.io-index)",
"regex",
"reqwest",
"reqwest-retry",
@@ -3368,9 +3384,9 @@ dependencies = [
"dmsort",
"elementtree",
"elsa",
- "fallible-iterator",
+ "fallible-iterator 0.2.0",
"flate2",
- "gimli",
+ "gimli 0.27.3",
"goblin",
"lazy_static",
"nom",
@@ -3468,9 +3484,9 @@ dependencies = [
[[package]]
name = "tempfile"
-version = "3.7.1"
+version = "3.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dc02fddf48964c42031a0b3fe0428320ecf3a73c401040fc0096f97794310651"
+checksum = "cb94d2f3cc536af71caac6b6fcebf65860b347e7ce0cc9ebe8f70d3e521054ef"
dependencies = [
"cfg-if 1.0.0",
"fastrand 2.0.0",
@@ -3555,9 +3571,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
[[package]]
name = "tokio"
-version = "1.30.0"
+version = "1.32.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2d3ce25f50619af8b0aec2eb23deebe84249e19e2ddd393a6e16e3300a6dadfd"
+checksum = "17ed6077ed6cd6c74735e21f37eb16dc3935f96878b1fe961074089cc80893f9"
dependencies = [
"backtrace",
"bytes",
@@ -3950,7 +3966,7 @@ dependencies = [
"os_pipe",
"tempfile",
"windows",
- "winreg 0.50.0",
+ "winreg 0.51.0",
]
[[package]]
@@ -4148,9 +4164,9 @@ dependencies = [
[[package]]
name = "winreg"
-version = "0.50.0"
+version = "0.51.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1"
+checksum = "937f3df7948156640f46aacef17a70db0de5917bda9c92b0f751f3a955b588fc"
dependencies = [
"cfg-if 1.0.0",
"windows-sys 0.48.0",
diff --git a/src/agent/cobertura/Cargo.toml b/src/agent/cobertura/Cargo.toml
index be57083057..ea121ad1b9 100644
--- a/src/agent/cobertura/Cargo.toml
+++ b/src/agent/cobertura/Cargo.toml
@@ -6,4 +6,8 @@ license = "MIT"
[dependencies]
anyhow = "1.0"
-quick-xml = "0.30"
+quick-xml = { git = "https://github.com/tafia/quick-xml/", features = [
+ "async-tokio",
+] }
+tokio = "1"
+async-trait = "0.1"
diff --git a/src/agent/cobertura/src/lib.rs b/src/agent/cobertura/src/lib.rs
index e919e08733..328617344d 100644
--- a/src/agent/cobertura/src/lib.rs
+++ b/src/agent/cobertura/src/lib.rs
@@ -3,7 +3,9 @@
use std::io::{Cursor, Write};
+use async_trait::async_trait;
use quick_xml::{Result, Writer};
+use tokio::io::AsyncWrite;
impl CoberturaCoverage {
pub fn to_string(&self) -> anyhow::Result {
@@ -28,6 +30,18 @@ pub trait WriteXml {
}
}
+#[async_trait]
+pub trait WriteXmlAsync {
+ async fn _write_xml_async(
+ &self,
+ writer: &mut Writer,
+ ) -> Result<()>;
+ async fn write_xml_async(&self, writer: W) -> Result<()> {
+ let mut writer = Writer::new(writer);
+ self._write_xml_async(&mut writer).await
+ }
+}
+
// Only write optional fields if present.
impl WriteXml for Option
where
@@ -42,6 +56,23 @@ where
}
}
+#[async_trait]
+impl WriteXmlAsync for Option
+where
+ T: WriteXmlAsync + Sync + Send + Unpin,
+{
+ async fn _write_xml_async(
+ &self,
+ writer: &mut Writer,
+ ) -> Result<()> {
+ if let Some(value) = self {
+ value._write_xml_async(writer).await?;
+ }
+
+ Ok(())
+ }
+}
+
impl WriteXml for Vec
where
T: WriteXml,
@@ -55,6 +86,23 @@ where
}
}
+#[async_trait]
+impl WriteXmlAsync for Vec
+where
+ T: WriteXmlAsync + Sync + Send + Unpin,
+{
+ async fn _write_xml_async(
+ &self,
+ writer: &mut Writer,
+ ) -> Result<()> {
+ for value in self {
+ value._write_xml_async(writer).await?;
+ }
+
+ Ok(())
+ }
+}
+
macro_rules! float {
($val: expr) => {
format!("{:.02}", $val).as_str()
@@ -131,6 +179,37 @@ impl WriteXml for CoberturaCoverage {
}
}
+#[async_trait]
+impl WriteXmlAsync for CoberturaCoverage {
+ async fn _write_xml_async(
+ &self,
+ writer: &mut Writer,
+ ) -> Result<()> {
+ writer
+ .create_element("coverage")
+ .with_attributes([
+ ("line-rate", float!(self.line_rate)),
+ ("branch-rate", float!(self.branch_rate)),
+ ("lines-covered", uint!(self.lines_covered)),
+ ("lines-valid", uint!(self.lines_valid)),
+ ("branches-covered", uint!(self.branches_covered)),
+ ("branches-valid", uint!(self.branches_valid)),
+ ("complexity", uint!(self.complexity)),
+ ("version", string!(self.version)),
+ ("timestamp", uint!(self.timestamp)),
+ ])
+ .write_inner_content_async(|w| async {
+ self.sources._write_xml_async(w).await?;
+ self.packages._write_xml_async(w).await?;
+
+ Ok(w)
+ })
+ .await?;
+
+ Ok(())
+ }
+}
+
//
#[derive(Clone, Debug, Default)]
pub struct Sources {
@@ -147,6 +226,24 @@ impl WriteXml for Sources {
}
}
+#[async_trait]
+impl WriteXmlAsync for Sources {
+ async fn _write_xml_async(
+ &self,
+ writer: &mut Writer,
+ ) -> Result<()> {
+ writer
+ .create_element("sources")
+ .write_inner_content_async(|w| async {
+ self.sources._write_xml_async(w).await?;
+ Ok(w)
+ })
+ .await?;
+
+ Ok(())
+ }
+}
+
//
#[derive(Clone, Debug, Default)]
pub struct Source {
@@ -164,6 +261,22 @@ impl WriteXml for Source {
}
}
+#[async_trait]
+impl WriteXmlAsync for Source {
+ async fn _write_xml_async(
+ &self,
+ writer: &mut Writer,
+ ) -> Result<()> {
+ writer
+ .create_element("source")
+ .with_attributes([("path", string!(self.path))])
+ .write_empty_async()
+ .await?;
+
+ Ok(())
+ }
+}
+
//
#[derive(Clone, Debug, Default)]
pub struct Packages {
@@ -180,6 +293,24 @@ impl WriteXml for Packages {
}
}
+#[async_trait]
+impl WriteXmlAsync for Packages {
+ async fn _write_xml_async(
+ &self,
+ writer: &mut Writer,
+ ) -> Result<()> {
+ writer
+ .create_element("packages")
+ .write_inner_content_async(|w| async {
+ self.packages._write_xml_async(w).await?;
+ Ok(w)
+ })
+ .await?;
+
+ Ok(())
+ }
+}
+
//
//
//
@@ -211,6 +342,30 @@ impl WriteXml for Package {
}
}
+#[async_trait]
+impl WriteXmlAsync for Package {
+ async fn _write_xml_async(
+ &self,
+ writer: &mut Writer,
+ ) -> Result<()> {
+ writer
+ .create_element("package")
+ .with_attributes([
+ ("name", string!(self.name)),
+ ("line-rate", float!(self.line_rate)),
+ ("branch-rate", float!(self.branch_rate)),
+ ("complexity", uint!(self.complexity)),
+ ])
+ .write_inner_content_async(|w| async {
+ self.classes._write_xml_async(w).await?;
+ Ok(w)
+ })
+ .await?;
+
+ Ok(())
+ }
+}
+
//
#[derive(Clone, Debug, Default)]
pub struct Classes {
@@ -227,6 +382,24 @@ impl WriteXml for Classes {
}
}
+#[async_trait]
+impl WriteXmlAsync for Classes {
+ async fn _write_xml_async(
+ &self,
+ writer: &mut Writer,
+ ) -> Result<()> {
+ writer
+ .create_element("classes")
+ .write_inner_content_async(|w| async {
+ self.classes._write_xml_async(w).await?;
+ Ok(w)
+ })
+ .await?;
+
+ Ok(())
+ }
+}
+
//
//
//
@@ -266,6 +439,32 @@ impl WriteXml for Class {
}
}
+#[async_trait]
+impl WriteXmlAsync for Class {
+ async fn _write_xml_async(
+ &self,
+ writer: &mut Writer,
+ ) -> Result<()> {
+ writer
+ .create_element("class")
+ .with_attributes([
+ ("name", string!(self.name)),
+ ("filename", string!(self.filename)),
+ ("line-rate", float!(self.line_rate)),
+ ("branch-rate", float!(self.branch_rate)),
+ ("complexity", uint!(self.complexity)),
+ ])
+ .write_inner_content_async(|w| async {
+ self.methods._write_xml_async(w).await?;
+ self.lines._write_xml_async(w).await?;
+ Ok(w)
+ })
+ .await?;
+
+ Ok(())
+ }
+}
+
//
#[derive(Clone, Debug, Default)]
pub struct Methods {
@@ -282,6 +481,24 @@ impl WriteXml for Methods {
}
}
+#[async_trait]
+impl WriteXmlAsync for Methods {
+ async fn _write_xml_async(
+ &self,
+ writer: &mut Writer,
+ ) -> Result<()> {
+ writer
+ .create_element("methods")
+ .write_inner_content_async(|w| async {
+ self.methods._write_xml_async(w).await?;
+ Ok(w)
+ })
+ .await?;
+
+ Ok(())
+ }
+}
+
//
//
//
@@ -313,6 +530,30 @@ impl WriteXml for Method {
}
}
+#[async_trait]
+impl WriteXmlAsync for Method {
+ async fn _write_xml_async(
+ &self,
+ writer: &mut Writer,
+ ) -> Result<()> {
+ writer
+ .create_element("method")
+ .with_attributes([
+ ("name", string!(self.name)),
+ ("signature", string!(self.signature)),
+ ("line-rate", float!(self.line_rate)),
+ ("branch-rate", float!(self.branch_rate)),
+ ])
+ .write_inner_content_async(|w| async {
+ self.lines._write_xml_async(w).await?;
+ Ok(w)
+ })
+ .await?;
+
+ Ok(())
+ }
+}
+
//
#[derive(Clone, Debug, Default)]
pub struct Lines {
@@ -329,6 +570,24 @@ impl WriteXml for Lines {
}
}
+#[async_trait]
+impl WriteXmlAsync for Lines {
+ async fn _write_xml_async(
+ &self,
+ writer: &mut Writer,
+ ) -> Result<()> {
+ writer
+ .create_element("lines")
+ .write_inner_content_async(|w| async {
+ self.lines._write_xml_async(w).await?;
+ Ok(w)
+ })
+ .await?;
+
+ Ok(())
+ }
+}
+
//
//
//
@@ -366,6 +625,36 @@ impl WriteXml for Line {
}
}
+#[async_trait]
+impl WriteXmlAsync for Line {
+ async fn _write_xml_async(
+ &self,
+ writer: &mut Writer,
+ ) -> Result<()> {
+ let condition_coverage = if let Some(s) = &self.condition_coverage {
+ s.as_str()
+ } else {
+ "100%"
+ };
+
+ writer
+ .create_element("line")
+ .with_attributes([
+ ("number", uint!(self.number)),
+ ("hits", uint!(self.hits)),
+ ("branch", boolean!(self.branch.unwrap_or_default())),
+ ("condition-coverage", condition_coverage),
+ ])
+ .write_inner_content_async(|w| async {
+ self.conditions._write_xml_async(w).await?;
+ Ok(w)
+ })
+ .await?;
+
+ Ok(())
+ }
+}
+
//
#[derive(Clone, Debug, Default)]
pub struct Conditions {
@@ -382,6 +671,23 @@ impl WriteXml for Conditions {
}
}
+#[async_trait]
+impl WriteXmlAsync for Conditions {
+ async fn _write_xml_async(
+ &self,
+ writer: &mut Writer,
+ ) -> Result<()> {
+ writer
+ .create_element("conditions")
+ .write_inner_content_async(|w| async {
+ self.conditions._write_xml_async(w).await?;
+ Ok(w)
+ })
+ .await?;
+
+ Ok(())
+ }
+}
//
//
//
@@ -407,3 +713,23 @@ impl WriteXml for Condition {
Ok(())
}
}
+
+#[async_trait]
+impl WriteXmlAsync for Condition {
+ async fn _write_xml_async(
+ &self,
+ writer: &mut Writer,
+ ) -> Result<()> {
+ writer
+ .create_element("condition")
+ .with_attributes([
+ ("number", uint!(self.number)),
+ ("type", uint!(self.r#type)),
+ ("coverage", uint!(self.coverage)),
+ ])
+ .write_empty_async()
+ .await?;
+
+ Ok(())
+ }
+}
diff --git a/src/agent/coverage/Cargo.toml b/src/agent/coverage/Cargo.toml
index 29e67523d9..2da324de08 100644
--- a/src/agent/coverage/Cargo.toml
+++ b/src/agent/coverage/Cargo.toml
@@ -26,17 +26,18 @@ debugger = { path = "../debugger" }
[target.'cfg(target_os = "linux")'.dependencies]
nix = "0.26"
-pete = "0.10"
+pete = "0.12"
# For procfs, opt out of the `chrono` freature; it pulls in an old version
# of `time`. We do not use the methods that the `chrono` feature enables.
procfs = { version = "0.15.1", default-features = false, features = ["flate2"] }
[dev-dependencies]
-clap = { version = "4.3", features = ["derive"] }
+clap = { version = "4.4", features = ["derive"] }
env_logger = "0.10.0"
pretty_assertions = "1.4.0"
insta = { version = "1.31.0", features = ["glob"] }
coverage = { path = "../coverage" }
cc = "1.0"
-tempfile = "3.7.0"
+tempfile = "3.8.0"
dunce = "1.0"
+tokio = { version = "1", features = ["full"] }
diff --git a/src/agent/coverage/examples/cobertura.rs b/src/agent/coverage/examples/cobertura.rs
index 2ba5ec2f6a..c783223175 100644
--- a/src/agent/coverage/examples/cobertura.rs
+++ b/src/agent/coverage/examples/cobertura.rs
@@ -8,11 +8,11 @@ use coverage::source::{Count, FileCoverage, Line, SourceCoverage};
use debuggable_module::path::FilePath;
fn main() -> Result<()> {
- println!("{}", generate_output()?);
+ println!("{}", generate_output()?.to_string()?);
Ok(())
}
-fn generate_output() -> Result {
+fn generate_output() -> Result {
let modoff = vec![
(r"/missing/lib.c", vec![1, 2, 3, 5, 8]),
(
@@ -40,19 +40,24 @@ fn generate_output() -> Result {
coverage.files.insert(file_path, file);
}
- CoberturaCoverage::from(&coverage).to_string()
+ Ok(CoberturaCoverage::from(&coverage))
}
#[cfg(test)]
mod test {
+ use std::io::Write;
+
use super::*;
+ use cobertura::WriteXml;
+ use cobertura::WriteXmlAsync;
use pretty_assertions::assert_eq;
+ use tokio::{self, io::AsyncWriteExt};
#[test]
// On Windows this produces different output due to filename parsing.
#[cfg(target_os = "linux")]
pub fn check_output() {
- let result = generate_output().unwrap();
+ let result = generate_output().unwrap().to_string().unwrap();
let expected = r#"
@@ -223,4 +228,28 @@ mod test {
assert_eq!(expected, result);
}
+
+ #[tokio::test]
+ async fn sync_and_async_are_identical() {
+ let mut sync_data = Vec::new();
+ let mut sync_writer = std::io::BufWriter::new(&mut sync_data);
+ generate_output()
+ .unwrap()
+ .write_xml(&mut sync_writer)
+ .unwrap();
+ sync_writer.flush();
+
+ let mut async_data = Vec::new();
+ let mut async_writer = tokio::io::BufWriter::new(&mut async_data);
+ generate_output()
+ .unwrap()
+ .write_xml_async(&mut async_writer)
+ .await
+ .unwrap();
+ async_writer.flush().await;
+
+ drop(sync_writer);
+ drop(async_writer);
+ assert_eq!(&sync_data, &async_data);
+ }
}
diff --git a/src/agent/coverage/src/record/linux/debugger.rs b/src/agent/coverage/src/record/linux/debugger.rs
index e2502e8d2e..c4512c4fbb 100644
--- a/src/agent/coverage/src/record/linux/debugger.rs
+++ b/src/agent/coverage/src/record/linux/debugger.rs
@@ -4,6 +4,7 @@
use std::collections::BTreeMap;
use std::io::Read;
use std::process::{Child, Command};
+use std::time::Duration;
use anyhow::{bail, format_err, Result};
use debuggable_module::path::FilePath;
@@ -75,7 +76,11 @@ impl<'eh> Debugger<'eh> {
// These calls should also be unnecessary no-ops, but we really want to avoid any dangling
// or zombie child processes.
let _ = child.kill();
- let _ = child.wait();
+
+ // We don't need to call child.wait() because of the following series of events:
+ // 1. pete, our ptracing library, spawns the child process with ptrace flags
+ // 2. rust stdlib set SIG_IGN as the SIGCHLD handler: https://github.com/rust-lang/rust/issues/110317
+ // 3. linux kernel automatically reaps pids when the above 2 hold: https://github.com/torvalds/linux/blob/44149752e9987a9eac5ad78e6d3a20934b5e018d/kernel/signal.c#L2089-L2110
let output = Output {
status,
@@ -198,8 +203,8 @@ impl DebuggerContext {
pub fn new() -> Self {
let breakpoints = Breakpoints::default();
let images = None;
- let tracer = Ptracer::new();
-
+ let mut tracer = Ptracer::new();
+ *tracer.poll_delay_mut() = Duration::from_millis(1);
Self {
breakpoints,
images,
diff --git a/src/agent/debuggable-module/Cargo.toml b/src/agent/debuggable-module/Cargo.toml
index 1cd11dfd30..a227432830 100644
--- a/src/agent/debuggable-module/Cargo.toml
+++ b/src/agent/debuggable-module/Cargo.toml
@@ -6,8 +6,8 @@ license = "MIT"
[dependencies]
anyhow = "1.0"
-elsa = "1.8.1"
-gimli = "0.27.2"
+elsa = "1.9.0"
+gimli = "0.28.0"
goblin = "0.6"
iced-x86 = "1.20"
log = "0.4.17"
@@ -21,4 +21,4 @@ symbolic = { version = "12.3", features = [
thiserror = "1.0"
[dev-dependencies]
-clap = { version = "4.3", features = ["derive"] }
+clap = { version = "4.4", features = ["derive"] }
diff --git a/src/agent/dynamic-library/Cargo.toml b/src/agent/dynamic-library/Cargo.toml
index 604d221700..ad3cc482d2 100644
--- a/src/agent/dynamic-library/Cargo.toml
+++ b/src/agent/dynamic-library/Cargo.toml
@@ -6,14 +6,14 @@ license = "MIT"
[dependencies]
anyhow = "1.0"
-clap = { version = "4.3.0", features = ["derive"] }
+clap = { version = "4.4.2", features = ["derive"] }
lazy_static = "1.4"
regex = "1.9"
thiserror = "1.0"
[target.'cfg(windows)'.dependencies]
debugger = { path = "../debugger" }
-winreg = "0.50"
+winreg = "0.51"
[dependencies.windows]
version = "0.48"
diff --git a/src/agent/onefuzz-agent/Cargo.toml b/src/agent/onefuzz-agent/Cargo.toml
index 5ce8669766..bc73d37973 100644
--- a/src/agent/onefuzz-agent/Cargo.toml
+++ b/src/agent/onefuzz-agent/Cargo.toml
@@ -22,7 +22,7 @@ reqwest = { version = "0.11", features = [
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
storage-queue = { path = "../storage-queue" }
-tokio = { version = "1.29", features = ["full"] }
+tokio = { version = "1.32", features = ["full"] }
url = { version = "2.4", features = ["serde"] }
uuid = { version = "1.4", features = ["serde", "v4"] }
clap = { version = "4", features = ["derive", "cargo"] }
@@ -31,13 +31,13 @@ onefuzz-telemetry = { path = "../onefuzz-telemetry" }
backtrace = "0.3"
ipc-channel = { git = "https://github.com/servo/ipc-channel", rev = "7f432aa" }
dynamic-library = { path = "../dynamic-library" }
-azure_core = { version = "0.13", default-features = false, features = [
+azure_core = { version = "0.14", default-features = false, features = [
"enable_reqwest",
] }
-azure_storage = { version = "0.13", default-features = false, features = [
+azure_storage = { version = "0.14", default-features = false, features = [
"enable_reqwest",
] }
-azure_storage_blobs = { version = "0.13", default-features = false, features = [
+azure_storage_blobs = { version = "0.14", default-features = false, features = [
"enable_reqwest",
] }
diff --git a/src/agent/onefuzz-task/Cargo.toml b/src/agent/onefuzz-task/Cargo.toml
index 4e0bd381b0..d5588a58e6 100644
--- a/src/agent/onefuzz-task/Cargo.toml
+++ b/src/agent/onefuzz-task/Cargo.toml
@@ -6,6 +6,14 @@ edition = "2021"
publish = false
license = "MIT"
+[lib]
+path = "src/lib.rs"
+name = "onefuzz_task_lib"
+
+[[bin]]
+path = "src/main.rs"
+name = "onefuzz-task"
+
[features]
integration_test = []
@@ -46,9 +54,9 @@ strum = "0.25"
strum_macros = "0.25"
stacktrace-parser = { path = "../stacktrace-parser" }
storage-queue = { path = "../storage-queue" }
-tempfile = "3.7.0"
+tempfile = "3.8.0"
thiserror = "1.0"
-tokio = { version = "1.29", features = ["full"] }
+tokio = { version = "1.32", features = ["full"] }
tokio-util = { version = "0.7", features = ["full"] }
tokio-stream = "0.1"
tui = { package = "ratatui", version = "0.22.0", default-features = false, features = [
@@ -62,13 +70,13 @@ chrono = { version = "0.4", default-features = false, features = [
] }
ipc-channel = { git = "https://github.com/servo/ipc-channel", rev = "7f432aa" }
-azure_core = { version = "0.13", default-features = false, features = [
+azure_core = { version = "0.14", default-features = false, features = [
"enable_reqwest",
] }
-azure_storage = { version = "0.13", default-features = false, features = [
+azure_storage = { version = "0.14", default-features = false, features = [
"enable_reqwest",
] }
-azure_storage_blobs = { version = "0.13", default-features = false, features = [
+azure_storage_blobs = { version = "0.14", default-features = false, features = [
"enable_reqwest",
] }
@@ -77,3 +85,4 @@ schemars = { version = "0.8.12", features = ["uuid1"] }
[dev-dependencies]
pretty_assertions = "1.4"
+tempfile = "3.8"
diff --git a/src/agent/onefuzz-task/src/lib.rs b/src/agent/onefuzz-task/src/lib.rs
new file mode 100644
index 0000000000..997eea549d
--- /dev/null
+++ b/src/agent/onefuzz-task/src/lib.rs
@@ -0,0 +1,9 @@
+#[macro_use]
+extern crate anyhow;
+#[macro_use]
+extern crate clap;
+#[macro_use]
+extern crate onefuzz_telemetry;
+
+pub mod local;
+pub mod tasks;
diff --git a/src/agent/onefuzz-task/src/tasks/coverage/generic.rs b/src/agent/onefuzz-task/src/tasks/coverage/generic.rs
index 4fde9efb31..a915271bb8 100644
--- a/src/agent/onefuzz-task/src/tasks/coverage/generic.rs
+++ b/src/agent/onefuzz-task/src/tasks/coverage/generic.rs
@@ -10,7 +10,7 @@ use std::time::Duration;
use anyhow::{bail, Context, Result};
use async_trait::async_trait;
-use cobertura::{CoberturaCoverage, WriteXml};
+use cobertura::{CoberturaCoverage, WriteXmlAsync};
use coverage::allowlist::AllowList;
use coverage::binary::{BinaryCoverage, DebugInfoCache};
use coverage::record::CoverageRecorder;
@@ -161,7 +161,7 @@ impl CoverageTask {
}
if seen_inputs {
- context.report_coverage_stats().await?;
+ context.report_coverage_stats().await;
context.save_and_sync_coverage().await?;
}
@@ -454,7 +454,7 @@ impl<'a> TaskContext<'a> {
Ok(count)
}
- pub async fn report_coverage_stats(&self) -> Result<()> {
+ pub async fn report_coverage_stats(&self) {
use EventData::*;
let coverage = RwLock::read(&self.coverage).await;
@@ -471,7 +471,6 @@ impl<'a> TaskContext<'a> {
]),
)
.await;
- Ok(())
}
pub async fn save_coverage(
@@ -525,11 +524,13 @@ impl<'a> TaskContext<'a> {
async fn save_cobertura_xml(source: &SourceCoverage, path: &Path) -> Result<(), anyhow::Error> {
let cobertura = CoberturaCoverage::from(source);
- let cobertura_coverage_file = std::fs::File::create(path)
+ let cobertura_coverage_file = tokio::fs::File::create(path)
+ .await
.with_context(|| format!("creating cobertura coverage file {}", path.display()))?;
- let cobertura_coverage_file_writer = std::io::BufWriter::new(cobertura_coverage_file);
+ let cobertura_coverage_file_writer = tokio::io::BufWriter::new(cobertura_coverage_file);
cobertura
- .write_xml(cobertura_coverage_file_writer)
+ .write_xml_async(cobertura_coverage_file_writer)
+ .await
.with_context(|| format!("serializing cobertura coverage to {}", path.display()))?;
Ok(())
}
@@ -565,7 +566,7 @@ impl<'a> Processor for TaskContext<'a> {
self.heartbeat.alive();
self.record_input(input).await?;
- self.report_coverage_stats().await?;
+ self.report_coverage_stats().await;
self.save_and_sync_coverage().await?;
Ok(())
diff --git a/src/agent/onefuzz-task/src/tasks/fuzz/libfuzzer/common.rs b/src/agent/onefuzz-task/src/tasks/fuzz/libfuzzer/common.rs
index bfd9f3f5cc..32f3372958 100644
--- a/src/agent/onefuzz-task/src/tasks/fuzz/libfuzzer/common.rs
+++ b/src/agent/onefuzz-task/src/tasks/fuzz/libfuzzer/common.rs
@@ -272,7 +272,7 @@ where
info!("config is: {:?}", self.config);
let fuzzer = L::from_config(&self.config).await?;
- let mut running = fuzzer.fuzz(crash_dir.path(), local_inputs, &inputs).await?;
+ let mut running = fuzzer.fuzz(crash_dir.path(), local_inputs, &inputs)?;
info!("child is: {:?}", running);
diff --git a/src/agent/onefuzz-task/tests/template_integration.rs b/src/agent/onefuzz-task/tests/template_integration.rs
new file mode 100644
index 0000000000..d0e68e5d02
--- /dev/null
+++ b/src/agent/onefuzz-task/tests/template_integration.rs
@@ -0,0 +1,212 @@
+use std::{
+ collections::HashSet,
+ ffi::OsStr,
+ path::{Path, PathBuf},
+};
+
+use tokio::fs;
+
+use anyhow::Result;
+use log::info;
+use onefuzz_task_lib::local::template;
+use std::time::Duration;
+use tokio::time::timeout;
+
+macro_rules! libfuzzer_tests {
+ ($($name:ident: $value:expr,)*) => {
+ $(
+ #[tokio::test(flavor = "multi_thread")]
+ #[cfg_attr(not(feature = "integration_test"), ignore)]
+ async fn $name() {
+ let _ = env_logger::builder().is_test(true).try_init();
+ let (config, libfuzzer_target) = $value;
+ test_libfuzzer_basic_template(PathBuf::from(config), PathBuf::from(libfuzzer_target)).await;
+ }
+ )*
+ }
+}
+
+// This is the format for adding other templates/targets for this macro
+// $TEST_NAME: ($RELATIVE_PATH_TO_TEMPLATE, $RELATIVE_PATH_TO_TARGET),
+// Make sure that you place the target binary in CI
+libfuzzer_tests! {
+ libfuzzer_basic: ("./tests/templates/libfuzzer_basic.yml", "./tests/targets/simple/fuzz.exe"),
+}
+
+async fn test_libfuzzer_basic_template(config: PathBuf, libfuzzer_target: PathBuf) {
+ assert_exists_and_is_file(&config).await;
+ assert_exists_and_is_file(&libfuzzer_target).await;
+
+ let test_layout = create_test_directory(&config, &libfuzzer_target)
+ .await
+ .expect("Failed to create test directory layout");
+
+ info!("Executed test from: {:?}", &test_layout.root);
+ info!("Running template for 1 minute...");
+ if let Ok(template_result) = timeout(
+ Duration::from_secs(60),
+ template::launch(&test_layout.config, None),
+ )
+ .await
+ {
+ // Something went wrong when running the template so lets print out the template to be helpful
+ info!("Printing config as it was used in the test:");
+ info!("{:?}", fs::read_to_string(&test_layout.config).await);
+ template_result.unwrap();
+ }
+
+ verify_test_layout_structure_did_not_change(&test_layout).await;
+ assert_directory_is_not_empty(&test_layout.inputs).await;
+ assert_directory_is_not_empty(&test_layout.crashes).await;
+ verify_coverage_dir(&test_layout.coverage).await;
+
+ let _ = fs::remove_dir_all(&test_layout.root).await;
+}
+
+async fn verify_test_layout_structure_did_not_change(test_layout: &TestLayout) {
+ assert_exists_and_is_dir(&test_layout.root).await;
+ assert_exists_and_is_file(&test_layout.config).await;
+ assert_exists_and_is_file(&test_layout.target_exe).await;
+ assert_exists_and_is_dir(&test_layout.crashdumps).await;
+ assert_exists_and_is_dir(&test_layout.coverage).await;
+ assert_exists_and_is_dir(&test_layout.crashes).await;
+ assert_exists_and_is_dir(&test_layout.inputs).await;
+ assert_exists_and_is_dir(&test_layout.regression_reports).await;
+}
+
+async fn verify_coverage_dir(coverage: &Path) {
+ warn_if_empty(coverage).await;
+}
+
+async fn assert_exists_and_is_dir(dir: &Path) {
+ assert!(dir.exists(), "Expected directory to exist. dir = {:?}", dir);
+ assert!(
+ dir.is_dir(),
+ "Expected path to be a directory. dir = {:?}",
+ dir
+ );
+}
+
+async fn warn_if_empty(dir: &Path) {
+ if dir_is_empty(dir).await {
+ println!("Expected directory to not be empty: {:?}", dir);
+ }
+}
+
+async fn assert_exists_and_is_file(file: &Path) {
+ assert!(file.exists(), "Expected file to exist. file = {:?}", file);
+ assert!(
+ file.is_file(),
+ "Expected path to be a file. file = {:?}",
+ file
+ );
+}
+
+async fn dir_is_empty(dir: &Path) -> bool {
+ fs::read_dir(dir)
+ .await
+ .unwrap_or_else(|_| panic!("Failed to list files in directory. dir = {:?}", dir))
+ .next_entry()
+ .await
+ .unwrap_or_else(|_| {
+ panic!(
+ "Failed to get next file in directory listing. dir = {:?}",
+ dir
+ )
+ })
+ .is_some()
+}
+
+async fn assert_directory_is_not_empty(dir: &Path) {
+ assert!(
+ dir_is_empty(dir).await,
+ "Expected directory to not be empty. dir = {:?}",
+ dir
+ );
+}
+
+async fn create_test_directory(config: &Path, target_exe: &Path) -> Result {
+ let mut test_directory = PathBuf::from(".").join(uuid::Uuid::new_v4().to_string());
+ fs::create_dir_all(&test_directory).await?;
+ test_directory = test_directory.canonicalize()?;
+
+ let mut inputs_directory = PathBuf::from(&test_directory).join("inputs");
+ fs::create_dir(&inputs_directory).await?;
+ inputs_directory = inputs_directory.canonicalize()?;
+
+ let mut crashes_directory = PathBuf::from(&test_directory).join("crashes");
+ fs::create_dir(&crashes_directory).await?;
+ crashes_directory = crashes_directory.canonicalize()?;
+
+ let mut crashdumps_directory = PathBuf::from(&test_directory).join("crashdumps");
+ fs::create_dir(&crashdumps_directory).await?;
+ crashdumps_directory = crashdumps_directory.canonicalize()?;
+
+ let mut coverage_directory = PathBuf::from(&test_directory).join("coverage");
+ fs::create_dir(&coverage_directory).await?;
+ coverage_directory = coverage_directory.canonicalize()?;
+
+ let mut regression_reports_directory =
+ PathBuf::from(&test_directory).join("regression_reports");
+ fs::create_dir(®ression_reports_directory).await?;
+ regression_reports_directory = regression_reports_directory.canonicalize()?;
+
+ let mut target_in_test = PathBuf::from(&test_directory).join("fuzz.exe");
+ fs::copy(target_exe, &target_in_test).await?;
+ target_in_test = target_in_test.canonicalize()?;
+
+ let mut interesting_extensions = HashSet::new();
+ interesting_extensions.insert(Some(OsStr::new("so")));
+ interesting_extensions.insert(Some(OsStr::new("pdb")));
+ let mut f = fs::read_dir(target_exe.parent().unwrap()).await?;
+ while let Ok(Some(f)) = f.next_entry().await {
+ if interesting_extensions.contains(&f.path().extension()) {
+ fs::copy(f.path(), PathBuf::from(&test_directory).join(f.file_name())).await?;
+ }
+ }
+
+ let mut config_data = fs::read_to_string(config).await?;
+
+ config_data = config_data
+ .replace("{TARGET_PATH}", target_in_test.to_str().unwrap())
+ .replace("{INPUTS_PATH}", inputs_directory.to_str().unwrap())
+ .replace("{CRASHES_PATH}", crashes_directory.to_str().unwrap())
+ .replace("{CRASHDUMPS_PATH}", crashdumps_directory.to_str().unwrap())
+ .replace("{COVERAGE_PATH}", coverage_directory.to_str().unwrap())
+ .replace(
+ "{REGRESSION_REPORTS_PATH}",
+ regression_reports_directory.to_str().unwrap(),
+ )
+ .replace("{TEST_DIRECTORY}", test_directory.to_str().unwrap());
+
+ let mut config_in_test =
+ PathBuf::from(&test_directory).join(config.file_name().unwrap_or_else(|| {
+ panic!("Failed to get file name for config. config = {:?}", config)
+ }));
+
+ fs::write(&config_in_test, &config_data).await?;
+ config_in_test = config_in_test.canonicalize()?;
+
+ Ok(TestLayout {
+ root: test_directory,
+ config: config_in_test,
+ target_exe: target_in_test,
+ inputs: inputs_directory,
+ crashes: crashes_directory,
+ crashdumps: crashdumps_directory,
+ coverage: coverage_directory,
+ regression_reports: regression_reports_directory,
+ })
+}
+
+#[derive(Debug)]
+struct TestLayout {
+ root: PathBuf,
+ config: PathBuf,
+ target_exe: PathBuf,
+ inputs: PathBuf,
+ crashes: PathBuf,
+ crashdumps: PathBuf,
+ coverage: PathBuf,
+ regression_reports: PathBuf,
+}
diff --git a/src/agent/onefuzz-task/tests/templates/libfuzzer_basic.yml b/src/agent/onefuzz-task/tests/templates/libfuzzer_basic.yml
new file mode 100644
index 0000000000..f6740cbc96
--- /dev/null
+++ b/src/agent/onefuzz-task/tests/templates/libfuzzer_basic.yml
@@ -0,0 +1,33 @@
+# yaml-language-server: $schema=../../src/local/schema.json
+
+required_args: &required_args
+ target_exe: '{TARGET_PATH}'
+ inputs: &inputs '{INPUTS_PATH}' # A folder containining your inputs
+ crashes: &crashes '{CRASHES_PATH}' # The folder where you want the crashing inputs to be output
+ crashdumps: '{CRASHDUMPS_PATH}' # The folder where you want the crash dumps to be output
+ coverage: '{COVERAGE_PATH}' # The folder where you want the code coverage to be output
+ regression_reports: '{REGRESSION_REPORTS_PATH}' # The folder where you want the regression reports to be output
+ target_env: {
+ 'LD_LIBRARY_PATH': '{TEST_DIRECTORY}',
+ }
+ target_options: []
+ check_fuzzer_help: false
+
+tasks:
+ - type: LibFuzzer
+ <<: *required_args
+ readonly_inputs: []
+
+ - type: LibfuzzerRegression
+ <<: *required_args
+
+ - type: "LibfuzzerCrashReport"
+ <<: *required_args
+ input_queue: *crashes
+
+ - type: "Coverage"
+ <<: *required_args
+ target_options:
+ - "{input}"
+ input_queue: *inputs
+ readonly_inputs: [*inputs]
diff --git a/src/agent/onefuzz-telemetry/Cargo.toml b/src/agent/onefuzz-telemetry/Cargo.toml
index 23574a013f..8f91478b1f 100644
--- a/src/agent/onefuzz-telemetry/Cargo.toml
+++ b/src/agent/onefuzz-telemetry/Cargo.toml
@@ -15,5 +15,5 @@ chrono = { version = "0.4", default-features = false, features = [
lazy_static = "1.4"
log = "0.4"
serde = { version = "1.0", features = ["derive"] }
-tokio = { version = "1.29", features = ["full"] }
+tokio = { version = "1.32", features = ["full"] }
uuid = { version = "1.4", features = ["serde", "v4"] }
diff --git a/src/agent/onefuzz/Cargo.toml b/src/agent/onefuzz/Cargo.toml
index 1f3c27985c..55042607fa 100644
--- a/src/agent/onefuzz/Cargo.toml
+++ b/src/agent/onefuzz/Cargo.toml
@@ -10,7 +10,7 @@ license = "MIT"
anyhow = "1.0"
async-trait = "0.1"
base64 = "0.21"
-bytes = "1.4"
+bytes = "1.5"
dunce = "1.0"
dynamic-library = { path = "../dynamic-library" }
futures = "0.3"
@@ -18,7 +18,7 @@ futures-util = "0.3"
hex = "0.4"
lazy_static = "1.4"
log = "0.4"
-notify = { version = "6.0.1", default-features = false }
+notify = { version = "6.1.1", default-features = false }
regex = "1.9.1"
reqwest = { version = "0.11", features = [
"json",
@@ -31,7 +31,7 @@ serde = "1.0"
serde_json = "1.0"
rand = "0.8"
serde_derive = "1.0"
-tokio = { version = "1.29", features = ["full"] }
+tokio = { version = "1.32", features = ["full"] }
tokio-stream = { version = "0.1", features = ["fs", "time", "tokio-util"] }
tokio-util = { version = "0.7", features = ["full"] }
uuid = { version = "1.4", features = ["serde", "v4"] }
@@ -40,7 +40,7 @@ url-escape = "0.1.0"
storage-queue = { path = "../storage-queue" }
strum = "0.25"
strum_macros = "0.25"
-tempfile = "3.7.0"
+tempfile = "3.8.0"
process_control = "4.0"
reqwest-retry = { path = "../reqwest-retry" }
onefuzz-telemetry = { path = "../onefuzz-telemetry" }
@@ -49,7 +49,7 @@ stacktrace-parser = { path = "../stacktrace-parser" }
backoff = { version = "0.4", features = ["tokio"] }
[target.'cfg(target_family = "windows")'.dependencies]
-winreg = "0.50"
+winreg = "0.51"
input-tester = { path = "../input-tester" }
debugger = { path = "../debugger" }
windows = { version = "0.48", features = [
@@ -62,10 +62,10 @@ cpp_demangle = "0.4"
nix = "0.26"
[target.'cfg(target_os = "linux")'.dependencies]
-pete = "0.10"
+pete = "0.12"
rstack = "0.3"
proc-maps = { version = "0.3", default-features = false }
[dev-dependencies]
-clap = { version = "4.3.0", features = ["derive"] }
+clap = { version = "4.4.2", features = ["derive"] }
pretty_assertions = "1.4.0"
diff --git a/src/agent/onefuzz/src/libfuzzer.rs b/src/agent/onefuzz/src/libfuzzer.rs
index 495f401bae..00b24bf4e9 100644
--- a/src/agent/onefuzz/src/libfuzzer.rs
+++ b/src/agent/onefuzz/src/libfuzzer.rs
@@ -339,7 +339,7 @@ impl LibFuzzer {
Ok(missing)
}
- pub async fn fuzz(
+ pub fn fuzz(
&self,
fault_dir: impl AsRef,
corpus_dir: impl AsRef,
@@ -352,8 +352,7 @@ impl LibFuzzer {
// specify that a new file `crash-` should be written to a
// _directory_ ``, we must ensure that the prefix includes a
// trailing path separator.
- let artifact_prefix: OsString =
- format!("-artifact_prefix={}/", fault_dir.as_ref().display()).into();
+ let artifact_prefix = artifact_prefix(fault_dir.as_ref());
let mut cmd = self.build_command(
Some(fault_dir.as_ref()),
@@ -363,10 +362,11 @@ impl LibFuzzer {
None,
)?;
+ debug!("Running command: {:?}", &cmd);
+
let child = cmd
.spawn()
.with_context(|| format_err!("libfuzzer failed to start: {}", self.exe.display()))?;
-
Ok(child)
}
@@ -441,6 +441,20 @@ impl LibFuzzer {
}
}
+#[cfg(target_os = "windows")]
+fn artifact_prefix(fault_dir: &Path) -> OsString {
+ if fault_dir.is_absolute() {
+ format!("-artifact_prefix={}\\", fault_dir.display()).into()
+ } else {
+ format!("-artifact_prefix={}/", fault_dir.display()).into()
+ }
+}
+
+#[cfg(not(target_os = "windows"))]
+fn artifact_prefix(fault_dir: &Path) -> OsString {
+ format!("-artifact_prefix={}/", fault_dir.display()).into()
+}
+
pub struct LibFuzzerLine {
_line: String,
iters: u64,
diff --git a/src/agent/reqwest-retry/Cargo.toml b/src/agent/reqwest-retry/Cargo.toml
index d7d12ff4e8..5ddfbbe419 100644
--- a/src/agent/reqwest-retry/Cargo.toml
+++ b/src/agent/reqwest-retry/Cargo.toml
@@ -19,5 +19,5 @@ reqwest = { version = "0.11", features = [
thiserror = "1.0"
[dev-dependencies]
-tokio = { version = "1.29", features = ["macros"] }
+tokio = { version = "1.32", features = ["macros"] }
wiremock = "0.5"
diff --git a/src/agent/storage-queue/Cargo.toml b/src/agent/storage-queue/Cargo.toml
index d5c1c09d08..58034cff18 100644
--- a/src/agent/storage-queue/Cargo.toml
+++ b/src/agent/storage-queue/Cargo.toml
@@ -10,7 +10,7 @@ anyhow = "1.0"
async-trait = "0.1"
backoff = { version = "0.4", features = ["tokio"] }
base64 = "0.21"
-bytes = { version = "1.4", features = ["serde"] }
+bytes = { version = "1.5", features = ["serde"] }
derivative = "2.2"
flume = "0.10"
num_cpus = "1.15"
@@ -26,6 +26,6 @@ serde = { version = "1.0", features = ["derive"] }
serde_derive = "1.0"
serde_json = "1.0"
bincode = "1.3"
-tokio = { version = "1.29", features = ["full"] }
+tokio = { version = "1.32", features = ["full"] }
queue-file = "1.4"
uuid = { version = "1.4", features = ["serde", "v4"] }
diff --git a/src/agent/win-util/Cargo.toml b/src/agent/win-util/Cargo.toml
index 1edaa3fc58..460ee0e197 100644
--- a/src/agent/win-util/Cargo.toml
+++ b/src/agent/win-util/Cargo.toml
@@ -12,7 +12,7 @@ log = "0.4"
os_pipe = "1.1"
[target.'cfg(windows)'.dependencies]
-winreg = "0.50"
+winreg = "0.51"
[dependencies.windows]
version = "0.48"
@@ -33,4 +33,4 @@ features = [
]
[dev-dependencies]
-tempfile = "3.7.0"
+tempfile = "3.8.0"
diff --git a/src/ci/agent.sh b/src/ci/agent.sh
index 4a49c975b3..4cca93168b 100755
--- a/src/ci/agent.sh
+++ b/src/ci/agent.sh
@@ -37,7 +37,7 @@ export RUST_BACKTRACE=full
# Run tests and collect coverage
# https://github.com/taiki-e/cargo-llvm-cov
-cargo llvm-cov nextest --all-targets --features slow-tests --locked --workspace --lcov --output-path "$output_dir/lcov.info"
+cargo llvm-cov nextest --all-targets --features slow-tests,integration_test --locked --workspace --lcov --output-path "$output_dir/lcov.info"
# TODO: re-enable integration tests.
# cargo test --release --manifest-path ./onefuzz-task/Cargo.toml --features integration_test -- --nocapture
diff --git a/src/ci/set-versions.sh b/src/ci/set-versions.sh
index 34c30ea37c..2271a752f4 100755
--- a/src/ci/set-versions.sh
+++ b/src/ci/set-versions.sh
@@ -10,8 +10,12 @@ GET_VERSION=${SCRIPT_DIR}/get-version.sh
VERSION=${1:-$(${GET_VERSION})}
cd ${SCRIPT_DIR}/../../
+arrVer=(${VERSION//./ })
+MAJOR=${arrVer[0]}
+MINOR=${arrVer[1]}
+
SET_VERSIONS="src/pytypes/onefuzztypes/__version__.py src/cli/onefuzz/__version__.py"
SET_REQS="src/cli/requirements.txt"
sed -i "s/0.0.0/${VERSION}/" ${SET_VERSIONS}
-sed -i "s/onefuzztypes==0.0.0/onefuzztypes==${VERSION}/" ${SET_REQS}
+sed -i "s/onefuzztypes==0.0.0/onefuzztypes==${MAJOR}.${MINOR}.*/" ${SET_REQS}
diff --git a/src/proxy-manager/Cargo.lock b/src/proxy-manager/Cargo.lock
index ca4813995e..e86da294a7 100644
--- a/src/proxy-manager/Cargo.lock
+++ b/src/proxy-manager/Cargo.lock
@@ -43,16 +43,15 @@ dependencies = [
[[package]]
name = "anstream"
-version = "0.3.2"
+version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0ca84f3628370c59db74ee214b3263d58f9aadd9b4fe7e711fd87dc452b7f163"
+checksum = "b1f58811cfac344940f1a400b6e6231ce35171f614f26439e80f8c1465c5cc0c"
dependencies = [
"anstyle",
"anstyle-parse",
"anstyle-query",
"anstyle-wincon",
"colorchoice",
- "is-terminal",
"utf8parse",
]
@@ -82,9 +81,9 @@ dependencies = [
[[package]]
name = "anstyle-wincon"
-version = "1.0.2"
+version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c677ab05e09154296dd37acecd46420c17b9713e8366facafa8fc0885167cf4c"
+checksum = "58f54d10c6dfa51283a066ceab3ec1ab78d13fae00aa49243a45e4571fb79dfd"
dependencies = [
"anstyle",
"windows-sys",
@@ -236,23 +235,22 @@ dependencies = [
[[package]]
name = "clap"
-version = "4.3.21"
+version = "4.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c27cdf28c0f604ba3f512b0c9a409f8de8513e4816705deb0498b627e7c3a3fd"
+checksum = "6a13b88d2c62ff462f88e4a121f17a82c1af05693a2f192b5c38d14de73c19f6"
dependencies = [
"clap_builder",
]
[[package]]
name = "clap_builder"
-version = "4.3.21"
+version = "4.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "08a9f1ab5e9f01a9b81f202e8562eb9a10de70abf9eaeac1be465c28b75aa4aa"
+checksum = "2bb9faaa7c2ef94b2743a21f5a29e6f0010dff4caa69ac8e9d6cf8b6fa74da08"
dependencies = [
"anstream",
"anstyle",
"clap_lex",
- "once_cell",
"strsim",
]
@@ -1474,9 +1472,9 @@ dependencies = [
[[package]]
name = "tempfile"
-version = "3.7.1"
+version = "3.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dc02fddf48964c42031a0b3fe0428320ecf3a73c401040fc0096f97794310651"
+checksum = "cb94d2f3cc536af71caac6b6fcebf65860b347e7ce0cc9ebe8f70d3e521054ef"
dependencies = [
"cfg-if",
"fastrand",
@@ -1531,9 +1529,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
[[package]]
name = "tokio"
-version = "1.30.0"
+version = "1.32.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2d3ce25f50619af8b0aec2eb23deebe84249e19e2ddd393a6e16e3300a6dadfd"
+checksum = "17ed6077ed6cd6c74735e21f37eb16dc3935f96878b1fe961074089cc80893f9"
dependencies = [
"backtrace",
"bytes",
diff --git a/src/proxy-manager/Cargo.toml b/src/proxy-manager/Cargo.toml
index c783e8d3aa..b2258e994b 100644
--- a/src/proxy-manager/Cargo.toml
+++ b/src/proxy-manager/Cargo.toml
@@ -20,7 +20,7 @@ serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
storage-queue = { path = "../agent/storage-queue" }
thiserror = "1.0"
-tokio = { version = "1.29", features = [
+tokio = { version = "1.32", features = [
"macros",
"rt-multi-thread",
"fs",
@@ -31,4 +31,4 @@ reqwest-retry = { path = "../agent/reqwest-retry" }
onefuzz-telemetry = { path = "../agent/onefuzz-telemetry" }
uuid = { version = "1.4", features = ["serde"] }
log = "0.4"
-tempfile = "3.7.0"
+tempfile = "3.8.0"
diff --git a/src/pytypes/onefuzztypes/models.py b/src/pytypes/onefuzztypes/models.py
index a5f8139e97..c888621600 100644
--- a/src/pytypes/onefuzztypes/models.py
+++ b/src/pytypes/onefuzztypes/models.py
@@ -273,6 +273,7 @@ class ADOTemplate(BaseModel):
unique_fields: List[str]
comment: Optional[str]
ado_fields: Dict[str, str]
+ ado_duplicate_fields: Optional[Dict[str, str]]
on_duplicate: ADODuplicateTemplate
# validator needed to convert auth_token to SecretData