diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 2dd85d7c92..5f07124dd7 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
@@ -115,7 +123,7 @@ jobs:
- uses: actions/checkout@v3
- uses: actions/setup-python@v4
with:
- python-version: 3.7
+ python-version: "3.10"
- name: lint
shell: bash
run: src/ci/check-check-pr.sh
@@ -129,7 +137,7 @@ jobs:
shell: bash
- uses: actions/setup-python@v4
with:
- python-version: 3.7
+ python-version: "3.10"
- uses: actions/download-artifact@v3
with:
name: artifact-onefuzztypes
@@ -182,7 +190,7 @@ jobs:
- uses: actions/checkout@v3
- uses: actions/setup-python@v4
with:
- python-version: 3.8
+ python-version: "3.10"
- name: lint
shell: bash
run: |
@@ -200,7 +208,7 @@ jobs:
- uses: actions/checkout@v3
- uses: actions/setup-python@v4
with:
- python-version: 3.8
+ python-version: "3.10"
- name: lint
shell: bash
run: |
@@ -216,7 +224,7 @@ jobs:
- run: src/ci/set-versions.sh
- uses: actions/setup-python@v4
with:
- python-version: 3.7
+ python-version: "3.10"
- run: src/ci/onefuzztypes.sh
- uses: actions/upload-artifact@v3
with:
@@ -473,7 +481,7 @@ jobs:
path: artifacts
- uses: actions/setup-python@v4
with:
- python-version: 3.7
+ python-version: "3.10"
- name: Lint
shell: bash
run: |
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..01daa7f7f1 100644
--- a/README.md
+++ b/README.md
@@ -1,5 +1,26 @@
#
+# :exclamation: IMPORTANT NOTICE :exclamation:
+
+**_August 31, 2023_**.
+
+**_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._**
+
+---
+**_Update: September 15 2023:_**
+**_Our current target to archive the project is September 30th, 2023._**
+
+---
+
[](https://github.com/microsoft/onefuzz/actions/workflows/ci.yml?query=branch%3Amain)
## A self-hosted Fuzzing-As-A-Service platform
diff --git a/contrib/deploy-onefuzz-via-azure-devops/Pipfile.lock b/contrib/deploy-onefuzz-via-azure-devops/Pipfile.lock
index e5dcd53773..caef7e7488 100644
--- a/contrib/deploy-onefuzz-via-azure-devops/Pipfile.lock
+++ b/contrib/deploy-onefuzz-via-azure-devops/Pipfile.lock
@@ -51,6 +51,7 @@
"sha256:fd57160949179ec517d32ac2ac898b5f20d68ed1a9c977346efbac9c2f1e779d"
],
"index": "pypi",
+ "markers": "python_full_version >= '3.6.2'",
"version": "==22.3.0"
},
"certifi": {
@@ -58,97 +59,112 @@
"sha256:539cc1d13202e33ca466e88b2807e29f4c13049d6d87031a3c110744495cb082",
"sha256:92d6037539857d8206b8f6ae472e8b77db8058fec5937a1ef3f54304089edbb9"
],
- "index": "pypi",
+ "markers": "python_version >= '3.6'",
"version": "==2023.7.22"
},
"charset-normalizer": {
"hashes": [
- "sha256:04e57ab9fbf9607b77f7d057974694b4f6b142da9ed4a199859d9d4d5c63fe96",
- "sha256:09393e1b2a9461950b1c9a45d5fd251dc7c6f228acab64da1c9c0165d9c7765c",
- "sha256:0b87549028f680ca955556e3bd57013ab47474c3124dc069faa0b6545b6c9710",
- "sha256:1000fba1057b92a65daec275aec30586c3de2401ccdcd41f8a5c1e2c87078706",
- "sha256:1249cbbf3d3b04902ff081ffbb33ce3377fa6e4c7356f759f3cd076cc138d020",
- "sha256:1920d4ff15ce893210c1f0c0e9d19bfbecb7983c76b33f046c13a8ffbd570252",
- "sha256:193cbc708ea3aca45e7221ae58f0fd63f933753a9bfb498a3b474878f12caaad",
- "sha256:1a100c6d595a7f316f1b6f01d20815d916e75ff98c27a01ae817439ea7726329",
- "sha256:1f30b48dd7fa1474554b0b0f3fdfdd4c13b5c737a3c6284d3cdc424ec0ffff3a",
- "sha256:203f0c8871d5a7987be20c72442488a0b8cfd0f43b7973771640fc593f56321f",
- "sha256:246de67b99b6851627d945db38147d1b209a899311b1305dd84916f2b88526c6",
- "sha256:2dee8e57f052ef5353cf608e0b4c871aee320dd1b87d351c28764fc0ca55f9f4",
- "sha256:2efb1bd13885392adfda4614c33d3b68dee4921fd0ac1d3988f8cbb7d589e72a",
- "sha256:2f4ac36d8e2b4cc1aa71df3dd84ff8efbe3bfb97ac41242fbcfc053c67434f46",
- "sha256:3170c9399da12c9dc66366e9d14da8bf7147e1e9d9ea566067bbce7bb74bd9c2",
- "sha256:3b1613dd5aee995ec6d4c69f00378bbd07614702a315a2cf6c1d21461fe17c23",
- "sha256:3bb3d25a8e6c0aedd251753a79ae98a093c7e7b471faa3aa9a93a81431987ace",
- "sha256:3bb7fda7260735efe66d5107fb7e6af6a7c04c7fce9b2514e04b7a74b06bf5dd",
- "sha256:41b25eaa7d15909cf3ac4c96088c1f266a9a93ec44f87f1d13d4a0e86c81b982",
- "sha256:45de3f87179c1823e6d9e32156fb14c1927fcc9aba21433f088fdfb555b77c10",
- "sha256:46fb8c61d794b78ec7134a715a3e564aafc8f6b5e338417cb19fe9f57a5a9bf2",
- "sha256:48021783bdf96e3d6de03a6e39a1171ed5bd7e8bb93fc84cc649d11490f87cea",
- "sha256:4957669ef390f0e6719db3613ab3a7631e68424604a7b448f079bee145da6e09",
- "sha256:5e86d77b090dbddbe78867a0275cb4df08ea195e660f1f7f13435a4649e954e5",
- "sha256:6339d047dab2780cc6220f46306628e04d9750f02f983ddb37439ca47ced7149",
- "sha256:681eb3d7e02e3c3655d1b16059fbfb605ac464c834a0c629048a30fad2b27489",
- "sha256:6c409c0deba34f147f77efaa67b8e4bb83d2f11c8806405f76397ae5b8c0d1c9",
- "sha256:7095f6fbfaa55defb6b733cfeb14efaae7a29f0b59d8cf213be4e7ca0b857b80",
- "sha256:70c610f6cbe4b9fce272c407dd9d07e33e6bf7b4aa1b7ffb6f6ded8e634e3592",
- "sha256:72814c01533f51d68702802d74f77ea026b5ec52793c791e2da806a3844a46c3",
- "sha256:7a4826ad2bd6b07ca615c74ab91f32f6c96d08f6fcc3902ceeedaec8cdc3bcd6",
- "sha256:7c70087bfee18a42b4040bb9ec1ca15a08242cf5867c58726530bdf3945672ed",
- "sha256:855eafa5d5a2034b4621c74925d89c5efef61418570e5ef9b37717d9c796419c",
- "sha256:8700f06d0ce6f128de3ccdbc1acaea1ee264d2caa9ca05daaf492fde7c2a7200",
- "sha256:89f1b185a01fe560bc8ae5f619e924407efca2191b56ce749ec84982fc59a32a",
- "sha256:8b2c760cfc7042b27ebdb4a43a4453bd829a5742503599144d54a032c5dc7e9e",
- "sha256:8c2f5e83493748286002f9369f3e6607c565a6a90425a3a1fef5ae32a36d749d",
- "sha256:8e098148dd37b4ce3baca71fb394c81dc5d9c7728c95df695d2dca218edf40e6",
- "sha256:94aea8eff76ee6d1cdacb07dd2123a68283cb5569e0250feab1240058f53b623",
- "sha256:95eb302ff792e12aba9a8b8f8474ab229a83c103d74a750ec0bd1c1eea32e669",
- "sha256:9bd9b3b31adcb054116447ea22caa61a285d92e94d710aa5ec97992ff5eb7cf3",
- "sha256:9e608aafdb55eb9f255034709e20d5a83b6d60c054df0802fa9c9883d0a937aa",
- "sha256:a103b3a7069b62f5d4890ae1b8f0597618f628b286b03d4bc9195230b154bfa9",
- "sha256:a386ebe437176aab38c041de1260cd3ea459c6ce5263594399880bbc398225b2",
- "sha256:a38856a971c602f98472050165cea2cdc97709240373041b69030be15047691f",
- "sha256:a401b4598e5d3f4a9a811f3daf42ee2291790c7f9d74b18d75d6e21dda98a1a1",
- "sha256:a7647ebdfb9682b7bb97e2a5e7cb6ae735b1c25008a70b906aecca294ee96cf4",
- "sha256:aaf63899c94de41fe3cf934601b0f7ccb6b428c6e4eeb80da72c58eab077b19a",
- "sha256:b0dac0ff919ba34d4df1b6131f59ce95b08b9065233446be7e459f95554c0dc8",
- "sha256:baacc6aee0b2ef6f3d308e197b5d7a81c0e70b06beae1f1fcacffdbd124fe0e3",
- "sha256:bf420121d4c8dce6b889f0e8e4ec0ca34b7f40186203f06a946fa0276ba54029",
- "sha256:c04a46716adde8d927adb9457bbe39cf473e1e2c2f5d0a16ceb837e5d841ad4f",
- "sha256:c0b21078a4b56965e2b12f247467b234734491897e99c1d51cee628da9786959",
- "sha256:c1c76a1743432b4b60ab3358c937a3fe1341c828ae6194108a94c69028247f22",
- "sha256:c4983bf937209c57240cff65906b18bb35e64ae872da6a0db937d7b4af845dd7",
- "sha256:c4fb39a81950ec280984b3a44f5bd12819953dc5fa3a7e6fa7a80db5ee853952",
- "sha256:c57921cda3a80d0f2b8aec7e25c8aa14479ea92b5b51b6876d975d925a2ea346",
- "sha256:c8063cf17b19661471ecbdb3df1c84f24ad2e389e326ccaf89e3fb2484d8dd7e",
- "sha256:ccd16eb18a849fd8dcb23e23380e2f0a354e8daa0c984b8a732d9cfaba3a776d",
- "sha256:cd6dbe0238f7743d0efe563ab46294f54f9bc8f4b9bcf57c3c666cc5bc9d1299",
- "sha256:d62e51710986674142526ab9f78663ca2b0726066ae26b78b22e0f5e571238dd",
- "sha256:db901e2ac34c931d73054d9797383d0f8009991e723dab15109740a63e7f902a",
- "sha256:e03b8895a6990c9ab2cdcd0f2fe44088ca1c65ae592b8f795c3294af00a461c3",
- "sha256:e1c8a2f4c69e08e89632defbfabec2feb8a8d99edc9f89ce33c4b9e36ab63037",
- "sha256:e4b749b9cc6ee664a3300bb3a273c1ca8068c46be705b6c31cf5d276f8628a94",
- "sha256:e6a5bf2cba5ae1bb80b154ed68a3cfa2fa00fde979a7f50d6598d3e17d9ac20c",
- "sha256:e857a2232ba53ae940d3456f7533ce6ca98b81917d47adc3c7fd55dad8fab858",
- "sha256:ee4006268ed33370957f55bf2e6f4d263eaf4dc3cfc473d1d90baff6ed36ce4a",
- "sha256:eef9df1eefada2c09a5e7a40991b9fc6ac6ef20b1372abd48d2794a316dc0449",
- "sha256:f058f6963fd82eb143c692cecdc89e075fa0828db2e5b291070485390b2f1c9c",
- "sha256:f25c229a6ba38a35ae6e25ca1264621cc25d4d38dca2942a7fce0b67a4efe918",
- "sha256:f2a1d0fd4242bd8643ce6f98927cf9c04540af6efa92323e9d3124f57727bfc1",
- "sha256:f7560358a6811e52e9c4d142d497f1a6e10103d3a6881f18d04dbce3729c0e2c",
- "sha256:f779d3ad205f108d14e99bb3859aa7dd8e9c68874617c72354d7ecaec2a054ac",
- "sha256:f87f746ee241d30d6ed93969de31e5ffd09a2961a051e60ae6bddde9ec3583aa"
+ "sha256:02673e456dc5ab13659f85196c534dc596d4ef260e4d86e856c3b2773ce09843",
+ "sha256:02af06682e3590ab952599fbadac535ede5d60d78848e555aa58d0c0abbde786",
+ "sha256:03680bb39035fbcffe828eae9c3f8afc0428c91d38e7d61aa992ef7a59fb120e",
+ "sha256:0570d21da019941634a531444364f2482e8db0b3425fcd5ac0c36565a64142c8",
+ "sha256:09c77f964f351a7369cc343911e0df63e762e42bac24cd7d18525961c81754f4",
+ "sha256:0d3d5b7db9ed8a2b11a774db2bbea7ba1884430a205dbd54a32d61d7c2a190fa",
+ "sha256:1063da2c85b95f2d1a430f1c33b55c9c17ffaf5e612e10aeaad641c55a9e2b9d",
+ "sha256:12ebea541c44fdc88ccb794a13fe861cc5e35d64ed689513a5c03d05b53b7c82",
+ "sha256:153e7b6e724761741e0974fc4dcd406d35ba70b92bfe3fedcb497226c93b9da7",
+ "sha256:15b26ddf78d57f1d143bdf32e820fd8935d36abe8a25eb9ec0b5a71c82eb3895",
+ "sha256:1872d01ac8c618a8da634e232f24793883d6e456a66593135aeafe3784b0848d",
+ "sha256:187d18082694a29005ba2944c882344b6748d5be69e3a89bf3cc9d878e548d5a",
+ "sha256:1b2919306936ac6efb3aed1fbf81039f7087ddadb3160882a57ee2ff74fd2382",
+ "sha256:232ac332403e37e4a03d209a3f92ed9071f7d3dbda70e2a5e9cff1c4ba9f0678",
+ "sha256:23e8565ab7ff33218530bc817922fae827420f143479b753104ab801145b1d5b",
+ "sha256:24817cb02cbef7cd499f7c9a2735286b4782bd47a5b3516a0e84c50eab44b98e",
+ "sha256:249c6470a2b60935bafd1d1d13cd613f8cd8388d53461c67397ee6a0f5dce741",
+ "sha256:24a91a981f185721542a0b7c92e9054b7ab4fea0508a795846bc5b0abf8118d4",
+ "sha256:2502dd2a736c879c0f0d3e2161e74d9907231e25d35794584b1ca5284e43f596",
+ "sha256:250c9eb0f4600361dd80d46112213dff2286231d92d3e52af1e5a6083d10cad9",
+ "sha256:278c296c6f96fa686d74eb449ea1697f3c03dc28b75f873b65b5201806346a69",
+ "sha256:2935ffc78db9645cb2086c2f8f4cfd23d9b73cc0dc80334bc30aac6f03f68f8c",
+ "sha256:2f4a0033ce9a76e391542c182f0d48d084855b5fcba5010f707c8e8c34663d77",
+ "sha256:30a85aed0b864ac88309b7d94be09f6046c834ef60762a8833b660139cfbad13",
+ "sha256:380c4bde80bce25c6e4f77b19386f5ec9db230df9f2f2ac1e5ad7af2caa70459",
+ "sha256:3ae38d325b512f63f8da31f826e6cb6c367336f95e418137286ba362925c877e",
+ "sha256:3b447982ad46348c02cb90d230b75ac34e9886273df3a93eec0539308a6296d7",
+ "sha256:3debd1150027933210c2fc321527c2299118aa929c2f5a0a80ab6953e3bd1908",
+ "sha256:4162918ef3098851fcd8a628bf9b6a98d10c380725df9e04caf5ca6dd48c847a",
+ "sha256:468d2a840567b13a590e67dd276c570f8de00ed767ecc611994c301d0f8c014f",
+ "sha256:4cc152c5dd831641e995764f9f0b6589519f6f5123258ccaca8c6d34572fefa8",
+ "sha256:542da1178c1c6af8873e143910e2269add130a299c9106eef2594e15dae5e482",
+ "sha256:557b21a44ceac6c6b9773bc65aa1b4cc3e248a5ad2f5b914b91579a32e22204d",
+ "sha256:5707a746c6083a3a74b46b3a631d78d129edab06195a92a8ece755aac25a3f3d",
+ "sha256:588245972aca710b5b68802c8cad9edaa98589b1b42ad2b53accd6910dad3545",
+ "sha256:5adf257bd58c1b8632046bbe43ee38c04e1038e9d37de9c57a94d6bd6ce5da34",
+ "sha256:619d1c96099be5823db34fe89e2582b336b5b074a7f47f819d6b3a57ff7bdb86",
+ "sha256:63563193aec44bce707e0c5ca64ff69fa72ed7cf34ce6e11d5127555756fd2f6",
+ "sha256:67b8cc9574bb518ec76dc8e705d4c39ae78bb96237cb533edac149352c1f39fe",
+ "sha256:6a685067d05e46641d5d1623d7c7fdf15a357546cbb2f71b0ebde91b175ffc3e",
+ "sha256:70f1d09c0d7748b73290b29219e854b3207aea922f839437870d8cc2168e31cc",
+ "sha256:750b446b2ffce1739e8578576092179160f6d26bd5e23eb1789c4d64d5af7dc7",
+ "sha256:7966951325782121e67c81299a031f4c115615e68046f79b85856b86ebffc4cd",
+ "sha256:7b8b8bf1189b3ba9b8de5c8db4d541b406611a71a955bbbd7385bbc45fcb786c",
+ "sha256:7f5d10bae5d78e4551b7be7a9b29643a95aded9d0f602aa2ba584f0388e7a557",
+ "sha256:805dfea4ca10411a5296bcc75638017215a93ffb584c9e344731eef0dcfb026a",
+ "sha256:81bf654678e575403736b85ba3a7867e31c2c30a69bc57fe88e3ace52fb17b89",
+ "sha256:82eb849f085624f6a607538ee7b83a6d8126df6d2f7d3b319cb837b289123078",
+ "sha256:85a32721ddde63c9df9ebb0d2045b9691d9750cb139c161c80e500d210f5e26e",
+ "sha256:86d1f65ac145e2c9ed71d8ffb1905e9bba3a91ae29ba55b4c46ae6fc31d7c0d4",
+ "sha256:86f63face3a527284f7bb8a9d4f78988e3c06823f7bea2bd6f0e0e9298ca0403",
+ "sha256:8eaf82f0eccd1505cf39a45a6bd0a8cf1c70dcfc30dba338207a969d91b965c0",
+ "sha256:93aa7eef6ee71c629b51ef873991d6911b906d7312c6e8e99790c0f33c576f89",
+ "sha256:96c2b49eb6a72c0e4991d62406e365d87067ca14c1a729a870d22354e6f68115",
+ "sha256:9cf3126b85822c4e53aa28c7ec9869b924d6fcfb76e77a45c44b83d91afd74f9",
+ "sha256:9fe359b2e3a7729010060fbca442ca225280c16e923b37db0e955ac2a2b72a05",
+ "sha256:a0ac5e7015a5920cfce654c06618ec40c33e12801711da6b4258af59a8eff00a",
+ "sha256:a3f93dab657839dfa61025056606600a11d0b696d79386f974e459a3fbc568ec",
+ "sha256:a4b71f4d1765639372a3b32d2638197f5cd5221b19531f9245fcc9ee62d38f56",
+ "sha256:aae32c93e0f64469f74ccc730a7cb21c7610af3a775157e50bbd38f816536b38",
+ "sha256:aaf7b34c5bc56b38c931a54f7952f1ff0ae77a2e82496583b247f7c969eb1479",
+ "sha256:abecce40dfebbfa6abf8e324e1860092eeca6f7375c8c4e655a8afb61af58f2c",
+ "sha256:abf0d9f45ea5fb95051c8bfe43cb40cda383772f7e5023a83cc481ca2604d74e",
+ "sha256:ac71b2977fb90c35d41c9453116e283fac47bb9096ad917b8819ca8b943abecd",
+ "sha256:ada214c6fa40f8d800e575de6b91a40d0548139e5dc457d2ebb61470abf50186",
+ "sha256:b09719a17a2301178fac4470d54b1680b18a5048b481cb8890e1ef820cb80455",
+ "sha256:b1121de0e9d6e6ca08289583d7491e7fcb18a439305b34a30b20d8215922d43c",
+ "sha256:b3b2316b25644b23b54a6f6401074cebcecd1244c0b8e80111c9a3f1c8e83d65",
+ "sha256:b3d9b48ee6e3967b7901c052b670c7dda6deb812c309439adaffdec55c6d7b78",
+ "sha256:b5bcf60a228acae568e9911f410f9d9e0d43197d030ae5799e20dca8df588287",
+ "sha256:b8f3307af845803fb0b060ab76cf6dd3a13adc15b6b451f54281d25911eb92df",
+ "sha256:c2af80fb58f0f24b3f3adcb9148e6203fa67dd3f61c4af146ecad033024dde43",
+ "sha256:c350354efb159b8767a6244c166f66e67506e06c8924ed74669b2c70bc8735b1",
+ "sha256:c5a74c359b2d47d26cdbbc7845e9662d6b08a1e915eb015d044729e92e7050b7",
+ "sha256:c71f16da1ed8949774ef79f4a0260d28b83b3a50c6576f8f4f0288d109777989",
+ "sha256:d47ecf253780c90ee181d4d871cd655a789da937454045b17b5798da9393901a",
+ "sha256:d7eff0f27edc5afa9e405f7165f85a6d782d308f3b6b9d96016c010597958e63",
+ "sha256:d97d85fa63f315a8bdaba2af9a6a686e0eceab77b3089af45133252618e70884",
+ "sha256:db756e48f9c5c607b5e33dd36b1d5872d0422e960145b08ab0ec7fd420e9d649",
+ "sha256:dc45229747b67ffc441b3de2f3ae5e62877a282ea828a5bdb67883c4ee4a8810",
+ "sha256:e0fc42822278451bc13a2e8626cf2218ba570f27856b536e00cfa53099724828",
+ "sha256:e39c7eb31e3f5b1f88caff88bcff1b7f8334975b46f6ac6e9fc725d829bc35d4",
+ "sha256:e46cd37076971c1040fc8c41273a8b3e2c624ce4f2be3f5dfcb7a430c1d3acc2",
+ "sha256:e5c1502d4ace69a179305abb3f0bb6141cbe4714bc9b31d427329a95acfc8bdd",
+ "sha256:edfe077ab09442d4ef3c52cb1f9dab89bff02f4524afc0acf2d46be17dc479f5",
+ "sha256:effe5406c9bd748a871dbcaf3ac69167c38d72db8c9baf3ff954c344f31c4cbe",
+ "sha256:f0d1e3732768fecb052d90d62b220af62ead5748ac51ef61e7b32c266cac9293",
+ "sha256:f5969baeaea61c97efa706b9b107dcba02784b1601c74ac84f2a532ea079403e",
+ "sha256:f8888e31e3a85943743f8fc15e71536bda1c81d5aa36d014a3c0c44481d7db6e",
+ "sha256:fc52b79d83a3fe3a360902d3f5d79073a993597d48114c29485e9431092905d8"
],
- "markers": "python_version >= '3.7'",
- "version": "==3.2.0"
+ "markers": "python_full_version >= '3.7.0'",
+ "version": "==3.3.0"
},
"click": {
"hashes": [
- "sha256:48ee849951919527a045bfe3bf7baa8a959c423134e1a5b98c05c20ba75a1cbd",
- "sha256:fa244bb30b3b5ee2cae3da8f55c9e5e0c0e86093306301fb418eb9dc40fbded5"
+ "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28",
+ "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"
],
"markers": "python_version >= '3.7'",
- "version": "==8.1.6"
+ "version": "==8.1.7"
},
"dill": {
"hashes": [
@@ -234,11 +250,11 @@
},
"pathspec": {
"hashes": [
- "sha256:2798de800fa92780e33acca925945e9a19a133b715067cf165b8866c15a31687",
- "sha256:d8af70af76652554bd134c22b3e8a1cc46ed7d91edcdd721ef1a0c51a84a5293"
+ "sha256:1d6ed233af05e679efb96b1851550ea95bbb64b7c490b0f5aa52996c11e92a20",
+ "sha256:e0d8d0ac2f12da61956eb2306b69f9469b42f4deb0f3cb6ed47b9cce9996ced3"
],
"markers": "python_version >= '3.7'",
- "version": "==0.11.1"
+ "version": "==0.11.2"
},
"pip": {
"hashes": [
@@ -246,15 +262,16 @@
"sha256:a3edacb89022ef5258bf61852728bf866632a394da837ca49eb4303635835f17"
],
"index": "pypi",
+ "markers": "python_version >= '3.7'",
"version": "==22.1.2"
},
"platformdirs": {
"hashes": [
- "sha256:1b42b450ad933e981d56e59f1b97495428c9bd60698baab9f3eb3d00d5822421",
- "sha256:ad8291ae0ae5072f66c16945166cb11c63394c7a3ad1b1bc9828ca3162da8c2f"
+ "sha256:cf8ee52a3afdb965072dcc652433e0c7e3e40cf5ea1477cd4b3b1d2eb75495b3",
+ "sha256:e9d171d00af68be50e9202731309c4e658fd8bc76f55c11c7dd760d023bda68e"
],
"markers": "python_version >= '3.7'",
- "version": "==3.9.1"
+ "version": "==3.11.0"
},
"pylint": {
"hashes": [
@@ -262,6 +279,7 @@
"sha256:705c620d388035bdd9ff8b44c5bcdd235bfb49d276d488dd2c8ff1736aa42526"
],
"index": "pypi",
+ "markers": "python_full_version >= '3.6.2'",
"version": "==2.13.9"
},
"requests": {
@@ -270,15 +288,16 @@
"sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1"
],
"index": "pypi",
+ "markers": "python_version >= '3.7'",
"version": "==2.31.0"
},
"setuptools": {
"hashes": [
- "sha256:11e52c67415a381d10d6b462ced9cfb97066179f0e871399e006c4ab101fc85f",
- "sha256:baf1fdb41c6da4cd2eae722e135500da913332ab3f2f5c7d33af9b492acb5235"
+ "sha256:4ac1475276d2f1c48684874089fefcd83bd7162ddaafb81fac866ba0db282a87",
+ "sha256:b454a35605876da60632df1a60f736524eb73cc47bbc9f3f1ef1b644de74fd2a"
],
- "markers": "python_version >= '3.7'",
- "version": "==68.0.0"
+ "markers": "python_version >= '3.8'",
+ "version": "==68.2.2"
},
"tomli": {
"hashes": [
@@ -290,19 +309,20 @@
},
"typing-extensions": {
"hashes": [
- "sha256:440d5dd3af93b060174bf433bccd69b0babc3b15b1a8dca43789fd7f61514b36",
- "sha256:b75ddc264f0ba5615db7ba217daeb99701ad295353c45f9e95963337ceeeffb2"
+ "sha256:8f92fc8806f9a6b641eaa5318da32b44d401efaac0f6678c9bc448ba3605faa0",
+ "sha256:df8e4339e9cb77357558cbdbceca33c303714cf861d1eef15e1070055ae8b7ef"
],
"markers": "python_version < '3.10'",
- "version": "==4.7.1"
+ "version": "==4.8.0"
},
"urllib3": {
"hashes": [
- "sha256:8d22f86aae8ef5e410d4f539fde9ce6b2113a001bb4d189e0aed70642d602b11",
- "sha256:de7df1803967d2c2a98e4b11bb7d6bd9210474c46e8a0401514e3a42a75ebde4"
+ "sha256:7a7c7003b000adf9e7ca2a377c9688bbc54ed41b985789ed576570342a375cd2",
+ "sha256:b19e1a85d206b56d7df1d5e683df4a7725252a964e3993648dd0fb5a1c157564"
],
+ "index": "pypi",
"markers": "python_version >= '3.7'",
- "version": "==2.0.4"
+ "version": "==2.0.6"
},
"wrapt": {
"hashes": [
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/docs/webhook_events.md b/docs/webhook_events.md
index a417b7465f..cd8c5932f6 100644
--- a/docs/webhook_events.md
+++ b/docs/webhook_events.md
@@ -2033,6 +2033,10 @@ If webhook is set to have Event Grid message format then the payload will look a
},
"original_crash_test_result": {
"$ref": "#/definitions/CrashTestResult"
+ },
+ "report_url": {
+ "title": "Report Url",
+ "type": "string"
}
},
"required": [
@@ -6427,6 +6431,10 @@ If webhook is set to have Event Grid message format then the payload will look a
},
"original_crash_test_result": {
"$ref": "#/definitions/CrashTestResult"
+ },
+ "report_url": {
+ "title": "Report Url",
+ "type": "string"
}
},
"required": [
diff --git a/src/ApiService/ApiService/FeatureFlags.cs b/src/ApiService/ApiService/FeatureFlags.cs
index aa4bc87079..e74396e882 100644
--- a/src/ApiService/ApiService/FeatureFlags.cs
+++ b/src/ApiService/ApiService/FeatureFlags.cs
@@ -8,4 +8,5 @@ public static class FeatureFlagConstants {
public const string EnableBlobRetentionPolicy = "EnableBlobRetentionPolicy";
public const string EnableDryRunBlobRetention = "EnableDryRunBlobRetention";
public const string EnableWorkItemCreation = "EnableWorkItemCreation";
+ public const string EnableContainerRetentionPolicies = "EnableContainerRetentionPolicies";
}
diff --git a/src/ApiService/ApiService/Functions/Jobs.cs b/src/ApiService/ApiService/Functions/Jobs.cs
index bef61adfc2..3f8746df1f 100644
--- a/src/ApiService/ApiService/Functions/Jobs.cs
+++ b/src/ApiService/ApiService/Functions/Jobs.cs
@@ -83,7 +83,7 @@ private async Task Post(HttpRequestData req, FunctionContext c
"job");
}
- await _context.Events.SendEvent(new EventJobCreated(job.JobId, job.Config, job.UserInfo));
+ await _context.Events.SendEvent(new EventJobCreated(job.JobId, job.Config, job.UserInfo, _context.ServiceConfiguration.OneFuzzVersion));
return await RequestHandling.Ok(req, JobResponse.ForJob(job, taskInfo: null));
}
diff --git a/src/ApiService/ApiService/Functions/QueueFileChanges.cs b/src/ApiService/ApiService/Functions/QueueFileChanges.cs
index acdd3e328d..9e22f113ad 100644
--- a/src/ApiService/ApiService/Functions/QueueFileChanges.cs
+++ b/src/ApiService/ApiService/Functions/QueueFileChanges.cs
@@ -1,5 +1,6 @@
using System.Text.Json;
using System.Text.Json.Nodes;
+using System.Threading.Tasks;
using Azure.Core;
using Microsoft.Azure.Functions.Worker;
using Microsoft.Extensions.Logging;
@@ -54,6 +55,8 @@ public async Async.Task Run(
return;
}
+ var storageAccount = new ResourceIdentifier(topicElement.GetString()!);
+
try {
// Setting isLastRetryAttempt to false will rethrow any exceptions
// With the intention that the azure functions runtime will handle requeing
@@ -61,7 +64,7 @@ public async Async.Task Run(
// requeuing ourselves because azure functions doesn't support retry policies
// for queue based functions.
- var result = await FileAdded(fileChangeEvent, isLastRetryAttempt: false);
+ var result = await FileAdded(storageAccount, fileChangeEvent, isLastRetryAttempt: false);
if (!result.IsOk && result.ErrorV.Code == ErrorCode.ADO_WORKITEM_PROCESSING_DISABLED) {
await RequeueMessage(msg, TimeSpan.FromDays(1));
}
@@ -71,16 +74,47 @@ public async Async.Task Run(
}
}
- private async Async.Task FileAdded(JsonDocument fileChangeEvent, bool isLastRetryAttempt) {
+ private async Async.Task FileAdded(ResourceIdentifier storageAccount, JsonDocument fileChangeEvent, bool isLastRetryAttempt) {
var data = fileChangeEvent.RootElement.GetProperty("data");
var url = data.GetProperty("url").GetString()!;
var parts = url.Split("/").Skip(3).ToList();
- var container = parts[0];
+ var container = Container.Parse(parts[0]);
var path = string.Join('/', parts.Skip(1));
- _log.LogInformation("file added : {Container} - {Path}", container, path);
- return await _notificationOperations.NewFiles(Container.Parse(container), path, isLastRetryAttempt);
+ _log.LogInformation("file added : {Container} - {Path}", container.String, path);
+
+ var (_, result) = await (
+ ApplyRetentionPolicy(storageAccount, container, path),
+ _notificationOperations.NewFiles(container, path, isLastRetryAttempt));
+
+ return result;
+ }
+
+ private async Async.Task ApplyRetentionPolicy(ResourceIdentifier storageAccount, Container container, string path) {
+ if (await _context.FeatureManagerSnapshot.IsEnabledAsync(FeatureFlagConstants.EnableContainerRetentionPolicies)) {
+ // default retention period can be applied to the container
+ // if one exists, we will set the expiry date on the newly-created blob, if it doesn't already have one
+ var account = await _storage.GetBlobServiceClientForAccount(storageAccount);
+ var containerClient = account.GetBlobContainerClient(container.String);
+ var containerProps = await containerClient.GetPropertiesAsync();
+ var retentionPeriod = RetentionPolicyUtils.GetContainerRetentionPeriodFromMetadata(containerProps.Value.Metadata);
+ if (!retentionPeriod.IsOk) {
+ _log.LogError("invalid retention period: {Error}", retentionPeriod.ErrorV);
+ } else if (retentionPeriod.OkV is TimeSpan period) {
+ var blobClient = containerClient.GetBlobClient(path);
+ var tags = (await blobClient.GetTagsAsync()).Value.Tags;
+ var expiryDate = DateTime.UtcNow + period;
+ var tag = RetentionPolicyUtils.CreateExpiryDateTag(DateOnly.FromDateTime(expiryDate));
+ if (tags.TryAdd(tag.Key, tag.Value)) {
+ _ = await blobClient.SetTagsAsync(tags);
+ _log.LogInformation("applied container retention policy ({Policy}) to {Path}", period, path);
+ return true;
+ }
+ }
+ }
+
+ return false;
}
private async Async.Task RequeueMessage(string msg, TimeSpan? visibilityTimeout = null) {
@@ -94,20 +128,22 @@ private async Async.Task RequeueMessage(string msg, TimeSpan? visibilityTimeout
newCustomDequeueCount = json["data"]!["customDequeueCount"]!.GetValue();
}
- var queueName = QueueFileChangesQueueName;
if (newCustomDequeueCount > MAX_DEQUEUE_COUNT) {
_log.LogWarning("Message retried more than {MAX_DEQUEUE_COUNT} times with no success: {msg}", MAX_DEQUEUE_COUNT, msg);
- queueName = QueueFileChangesPoisonQueueName;
+ await _context.Queue.QueueObject(
+ QueueFileChangesPoisonQueueName,
+ json,
+ StorageType.Config)
+ .IgnoreResult();
+ } else {
+ json!["data"]!["customDequeueCount"] = newCustomDequeueCount + 1;
+ await _context.Queue.QueueObject(
+ QueueFileChangesQueueName,
+ json,
+ StorageType.Config,
+ visibilityTimeout ?? CalculateExponentialBackoff(newCustomDequeueCount))
+ .IgnoreResult();
}
-
- json!["data"]!["customDequeueCount"] = newCustomDequeueCount + 1;
-
- await _context.Queue.QueueObject(
- queueName,
- json,
- StorageType.Config,
- visibilityTimeout ?? CalculateExponentialBackoff(newCustomDequeueCount))
- .IgnoreResult();
}
// Possible return values:
diff --git a/src/ApiService/ApiService/Functions/QueueNodeHeartbeat.cs b/src/ApiService/ApiService/Functions/QueueNodeHeartbeat.cs
index ba19175938..649443cf28 100644
--- a/src/ApiService/ApiService/Functions/QueueNodeHeartbeat.cs
+++ b/src/ApiService/ApiService/Functions/QueueNodeHeartbeat.cs
@@ -41,9 +41,8 @@ public async Async.Task Run([QueueTrigger("node-heartbeat", Connection = "AzureW
var nodeHeartbeatEvent = new EventNodeHeartbeat(node.MachineId, node.ScalesetId, node.PoolName, node.State);
// TODO: do we still send event if we fail do update the table ?
await events.SendEvent(nodeHeartbeatEvent);
- if (await _context.FeatureManagerSnapshot.IsEnabledAsync(FeatureFlagConstants.EnableCustomMetricTelemetry)) {
- metrics.SendMetric(1, nodeHeartbeatEvent);
- }
+ metrics.SendMetric(1, nodeHeartbeatEvent);
+
}
}
diff --git a/src/ApiService/ApiService/Functions/QueueTaskHeartbeat.cs b/src/ApiService/ApiService/Functions/QueueTaskHeartbeat.cs
index 6eba20c9cf..850e77f71f 100644
--- a/src/ApiService/ApiService/Functions/QueueTaskHeartbeat.cs
+++ b/src/ApiService/ApiService/Functions/QueueTaskHeartbeat.cs
@@ -45,8 +45,7 @@ public async Async.Task Run([QueueTrigger("task-heartbeat", Connection = "AzureW
var taskHeartBeatEvent = new EventTaskHeartbeat(newTask.JobId, newTask.TaskId, job.Config.Project, job.Config.Name, newTask.State, newTask.Config);
await _events.SendEvent(taskHeartBeatEvent);
- if (await _context.FeatureManagerSnapshot.IsEnabledAsync(FeatureFlagConstants.EnableCustomMetricTelemetry)) {
- _metrics.SendMetric(1, taskHeartBeatEvent);
- }
+ _metrics.SendMetric(1, taskHeartBeatEvent);
+
}
}
diff --git a/src/ApiService/ApiService/OneFuzzTypes/Enums.cs b/src/ApiService/ApiService/OneFuzzTypes/Enums.cs
index b1f7225b5f..4692debfe8 100644
--- a/src/ApiService/ApiService/OneFuzzTypes/Enums.cs
+++ b/src/ApiService/ApiService/OneFuzzTypes/Enums.cs
@@ -49,6 +49,8 @@ public enum ErrorCode {
ADO_VALIDATION_MISSING_PAT_SCOPES = 492,
ADO_WORKITEM_PROCESSING_DISABLED = 494,
ADO_VALIDATION_INVALID_PATH = 495,
+ ADO_VALIDATION_INVALID_PROJECT = 496,
+ INVALID_RETENTION_PERIOD = 497,
// NB: if you update this enum, also update enums.py
}
diff --git a/src/ApiService/ApiService/OneFuzzTypes/Events.cs b/src/ApiService/ApiService/OneFuzzTypes/Events.cs
index d81e083db4..b06391f12f 100644
--- a/src/ApiService/ApiService/OneFuzzTypes/Events.cs
+++ b/src/ApiService/ApiService/OneFuzzTypes/Events.cs
@@ -124,7 +124,8 @@ TaskConfig Config
public record EventJobCreated(
Guid JobId,
JobConfig Config,
- StoredUserInfo? UserInfo
+ StoredUserInfo? UserInfo,
+ string OneFuzzVersion
) : BaseEvent();
diff --git a/src/ApiService/ApiService/OneFuzzTypes/Model.cs b/src/ApiService/ApiService/OneFuzzTypes/Model.cs
index b839f52ddc..3d67de106d 100644
--- a/src/ApiService/ApiService/OneFuzzTypes/Model.cs
+++ b/src/ApiService/ApiService/OneFuzzTypes/Model.cs
@@ -678,7 +678,8 @@ public record ADODuplicateTemplate(
Dictionary SetState,
Dictionary AdoFields,
string? Comment = null,
- List>? Unless = null
+ List>? Unless = null,
+ List? RegressionIgnoreStates = null
);
public record AdoTemplate(
@@ -689,6 +690,7 @@ public record AdoTemplate(
List UniqueFields,
Dictionary AdoFields,
ADODuplicateTemplate OnDuplicate,
+ Dictionary? AdoDuplicateFields = null,
string? Comment = null
) : NotificationTemplate {
public async Task Validate() {
@@ -704,8 +706,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/OneFuzzTypes/Requests.cs b/src/ApiService/ApiService/OneFuzzTypes/Requests.cs
index 8f3d16aa63..f3cc407b15 100644
--- a/src/ApiService/ApiService/OneFuzzTypes/Requests.cs
+++ b/src/ApiService/ApiService/OneFuzzTypes/Requests.cs
@@ -131,7 +131,7 @@ public record NotificationSearch(
public record NotificationTest(
- [property: Required] Report Report,
+ [property: Required] IReport Report,
[property: Required] Notification Notification
) : BaseRequest;
diff --git a/src/ApiService/ApiService/onefuzzlib/NotificationOperations.cs b/src/ApiService/ApiService/onefuzzlib/NotificationOperations.cs
index dc133e1fba..0eca5b1e00 100644
--- a/src/ApiService/ApiService/onefuzzlib/NotificationOperations.cs
+++ b/src/ApiService/ApiService/onefuzzlib/NotificationOperations.cs
@@ -22,13 +22,12 @@ public NotificationOperations(ILogger log, IOnefuzzConte
}
public async Async.Task NewFiles(Container container, string filename, bool isLastRetryAttempt) {
- var result = OneFuzzResultVoid.Ok;
-
// We don't want to store file added events for the events container because that causes an infinite loop
if (container == WellKnownContainers.Events) {
- return result;
+ return Result.Ok();
}
+ var result = OneFuzzResultVoid.Ok;
var notifications = GetNotifications(container);
var hasNotifications = await notifications.AnyAsync();
var reportOrRegression = await _context.Reports.GetReportOrRegression(container, filename, expectReports: hasNotifications);
diff --git a/src/ApiService/ApiService/onefuzzlib/RententionPolicy.cs b/src/ApiService/ApiService/onefuzzlib/RententionPolicy.cs
deleted file mode 100644
index 4052db93e1..0000000000
--- a/src/ApiService/ApiService/onefuzzlib/RententionPolicy.cs
+++ /dev/null
@@ -1,24 +0,0 @@
-namespace Microsoft.OneFuzz.Service;
-
-
-public interface IRetentionPolicy {
- DateOnly GetExpiryDate();
-}
-
-public class RetentionPolicyUtils {
- public const string EXPIRY_TAG = "Expiry";
- public static KeyValuePair CreateExpiryDateTag(DateOnly expiryDate) =>
- new(EXPIRY_TAG, expiryDate.ToString());
-
- public static DateOnly? GetExpiryDateTagFromTags(IDictionary? blobTags) {
- if (blobTags != null &&
- blobTags.TryGetValue(EXPIRY_TAG, out var expiryTag) &&
- !string.IsNullOrWhiteSpace(expiryTag) &&
- DateOnly.TryParse(expiryTag, out var expiryDate)) {
- return expiryDate;
- }
- return null;
- }
-
- public static string CreateExpiredBlobTagFilter() => $@"""{EXPIRY_TAG}"" <= '{DateOnly.FromDateTime(DateTime.UtcNow)}'";
-}
diff --git a/src/ApiService/ApiService/onefuzzlib/Reports.cs b/src/ApiService/ApiService/onefuzzlib/Reports.cs
index c1c4aad3be..fdda7259e9 100644
--- a/src/ApiService/ApiService/onefuzzlib/Reports.cs
+++ b/src/ApiService/ApiService/onefuzzlib/Reports.cs
@@ -1,7 +1,10 @@
using System.Text.Json;
+using System.Text.Json.Serialization;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Microsoft.OneFuzz.Service.OneFuzzLib.Orm;
+
+
namespace Microsoft.OneFuzz.Service;
public interface IReports {
@@ -85,6 +88,7 @@ public static IReport ParseReportOrRegression(string content, Uri reportUrl) {
}
}
+[JsonConverter(typeof(ReportConverter))]
public interface IReport {
Uri? ReportUrl {
init;
@@ -95,3 +99,19 @@ public string FileName() {
return string.Concat(segments);
}
};
+
+public class ReportConverter : JsonConverter {
+
+ public override IReport? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) {
+ using var templateJson = JsonDocument.ParseValue(ref reader);
+
+ if (templateJson.RootElement.TryGetProperty("crash_test_result", out _)) {
+ return templateJson.Deserialize(options);
+ }
+ return templateJson.Deserialize(options);
+ }
+
+ public override void Write(Utf8JsonWriter writer, IReport value, JsonSerializerOptions options) {
+ throw new NotImplementedException();
+ }
+}
diff --git a/src/ApiService/ApiService/onefuzzlib/RetentionPolicy.cs b/src/ApiService/ApiService/onefuzzlib/RetentionPolicy.cs
new file mode 100644
index 0000000000..48d81df5c7
--- /dev/null
+++ b/src/ApiService/ApiService/onefuzzlib/RetentionPolicy.cs
@@ -0,0 +1,43 @@
+using System.Xml;
+
+namespace Microsoft.OneFuzz.Service;
+
+
+public interface IRetentionPolicy {
+ DateOnly GetExpiryDate();
+}
+
+public class RetentionPolicyUtils {
+ public const string EXPIRY_TAG = "Expiry";
+ public static KeyValuePair CreateExpiryDateTag(DateOnly expiryDate) =>
+ new(EXPIRY_TAG, expiryDate.ToString());
+
+ public static DateOnly? GetExpiryDateTagFromTags(IDictionary? blobTags) {
+ if (blobTags != null &&
+ blobTags.TryGetValue(EXPIRY_TAG, out var expiryTag) &&
+ !string.IsNullOrWhiteSpace(expiryTag) &&
+ DateOnly.TryParse(expiryTag, out var expiryDate)) {
+ return expiryDate;
+ }
+ return null;
+ }
+
+ public static string CreateExpiredBlobTagFilter() => $@"""{EXPIRY_TAG}"" <= '{DateOnly.FromDateTime(DateTime.UtcNow)}'";
+
+ // NB: this must match the value used on the CLI side
+ public const string CONTAINER_RETENTION_KEY = "onefuzz_retentionperiod";
+
+ public static OneFuzzResult GetContainerRetentionPeriodFromMetadata(IDictionary? containerMetadata) {
+ if (containerMetadata is not null &&
+ containerMetadata.TryGetValue(CONTAINER_RETENTION_KEY, out var retentionString) &&
+ !string.IsNullOrWhiteSpace(retentionString)) {
+ try {
+ return Result.Ok(XmlConvert.ToTimeSpan(retentionString));
+ } catch (Exception ex) {
+ return Error.Create(ErrorCode.INVALID_RETENTION_PERIOD, ex.Message);
+ }
+ }
+
+ return Result.Ok(null);
+ }
+}
diff --git a/src/ApiService/ApiService/onefuzzlib/notifications/Ado.cs b/src/ApiService/ApiService/onefuzzlib/notifications/Ado.cs
index e05bb9bc24..2b01afb37f 100644
--- a/src/ApiService/ApiService/onefuzzlib/notifications/Ado.cs
+++ b/src/ApiService/ApiService/onefuzzlib/notifications/Ado.cs
@@ -19,6 +19,7 @@ public class Ado : NotificationsBase, IAdo {
// https://github.com/MicrosoftDocs/azure-devops-docs/issues/5890#issuecomment-539632059
private const int MAX_SYSTEM_TITLE_LENGTH = 128;
private const string TITLE_FIELD = "System.Title";
+ private static List DEFAULT_REGRESSION_IGNORE_STATES = new() { "New", "Commited", "Active" };
public Ado(ILogger logTracer, IOnefuzzContext context) : base(logTracer, context) {
}
@@ -56,7 +57,7 @@ public async Async.Task NotifyAdo(AdoTemplate config, Contain
_logTracer.LogEvent(adoEventType);
try {
- await ProcessNotification(_context, container, filename, config, report, _logTracer, notificationInfo);
+ await ProcessNotification(_context, container, filename, config, report, _logTracer, notificationInfo, isRegression: reportable is RegressionReport);
} catch (Exception e)
when (e is VssUnauthorizedException || e is VssAuthenticationException || e is VssServiceException) {
if (config.AdoFields.TryGetValue("System.AssignedTo", out var assignedTo)) {
@@ -89,30 +90,97 @@ private static bool IsTransient(Exception e) {
return errorCodes.Any(errorStr.Contains);
}
- private static async Async.Task ValidatePath(string project, string path, TreeStructureGroup structureGroup, WorkItemTrackingHttpClient client) {
- var pathType = (structureGroup == TreeStructureGroup.Areas) ? "Area" : "Iteration";
- var pathParts = path.Split('\\');
- if (!string.Equals(pathParts[0], project, StringComparison.OrdinalIgnoreCase)) {
+ public static OneFuzzResultVoid ValidateTreePath(IEnumerable path, WorkItemClassificationNode? root) {
+ if (root is null) {
+ return OneFuzzResultVoid.Error(ErrorCode.ADO_VALIDATION_INVALID_PROJECT, new string[] {
+ $"Path \"{string.Join('\\', path)}\" is invalid. The specified ADO project doesn't exist.",
+ "Double check the 'project' field in your ADO config.",
+ });
+ }
+
+ string treeNodeTypeName;
+ switch (root.StructureType) {
+ case TreeNodeStructureType.Area:
+ treeNodeTypeName = "Area";
+ break;
+ case TreeNodeStructureType.Iteration:
+ treeNodeTypeName = "Iteration";
+ break;
+ default:
+ return OneFuzzResultVoid.Error(ErrorCode.ADO_VALIDATION_INVALID_PATH, new string[] {
+ $"Path root \"{root.Name}\" is an unsupported type. Expected Area or Iteration but got {root.StructureType}.",
+ });
+ }
+
+ // Validate path based on
+ // https://learn.microsoft.com/en-us/azure/devops/organizations/settings/about-areas-iterations?view=azure-devops#naming-restrictions
+ var maxNodeLength = 255;
+ var maxDepth = 13;
+ // Invalid characters from the link above plus the escape sequences (since they have backslashes and produce confusingly formatted errors if not caught here)
+ var invalidChars = new char[] { '/', ':', '*', '?', '"', '<', '>', '|', ';', '#', '$', '*', '{', '}', ',', '+', '=', '[', ']' };
+
+ // Ensure that none of the path parts are too long
+ var erroneous = path.FirstOrDefault(part => part.Length > maxNodeLength);
+ if (erroneous != null) {
+ return OneFuzzResultVoid.Error(ErrorCode.ADO_VALIDATION_INVALID_PATH, new string[] {
+ $"{treeNodeTypeName} Path \"{string.Join('\\', path)}\" is invalid. \"{erroneous}\" is too long. It must be less than {maxNodeLength} characters.",
+ "Learn more about naming restrictions here: https://learn.microsoft.com/en-us/azure/devops/organizations/settings/about-areas-iterations?view=azure-devops#naming-restrictions"
+ });
+ }
+
+ // Ensure that none of the path parts contain invalid characters
+ erroneous = path.FirstOrDefault(part => invalidChars.Any(part.Contains));
+ if (erroneous != null) {
return OneFuzzResultVoid.Error(ErrorCode.ADO_VALIDATION_INVALID_PATH, new string[] {
- $"Path \"{path}\" is invalid. It must start with the project name, \"{project}\".",
- $"Example: \"{project}\\{path}\".",
+ $"{treeNodeTypeName} Path \"{string.Join('\\', path)}\" is invalid. \"{erroneous}\" contains an invalid character ({string.Join(" ", invalidChars)}).",
+ "Make sure that the path is separated by backslashes (\\) and not forward slashes (/).",
+ "Learn more about naming restrictions here: https://learn.microsoft.com/en-us/azure/devops/organizations/settings/about-areas-iterations?view=azure-devops#naming-restrictions"
});
}
- var current = await client.GetClassificationNodeAsync(project, structureGroup, depth: pathParts.Length - 1);
- if (current == null) {
+ // Ensure no unicode control characters
+ erroneous = path.FirstOrDefault(part => part.Any(ch => char.IsControl(ch)));
+ if (erroneous != null) {
return OneFuzzResultVoid.Error(ErrorCode.ADO_VALIDATION_INVALID_PATH, new string[] {
- $"{pathType} Path \"{path}\" is invalid. \"{project}\" is not a valid project.",
+ // More about control codes and their range here: https://en.wikipedia.org/wiki/Unicode_control_characters
+ $"{treeNodeTypeName} Path \"{string.Join('\\', path)}\" is invalid. \"{erroneous}\" contains a unicode control character (\\u0000 - \\u001F or \\u007F - \\u009F).",
+ "Make sure that you're path doesn't contain any escape characters (\\0 \\a \\b \\f \\n \\r \\t \\v).",
+ "Learn more about naming restrictions here: https://learn.microsoft.com/en-us/azure/devops/organizations/settings/about-areas-iterations?view=azure-devops#naming-restrictions"
});
}
- foreach (var part in pathParts.Skip(1)) {
+ // Ensure that there aren't too many path parts
+ if (path.Count() > maxDepth) {
+ return OneFuzzResultVoid.Error(ErrorCode.ADO_VALIDATION_INVALID_PATH, new string[] {
+ $"{treeNodeTypeName} Path \"{string.Join('\\', path)}\" is invalid. It must be less than {maxDepth} levels deep.",
+ "Learn more about naming restrictions here: https://learn.microsoft.com/en-us/azure/devops/organizations/settings/about-areas-iterations?view=azure-devops#naming-restrictions"
+ });
+ }
+
+
+ // Path should always start with the project name ADO expects an absolute path
+ if (!string.Equals(path.First(), root.Name, StringComparison.OrdinalIgnoreCase)) {
+ return OneFuzzResultVoid.Error(ErrorCode.ADO_VALIDATION_INVALID_PATH, new string[] {
+ $"{treeNodeTypeName} Path \"{string.Join('\\', path)}\" is invalid. It must start with the project name, \"{root.Name}\".",
+ $"Example: \"{root.Name}\\{path}\".",
+ });
+ }
+
+ // Validate that each part of the path is a valid child of the previous part
+ var current = root;
+ foreach (var part in path.Skip(1)) {
var child = current.Children?.FirstOrDefault(x => string.Equals(x.Name, part, StringComparison.OrdinalIgnoreCase));
if (child == null) {
- return OneFuzzResultVoid.Error(ErrorCode.ADO_VALIDATION_INVALID_PATH, new string[] {
- $"{pathType} Path \"{path}\" is invalid. \"{part}\" is not a valid child of \"{current.Name}\".",
- $"Valid children of \"{current.Name}\" are: [{string.Join(',', current.Children?.Select(x => $"\"{x.Name}\"") ?? new List())}].",
- });
+ if (current.Children is null || !current.Children.Any()) {
+ return OneFuzzResultVoid.Error(ErrorCode.ADO_VALIDATION_INVALID_PATH, new string[] {
+ $"{treeNodeTypeName} Path \"{string.Join('\\', path)}\" is invalid. \"{current.Name}\" has no children.",
+ });
+ } else {
+ return OneFuzzResultVoid.Error(ErrorCode.ADO_VALIDATION_INVALID_PATH, new string[] {
+ $"{treeNodeTypeName} Path \"{string.Join('\\', path)}\" is invalid. \"{part}\" is not a valid child of \"{current.Name}\".",
+ $"Valid children of \"{current.Name}\" are: [{string.Join(',', current.Children?.Select(x => $"\"{x.Name}\"") ?? new List())}].",
+ });
+ }
}
current = child;
@@ -195,14 +263,19 @@ await policy.ExecuteAsync(async () => {
try {
// Validate AreaPath and IterationPath exist
+ // This also validates that the config.Project exists
if (config.AdoFields.TryGetValue("System.AreaPath", out var areaPathString)) {
- var validateAreaPath = await ValidatePath(config.Project, areaPathString, TreeStructureGroup.Areas, witClient);
+ var path = areaPathString.Split('\\');
+ var root = await witClient.GetClassificationNodeAsync(config.Project, TreeStructureGroup.Areas, depth: path.Length - 1);
+ var validateAreaPath = ValidateTreePath(path, root);
if (!validateAreaPath.IsOk) {
return validateAreaPath;
}
}
if (config.AdoFields.TryGetValue("System.IterationPath", out var iterationPathString)) {
- var validateIterationPath = await ValidatePath(config.Project, iterationPathString, TreeStructureGroup.Iterations, witClient);
+ var path = iterationPathString.Split('\\');
+ var root = await witClient.GetClassificationNodeAsync(config.Project, TreeStructureGroup.Iterations, depth: path.Length - 1);
+ var validateIterationPath = ValidateTreePath(path, root);
if (!validateIterationPath.IsOk) {
return validateIterationPath;
}
@@ -226,7 +299,7 @@ private static async Async.Task> GetValidFiel
.ToDictionary(field => field.ReferenceName.ToLowerInvariant());
}
- private static async Async.Task ProcessNotification(IOnefuzzContext context, Container container, string filename, AdoTemplate config, Report report, ILogger logTracer, IList<(string, string)> notificationInfo, Renderer? renderer = null) {
+ private static async Async.Task ProcessNotification(IOnefuzzContext context, Container container, string filename, AdoTemplate config, Report report, ILogger logTracer, IList<(string, string)> notificationInfo, Renderer? renderer = null, bool isRegression = false) {
if (!config.AdoFields.TryGetValue(TITLE_FIELD, out var issueTitle)) {
issueTitle = "{{ report.crash_site }} - {{ report.executable }}";
}
@@ -239,7 +312,7 @@ private static async Async.Task ProcessNotification(IOnefuzzContext context, Con
var renderedConfig = RenderAdoTemplate(logTracer, renderer, config, instanceUrl);
var ado = new AdoConnector(renderedConfig, project!, client, instanceUrl, logTracer, await GetValidFields(client, project));
- await ado.Process(notificationInfo);
+ await ado.Process(notificationInfo, isRegression);
}
public static RenderedAdoTemplate RenderAdoTemplate(ILogger logTracer, Renderer renderer, AdoTemplate original, Uri instanceUrl) {
@@ -280,7 +353,8 @@ public static RenderedAdoTemplate RenderAdoTemplate(ILogger logTracer, Renderer
original.OnDuplicate.SetState,
onDuplicateAdoFields,
original.OnDuplicate.Comment != null ? Render(renderer, original.OnDuplicate.Comment, instanceUrl, logTracer) : null,
- onDuplicateUnless
+ onDuplicateUnless,
+ original.OnDuplicate.RegressionIgnoreStates
);
return new RenderedAdoTemplate(
@@ -291,6 +365,7 @@ public static RenderedAdoTemplate RenderAdoTemplate(ILogger logTracer, Renderer
original.UniqueFields,
adoFields,
onDuplicate,
+ original.AdoDuplicateFields,
original.Comment != null ? Render(renderer, original.Comment, instanceUrl, logTracer) : null
);
}
@@ -525,7 +600,7 @@ private async Async.Task CreateNew() {
return (taskType, document);
}
- public async Async.Task Process(IList<(string, string)> notificationInfo) {
+ public async Async.Task Process(IList<(string, string)> notificationInfo, bool isRegression) {
var updated = false;
WorkItem? oldestWorkItem = null;
await foreach (var workItem in ExistingWorkItems(notificationInfo)) {
@@ -535,10 +610,17 @@ 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;
}
+ var regressionStatesToIgnore = _config.OnDuplicate.RegressionIgnoreStates != null ? _config.OnDuplicate.RegressionIgnoreStates : DEFAULT_REGRESSION_IGNORE_STATES;
+ if (isRegression) {
+ var state = (string)workItem.Fields["System.State"];
+ if (regressionStatesToIgnore.Contains(state, StringComparer.InvariantCultureIgnoreCase))
+ continue;
+ }
+
using (_logTracer.BeginScope("Non-duplicate work item")) {
_logTracer.AddTags(new List<(string, string)> { ("NonDuplicateWorkItemId", $"{workItem.Id}") });
_logTracer.LogInformation("Found matching non-duplicate work item");
@@ -548,40 +630,46 @@ public async Async.Task Process(IList<(string, string)> notificationInfo) {
updated = true;
}
- if (!updated) {
- if (oldestWorkItem != null) {
- // We have matching work items but all are duplicates
- _logTracer.AddTags(notificationInfo);
- _logTracer.LogInformation($"All matching work items were duplicates, re-opening the oldest one");
- var stateChanged = await UpdateExisting(oldestWorkItem, notificationInfo);
- if (stateChanged) {
- // add a comment if we re-opened the bug
- _ = await _client.AddCommentAsync(
- new CommentCreate() {
- Text =
- "This work item was re-opened because OneFuzz could only find related work items that are marked as duplicate."
- },
- _project,
- (int)oldestWorkItem.Id!);
- }
- } else {
- // We never saw a work item like this before, it must be new
- var entry = await CreateNew();
- var adoEventType = "AdoNewItem";
- _logTracer.AddTags(notificationInfo);
- _logTracer.AddTag("WorkItemId", entry.Id.HasValue ? entry.Id.Value.ToString() : "");
- _logTracer.LogEvent(adoEventType);
+ if (updated || isRegression) {
+ return;
+ }
+
+ if (oldestWorkItem != null) {
+ // We have matching work items but all are duplicates
+ _logTracer.AddTags(notificationInfo);
+ _logTracer.LogInformation($"All matching work items were duplicates, re-opening the oldest one");
+ var stateChanged = await UpdateExisting(oldestWorkItem, notificationInfo);
+ if (stateChanged) {
+ // add a comment if we re-opened the bug
+ _ = await _client.AddCommentAsync(
+ new CommentCreate() {
+ Text =
+ "This work item was re-opened because OneFuzz could only find related work items that are marked as duplicate."
+ },
+ _project,
+ (int)oldestWorkItem.Id!);
}
+ } else {
+ // We never saw a work item like this before, it must be new
+ var entry = await CreateNew();
+ var adoEventType = "AdoNewItem";
+ _logTracer.AddTags(notificationInfo);
+ _logTracer.AddTag("WorkItemId", entry.Id.HasValue ? entry.Id.Value.ToString() : "");
+ _logTracer.LogEvent(adoEventType);
}
}
- 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/ApiService/Tests/TreePathTests.cs b/src/ApiService/Tests/TreePathTests.cs
new file mode 100644
index 0000000000..fba818793c
--- /dev/null
+++ b/src/ApiService/Tests/TreePathTests.cs
@@ -0,0 +1,148 @@
+using System.Collections.Generic;
+using System.Linq;
+using Microsoft.OneFuzz.Service;
+using Microsoft.TeamFoundation.WorkItemTracking.WebApi.Models;
+using Xunit;
+
+namespace Tests;
+
+// This might be a good candidate for property based testing
+// https://fscheck.github.io/FsCheck//QuickStart.html
+public class TreePathTests {
+ private static IEnumerable SplitPath(string path) {
+ return path.Split('\\');
+ }
+
+ private static WorkItemClassificationNode MockTreeNode(IEnumerable path, TreeNodeStructureType structureType) {
+ var root = new WorkItemClassificationNode() {
+ Name = path.First(),
+ StructureType = structureType
+ };
+
+ var current = root;
+ foreach (var segment in path.Skip(1)) {
+ var child = new WorkItemClassificationNode {
+ Name = segment
+ };
+ current.Children = new[] { child };
+ current = child;
+ }
+
+ return root;
+ }
+
+
+ [Fact]
+ public void TestValidPath() {
+ var path = SplitPath(@"project\foo\bar\baz");
+ var root = MockTreeNode(path, TreeNodeStructureType.Area);
+
+ var result = Ado.ValidateTreePath(path, root);
+
+ Assert.True(result.IsOk);
+ }
+
+ [Fact]
+ public void TestNullTreeNode() { // A null tree node indicates an invalid ADO project was used in the query
+ var path = SplitPath(@"project\foo\bar\baz");
+
+ var result = Ado.ValidateTreePath(path, null);
+
+ Assert.False(result.IsOk);
+ Assert.Equal(ErrorCode.ADO_VALIDATION_INVALID_PROJECT, result.ErrorV!.Code);
+ Assert.Contains("ADO project doesn't exist", result.ErrorV!.Errors![0]);
+ }
+
+ [Fact]
+ public void TestPathPartTooLong() {
+ var path = SplitPath(@"project\foo\barbazquxquuxcorgegraultgarplywaldofredplughxyzzythudbarbazquxquuxcorgegraultgarplywaldofredplughxyzzythudbarbazquxquuxcorgegraultgarplywaldofredplughxyzzythudbarbazquxquuxcorgegraultgarplywaldofredplughxyzzythudbarbazquxquuxcorgegraultgarplywaldofredplughxyzzythud\baz");
+ var root = MockTreeNode(path, TreeNodeStructureType.Iteration);
+
+ var result = Ado.ValidateTreePath(path, root);
+
+ Assert.False(result.IsOk);
+ Assert.Equal(ErrorCode.ADO_VALIDATION_INVALID_PATH, result.ErrorV!.Code);
+ Assert.Contains("too long", result.ErrorV!.Errors![0]);
+ }
+
+ [Theory]
+ [InlineData("project/foo/bar/baz")]
+ [InlineData("project\\foo:\\bar\\baz")]
+ public void TestPathContainsInvalidChar(string invalidPath) {
+ var path = SplitPath(invalidPath);
+ var treePath = SplitPath(@"project\foo\bar\baz");
+ var root = MockTreeNode(treePath, TreeNodeStructureType.Area);
+
+ var result = Ado.ValidateTreePath(path, root);
+
+ Assert.False(result.IsOk);
+ Assert.Equal(ErrorCode.ADO_VALIDATION_INVALID_PATH, result.ErrorV!.Code);
+ Assert.Contains("invalid character", result.ErrorV!.Errors![0]);
+ }
+
+ [Theory]
+ [InlineData("project\\foo\\ba\u0005r\\baz")]
+ [InlineData("project\\\nfoo\\bar\\baz")]
+ public void TestPathContainsUnicodeControlChar(string invalidPath) {
+ var path = SplitPath(invalidPath);
+ var treePath = SplitPath(@"project\foo\bar\baz");
+ var root = MockTreeNode(treePath, TreeNodeStructureType.Area);
+
+ var result = Ado.ValidateTreePath(path, root);
+
+ Assert.False(result.IsOk);
+ Assert.Equal(ErrorCode.ADO_VALIDATION_INVALID_PATH, result.ErrorV!.Code);
+ Assert.Contains("unicode control character", result.ErrorV!.Errors![0]);
+ }
+
+ [Fact]
+ public void TestPathTooDeep() {
+ var path = SplitPath(@"project\foo\bar\baz\qux\quux\corge\grault\garply\waldo\fred\plugh\xyzzy\thud");
+ var root = MockTreeNode(path, TreeNodeStructureType.Area);
+
+ var result = Ado.ValidateTreePath(path, root);
+
+ Assert.False(result.IsOk);
+ Assert.Equal(ErrorCode.ADO_VALIDATION_INVALID_PATH, result.ErrorV!.Code);
+ Assert.Contains("levels deep", result.ErrorV!.Errors![0]);
+ }
+
+ [Fact]
+ public void TestPathWithoutProjectName() {
+ var path = SplitPath(@"foo\bar\baz");
+ var treePath = SplitPath(@"project\foo\bar\baz");
+ var root = MockTreeNode(treePath, TreeNodeStructureType.Iteration);
+
+ var result = Ado.ValidateTreePath(path, root);
+
+ Assert.False(result.IsOk);
+ Assert.Equal(ErrorCode.ADO_VALIDATION_INVALID_PATH, result.ErrorV!.Code);
+ Assert.Contains("start with the project name", result.ErrorV!.Errors![0]);
+ }
+
+ [Fact]
+ public void TestPathWithInvalidChild() {
+ var path = SplitPath(@"project\foo\baz");
+ var treePath = SplitPath(@"project\foo\bar");
+ var root = MockTreeNode(treePath, TreeNodeStructureType.Iteration);
+
+ var result = Ado.ValidateTreePath(path, root);
+
+ Assert.False(result.IsOk);
+ Assert.Equal(ErrorCode.ADO_VALIDATION_INVALID_PATH, result.ErrorV!.Code);
+ Assert.Contains("not a valid child", result.ErrorV!.Errors![0]);
+ }
+
+ [Fact]
+ public void TestPathWithExtraChild() {
+ var path = SplitPath(@"project\foo\bar\baz");
+ var treePath = SplitPath(@"project\foo\bar");
+ var root = MockTreeNode(treePath, TreeNodeStructureType.Iteration);
+
+ var result = Ado.ValidateTreePath(path, root);
+
+ Assert.False(result.IsOk);
+ Assert.Equal(ErrorCode.ADO_VALIDATION_INVALID_PATH, result.ErrorV!.Code);
+ Assert.Contains("has no children", result.ErrorV!.Errors![0]);
+ }
+}
diff --git a/src/agent/Cargo.lock b/src/agent/Cargo.lock
index 254684be97..eb35241201 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.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "86b0f0eea648347e40f5f7f7e6bfea4553bcefad0fbf52044ea339e5ce3aba61"
+checksum = "1f20eb684aea745292c540173304383c9cba9697d1c31d307620a57d6f878fa9"
dependencies = [
"async-trait",
"base64 0.21.2",
@@ -214,7 +213,7 @@ dependencies = [
"log",
"paste",
"pin-project",
- "quick-xml 0.29.0",
+ "quick-xml",
"rand 0.8.5",
"reqwest",
"rustc_version",
@@ -227,9 +226,9 @@ dependencies = [
[[package]]
name = "azure_storage"
-version = "0.13.0"
+version = "0.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "32d9cfa13ed9acb51cd663e04f343bd550a92b455add96c90de387a9a6bc4dbc"
+checksum = "bf64f9d78e573f64e189fa7188c4e6a0f605e27740105a8d32038b3ba8c913be"
dependencies = [
"RustyXML",
"async-trait",
@@ -250,9 +249,9 @@ dependencies = [
[[package]]
name = "azure_storage_blobs"
-version = "0.13.1"
+version = "0.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "57cb0fe58af32a3fb49e560613cb1e4937f9f13161a2c1caf1bba0224435f2af"
+checksum = "a61299a8b65b88acba1a079a0b5e8a39970a12cb53e35ada2641687edb022d5a"
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,7 @@ name = "cobertura"
version = "0.1.0"
dependencies = [
"anyhow",
- "quick-xml 0.30.0",
+ "quick-xml",
]
[[package]]
@@ -575,6 +572,7 @@ dependencies = [
"nix",
"pete",
"pretty_assertions",
+ "process_control",
"procfs",
"regex",
"symbolic",
@@ -749,7 +747,7 @@ dependencies = [
"anyhow",
"clap",
"elsa",
- "gimli",
+ "gimli 0.28.0",
"goblin",
"iced-x86",
"log",
@@ -863,7 +861,7 @@ dependencies = [
"regex",
"thiserror",
"windows",
- "winreg 0.50.0",
+ "winreg 0.51.0",
]
[[package]]
@@ -883,9 +881,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 +966,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 +1261,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",
]
@@ -1590,9 +1604,9 @@ dependencies = [
[[package]]
name = "insta"
-version = "1.31.0"
+version = "1.32.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a0770b0a3d4c70567f0d58331f3088b0e4c4f56c9b8d764efe654b4a5d46de3a"
+checksum = "a3e02c584f4595792d09509a94cdb92a3cef7592b1eb2d9877ee6f527062d0ea"
dependencies = [
"console",
"globset",
@@ -2035,18 +2049,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 +2166,7 @@ dependencies = [
"urlparse",
"uuid",
"windows",
- "winreg 0.50.0",
+ "winreg 0.51.0",
]
[[package]]
@@ -2193,7 +2208,7 @@ dependencies = [
"coverage",
"debuggable-module",
"pretty_assertions",
- "quick-xml 0.30.0",
+ "quick-xml",
"serde",
"serde_json",
]
@@ -2403,7 +2418,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 +2451,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",
@@ -2587,16 +2602,6 @@ dependencies = [
"snafu",
]
-[[package]]
-name = "quick-xml"
-version = "0.29.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "81b9228215d82c7b61490fec1de287136b5de6f5700f6e58ea9ad61a7964ca51"
-dependencies = [
- "memchr",
- "serde",
-]
-
[[package]]
name = "quick-xml"
version = "0.30.0"
@@ -2724,9 +2729,9 @@ dependencies = [
[[package]]
name = "rayon"
-version = "1.7.0"
+version = "1.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1d2df5196e37bcc87abebc0053e20787d73847bb33134a69841207dd0a47f03b"
+checksum = "9c27db03db7734835b3f53954b534c91069375ce6ccaa2e065441e07d9b6cdb1"
dependencies = [
"either",
"rayon-core",
@@ -2734,14 +2739,12 @@ dependencies = [
[[package]]
name = "rayon-core"
-version = "1.11.0"
+version = "1.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4b8f95bd6966f5c87776639160a66bd8ab9895d9d4ab01ddba9fc60661aebe8d"
+checksum = "5ce3fb6ad83f861aac485e76e1985cd109d9a3713802152be56c3b1f0e0658ed"
dependencies = [
- "crossbeam-channel",
"crossbeam-deque",
"crossbeam-utils",
- "num_cpus",
]
[[package]]
@@ -3278,7 +3281,7 @@ dependencies = [
"flume",
"num_cpus",
"queue-file",
- "quick-xml 0.30.0",
+ "quick-xml",
"regex",
"reqwest",
"reqwest-retry",
@@ -3368,9 +3371,9 @@ dependencies = [
"dmsort",
"elementtree",
"elsa",
- "fallible-iterator",
+ "fallible-iterator 0.2.0",
"flate2",
- "gimli",
+ "gimli 0.27.3",
"goblin",
"lazy_static",
"nom",
@@ -3468,9 +3471,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 +3558,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 +3953,7 @@ dependencies = [
"os_pipe",
"tempfile",
"windows",
- "winreg 0.50.0",
+ "winreg 0.51.0",
]
[[package]]
@@ -4148,9 +4151,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/coverage/Cargo.toml b/src/agent/coverage/Cargo.toml
index 29e67523d9..e1ced7050f 100644
--- a/src/agent/coverage/Cargo.toml
+++ b/src/agent/coverage/Cargo.toml
@@ -20,23 +20,24 @@ symbolic = { version = "12.3", features = [
"symcache",
] }
thiserror = "1.0"
+process_control = "4.0"
[target.'cfg(target_os = "windows")'.dependencies]
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"] }
+insta = { version = "1.32.0", features = ["glob"] }
coverage = { path = "../coverage" }
cc = "1.0"
-tempfile = "3.7.0"
+tempfile = "3.8.0"
dunce = "1.0"
diff --git a/src/agent/coverage/fuzz/.gitignore b/src/agent/coverage/fuzz/.gitignore
new file mode 100644
index 0000000000..1a45eee776
--- /dev/null
+++ b/src/agent/coverage/fuzz/.gitignore
@@ -0,0 +1,4 @@
+target
+corpus
+artifacts
+coverage
diff --git a/src/agent/coverage/fuzz/Cargo.lock b/src/agent/coverage/fuzz/Cargo.lock
new file mode 100644
index 0000000000..6096b84473
--- /dev/null
+++ b/src/agent/coverage/fuzz/Cargo.lock
@@ -0,0 +1,1480 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 3
+
+[[package]]
+name = "addr2line"
+version = "0.20.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f4fa78e18c64fce05e902adecd7a5eed15a5e0a3439f7b0e169f0252214865e3"
+dependencies = [
+ "gimli 0.27.3",
+]
+
+[[package]]
+name = "adler"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
+
+[[package]]
+name = "aho-corasick"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "43f6cb1bf222025340178f382c426f13757b2960e89779dfcb319c32542a5a41"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "anyhow"
+version = "1.0.72"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3b13c32d80ecc7ab747b80c3784bce54ee8a7a0cc4fbda9bf4cda2cf6fe90854"
+dependencies = [
+ "backtrace",
+]
+
+[[package]]
+name = "arbitrary"
+version = "1.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e2d098ff73c1ca148721f37baad5ea6a465a13f9573aba8641fbbbae8164a54e"
+
+[[package]]
+name = "arrayvec"
+version = "0.7.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711"
+
+[[package]]
+name = "atexit"
+version = "0.1.0"
+dependencies = [
+ "ctrlc",
+ "lazy_static",
+ "log",
+]
+
+[[package]]
+name = "autocfg"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
+
+[[package]]
+name = "backtrace"
+version = "0.3.68"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4319208da049c43661739c5fade2ba182f09d1dc2299b32298d3a31692b17e12"
+dependencies = [
+ "addr2line",
+ "cc",
+ "cfg-if",
+ "libc",
+ "miniz_oxide",
+ "object",
+ "rustc-demangle",
+]
+
+[[package]]
+name = "binary-merge"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "597bb81c80a54b6a4381b23faba8d7774b144c94cbd1d6fe3f1329bd776554ab"
+
+[[package]]
+name = "bitflags"
+version = "1.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
+
+[[package]]
+name = "bitflags"
+version = "2.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "630be753d4e58660abd17930c71b647fe46c27ea6b63cc59e1e3851406972e42"
+
+[[package]]
+name = "brownstone"
+version = "3.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c5839ee4f953e811bfdcf223f509cb2c6a3e1447959b0bff459405575bc17f22"
+dependencies = [
+ "arrayvec",
+]
+
+[[package]]
+name = "byteorder"
+version = "1.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610"
+
+[[package]]
+name = "cc"
+version = "1.0.79"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f"
+dependencies = [
+ "jobserver",
+]
+
+[[package]]
+name = "cfg-if"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
+
+[[package]]
+name = "cobertura"
+version = "0.1.0"
+dependencies = [
+ "anyhow",
+ "quick-xml",
+]
+
+[[package]]
+name = "coverage"
+version = "0.1.0"
+dependencies = [
+ "anyhow",
+ "cobertura",
+ "debuggable-module",
+ "debugger",
+ "iced-x86",
+ "log",
+ "nix",
+ "pete",
+ "procfs",
+ "regex",
+ "symbolic",
+ "thiserror",
+]
+
+[[package]]
+name = "coverage-fuzz"
+version = "0.0.0"
+dependencies = [
+ "coverage",
+ "debuggable-module",
+ "libfuzzer-sys",
+ "tempfile",
+]
+
+[[package]]
+name = "cpp_demangle"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ee34052ee3d93d6d8f3e6f81d85c47921f6653a19a7b70e939e3e602d893a674"
+dependencies = [
+ "cfg-if",
+]
+
+[[package]]
+name = "crc32fast"
+version = "1.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d"
+dependencies = [
+ "cfg-if",
+]
+
+[[package]]
+name = "crossbeam-utils"
+version = "0.8.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5a22b2d63d4d1dc0b7f1b6b2747dd0088008a9be28b6ddf0b1e7d335e3037294"
+dependencies = [
+ "cfg-if",
+]
+
+[[package]]
+name = "ctrlc"
+version = "3.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2a011bbe2c35ce9c1f143b7af6f94f29a167beb4cd1d29e6740ce836f723120e"
+dependencies = [
+ "nix",
+ "windows-sys 0.48.0",
+]
+
+[[package]]
+name = "debuggable-module"
+version = "0.1.0"
+dependencies = [
+ "anyhow",
+ "elsa",
+ "gimli 0.28.0",
+ "goblin 0.6.1",
+ "iced-x86",
+ "log",
+ "pdb",
+ "regex",
+ "symbolic",
+ "thiserror",
+]
+
+[[package]]
+name = "debugger"
+version = "0.1.0"
+dependencies = [
+ "anyhow",
+ "fnv",
+ "goblin 0.6.1",
+ "iced-x86",
+ "log",
+ "memmap2 0.7.1",
+ "rand",
+ "serde",
+ "win-util",
+ "windows",
+]
+
+[[package]]
+name = "debugid"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bef552e6f588e446098f6ba40d89ac146c8c7b64aade83c051ee00bb5d2bc18d"
+dependencies = [
+ "serde",
+ "uuid",
+]
+
+[[package]]
+name = "dmsort"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f0bc8fbe9441c17c9f46f75dfe27fa1ddb6c68a461ccaed0481419219d4f10d3"
+
+[[package]]
+name = "elementtree"
+version = "1.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3efd4742acf458718a6456e0adf0b4d734d6b783e452bbf1ac36bf31f4085cb3"
+dependencies = [
+ "string_cache",
+]
+
+[[package]]
+name = "elsa"
+version = "1.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "714f766f3556b44e7e4776ad133fcc3445a489517c25c704ace411bb14790194"
+dependencies = [
+ "stable_deref_trait",
+]
+
+[[package]]
+name = "equivalent"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
+
+[[package]]
+name = "errno"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4bcfec3a70f97c962c307b2d2c56e358cf1d00b558d74262b5f929ee8cc7e73a"
+dependencies = [
+ "errno-dragonfly",
+ "libc",
+ "windows-sys 0.48.0",
+]
+
+[[package]]
+name = "errno-dragonfly"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf"
+dependencies = [
+ "cc",
+ "libc",
+]
+
+[[package]]
+name = "fallible-iterator"
+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 = "2.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6999dc1837253364c2ebb0704ba97994bd874e8f195d665c50b7548f6ea92764"
+
+[[package]]
+name = "flate2"
+version = "1.0.26"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3b9429470923de8e8cbd4d2dc513535400b4b3fef0319fb5c4e1f520a7bef743"
+dependencies = [
+ "crc32fast",
+ "miniz_oxide",
+]
+
+[[package]]
+name = "fnv"
+version = "1.0.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
+
+[[package]]
+name = "form_urlencoded"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a62bc1cf6f830c2ec14a513a9fb124d0a213a629668a4186f329db21fe045652"
+dependencies = [
+ "percent-encoding",
+]
+
+[[package]]
+name = "getrandom"
+version = "0.2.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "wasi",
+]
+
+[[package]]
+name = "gimli"
+version = "0.27.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b6c80984affa11d98d1b88b66ac8853f143217b399d3c74116778ff8fdb4ed2e"
+
+[[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",
+]
+
+[[package]]
+name = "goblin"
+version = "0.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0d6b4de4a8eb6c46a8c77e1d3be942cb9a8bf073c22374578e5ba4b08ed0ff68"
+dependencies = [
+ "log",
+ "plain",
+ "scroll",
+]
+
+[[package]]
+name = "goblin"
+version = "0.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f27c1b4369c2cd341b5de549380158b105a04c331be5db9110eef7b6d2742134"
+dependencies = [
+ "log",
+ "plain",
+ "scroll",
+]
+
+[[package]]
+name = "hashbrown"
+version = "0.12.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
+
+[[package]]
+name = "hashbrown"
+version = "0.14.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a"
+
+[[package]]
+name = "hermit-abi"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "443144c8cdadd93ebf52ddb4056d257f5b52c04d3c804e657d19eb73fc33668b"
+
+[[package]]
+name = "hex"
+version = "0.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
+
+[[package]]
+name = "iced-x86"
+version = "1.20.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cdd366a53278429c028367e0ba22a46cab6d565a57afb959f06e92c7a69e7828"
+dependencies = [
+ "lazy_static",
+]
+
+[[package]]
+name = "idna"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7d20d6b07bfbc108882d88ed8e37d39636dcc260e15e30c45e6ba089610b917c"
+dependencies = [
+ "unicode-bidi",
+ "unicode-normalization",
+]
+
+[[package]]
+name = "indent_write"
+version = "2.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0cfe9645a18782869361d9c8732246be7b410ad4e919d3609ebabdac00ba12c3"
+
+[[package]]
+name = "indexmap"
+version = "1.9.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99"
+dependencies = [
+ "autocfg",
+ "hashbrown 0.12.3",
+]
+
+[[package]]
+name = "indexmap"
+version = "2.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d5477fe2230a79769d8dc68e0eabf5437907c0457a5614a9e8dddb67f65eb65d"
+dependencies = [
+ "equivalent",
+ "hashbrown 0.14.0",
+]
+
+[[package]]
+name = "inplace-vec-builder"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cf64c2edc8226891a71f127587a2861b132d2b942310843814d5001d99a1d307"
+dependencies = [
+ "smallvec",
+]
+
+[[package]]
+name = "io-lifetimes"
+version = "1.0.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2"
+dependencies = [
+ "hermit-abi",
+ "libc",
+ "windows-sys 0.48.0",
+]
+
+[[package]]
+name = "itoa"
+version = "1.0.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38"
+
+[[package]]
+name = "jobserver"
+version = "0.1.26"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "936cfd212a0155903bcbc060e316fb6cc7cbf2e1907329391ebadc1fe0ce77c2"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "joinery"
+version = "2.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "72167d68f5fce3b8655487b8038691a3c9984ee769590f93f2a631f4ad64e4f5"
+
+[[package]]
+name = "lazy_static"
+version = "1.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
+
+[[package]]
+name = "leb128"
+version = "0.2.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67"
+
+[[package]]
+name = "libc"
+version = "0.2.147"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3"
+
+[[package]]
+name = "libfuzzer-sys"
+version = "0.4.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "beb09950ae85a0a94b27676cccf37da5ff13f27076aa1adbc6545dd0d0e1bd4e"
+dependencies = [
+ "arbitrary",
+ "cc",
+ "once_cell",
+]
+
+[[package]]
+name = "linux-raw-sys"
+version = "0.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f051f77a7c8e6957c0696eac88f26b0117e54f52d3fc682ab19397a8812846a4"
+
+[[package]]
+name = "linux-raw-sys"
+version = "0.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "09fc20d2ca12cb9f044c93e3bd6d32d523e6e2ec3db4f7b2939cd99026ecd3f0"
+
+[[package]]
+name = "lock_api"
+version = "0.4.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c1cc9717a20b1bb222f333e6a92fd32f7d8a18ddc5a3191a11af45dcbf4dcd16"
+dependencies = [
+ "autocfg",
+ "scopeguard",
+]
+
+[[package]]
+name = "log"
+version = "0.4.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b06a4cde4c0f271a446782e3eff8de789548ce57dbc8eca9292c27f4a42004b4"
+
+[[package]]
+name = "maybe-owned"
+version = "0.3.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4facc753ae494aeb6e3c22f839b158aebd4f9270f55cd3c79906c45476c47ab4"
+
+[[package]]
+name = "memchr"
+version = "2.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
+
+[[package]]
+name = "memmap2"
+version = "0.5.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "83faa42c0a078c393f6b29d5db232d8be22776a891f8f56e5284faee4a20b327"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "memmap2"
+version = "0.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f49388d20533534cd19360ad3d6a7dadc885944aa802ba3995040c5ec11288c6"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "memoffset"
+version = "0.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5de893c32cde5f383baa4c04c5d6dbdd735cfd4a794b0debdb2bb1b421da5ff4"
+dependencies = [
+ "autocfg",
+]
+
+[[package]]
+name = "memoffset"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d61c719bcfbcf5d62b3a09efa6088de8c54bc0bfcd3ea7ae39fcc186108b8de1"
+dependencies = [
+ "autocfg",
+]
+
+[[package]]
+name = "minimal-lexical"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
+
+[[package]]
+name = "miniz_oxide"
+version = "0.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7"
+dependencies = [
+ "adler",
+]
+
+[[package]]
+name = "msvc-demangler"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bfb67c6dd0fa9b00619c41c5700b6f92d5f418be49b45ddb9970fbd4569df3c8"
+dependencies = [
+ "bitflags 1.3.2",
+]
+
+[[package]]
+name = "new_debug_unreachable"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e4a24736216ec316047a1fc4252e27dabb04218aa4a3f37c6e7ddbf1f9782b54"
+
+[[package]]
+name = "nix"
+version = "0.26.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bfdda3d196821d6af13126e40375cdf7da646a96114af134d5f417a9a1dc8e1a"
+dependencies = [
+ "bitflags 1.3.2",
+ "cfg-if",
+ "libc",
+ "memoffset 0.7.1",
+ "pin-utils",
+ "static_assertions",
+]
+
+[[package]]
+name = "nom"
+version = "7.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a"
+dependencies = [
+ "memchr",
+ "minimal-lexical",
+]
+
+[[package]]
+name = "nom-supreme"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2bd3ae6c901f1959588759ff51c95d24b491ecb9ff91aa9c2ef4acc5b1dcab27"
+dependencies = [
+ "brownstone",
+ "indent_write",
+ "joinery",
+ "memchr",
+ "nom",
+]
+
+[[package]]
+name = "object"
+version = "0.31.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8bda667d9f2b5051b8833f59f3bf748b28ef54f850f4fcb389a252aa383866d1"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "once_cell"
+version = "1.18.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d"
+
+[[package]]
+name = "os_pipe"
+version = "1.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0ae859aa07428ca9a929b936690f8b12dc5f11dd8c6992a18ca93919f28bc177"
+dependencies = [
+ "libc",
+ "windows-sys 0.48.0",
+]
+
+[[package]]
+name = "parking_lot"
+version = "0.12.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f"
+dependencies = [
+ "lock_api",
+ "parking_lot_core",
+]
+
+[[package]]
+name = "parking_lot_core"
+version = "0.9.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "93f00c865fe7cabf650081affecd3871070f26767e7b2070a3ffae14c654b447"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "redox_syscall",
+ "smallvec",
+ "windows-targets 0.48.1",
+]
+
+[[package]]
+name = "pdb"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "82040a392923abe6279c00ab4aff62d5250d1c8555dc780e4b02783a7aa74863"
+dependencies = [
+ "fallible-iterator 0.2.0",
+ "scroll",
+ "uuid",
+]
+
+[[package]]
+name = "pdb-addr2line"
+version = "0.10.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c4e89a9f2f40b2389ba6da0814c8044bf942bece03dffa1514f84e3b525f4f9a"
+dependencies = [
+ "bitflags 1.3.2",
+ "elsa",
+ "maybe-owned",
+ "pdb",
+ "range-collections",
+ "thiserror",
+]
+
+[[package]]
+name = "percent-encoding"
+version = "2.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94"
+
+[[package]]
+name = "pete"
+version = "0.12.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0f09c1c1ad40df294ff8643fe88a3dc64fff3293b6bc0ed9f71aff71f7086cbd"
+dependencies = [
+ "libc",
+ "memoffset 0.8.0",
+ "nix",
+ "thiserror",
+]
+
+[[package]]
+name = "phf_shared"
+version = "0.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b6796ad771acdc0123d2a88dc428b5e38ef24456743ddb1744ed628f9815c096"
+dependencies = [
+ "siphasher",
+]
+
+[[package]]
+name = "pin-project-lite"
+version = "0.2.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4c40d25201921e5ff0c862a505c6557ea88568a4e3ace775ab55e93f2f4f9d57"
+
+[[package]]
+name = "pin-utils"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
+
+[[package]]
+name = "plain"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6"
+
+[[package]]
+name = "ppv-lite86"
+version = "0.2.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
+
+[[package]]
+name = "precomputed-hash"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c"
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.66"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "procfs"
+version = "0.15.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "943ca7f9f29bab5844ecd8fdb3992c5969b6622bb9609b9502fef9b4310e3f1f"
+dependencies = [
+ "bitflags 1.3.2",
+ "byteorder",
+ "flate2",
+ "hex",
+ "lazy_static",
+ "rustix 0.36.15",
+]
+
+[[package]]
+name = "quick-xml"
+version = "0.30.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "eff6510e86862b57b210fd8cbe8ed3f0d7d600b9c2863cd4549a2e033c66e956"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.31"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5fe8a65d69dd0808184ebb5f836ab526bb259db23c657efa38711b1072ee47f0"
+dependencies = [
+ "proc-macro2",
+]
+
+[[package]]
+name = "rand"
+version = "0.8.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
+dependencies = [
+ "libc",
+ "rand_chacha",
+ "rand_core",
+]
+
+[[package]]
+name = "rand_chacha"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
+dependencies = [
+ "ppv-lite86",
+ "rand_core",
+]
+
+[[package]]
+name = "rand_core"
+version = "0.6.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
+dependencies = [
+ "getrandom",
+]
+
+[[package]]
+name = "range-collections"
+version = "0.2.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "61fdfd79629e2b44a1d34b4d227957174cb858e6b86ee45fad114edbcfc903ab"
+dependencies = [
+ "binary-merge",
+ "inplace-vec-builder",
+ "smallvec",
+]
+
+[[package]]
+name = "redox_syscall"
+version = "0.3.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29"
+dependencies = [
+ "bitflags 1.3.2",
+]
+
+[[package]]
+name = "regex"
+version = "1.9.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b2eae68fc220f7cf2532e4494aded17545fce192d59cd996e0fe7887f4ceb575"
+dependencies = [
+ "aho-corasick",
+ "memchr",
+ "regex-automata",
+ "regex-syntax",
+]
+
+[[package]]
+name = "regex-automata"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "39354c10dd07468c2e73926b23bb9c2caca74c5501e38a35da70406f1d923310"
+dependencies = [
+ "aho-corasick",
+ "memchr",
+ "regex-syntax",
+]
+
+[[package]]
+name = "regex-syntax"
+version = "0.7.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e5ea92a5b6195c6ef2a0295ea818b312502c6fc94dde986c5553242e18fd4ce2"
+
+[[package]]
+name = "rustc-demangle"
+version = "0.1.23"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76"
+
+[[package]]
+name = "rustix"
+version = "0.36.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c37f1bd5ef1b5422177b7646cba67430579cfe2ace80f284fee876bca52ad941"
+dependencies = [
+ "bitflags 1.3.2",
+ "errno",
+ "io-lifetimes",
+ "libc",
+ "linux-raw-sys 0.1.4",
+ "windows-sys 0.45.0",
+]
+
+[[package]]
+name = "rustix"
+version = "0.38.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0a962918ea88d644592894bc6dc55acc6c0956488adcebbfb6e273506b7fd6e5"
+dependencies = [
+ "bitflags 2.3.3",
+ "errno",
+ "libc",
+ "linux-raw-sys 0.4.3",
+ "windows-sys 0.48.0",
+]
+
+[[package]]
+name = "ryu"
+version = "1.0.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741"
+
+[[package]]
+name = "scopeguard"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
+
+[[package]]
+name = "scroll"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "04c565b551bafbef4157586fa379538366e4385d42082f255bfd96e4fe8519da"
+dependencies = [
+ "scroll_derive",
+]
+
+[[package]]
+name = "scroll_derive"
+version = "0.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1db149f81d46d2deba7cd3c50772474707729550221e69588478ebf9ada425ae"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "serde"
+version = "1.0.174"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3b88756493a5bd5e5395d53baa70b194b05764ab85b59e43e4b8f4e1192fa9b1"
+dependencies = [
+ "serde_derive",
+]
+
+[[package]]
+name = "serde_derive"
+version = "1.0.174"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6e5c3a298c7f978e53536f95a63bdc4c4a64550582f31a0359a9afda6aede62e"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "serde_json"
+version = "1.0.103"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d03b412469450d4404fe8499a268edd7f8b79fecb074b0d812ad64ca21f4031b"
+dependencies = [
+ "itoa",
+ "ryu",
+ "serde",
+]
+
+[[package]]
+name = "siphasher"
+version = "0.3.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7bd3e3206899af3f8b12af284fafc038cc1dc2b41d1b89dd17297221c5d225de"
+
+[[package]]
+name = "smallvec"
+version = "1.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "62bb4feee49fdd9f707ef802e22365a35de4b7b299de4763d44bfea899442ff9"
+
+[[package]]
+name = "stable_deref_trait"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
+
+[[package]]
+name = "static_assertions"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
+
+[[package]]
+name = "string_cache"
+version = "0.8.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f91138e76242f575eb1d3b38b4f1362f10d3a43f47d182a5b359af488a02293b"
+dependencies = [
+ "new_debug_unreachable",
+ "once_cell",
+ "parking_lot",
+ "phf_shared",
+ "precomputed-hash",
+ "serde",
+]
+
+[[package]]
+name = "symbolic"
+version = "12.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d3b5247a96aeefec188691938459892bffd23f1c3e9900dc08ac5248fe3bf08e"
+dependencies = [
+ "symbolic-common",
+ "symbolic-debuginfo",
+ "symbolic-demangle",
+ "symbolic-symcache",
+]
+
+[[package]]
+name = "symbolic-common"
+version = "12.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9e0e9bc48b3852f36a84f8d0da275d50cb3c2b88b59b9ec35fdd8b7fa239e37d"
+dependencies = [
+ "debugid",
+ "memmap2 0.5.10",
+ "stable_deref_trait",
+ "uuid",
+]
+
+[[package]]
+name = "symbolic-debuginfo"
+version = "12.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7ef9a1b95a8ea7b5afb550da0d93ecc706de3ce869a9674fc3bc51fadc019feb"
+dependencies = [
+ "debugid",
+ "dmsort",
+ "elementtree",
+ "elsa",
+ "fallible-iterator 0.3.0",
+ "flate2",
+ "gimli 0.28.0",
+ "goblin 0.7.1",
+ "lazy_static",
+ "nom",
+ "nom-supreme",
+ "once_cell",
+ "parking_lot",
+ "pdb-addr2line",
+ "regex",
+ "scroll",
+ "serde",
+ "serde_json",
+ "smallvec",
+ "symbolic-common",
+ "symbolic-ppdb",
+ "thiserror",
+ "wasmparser",
+ "zip",
+]
+
+[[package]]
+name = "symbolic-demangle"
+version = "12.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "691e53bdc0702aba3a5abc2cffff89346fcbd4050748883c7e2f714b33a69045"
+dependencies = [
+ "cc",
+ "cpp_demangle",
+ "msvc-demangler",
+ "rustc-demangle",
+ "symbolic-common",
+]
+
+[[package]]
+name = "symbolic-ppdb"
+version = "12.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b95399a30236ac95fd9ce69a008b8a18e58859e9780a13bcb16fda545802f876"
+dependencies = [
+ "flate2",
+ "indexmap 1.9.3",
+ "serde",
+ "serde_json",
+ "symbolic-common",
+ "thiserror",
+ "uuid",
+ "watto",
+]
+
+[[package]]
+name = "symbolic-symcache"
+version = "12.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4339f37007c0fd6d6dddaf6f04619a4a5d6308e71eabbd45c30e0af124014259"
+dependencies = [
+ "indexmap 2.0.0",
+ "symbolic-common",
+ "symbolic-debuginfo",
+ "thiserror",
+ "tracing",
+ "watto",
+]
+
+[[package]]
+name = "syn"
+version = "2.0.27"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b60f673f44a8255b9c8c657daf66a596d435f2da81a555b06dc644d080ba45e0"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "tempfile"
+version = "3.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5486094ee78b2e5038a6382ed7645bc084dc2ec433426ca4c3cb61e2007b8998"
+dependencies = [
+ "cfg-if",
+ "fastrand",
+ "redox_syscall",
+ "rustix 0.38.4",
+ "windows-sys 0.48.0",
+]
+
+[[package]]
+name = "thiserror"
+version = "1.0.44"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "611040a08a0439f8248d1990b111c95baa9c704c805fa1f62104b39655fd7f90"
+dependencies = [
+ "thiserror-impl",
+]
+
+[[package]]
+name = "thiserror-impl"
+version = "1.0.44"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "090198534930841fab3a5d1bb637cde49e339654e606195f8d9c76eeb081dc96"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "tinyvec"
+version = "1.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50"
+dependencies = [
+ "tinyvec_macros",
+]
+
+[[package]]
+name = "tinyvec_macros"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
+
+[[package]]
+name = "tracing"
+version = "0.1.37"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8"
+dependencies = [
+ "cfg-if",
+ "pin-project-lite",
+ "tracing-attributes",
+ "tracing-core",
+]
+
+[[package]]
+name = "tracing-attributes"
+version = "0.1.26"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5f4f31f56159e98206da9efd823404b79b6ef3143b4a7ab76e67b1751b25a4ab"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "tracing-core"
+version = "0.1.31"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0955b8137a1df6f1a2e9a37d8a6656291ff0297c1a97c24e0d8425fe2312f79a"
+dependencies = [
+ "once_cell",
+]
+
+[[package]]
+name = "unicode-bidi"
+version = "0.3.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460"
+
+[[package]]
+name = "unicode-ident"
+version = "1.0.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c"
+
+[[package]]
+name = "unicode-normalization"
+version = "0.1.22"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921"
+dependencies = [
+ "tinyvec",
+]
+
+[[package]]
+name = "url"
+version = "2.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "50bff7831e19200a85b17131d085c25d7811bc4e186efdaf54bbd132994a88cb"
+dependencies = [
+ "form_urlencoded",
+ "idna",
+ "percent-encoding",
+]
+
+[[package]]
+name = "uuid"
+version = "1.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "79daa5ed5740825c40b389c5e50312b9c86df53fccd33f281df655642b43869d"
+
+[[package]]
+name = "wasi"
+version = "0.11.0+wasi-snapshot-preview1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
+
+[[package]]
+name = "wasmparser"
+version = "0.102.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "48134de3d7598219ab9eaf6b91b15d8e50d31da76b8519fe4ecfcec2cf35104b"
+dependencies = [
+ "indexmap 1.9.3",
+ "url",
+]
+
+[[package]]
+name = "watto"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6746b5315e417144282a047ebb82260d45c92d09bf653fa9ec975e3809be942b"
+dependencies = [
+ "leb128",
+ "thiserror",
+]
+
+[[package]]
+name = "win-util"
+version = "0.1.0"
+dependencies = [
+ "anyhow",
+ "atexit",
+ "log",
+ "os_pipe",
+ "windows",
+ "winreg",
+]
+
+[[package]]
+name = "windows"
+version = "0.48.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f"
+dependencies = [
+ "windows-targets 0.48.1",
+]
+
+[[package]]
+name = "windows-sys"
+version = "0.45.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0"
+dependencies = [
+ "windows-targets 0.42.2",
+]
+
+[[package]]
+name = "windows-sys"
+version = "0.48.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
+dependencies = [
+ "windows-targets 0.48.1",
+]
+
+[[package]]
+name = "windows-targets"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071"
+dependencies = [
+ "windows_aarch64_gnullvm 0.42.2",
+ "windows_aarch64_msvc 0.42.2",
+ "windows_i686_gnu 0.42.2",
+ "windows_i686_msvc 0.42.2",
+ "windows_x86_64_gnu 0.42.2",
+ "windows_x86_64_gnullvm 0.42.2",
+ "windows_x86_64_msvc 0.42.2",
+]
+
+[[package]]
+name = "windows-targets"
+version = "0.48.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "05d4b17490f70499f20b9e791dcf6a299785ce8af4d709018206dc5b4953e95f"
+dependencies = [
+ "windows_aarch64_gnullvm 0.48.0",
+ "windows_aarch64_msvc 0.48.0",
+ "windows_i686_gnu 0.48.0",
+ "windows_i686_msvc 0.48.0",
+ "windows_x86_64_gnu 0.48.0",
+ "windows_x86_64_gnullvm 0.48.0",
+ "windows_x86_64_msvc 0.48.0",
+]
+
+[[package]]
+name = "windows_aarch64_gnullvm"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8"
+
+[[package]]
+name = "windows_aarch64_gnullvm"
+version = "0.48.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc"
+
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43"
+
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.48.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3"
+
+[[package]]
+name = "windows_i686_gnu"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f"
+
+[[package]]
+name = "windows_i686_gnu"
+version = "0.48.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241"
+
+[[package]]
+name = "windows_i686_msvc"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060"
+
+[[package]]
+name = "windows_i686_msvc"
+version = "0.48.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00"
+
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36"
+
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.48.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1"
+
+[[package]]
+name = "windows_x86_64_gnullvm"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3"
+
+[[package]]
+name = "windows_x86_64_gnullvm"
+version = "0.48.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953"
+
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0"
+
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.48.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a"
+
+[[package]]
+name = "winreg"
+version = "0.51.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "937f3df7948156640f46aacef17a70db0de5917bda9c92b0f751f3a955b588fc"
+dependencies = [
+ "cfg-if",
+ "windows-sys 0.48.0",
+]
+
+[[package]]
+name = "zip"
+version = "0.6.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "760394e246e4c28189f19d488c058bf16f564016aefac5d32bb1f3b51d5e9261"
+dependencies = [
+ "byteorder",
+ "crc32fast",
+ "crossbeam-utils",
+ "flate2",
+]
diff --git a/src/agent/coverage/fuzz/Cargo.toml b/src/agent/coverage/fuzz/Cargo.toml
new file mode 100644
index 0000000000..6f23f1b7a0
--- /dev/null
+++ b/src/agent/coverage/fuzz/Cargo.toml
@@ -0,0 +1,30 @@
+[package]
+name = "coverage-fuzz"
+version = "0.0.0"
+publish = false
+edition = "2021"
+
+[package.metadata]
+cargo-fuzz = true
+
+[dependencies]
+libfuzzer-sys = "0.4"
+tempfile = "3.7"
+debuggable-module = { path = "../../debuggable-module" }
+
+
+[dependencies.coverage]
+path = ".."
+
+# Prevent this from interfering with workspaces
+[workspace]
+members = ["."]
+
+[profile.release]
+debug = 1
+
+[[bin]]
+name = "fuzz_target_record_coverage"
+path = "fuzz_targets/fuzz_target_record_coverage.rs"
+test = false
+doc = false
diff --git a/src/agent/coverage/fuzz/fuzz_targets/fuzz_target_record_coverage.rs b/src/agent/coverage/fuzz/fuzz_targets/fuzz_target_record_coverage.rs
new file mode 100644
index 0000000000..c097a1f279
--- /dev/null
+++ b/src/agent/coverage/fuzz/fuzz_targets/fuzz_target_record_coverage.rs
@@ -0,0 +1,51 @@
+#![no_main]
+
+use libfuzzer_sys::fuzz_target;
+use std::env;
+use std::fs;
+use std::io::Write;
+use std::process::Command;
+use std::sync::Arc;
+use std::time::Duration;
+
+use tempfile::NamedTempFile;
+
+use coverage::allowlist::AllowList;
+use coverage::binary::BinaryCoverage;
+use coverage::record::CoverageRecorder;
+
+use debuggable_module::loader::Loader;
+
+const INPUT_MARKER: &str = "@@";
+
+fuzz_target!(|data: &[u8]| {
+ if data.len() == 0 {
+ return;
+ }
+
+ // Write mutated bytes to a file
+ let mut file = NamedTempFile::new_in(env::current_dir().unwrap()).unwrap();
+ file.write_all(data);
+ let path = String::from(file.path().to_str().unwrap());
+
+ // Make sure the file is executable
+ Command::new("chmod").args(["+wrx", &path]).spawn().unwrap().wait();
+ file.keep().unwrap();
+
+ let timeout = Duration::from_secs(5);
+
+ let allowlist = AllowList::default();
+
+ let _coverage = BinaryCoverage::default();
+ let loader = Arc::new(Loader::new());
+
+ let cmd = Command::new(&path);
+
+ let _recorded = CoverageRecorder::new(cmd)
+ .module_allowlist(allowlist.clone())
+ .loader(loader)
+ .timeout(timeout)
+ .record();
+
+ fs::remove_file(path);
+});
diff --git a/src/agent/coverage/src/allowlist.rs b/src/agent/coverage/src/allowlist.rs
index 079f415004..2c67130375 100644
--- a/src/agent/coverage/src/allowlist.rs
+++ b/src/agent/coverage/src/allowlist.rs
@@ -142,7 +142,12 @@ fn glob_to_regex(expr: &str) -> Result {
let expr = expr.replace(r"\*", ".*");
// Anchor to line start and end.
- let expr = format!("^{expr}$");
+ // On Windows we should also ignore case.
+ let expr = if cfg!(windows) {
+ format!("(?i)^{expr}$")
+ } else {
+ format!("^{expr}$")
+ };
Ok(Regex::new(&expr)?)
}
diff --git a/src/agent/coverage/src/allowlist/tests.rs b/src/agent/coverage/src/allowlist/tests.rs
index 0f46ef3df8..8d22d93962 100644
--- a/src/agent/coverage/src/allowlist/tests.rs
+++ b/src/agent/coverage/src/allowlist/tests.rs
@@ -175,3 +175,21 @@ fn test_allowlist_escape() -> Result<()> {
Ok(())
}
+
+#[cfg(target_os = "windows")]
+#[test]
+fn test_windows_allowlists_are_not_case_sensitive() -> Result<()> {
+ let allowlist = AllowList::parse("vccrt")?;
+ assert!(allowlist.is_allowed("VCCRT"));
+
+ Ok(())
+}
+
+#[cfg(not(target_os = "windows"))]
+#[test]
+fn test_linux_allowlists_are_case_sensitive() -> Result<()> {
+ let allowlist = AllowList::parse("vccrt")?;
+ assert!(!allowlist.is_allowed("VCCRT"));
+
+ Ok(())
+}
diff --git a/src/agent/coverage/src/record.rs b/src/agent/coverage/src/record.rs
index 534d1d4d63..44faded302 100644
--- a/src/agent/coverage/src/record.rs
+++ b/src/agent/coverage/src/record.rs
@@ -1,7 +1,7 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
-use std::process::{Command, ExitStatus, Stdio};
+use std::process::{Command, Stdio};
use std::sync::Arc;
use std::time::Duration;
@@ -120,32 +120,58 @@ impl CoverageRecorder {
#[cfg(target_os = "windows")]
pub fn record(self) -> Result {
+ use anyhow::bail;
use debugger::Debugger;
+ use process_control::{ChildExt, Control};
use windows::WindowsRecorder;
+ let child = Debugger::create_child(self.cmd)?;
+
+ // Spawn a thread to wait for the target process to exit.
+ let taget_process = std::thread::spawn(move || {
+ let output = child
+ .controlled_with_output()
+ .time_limit(self.timeout)
+ .terminate_for_timeout()
+ .wait();
+ output
+ });
+
let loader = self.loader.clone();
+ let mut recorder =
+ WindowsRecorder::new(&loader, self.module_allowlist, self.cache.as_ref());
- crate::timer::timed(self.timeout, move || {
- let mut recorder =
- WindowsRecorder::new(&loader, self.module_allowlist, self.cache.as_ref());
- let (mut dbg, child) = Debugger::init(self.cmd, &mut recorder)?;
- dbg.run(&mut recorder)?;
-
- // If the debugger callbacks fail, this may return with a spurious clean exit.
- let output = child.wait_with_output()?.into();
-
- // Check if debugging was stopped due to a callback error.
- //
- // If so, the debugger terminated the target, and the recorded coverage and
- // output are both invalid.
- if let Some(err) = recorder.stop_error {
- return Err(err);
+ // The debugger is initialized in the same thread that created the target process to be able to receive the debug events
+ let mut dbg = Debugger::init_debugger(&mut recorder)?;
+ dbg.run(&mut recorder)?;
+
+ // If the debugger callbacks fail, this may return with a spurious clean exit.
+ let output = match taget_process.join() {
+ Err(err) => {
+ bail!("failed to launch target thread: {:?}", err)
+ }
+ Ok(Err(err)) => {
+ bail!("failed to launch target process: {:?}", err)
}
+ Ok(Ok(None)) => {
+ bail!(crate::timer::TimerError::Timeout(self.timeout))
+ }
+ Ok(Ok(Some(output))) => output,
+ };
- let coverage = recorder.coverage;
+ // Check if debugging was stopped due to a callback error.
+ //
+ // If so, the debugger terminated the target, and the recorded coverage and
+ // output are both invalid.
+ if let Some(err) = recorder.stop_error {
+ return Err(err);
+ }
- Ok(Recorded { coverage, output })
- })?
+ let coverage = recorder.coverage;
+ Ok(Recorded {
+ coverage,
+ output: output.into(),
+ })
}
}
@@ -157,11 +183,24 @@ pub struct Recorded {
#[derive(Clone, Debug, Default)]
pub struct Output {
- pub status: Option,
+ pub status: Option,
pub stderr: String,
pub stdout: String,
}
+impl From for Output {
+ fn from(output: process_control::Output) -> Self {
+ let status = Some(output.status);
+ let stdout = String::from_utf8_lossy(&output.stdout).into_owned();
+ let stderr = String::from_utf8_lossy(&output.stderr).into_owned();
+ Self {
+ status,
+ stdout,
+ stderr,
+ }
+ }
+}
+
impl From for Output {
fn from(output: std::process::Output) -> Self {
let status = Some(output.status);
@@ -169,7 +208,7 @@ impl From for Output {
let stderr = String::from_utf8_lossy(&output.stderr).into_owned();
Self {
- status,
+ status: status.map(Into::into),
stdout,
stderr,
}
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/coverage/src/record/windows.rs b/src/agent/coverage/src/record/windows.rs
index 076aa70d37..32d22b5534 100644
--- a/src/agent/coverage/src/record/windows.rs
+++ b/src/agent/coverage/src/record/windows.rs
@@ -4,7 +4,7 @@
use std::collections::BTreeMap;
use std::path::Path;
-use anyhow::{anyhow, bail, Error, Result};
+use anyhow::{anyhow, Error, Result};
use debuggable_module::debuginfo::{DebugInfo, Function};
use debuggable_module::load_module::LoadModule;
use debuggable_module::loader::Loader;
@@ -132,20 +132,24 @@ impl<'cache, 'data> WindowsRecorder<'cache, 'data> {
return Ok(());
}
- let breakpoint = self.breakpoints.remove(id);
-
- let Some(breakpoint) = breakpoint else {
- let stack = dbg.get_current_stack()?;
- bail!("stopped on dangling breakpoint, debuggee stack:\n{}", stack);
- };
-
- let coverage = self
- .coverage
- .modules
- .get_mut(&breakpoint.module)
- .ok_or_else(|| anyhow!("coverage not initialized for module: {}", breakpoint.module))?;
-
- coverage.increment(breakpoint.offset);
+ match self.breakpoints.remove(id) {
+ Some(breakpoint) => {
+ let coverage = self
+ .coverage
+ .modules
+ .get_mut(&breakpoint.module)
+ .ok_or_else(|| {
+ anyhow!("coverage not initialized for module: {}", breakpoint.module)
+ })?;
+
+ coverage.increment(breakpoint.offset);
+ }
+ // ASAN can set breakpoints which we don't know about, meaning they're not in `self.breakpoints`
+ None => {
+ let stack = dbg.get_current_stack()?;
+ warn!("stopped on dangling breakpoint, debuggee stack:\n{}", stack);
+ }
+ }
Ok(())
}
diff --git a/src/agent/coverage/src/source.rs b/src/agent/coverage/src/source.rs
index b556fe447a..e06e8aa285 100644
--- a/src/agent/coverage/src/source.rs
+++ b/src/agent/coverage/src/source.rs
@@ -2,6 +2,7 @@
// Licensed under the MIT License.
use std::collections::{BTreeMap, BTreeSet};
+
use std::num::NonZeroU32;
use anyhow::{Context, Result};
@@ -11,6 +12,7 @@ use debuggable_module::load_module::LoadModule;
use debuggable_module::loader::Loader;
use debuggable_module::path::FilePath;
use debuggable_module::{Module, Offset};
+use symbolic::symcache::transform::{SourceLocation, Transformer};
use crate::allowlist::AllowList;
use crate::binary::BinaryCoverage;
@@ -69,6 +71,30 @@ pub fn binary_to_source_coverage(
let mut symcache = vec![];
let mut converter = SymCacheConverter::new();
+ if cfg!(windows) {
+ use symbolic::symcache::transform::Function;
+ struct CaseInsensitive {}
+ impl Transformer for CaseInsensitive {
+ fn transform_function<'f>(&'f mut self, f: Function<'f>) -> Function<'f> {
+ f
+ }
+
+ fn transform_source_location<'f>(
+ &'f mut self,
+ mut sl: SourceLocation<'f>,
+ ) -> SourceLocation<'f> {
+ sl.file.name = sl.file.name.to_ascii_lowercase().into();
+ sl.file.directory = sl.file.directory.map(|d| d.to_ascii_lowercase().into());
+ sl.file.comp_dir = sl.file.comp_dir.map(|d| d.to_ascii_lowercase().into());
+ sl
+ }
+ }
+
+ let case_insensitive_transformer = CaseInsensitive {};
+
+ converter.add_transformer(case_insensitive_transformer);
+ }
+
let exe = Object::parse(module.executable_data())?;
converter.process_object(&exe)?;
diff --git a/src/agent/coverage/src/timer.rs b/src/agent/coverage/src/timer.rs
index 760e453b28..3271666d67 100644
--- a/src/agent/coverage/src/timer.rs
+++ b/src/agent/coverage/src/timer.rs
@@ -7,6 +7,7 @@ use std::time::Duration;
use thiserror::Error;
+#[allow(dead_code)]
pub fn timed(timeout: Duration, function: F) -> Result
where
T: Send + 'static,
diff --git a/src/agent/coverage/tests/snapshot.rs b/src/agent/coverage/tests/snapshot.rs
index 75d524e2da..7c6cb301b4 100644
--- a/src/agent/coverage/tests/snapshot.rs
+++ b/src/agent/coverage/tests/snapshot.rs
@@ -43,7 +43,8 @@ fn windows_snapshot_tests() {
};
// filter to just the input test file:
- let source_allowlist = AllowList::parse(&input_path.to_string_lossy()).unwrap();
+ let source_allowlist =
+ AllowList::parse(&input_path.to_string_lossy().to_ascii_lowercase()).unwrap();
let exe_cmd = std::process::Command::new(&exe_name);
let recorded = coverage::CoverageRecorder::new(exe_cmd)
@@ -57,9 +58,18 @@ fn windows_snapshot_tests() {
coverage::source::binary_to_source_coverage(&recorded.coverage, &source_allowlist)
.expect("binary_to_source_coverage");
+ println!("{:?}", source.files.keys());
+
+ // For Windows, the source coverage is tracked using case-insensitive paths.
+ // The conversion from case-sensitive to insensitive is done when converting from binary to source coverage.
+ // By naming our test file with a capital letter, we can ensure that the case-insensitive conversion is working.
+ source.files.keys().for_each(|k| {
+ assert_eq!(k.to_string().to_ascii_lowercase(), k.to_string());
+ });
+
let file_coverage = source
.files
- .get(&FilePath::new(input_path.to_string_lossy()).unwrap())
+ .get(&FilePath::new(input_path.to_string_lossy().to_ascii_lowercase()).unwrap())
.expect("coverage for input");
let mut result = String::new();
diff --git a/src/agent/coverage/tests/snapshots/snapshot__windows_snapshot_tests.snap b/src/agent/coverage/tests/snapshots/snapshot__windows_snapshot_tests.snap
index 016717f8ab..12a38f4ef0 100644
--- a/src/agent/coverage/tests/snapshots/snapshot__windows_snapshot_tests.snap
+++ b/src/agent/coverage/tests/snapshots/snapshot__windows_snapshot_tests.snap
@@ -1,7 +1,7 @@
---
source: coverage/tests/snapshot.rs
expression: result
-input_file: coverage/tests/windows/inlinee.cpp
+input_file: coverage/tests/windows/Inlinee.cpp
---
[ ] #include
[ ]
diff --git a/src/agent/coverage/tests/windows/inlinee.cpp b/src/agent/coverage/tests/windows/Inlinee.cpp
similarity index 100%
rename from src/agent/coverage/tests/windows/inlinee.cpp
rename to src/agent/coverage/tests/windows/Inlinee.cpp
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/debugger/src/debugger.rs b/src/agent/debugger/src/debugger.rs
index d7c0f1ba5e..ae67f66fed 100644
--- a/src/agent/debugger/src/debugger.rs
+++ b/src/agent/debugger/src/debugger.rs
@@ -134,15 +134,7 @@ pub struct Debugger {
}
impl Debugger {
- pub fn init(
- mut command: Command,
- callbacks: &mut impl DebugEventHandler,
- ) -> Result<(Self, Child)> {
- let child = command
- .creation_flags(DEBUG_ONLY_THIS_PROCESS.0)
- .spawn()
- .context("debugee failed to start")?;
-
+ pub fn init_debugger(callbacks: &mut impl DebugEventHandler) -> Result {
unsafe { DebugSetProcessKillOnExit(TRUE) }
.ok()
.context("Setting DebugSetProcessKillOnExit to TRUE")?;
@@ -186,12 +178,27 @@ impl Debugger {
return Err(last_os_error());
}
- Ok((debugger, child))
+ Ok(debugger)
} else {
anyhow::bail!("Unexpected event: {}", de)
}
}
+ pub fn create_child(mut command: Command) -> Result {
+ let child = command
+ .creation_flags(DEBUG_ONLY_THIS_PROCESS.0)
+ .spawn()
+ .context("debugee failed to start")?;
+
+ Ok(child)
+ }
+
+ pub fn init(command: Command, callbacks: &mut impl DebugEventHandler) -> Result<(Self, Child)> {
+ let child = Self::create_child(command)?;
+ let debugger = Self::init_debugger(callbacks)?;
+ Ok((debugger, child))
+ }
+
pub fn target(&mut self) -> &mut Target {
&mut self.target
}
diff --git a/src/agent/debugger/src/module.rs b/src/agent/debugger/src/module.rs
index acea7ace7f..aefdb8a92e 100644
--- a/src/agent/debugger/src/module.rs
+++ b/src/agent/debugger/src/module.rs
@@ -46,7 +46,6 @@ impl Module {
error!("Error getting path from file handle: {}", e);
"???".into()
});
-
let image_details = get_image_details(&path)?;
Ok(Module {
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/input-tester/Cargo.toml b/src/agent/input-tester/Cargo.toml
index fe5ac6032c..93aac3172b 100644
--- a/src/agent/input-tester/Cargo.toml
+++ b/src/agent/input-tester/Cargo.toml
@@ -13,7 +13,7 @@ fnv = "1.0"
hex = "0.4"
log = "0.4"
num_cpus = "1.15"
-rayon = "1.7"
+rayon = "1.8"
sha2 = "0.10.2"
win-util = { path = "../win-util" }
diff --git a/src/agent/onefuzz-agent/Cargo.toml b/src/agent/onefuzz-agent/Cargo.toml
index 5ce8669766..3e7f00a8b0 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.15", default-features = false, features = [
"enable_reqwest",
] }
-azure_storage = { version = "0.13", default-features = false, features = [
+azure_storage = { version = "0.15", default-features = false, features = [
"enable_reqwest",
] }
-azure_storage_blobs = { version = "0.13", default-features = false, features = [
+azure_storage_blobs = { version = "0.15", default-features = false, features = [
"enable_reqwest",
] }
diff --git a/src/agent/onefuzz-task/Cargo.toml b/src/agent/onefuzz-task/Cargo.toml
index 4e0bd381b0..4b3e8e8c43 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.15", default-features = false, features = [
"enable_reqwest",
] }
-azure_storage = { version = "0.13", default-features = false, features = [
+azure_storage = { version = "0.15", default-features = false, features = [
"enable_reqwest",
] }
-azure_storage_blobs = { version = "0.13", default-features = false, features = [
+azure_storage_blobs = { version = "0.15", 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/check_for_update.rs b/src/agent/onefuzz-task/src/check_for_update.rs
new file mode 100644
index 0000000000..51c0178158
--- /dev/null
+++ b/src/agent/onefuzz-task/src/check_for_update.rs
@@ -0,0 +1,78 @@
+use std::process::Stdio;
+
+use anyhow::Result;
+use serde_json::Value;
+
+pub fn run(onefuzz_built_version: &str) -> Result<()> {
+ // Find onefuzz cli
+ let common_names = ["onefuzz", "onefuzz.exe", "onefuzz.cmd"];
+ let mut valid_commands: Vec<_> = common_names
+ .into_iter()
+ .map(|name| {
+ (
+ name,
+ std::process::Command::new(name)
+ .stderr(Stdio::null())
+ .stdout(Stdio::null())
+ .arg("-h")
+ .spawn(),
+ )
+ })
+ .filter_map(|(name, child)| child.ok().map(|c| (name, c)))
+ .collect();
+
+ if valid_commands.is_empty() {
+ bail!(
+ "Could not find any of the following common names for the onefuzz-cli: {:?}",
+ common_names
+ );
+ }
+
+ let (name, child) = valid_commands
+ .first_mut()
+ .expect("Expected valid_commands to not be empty");
+
+ info!("Found the onefuzz cli at: {}", name);
+
+ // We just used this to check if it exists, we'll invoke it again later
+ let _ = child.kill();
+
+ // Run onefuzz info get
+ let output = std::process::Command::new(&name)
+ .args(["info", "get"])
+ .output()?;
+
+ if !output.status.success() {
+ bail!(
+ "Failed to run command `{} info get`. stderr: {:?}, stdout: {:?}",
+ name,
+ String::from_utf8(output.stderr),
+ String::from_utf8(output.stdout)
+ )
+ }
+
+ let stdout = String::from_utf8(output.stdout)?;
+ let info: Value = serde_json::from_str(&stdout)?;
+
+ if let Some(onefuzz_service_version) = info["versions"]["onefuzz"]["version"].as_str() {
+ if onefuzz_service_version == onefuzz_built_version {
+ println!("You are up to date!");
+ } else {
+ println!(
+ "Version mismatch. onefuzz-task version: {} | onefuzz service version: {}",
+ onefuzz_built_version, onefuzz_service_version
+ );
+ println!(
+ "To update, please run the following command: {} tools get .",
+ name
+ );
+ println!("Then extract the onefuzz-task binary from the appropriate OS folder");
+ }
+ return Ok(());
+ }
+
+ bail!(
+ "Failed to get onefuzz service version from cli response: {}",
+ stdout
+ )
+}
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/local/cmd.rs b/src/agent/onefuzz-task/src/local/cmd.rs
index eabefb71ee..cb800d445e 100644
--- a/src/agent/onefuzz-task/src/local/cmd.rs
+++ b/src/agent/onefuzz-task/src/local/cmd.rs
@@ -1,19 +1,18 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
+use super::{create_template, template};
#[cfg(any(target_os = "linux", target_os = "windows"))]
use crate::local::coverage;
use crate::local::{common::add_common_config, libfuzzer_fuzz, tui::TerminalUi};
use anyhow::{Context, Result};
+
use clap::{Arg, ArgAction, Command};
use std::time::Duration;
use std::{path::PathBuf, str::FromStr};
use strum::IntoEnumIterator;
use strum_macros::{EnumIter, EnumString, IntoStaticStr};
use tokio::{select, time::timeout};
-
-use super::template;
-
#[derive(Debug, PartialEq, Eq, EnumString, IntoStaticStr, EnumIter)]
#[strum(serialize_all = "kebab-case")]
enum Commands {
@@ -21,6 +20,7 @@ enum Commands {
Coverage,
LibfuzzerFuzz,
Template,
+ CreateTemplate,
}
const TIMEOUT: &str = "timeout";
@@ -43,7 +43,7 @@ pub async fn run(args: clap::ArgMatches) -> Result<()> {
let sub_args = sub_args.clone();
- let terminal = if start_ui {
+ let terminal = if start_ui && command != Commands::CreateTemplate {
Some(TerminalUi::init()?)
} else {
env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info")).init();
@@ -62,6 +62,7 @@ pub async fn run(args: clap::ArgMatches) -> Result<()> {
template::launch(config, event_sender).await
}
+ Commands::CreateTemplate => create_template::run(),
}
});
@@ -116,6 +117,7 @@ pub fn args(name: &'static str) -> Command {
.args(vec![Arg::new("config")
.value_parser(value_parser!(std::path::PathBuf))
.required(true)]),
+ Commands::CreateTemplate => create_template::args(subcommand.into()),
};
cmd = if add_common {
diff --git a/src/agent/onefuzz-task/src/local/coverage.rs b/src/agent/onefuzz-task/src/local/coverage.rs
index d091b70695..48e32cb861 100644
--- a/src/agent/onefuzz-task/src/local/coverage.rs
+++ b/src/agent/onefuzz-task/src/local/coverage.rs
@@ -148,7 +148,20 @@ pub struct Coverage {
}
#[async_trait]
-impl Template for Coverage {
+impl Template for Coverage {
+ fn example_values() -> Coverage {
+ Coverage {
+ target_exe: PathBuf::from("path_to_your_exe"),
+ target_env: HashMap::new(),
+ target_options: vec![],
+ target_timeout: None,
+ module_allowlist: None,
+ source_allowlist: None,
+ input_queue: Some(PathBuf::from("path_to_your_inputs")),
+ readonly_inputs: vec![PathBuf::from("path_to_readonly_inputs")],
+ coverage: PathBuf::from("path_to_where_you_want_coverage_to_be_output"),
+ }
+ }
async fn run(&self, context: &RunContext) -> Result<()> {
let ri: Result> = self
.readonly_inputs
diff --git a/src/agent/onefuzz-task/src/local/create_template.rs b/src/agent/onefuzz-task/src/local/create_template.rs
new file mode 100644
index 0000000000..474b677ad0
--- /dev/null
+++ b/src/agent/onefuzz-task/src/local/create_template.rs
@@ -0,0 +1,285 @@
+use crate::local::template::CommonProperties;
+
+use super::template::{TaskConfig, TaskConfigDiscriminants, TaskGroup};
+use anyhow::Result;
+use clap::Command;
+use std::str::FromStr;
+use std::{
+ io,
+ path::{Path, PathBuf},
+};
+
+use strum::VariantNames;
+
+use crate::local::{
+ coverage::Coverage, generic_analysis::Analysis, generic_crash_report::CrashReport,
+ generic_generator::Generator, libfuzzer::LibFuzzer,
+ libfuzzer_crash_report::LibfuzzerCrashReport, libfuzzer_merge::LibfuzzerMerge,
+ libfuzzer_regression::LibfuzzerRegression, libfuzzer_test_input::LibfuzzerTestInput,
+ template::Template, test_input::TestInput,
+};
+
+use crossterm::{
+ event::{self, DisableMouseCapture, EnableMouseCapture, Event, KeyCode, KeyEventKind},
+ execute,
+ terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
+};
+use tui::{prelude::*, widgets::*};
+
+pub fn args(name: &'static str) -> Command {
+ Command::new(name).about("interactively create a template")
+}
+
+pub fn run() -> Result<()> {
+ // setup terminal
+ enable_raw_mode()?;
+ let mut stdout = io::stdout();
+ execute!(stdout, EnterAlternateScreen, EnableMouseCapture)?;
+ let backend = CrosstermBackend::new(stdout);
+ let mut terminal = Terminal::new(backend)?;
+
+ // create app and run it
+ let app = App::new();
+ let res = run_app(&mut terminal, app);
+
+ // restore terminal
+ disable_raw_mode()?;
+ execute!(
+ terminal.backend_mut(),
+ LeaveAlternateScreen,
+ DisableMouseCapture
+ )?;
+ terminal.show_cursor()?;
+
+ match res {
+ Ok(None) => { /* user quit, do nothing */ }
+ Ok(Some(path)) => match path.canonicalize() {
+ Ok(canonical_path) => println!("Wrote the template to: {:?}", canonical_path),
+ _ => println!("Wrote the template to: {:?}", path),
+ },
+ Err(e) => println!("Failed to write template due to {}", e),
+ }
+
+ Ok(())
+}
+
+fn run_app(terminal: &mut Terminal, mut app: App) -> Result