diff --git a/.github/workflows/cron-daily-fuzz.yml b/.github/workflows/cron-daily-fuzz.yml index 6d8c00208..7931675f3 100644 --- a/.github/workflows/cron-daily-fuzz.yml +++ b/.github/workflows/cron-daily-fuzz.yml @@ -9,9 +9,21 @@ on: - cron: '00 06 * * *' jobs: + Prepare: + runs-on: ubuntu-24.04 + outputs: + nightly_version: ${{ steps.read_toolchain.outputs.nightly_version }} + steps: + - name: "Checkout repo" + uses: actions/checkout@v4 + - name: "Read nightly version" + id: read_toolchain + run: echo "nightly_version=$(cat nightly-version)" >> $GITHUB_OUTPUT + fuzz: if: ${{ !github.event.act }} - runs-on: ubuntu-20.04 + needs: Prepare + runs-on: ubuntu-25.04 strategy: fail-fast: false matrix: @@ -47,9 +59,15 @@ roundtrip_semantic, fuzz/target target key: cache-${{ matrix.target }}-${{ hashFiles('**/Cargo.toml','**/Cargo.lock') }} + + - name: Install toolchain - uses: dtolnay/rust-toolchain@stable with: - toolchain: '1.65.0' + toolchain: ${{ needs.Prepare.outputs.nightly_version }} + + - name: Install Dependencies + run: cargo update && cargo update -p cc --precise 1.0.83 && cargo install --force cargo-fuzz + - name: fuzz run: cd fuzz && ./fuzz.sh "${{ matrix.fuzz_target }}" - run: echo "${{ matrix.fuzz_target }}" >executed_${{ matrix.fuzz_target }} @@ -63,7 +81,7 @@ roundtrip_semantic, needs: fuzz runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - uses: actions/download-artifact@v4 - name: Display structure of downloaded files run: ls -R diff --git a/Cargo-minimal.lock b/Cargo-minimal.lock index 861bd2c62..c08dbaafd 100644 --- a/Cargo-minimal.lock +++ b/Cargo-minimal.lock @@ -11,6 +11,12 @@ dependencies = [ "memchr", ] +[[package]] +name = "arbitrary" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1" + [[package]] name = "arrayvec" version = "0.7.4" @@ -110,7 +116,7 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" name = "descriptor-fuzz" version = "0.0.1" dependencies = [ - "honggfuzz", + "libfuzzer-sys", "miniscript 12.3.0", "miniscript 13.0.0", "regex", @@ -148,23 +154,6 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3011d1213f159867b13cfd6ac92d2cd5f1345762c63be3554e84092d85a50bbd" -[[package]] -name = "honggfuzz" -version = "0.5.56" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c76b6234c13c9ea73946d1379d33186151148e0da231506b964b44f3d023505" -dependencies = [ - "lazy_static", - "memmap2", - "rustc_version", -] - -[[package]] -name = "lazy_static" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" - [[package]] name = "libc" version = "0.2.154" @@ -172,19 +161,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ae743338b92ff9146ce83992f766a31066a91a8c84a45e0e9f21e7cf6de6d346" [[package]] -name = "memchr" -version = "2.5.0" +name = "libfuzzer-sys" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" +checksum = "86c975d637bc2a2f99440932b731491fc34c7f785d239e38af3addd3c2fd0e46" +dependencies = [ + "arbitrary", + "cc", +] [[package]] -name = "memmap2" -version = "0.9.4" +name = "memchr" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe751422e4a8caa417e13c3ea66452215d7d63e19e604f4980461212f3ae1322" -dependencies = [ - "libc", -] +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" [[package]] name = "miniscript" @@ -279,15 +269,6 @@ version = "0.6.29" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" -[[package]] -name = "rustc_version" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" -dependencies = [ - "semver", -] - [[package]] name = "secp256k1" version = "0.29.0" @@ -309,12 +290,6 @@ dependencies = [ "cc", ] -[[package]] -name = "semver" -version = "1.0.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92d43fe69e652f3df9bdc2b85b2854a0825b86e4fb76bc44d945137d053639ca" - [[package]] name = "serde" version = "1.0.199" diff --git a/Cargo-recent.lock b/Cargo-recent.lock index 861bd2c62..4c0cf8036 100644 --- a/Cargo-recent.lock +++ b/Cargo-recent.lock @@ -11,6 +11,12 @@ dependencies = [ "memchr", ] +[[package]] +name = "arbitrary" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1" + [[package]] name = "arrayvec" version = "0.7.4" @@ -96,9 +102,13 @@ dependencies = [ [[package]] name = "cc" -version = "1.0.28" +version = "1.2.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb4a8b715cb4597106ea87c7c84b2f1d452c7492033765df7f32651e66fcf749" +checksum = "5252b3d2648e5eedbc1a6f501e3c795e07025c1e93bbf8bbdd6eef7f447a6d54" +dependencies = [ + "find-msvc-tools", + "shlex", +] [[package]] name = "cfg-if" @@ -110,12 +120,18 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" name = "descriptor-fuzz" version = "0.0.1" dependencies = [ - "honggfuzz", + "libfuzzer-sys", "miniscript 12.3.0", "miniscript 13.0.0", "regex", ] +[[package]] +name = "find-msvc-tools" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fd99930f64d146689264c637b5af2f0233a933bef0d8570e2526bf9e083192d" + [[package]] name = "getrandom" version = "0.2.14" @@ -148,23 +164,6 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3011d1213f159867b13cfd6ac92d2cd5f1345762c63be3554e84092d85a50bbd" -[[package]] -name = "honggfuzz" -version = "0.5.56" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c76b6234c13c9ea73946d1379d33186151148e0da231506b964b44f3d023505" -dependencies = [ - "lazy_static", - "memmap2", - "rustc_version", -] - -[[package]] -name = "lazy_static" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" - [[package]] name = "libc" version = "0.2.154" @@ -172,19 +171,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ae743338b92ff9146ce83992f766a31066a91a8c84a45e0e9f21e7cf6de6d346" [[package]] -name = "memchr" -version = "2.5.0" +name = "libfuzzer-sys" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" +checksum = "8a9dc6556604b8ad76486563d5a47fad989b643932fa006e76e23d948bef0f5b" +dependencies = [ + "arbitrary", + "cc", +] [[package]] -name = "memmap2" -version = "0.9.4" +name = "memchr" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe751422e4a8caa417e13c3ea66452215d7d63e19e604f4980461212f3ae1322" -dependencies = [ - "libc", -] +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" [[package]] name = "miniscript" @@ -279,15 +279,6 @@ version = "0.6.29" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" -[[package]] -name = "rustc_version" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" -dependencies = [ - "semver", -] - [[package]] name = "secp256k1" version = "0.29.0" @@ -309,12 +300,6 @@ dependencies = [ "cc", ] -[[package]] -name = "semver" -version = "1.0.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92d43fe69e652f3df9bdc2b85b2854a0825b86e4fb76bc44d945137d053639ca" - [[package]] name = "serde" version = "1.0.199" @@ -344,6 +329,12 @@ dependencies = [ "serde", ] +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + [[package]] name = "syn" version = "2.0.56" diff --git a/fuzz/Cargo.toml b/fuzz/Cargo.toml index 205182e0e..88b571fac 100644 --- a/fuzz/Cargo.toml +++ b/fuzz/Cargo.toml @@ -10,7 +10,7 @@ publish = false cargo-fuzz = true [dependencies] -honggfuzz = { version = "0.5.56", default-features = false } +libfuzzer-sys = "0.4" # We shouldn't need an explicit version on the next line, but Andrew's tools # choke on it otherwise. See https://github.com/nix-community/crate2nix/issues/373 miniscript = { path = "..", features = [ "compiler" ], version = "13.0" } @@ -18,6 +18,9 @@ old_miniscript = { package = "miniscript", features = [ "compiler" ], version = regex = "1.0" +[lints.rust] +unexpected_cfgs = { level = "warn", check-cfg = ['cfg(fuzzing)'] } + [[bin]] name = "compile_descriptor" path = "fuzz_targets/compile_descriptor.rs" diff --git a/fuzz/README.md b/fuzz/README.md index df865391f..a58a994cb 100644 --- a/fuzz/README.md +++ b/fuzz/README.md @@ -1,29 +1,20 @@ # Fuzzing -`miniscript` has a fuzzing harness setup for use with honggfuzz. +`miniscript` has a fuzzing harness setup for use with cargo-fuzz. To run the fuzz-tests as in CI -- briefly fuzzing every target -- simply run - ./fuzz.sh + RUSTUP_TOOLCHAIN=nightly ./fuzz.sh in this directory. -To build honggfuzz, you must have libunwind on your system, as well as -libopcodes and libbfd from binutils **2.38** on your system. The most -recently-released binutils 2.39 has changed their API in a breaking way. +You need a nightly compiler to run the fuzz tests. You will also need +`cargo-fuzz` installed: -On Nix, you can obtain these libraries, and disable some hardening flags -which conflict with the way honggfuzz builds its targets, by running + cargo install --force cargo-fuzz - nix-shell -p libopcodes_2_38 -p libunwind - # In the nix-shell run these - NIX_HARDENING_ENABLE=''${NIX_HARDENING_ENABLE/fortify/} - NIX_HARDENING_ENABLE=''${NIX_HARDENING_ENABLE/fortify3/} - -and then run fuzz.sh as above. - -# Fuzzing with weak cryptography +## Fuzzing with weak cryptography You may wish to replace the hashing and signing code with broken crypto, which will be faster and enable the fuzzer to do otherwise impossible @@ -46,7 +37,7 @@ secp256k1 library with broken cryptography. Needless to say, NEVER COMPILE REAL CODE WITH THESE FLAGS because if a fuzzer can break your crypto, so can anybody. -# Long-term fuzzing +## Long-term fuzzing To see the full list of targets, the most straightforward way is to run @@ -59,22 +50,19 @@ To run each of them for an hour, run To run a single fuzztest indefinitely, run - cargo hfuzz run + cargo +nightly fuzz run `cycle.sh` uses the `chrt` utility to try to reduce the priority of the jobs. If you would like to run for longer, the most straightforward way is to edit `cycle.sh` before starting. To run the fuzz-tests in parallel, you will need to implement a custom harness. -# Adding fuzz tests +## Adding fuzz tests All fuzz tests can be found in the `fuzz_target/` directory. Adding a new one is as simple as copying an existing one and editing the `do_test` function to do what you want. -If your test clearly belongs to a specific crate, please put it in that -crate's directory. Otherwise you can put it directly in `fuzz_target/`. - If you need to add dependencies, edit the file `generate-files.sh` to add it to the generated `Cargo.toml`. @@ -91,43 +79,24 @@ If it is working, you will see a rapid stream of data for many seconds (you can hit Ctrl+C to stop it early). If not, you should quickly see an error. -# Reproducing Failures +## Computing code coverage -If a fuzztest fails, it will exit with a summary which looks something like +Compute the code coverage of the corpus of a given target using the following command: -``` -... - fuzzTarget : hfuzz_target/x86_64-unknown-linux-gnu/release/hashes_sha256 -CRASH: -DESCRIPTION: -ORIG_FNAME: 00000000000000000000000000000000.00000000.honggfuzz.cov -FUZZ_FNAME: hfuzz_workspace/hashes_sha256/SIGABRT.PC.7ffff7c8abc7.STACK.18826d9b64.CODE.-6.ADDR.0.INSTR.mov____%eax,%ebp.fuzz -... -===================================================================== -fff400610004 +```bash +cargo fuzz coverage TARGET ``` -The final line is a hex-encoded version of the input that caused the crash. You -can test this directly by editing the `duplicate_crash` test to copy/paste the -hex output into the call to `extend_vec_from_hex`. Then run the test with +Generate a human-readable HTML coverage report using a command as below. _The exact paths might differ depending on the target architecture._ - cargo test +The demangler `rustfilt` must be installed. -Note that if you set your `RUSTFLAGS` while fuzzing (see above) you must make -sure they are set the same way when running `cargo test`. +```bash +cargo cov -- show -Xdemangler=rustfilt target/x86_64-unknown-linux-gnu/coverage/x86_64-unknown-linux-gnu/release/TARGET -instr-profile=fuzz/coverage/TARGET/coverage.profdata -show-line-counts-or-regions -show-instantiations --format html --output-dir=OUTPUT_DIR -ignore-filename-regex="\.cargo" +``` -If the `duplicate_crash` function is not present, please add it. A template is -as follows: +More information is available in the [rustc book](https://doc.rust-lang.org/stable/rustc/instrument-coverage.html#running-the-instrumented-binary-to-generate-raw-coverage-profiling-data). -``` -#[cfg(test)] -mod tests { - use miniscript::bitcoin::hex::FromHex; - - #[test] - fn duplicate_crash() { - let v = Vec::from_hex("abcd").unwrap(); - super::do_test(&v); - } -} -``` +## Reproducing and Minimizing Failures + +(todo -- wait for some failures to happen before filling in this section) diff --git a/fuzz/fuzz.sh b/fuzz/fuzz.sh index 75a9789a5..2f287b132 100755 --- a/fuzz/fuzz.sh +++ b/fuzz/fuzz.sh @@ -1,10 +1,10 @@ #!/usr/bin/env bash -set -ex +set -o errexit # exit immediately if any command fails +set -o xtrace # print trace of executed commands -REPO_DIR=$(git rev-parse --show-toplevel || jj workspace show) +REPO_DIR=$(git rev-parse --show-toplevel) -# can't find the file because of the ENV var -# shellcheck source=/dev/null +# shellcheck source=./fuzz/fuzz-util.sh source "$REPO_DIR/fuzz/fuzz-util.sh" # Check that input files are correct Windows file names @@ -19,17 +19,8 @@ fi cargo --version rustc --version -# Testing -cargo install --force honggfuzz --no-default-features +# Run fuzz target for targetFile in $targetFiles; do targetName=$(targetFileToName "$targetFile") - echo "Fuzzing target $targetName ($targetFile)" - if [ -d "hfuzz_input/$targetName" ]; then - HFUZZ_INPUT_ARGS="-f hfuzz_input/$targetName/input\"" - else - HFUZZ_INPUT_ARGS="" - fi - HFUZZ_RUN_ARGS="--run_time 30 --exit_upon_crash -v $HFUZZ_INPUT_ARGS" cargo hfuzz run "$targetName" - - checkReport "$targetName" + cargo-fuzz run "$targetName" -- -max_total_time=30 done diff --git a/fuzz/fuzz_targets/compile_descriptor.rs b/fuzz/fuzz_targets/compile_descriptor.rs index b246ed7a7..59b779e8d 100644 --- a/fuzz/fuzz_targets/compile_descriptor.rs +++ b/fuzz/fuzz_targets/compile_descriptor.rs @@ -1,8 +1,9 @@ -#![allow(unexpected_cfgs)] +// SPDX-License-Identifier: CC0-1.0 + +#![cfg_attr(fuzzing, no_main)] use std::str::FromStr; -use honggfuzz::fuzz; use miniscript::{policy, Miniscript, Segwitv0}; use policy::Liftable; @@ -28,10 +29,19 @@ fn do_test(data: &[u8]) { } } -fn main() { - loop { - fuzz!(|data| { - do_test(data); - }); +#[cfg(fuzzing)] +libfuzzer_sys::fuzz_target!(|data| { do_test(data); }); + +#[cfg(not(fuzzing))] +fn main() { do_test(&[]); } + +#[cfg(test)] +mod tests { + use miniscript::hex; + + #[test] + fn duplicate_crash() { + let v = hex::decode_to_vec("abcd").unwrap(); + super::do_test(&v); } } diff --git a/fuzz/fuzz_targets/compile_taproot.rs b/fuzz/fuzz_targets/compile_taproot.rs index 4d43ffff2..3bbd00384 100644 --- a/fuzz/fuzz_targets/compile_taproot.rs +++ b/fuzz/fuzz_targets/compile_taproot.rs @@ -1,8 +1,9 @@ -#![allow(unexpected_cfgs)] +// SPDX-License-Identifier: CC0-1.0 + +#![cfg_attr(fuzzing, no_main)] use std::str::FromStr; -use honggfuzz::fuzz; use miniscript::{policy, Miniscript, Tap}; use policy::Liftable; @@ -28,10 +29,19 @@ fn do_test(data: &[u8]) { } } -fn main() { - loop { - fuzz!(|data| { - do_test(data); - }); +#[cfg(fuzzing)] +libfuzzer_sys::fuzz_target!(|data| { do_test(data); }); + +#[cfg(not(fuzzing))] +fn main() { do_test(&[]); } + +#[cfg(test)] +mod tests { + use miniscript::hex; + + #[test] + fn duplicate_crash() { + let v = hex::decode_to_vec("abcd").unwrap(); + super::do_test(&v); } } diff --git a/fuzz/fuzz_targets/miniscript_satisfy.rs b/fuzz/fuzz_targets/miniscript_satisfy.rs index d22bde00c..03bbfee86 100644 --- a/fuzz/fuzz_targets/miniscript_satisfy.rs +++ b/fuzz/fuzz_targets/miniscript_satisfy.rs @@ -1,9 +1,10 @@ -#![allow(unexpected_cfgs)] +// SPDX-License-Identifier: CC0-1.0 + +#![cfg_attr(fuzzing, no_main)] use std::sync::atomic::{AtomicUsize, Ordering}; use descriptor_fuzz::FuzzPk; -use honggfuzz::fuzz; use miniscript::bitcoin::hashes::hash160; use miniscript::bitcoin::locktime::{absolute, relative}; use miniscript::bitcoin::taproot::Signature; @@ -156,13 +157,11 @@ fn do_test(data: &[u8]) { }; } -fn main() { - loop { - fuzz!(|data| { - do_test(data); - }); - } -} +#[cfg(fuzzing)] +libfuzzer_sys::fuzz_target!(|data| { do_test(data); }); + +#[cfg(not(fuzzing))] +fn main() { do_test(&[]); } #[cfg(test)] mod tests { diff --git a/fuzz/fuzz_targets/parse_descriptor.rs b/fuzz/fuzz_targets/parse_descriptor.rs index 040b6730d..410f2a8e6 100644 --- a/fuzz/fuzz_targets/parse_descriptor.rs +++ b/fuzz/fuzz_targets/parse_descriptor.rs @@ -1,8 +1,9 @@ -#![allow(unexpected_cfgs)] +// SPDX-License-Identifier: CC0-1.0 + +#![cfg_attr(fuzzing, no_main)] use std::str::FromStr; -use honggfuzz::fuzz; use miniscript::DescriptorPublicKey; fn do_test(data: &[u8]) { @@ -13,10 +14,19 @@ fn do_test(data: &[u8]) { } } -fn main() { - loop { - fuzz!(|data| { - do_test(data); - }); +#[cfg(fuzzing)] +libfuzzer_sys::fuzz_target!(|data| { do_test(data); }); + +#[cfg(not(fuzzing))] +fn main() { do_test(&[]); } + +#[cfg(test)] +mod tests { + use miniscript::hex; + + #[test] + fn duplicate_crash() { + let v = hex::decode_to_vec("abcd").unwrap(); + super::do_test(&v); } } diff --git a/fuzz/fuzz_targets/parse_descriptor_definite.rs b/fuzz/fuzz_targets/parse_descriptor_definite.rs index f6a43476c..d95f6b498 100644 --- a/fuzz/fuzz_targets/parse_descriptor_definite.rs +++ b/fuzz/fuzz_targets/parse_descriptor_definite.rs @@ -1,6 +1,7 @@ -#![allow(unexpected_cfgs)] +// SPDX-License-Identifier: CC0-1.0 + +#![cfg_attr(fuzzing, no_main)] -use honggfuzz::fuzz; use miniscript::{DefiniteDescriptorKey, Descriptor}; fn do_test(data: &[u8]) { @@ -12,10 +13,19 @@ fn do_test(data: &[u8]) { } } -fn main() { - loop { - fuzz!(|data| { - do_test(data); - }); +#[cfg(fuzzing)] +libfuzzer_sys::fuzz_target!(|data| { do_test(data); }); + +#[cfg(not(fuzzing))] +fn main() { do_test(&[]); } + +#[cfg(test)] +mod tests { + use miniscript::hex; + + #[test] + fn duplicate_crash() { + let v = hex::decode_to_vec("abcd").unwrap(); + super::do_test(&v); } } diff --git a/fuzz/fuzz_targets/parse_descriptor_priv.rs b/fuzz/fuzz_targets/parse_descriptor_priv.rs index 98bfb992c..6160bd199 100644 --- a/fuzz/fuzz_targets/parse_descriptor_priv.rs +++ b/fuzz/fuzz_targets/parse_descriptor_priv.rs @@ -1,6 +1,7 @@ -#![allow(unexpected_cfgs)] +// SPDX-License-Identifier: CC0-1.0 + +#![cfg_attr(fuzzing, no_main)] -use honggfuzz::fuzz; use miniscript::bitcoin::secp256k1; use miniscript::Descriptor; @@ -14,10 +15,19 @@ fn do_test(data: &[u8]) { } } -fn main() { - loop { - fuzz!(|data| { - do_test(data); - }); +#[cfg(fuzzing)] +libfuzzer_sys::fuzz_target!(|data| { do_test(data); }); + +#[cfg(not(fuzzing))] +fn main() { do_test(&[]); } + +#[cfg(test)] +mod tests { + use miniscript::hex; + + #[test] + fn duplicate_crash() { + let v = hex::decode_to_vec("abcd").unwrap(); + super::do_test(&v); } } diff --git a/fuzz/fuzz_targets/parse_descriptor_secret.rs b/fuzz/fuzz_targets/parse_descriptor_secret.rs index 76c4eb4fc..2f3827a97 100644 --- a/fuzz/fuzz_targets/parse_descriptor_secret.rs +++ b/fuzz/fuzz_targets/parse_descriptor_secret.rs @@ -1,8 +1,9 @@ -#![allow(unexpected_cfgs)] +// SPDX-License-Identifier: CC0-1.0 + +#![cfg_attr(fuzzing, no_main)] use std::str::FromStr; -use honggfuzz::fuzz; use miniscript::descriptor::DescriptorSecretKey; fn do_test(data: &[u8]) { @@ -13,10 +14,19 @@ fn do_test(data: &[u8]) { } } -fn main() { - loop { - fuzz!(|data| { - do_test(data); - }); +#[cfg(fuzzing)] +libfuzzer_sys::fuzz_target!(|data| { do_test(data); }); + +#[cfg(not(fuzzing))] +fn main() { do_test(&[]); } + +#[cfg(test)] +mod tests { + use miniscript::hex; + + #[test] + fn duplicate_crash() { + let v = hex::decode_to_vec("abcd").unwrap(); + super::do_test(&v); } } diff --git a/fuzz/fuzz_targets/regression_compiler.rs b/fuzz/fuzz_targets/regression_compiler.rs index 8703e7f3f..79154cd3d 100644 --- a/fuzz/fuzz_targets/regression_compiler.rs +++ b/fuzz/fuzz_targets/regression_compiler.rs @@ -1,5 +1,8 @@ +// SPDX-License-Identifier: CC0-1.0 + +#![cfg_attr(fuzzing, no_main)] + use descriptor_fuzz::FuzzPk; -use honggfuzz::fuzz; use miniscript::{policy, ParseError, ParseNumError}; use old_miniscript::policy as old_policy; @@ -51,13 +54,11 @@ fn do_test(data: &[u8]) { } } -fn main() { - loop { - fuzz!(|data| { - do_test(data); - }); - } -} +#[cfg(fuzzing)] +libfuzzer_sys::fuzz_target!(|data| { do_test(data); }); + +#[cfg(not(fuzzing))] +fn main() { do_test(&[]); } #[cfg(test)] mod tests { diff --git a/fuzz/fuzz_targets/regression_descriptor_parse.rs b/fuzz/fuzz_targets/regression_descriptor_parse.rs index 379b01814..6d422b640 100644 --- a/fuzz/fuzz_targets/regression_descriptor_parse.rs +++ b/fuzz/fuzz_targets/regression_descriptor_parse.rs @@ -1,6 +1,9 @@ +// SPDX-License-Identifier: CC0-1.0 + +#![cfg_attr(fuzzing, no_main)] + use core::str::FromStr; -use honggfuzz::fuzz; use miniscript::Descriptor; use old_miniscript::Descriptor as OldDescriptor; @@ -45,13 +48,11 @@ fn do_test(data: &[u8]) { } } -fn main() { - loop { - fuzz!(|data| { - do_test(data); - }); - } -} +#[cfg(fuzzing)] +libfuzzer_sys::fuzz_target!(|data| { do_test(data); }); + +#[cfg(not(fuzzing))] +fn main() { do_test(&[]); } #[cfg(test)] mod tests { diff --git a/fuzz/fuzz_targets/regression_taptree.rs b/fuzz/fuzz_targets/regression_taptree.rs index ab50d06bd..92aedbffd 100644 --- a/fuzz/fuzz_targets/regression_taptree.rs +++ b/fuzz/fuzz_targets/regression_taptree.rs @@ -1,7 +1,10 @@ +// SPDX-License-Identifier: CC0-1.0 + +#![cfg_attr(fuzzing, no_main)] + use std::collections::{HashMap, HashSet}; use descriptor_fuzz::FuzzPk; -use honggfuzz::fuzz; use miniscript::descriptor::Tr; use old_miniscript::descriptor::Tr as OldTr; @@ -49,13 +52,11 @@ fn do_test(data: &[u8]) { } } -fn main() { - loop { - fuzz!(|data| { - do_test(data); - }); - } -} +#[cfg(fuzzing)] +libfuzzer_sys::fuzz_target!(|data| { do_test(data); }); + +#[cfg(not(fuzzing))] +fn main() { do_test(&[]); } #[cfg(test)] mod tests { diff --git a/fuzz/fuzz_targets/roundtrip_concrete.rs b/fuzz/fuzz_targets/roundtrip_concrete.rs index 698d2e908..bdaa6d962 100644 --- a/fuzz/fuzz_targets/roundtrip_concrete.rs +++ b/fuzz/fuzz_targets/roundtrip_concrete.rs @@ -1,8 +1,9 @@ -#![allow(unexpected_cfgs)] +// SPDX-License-Identifier: CC0-1.0 + +#![cfg_attr(fuzzing, no_main)] use std::str::FromStr; -use honggfuzz::fuzz; use miniscript::policy; use regex::Regex; @@ -20,13 +21,11 @@ fn do_test(data: &[u8]) { } } -fn main() { - loop { - fuzz!(|data| { - do_test(data); - }); - } -} +#[cfg(fuzzing)] +libfuzzer_sys::fuzz_target!(|data| { do_test(data); }); + +#[cfg(not(fuzzing))] +fn main() { do_test(&[]); } #[cfg(test)] mod tests { diff --git a/fuzz/fuzz_targets/roundtrip_descriptor.rs b/fuzz/fuzz_targets/roundtrip_descriptor.rs index de9232a62..7392a56d3 100644 --- a/fuzz/fuzz_targets/roundtrip_descriptor.rs +++ b/fuzz/fuzz_targets/roundtrip_descriptor.rs @@ -1,8 +1,9 @@ -#![allow(unexpected_cfgs)] +// SPDX-License-Identifier: CC0-1.0 + +#![cfg_attr(fuzzing, no_main)] use std::str::FromStr; -use honggfuzz::fuzz; use miniscript::Descriptor; fn do_test(data: &[u8]) { @@ -15,13 +16,11 @@ fn do_test(data: &[u8]) { } } -fn main() { - loop { - fuzz!(|data| { - do_test(data); - }); - } -} +#[cfg(fuzzing)] +libfuzzer_sys::fuzz_target!(|data| { do_test(data); }); + +#[cfg(not(fuzzing))] +fn main() { do_test(&[]); } #[cfg(test)] mod tests { diff --git a/fuzz/fuzz_targets/roundtrip_miniscript_script.rs b/fuzz/fuzz_targets/roundtrip_miniscript_script.rs index 8c21e66c6..ab2e290dc 100644 --- a/fuzz/fuzz_targets/roundtrip_miniscript_script.rs +++ b/fuzz/fuzz_targets/roundtrip_miniscript_script.rs @@ -1,6 +1,7 @@ -#![allow(unexpected_cfgs)] +// SPDX-License-Identifier: CC0-1.0 + +#![cfg_attr(fuzzing, no_main)] -use honggfuzz::fuzz; use miniscript::bitcoin::blockdata::script; use miniscript::{Miniscript, Segwitv0}; @@ -15,13 +16,11 @@ fn do_test(data: &[u8]) { } } -fn main() { - loop { - fuzz!(|data| { - do_test(data); - }); - } -} +#[cfg(fuzzing)] +libfuzzer_sys::fuzz_target!(|data| { do_test(data); }); + +#[cfg(not(fuzzing))] +fn main() { do_test(&[]); } #[cfg(test)] mod tests { diff --git a/fuzz/fuzz_targets/roundtrip_miniscript_script_tap.rs b/fuzz/fuzz_targets/roundtrip_miniscript_script_tap.rs index 7dd16fc9c..93f307405 100644 --- a/fuzz/fuzz_targets/roundtrip_miniscript_script_tap.rs +++ b/fuzz/fuzz_targets/roundtrip_miniscript_script_tap.rs @@ -1,6 +1,7 @@ -#![allow(unexpected_cfgs)] +// SPDX-License-Identifier: CC0-1.0 + +#![cfg_attr(fuzzing, no_main)] -use honggfuzz::fuzz; use miniscript::bitcoin::blockdata::script; use miniscript::{Miniscript, Tap}; @@ -15,10 +16,19 @@ fn do_test(data: &[u8]) { } } -fn main() { - loop { - fuzz!(|data| { - do_test(data); - }); +#[cfg(fuzzing)] +libfuzzer_sys::fuzz_target!(|data| { do_test(data); }); + +#[cfg(not(fuzzing))] +fn main() { do_test(&[]); } + +#[cfg(test)] +mod tests { + use miniscript::hex; + + #[test] + fn duplicate_crash() { + let v = hex::decode_to_vec("abcd").unwrap(); + super::do_test(&v); } } diff --git a/fuzz/fuzz_targets/roundtrip_miniscript_str.rs b/fuzz/fuzz_targets/roundtrip_miniscript_str.rs index 93bbdc5c0..a06ca9689 100644 --- a/fuzz/fuzz_targets/roundtrip_miniscript_str.rs +++ b/fuzz/fuzz_targets/roundtrip_miniscript_str.rs @@ -1,8 +1,9 @@ -#![allow(unexpected_cfgs)] +// SPDX-License-Identifier: CC0-1.0 + +#![cfg_attr(fuzzing, no_main)] use std::str::FromStr; -use honggfuzz::fuzz; use miniscript::{Miniscript, Segwitv0, Tap}; fn do_test(data: &[u8]) { @@ -20,13 +21,11 @@ fn do_test(data: &[u8]) { } } -fn main() { - loop { - fuzz!(|data| { - do_test(data); - }); - } -} +#[cfg(fuzzing)] +libfuzzer_sys::fuzz_target!(|data| { do_test(data); }); + +#[cfg(not(fuzzing))] +fn main() { do_test(&[]); } #[cfg(test)] mod tests { diff --git a/fuzz/fuzz_targets/roundtrip_semantic.rs b/fuzz/fuzz_targets/roundtrip_semantic.rs index 668faa354..8d9aa626e 100644 --- a/fuzz/fuzz_targets/roundtrip_semantic.rs +++ b/fuzz/fuzz_targets/roundtrip_semantic.rs @@ -1,8 +1,9 @@ -#![allow(unexpected_cfgs)] +// SPDX-License-Identifier: CC0-1.0b + +#![cfg_attr(fuzzing, no_main)] use std::str::FromStr; -use honggfuzz::fuzz; use miniscript::policy; type Policy = policy::Semantic; @@ -15,10 +16,8 @@ fn do_test(data: &[u8]) { } } -fn main() { - loop { - fuzz!(|data| { - do_test(data); - }); - } -} +#[cfg(fuzzing)] +libfuzzer_sys::fuzz_target!(|data| { do_test(data); }); + +#[cfg(not(fuzzing))] +fn main() { do_test(&[]); } diff --git a/fuzz/generate-files.sh b/fuzz/generate-files.sh index d9ce4d408..fae88036d 100755 --- a/fuzz/generate-files.sh +++ b/fuzz/generate-files.sh @@ -22,13 +22,16 @@ publish = false cargo-fuzz = true [dependencies] -honggfuzz = { version = "0.5.56", default-features = false } +libfuzzer-sys = "0.4" # We shouldn't need an explicit version on the next line, but Andrew's tools # choke on it otherwise. See https://github.com/nix-community/crate2nix/issues/373 miniscript = { path = "..", features = [ "compiler" ], version = "13.0" } old_miniscript = { package = "miniscript", features = [ "compiler" ], version = "12.3" } regex = "1.0" + +[lints.rust] +unexpected_cfgs = { level = "warn", check-cfg = ['cfg(fuzzing)'] } EOF for targetFile in $(listTargetFiles); do @@ -54,9 +57,21 @@ on: - cron: '00 06 * * *' jobs: + Prepare: + runs-on: ubuntu-24.04 + outputs: + nightly_version: \${{ steps.read_toolchain.outputs.nightly_version }} + steps: + - name: "Checkout repo" + uses: actions/checkout@v4 + - name: "Read nightly version" + id: read_toolchain + run: echo "nightly_version=\$(cat nightly-version)" >> \$GITHUB_OUTPUT + fuzz: if: \${{ !github.event.act }} - runs-on: ubuntu-20.04 + needs: Prepare + runs-on: ubuntu-25.04 strategy: fail-fast: false matrix: @@ -77,9 +92,15 @@ $(for name in $(listTargetNames); do echo "$name,"; done) fuzz/target target key: cache-\${{ matrix.target }}-\${{ hashFiles('**/Cargo.toml','**/Cargo.lock') }} + + - name: Install toolchain - uses: dtolnay/rust-toolchain@stable with: - toolchain: '1.65.0' + toolchain: \${{ needs.Prepare.outputs.nightly_version }} + + - name: Install Dependencies + run: cargo update && cargo update -p cc --precise 1.0.83 && cargo install --force cargo-fuzz + - name: fuzz run: cd fuzz && ./fuzz.sh "\${{ matrix.fuzz_target }}" - run: echo "\${{ matrix.fuzz_target }}" >executed_\${{ matrix.fuzz_target }} @@ -93,7 +114,7 @@ $(for name in $(listTargetNames); do echo "$name,"; done) needs: fuzz runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - uses: actions/download-artifact@v4 - name: Display structure of downloaded files run: ls -R diff --git a/nightly-version b/nightly-version index ba747383d..0db8f0002 100644 --- a/nightly-version +++ b/nightly-version @@ -1 +1 @@ -nightly-2025-03-21 +nightly-2025-09-05 diff --git a/src/descriptor/key.rs b/src/descriptor/key.rs index 2162010d7..241f931ee 100644 --- a/src/descriptor/key.rs +++ b/src/descriptor/key.rs @@ -1203,9 +1203,7 @@ impl DescriptorXKey { }; if &compare_fingerprint == fingerprint - && compare_path - .into_iter() - .eq(path_excluding_wildcard.into_iter()) + && compare_path.into_iter().eq(&path_excluding_wildcard) { Some(path_excluding_wildcard) } else { diff --git a/src/descriptor/mod.rs b/src/descriptor/mod.rs index 44b93ce59..9bc6f2c31 100644 --- a/src/descriptor/mod.rs +++ b/src/descriptor/mod.rs @@ -294,7 +294,7 @@ impl Descriptor { /// /// If the descriptor is not a Taproot descriptor, **or** if the descriptor is a /// Taproot descriptor containing only a keyspend, returns an empty iterator. - pub fn tap_tree_iter(&self) -> tr::TapTreeIter { + pub fn tap_tree_iter(&self) -> tr::TapTreeIter<'_, Pk> { if let Descriptor::Tr(ref tr) = self { if let Some(tree) = tr.tap_tree() { return tree.leaves(); diff --git a/src/descriptor/tr/mod.rs b/src/descriptor/tr/mod.rs index f0f4ccfc4..203e3cee2 100644 --- a/src/descriptor/tr/mod.rs +++ b/src/descriptor/tr/mod.rs @@ -108,7 +108,7 @@ impl Tr { /// /// The yielded elements include the Miniscript for each leave as well as its depth /// in the tree, which is the data required by PSBT (BIP 371). - pub fn leaves(&self) -> TapTreeIter { + pub fn leaves(&self) -> TapTreeIter<'_, Pk> { match self.tree { Some(ref t) => t.leaves(), None => TapTreeIter::empty(), diff --git a/src/descriptor/tr/spend_info.rs b/src/descriptor/tr/spend_info.rs index 3bace2d18..b0d23121d 100644 --- a/src/descriptor/tr/spend_info.rs +++ b/src/descriptor/tr/spend_info.rs @@ -181,7 +181,7 @@ impl TrSpendInfo { /// This yields the same leaves in the same order as [`super::Tr::leaves`] on the original /// [`super::Tr`]. However, in addition to yielding the leaves and their depths, it also /// yields their scripts, leafhashes, and control blocks. - pub fn leaves(&self) -> TrSpendInfoIter { + pub fn leaves(&self) -> TrSpendInfoIter<'_, Pk> { TrSpendInfoIter { spend_info: self, index: 0, diff --git a/src/descriptor/tr/taptree.rs b/src/descriptor/tr/taptree.rs index 35f23a95e..27e860390 100644 --- a/src/descriptor/tr/taptree.rs +++ b/src/descriptor/tr/taptree.rs @@ -53,7 +53,7 @@ impl TapTree { /// /// The yielded elements include the Miniscript for each leave as well as its depth /// in the tree, which is the data required by PSBT (BIP 371). - pub fn leaves(&self) -> TapTreeIter { TapTreeIter::from_tree(self) } + pub fn leaves(&self) -> TapTreeIter<'_, Pk> { TapTreeIter::from_tree(self) } /// Converts keys from one type of public key to another. pub fn translate_pk( diff --git a/src/miniscript/iter.rs b/src/miniscript/iter.rs index f82e329aa..0ac48e651 100644 --- a/src/miniscript/iter.rs +++ b/src/miniscript/iter.rs @@ -18,12 +18,12 @@ impl Miniscript { /// Creates a new [Iter] iterator that will iterate over all [Miniscript] items within /// AST by traversing its branches. For the specific algorithm please see /// [Iter::next] function. - pub fn iter(&self) -> Iter { Iter::new(self) } + pub fn iter(&self) -> Iter<'_, Pk, Ctx> { Iter::new(self) } /// Creates a new [PkIter] iterator that will iterate over all plain public keys (and not /// key hash values) present in [Miniscript] items within AST by traversing all its branches. /// For the specific algorithm please see [PkIter::next] function. - pub fn iter_pk(&self) -> PkIter { PkIter::new(self) } + pub fn iter_pk(&self) -> PkIter<'_, Pk, Ctx> { PkIter::new(self) } /// Enumerates all child nodes of the current AST node (`self`) and returns a `Vec` referencing /// them. diff --git a/src/miniscript/mod.rs b/src/miniscript/mod.rs index e319304a2..9b1b85bf2 100644 --- a/src/miniscript/mod.rs +++ b/src/miniscript/mod.rs @@ -604,7 +604,7 @@ impl Miniscript { /// The type information and extra properties are implied by the AST. impl PartialOrd for Miniscript { fn partial_cmp(&self, other: &Miniscript) -> Option { - Some(self.node.cmp(&other.node)) + Some(self.cmp(other)) } } diff --git a/src/policy/concrete.rs b/src/policy/concrete.rs index 01ac117a2..a2c41a987 100644 --- a/src/policy/concrete.rs +++ b/src/policy/concrete.rs @@ -192,7 +192,7 @@ impl Policy { /// Since this splitting might lead to exponential blow-up, we constrain the number of /// leaf-nodes to [`MAX_COMPILATION_LEAVES`]. #[cfg(feature = "compiler")] - fn tapleaf_probability_iter(&self) -> TapleafProbabilityIter { + fn tapleaf_probability_iter(&self) -> TapleafProbabilityIter<'_, Pk> { TapleafProbabilityIter { stack: vec![(1.0, self)] } } @@ -346,7 +346,7 @@ impl Policy { /// /// It is **not recommended** to use policy as a stable identifier for a miniscript. You should /// use the policy compiler once, and then use the miniscript output as a stable identifier. See - /// the compiler document in [`doc/compiler.md`] for more details. + /// the compiler document in `doc/compiler.md` for more details. #[cfg(feature = "compiler")] pub fn compile_to_descriptor( &self, @@ -375,7 +375,7 @@ impl Policy { /// /// It is **not recommended** to use policy as a stable identifier for a miniscript. You should /// use the policy compiler once, and then use the miniscript output as a stable identifier. See - /// the compiler document in doc/compiler.md for more details. + /// the compiler document in `doc/compiler.md` for more details. #[cfg(feature = "compiler")] pub fn compile(&self) -> Result, CompilerError> { self.is_valid()?; diff --git a/src/primitives/threshold.rs b/src/primitives/threshold.rs index ee0c1bda5..f1021e9a4 100644 --- a/src/primitives/threshold.rs +++ b/src/primitives/threshold.rs @@ -217,7 +217,7 @@ impl Threshold { pub fn into_data(self) -> Vec { self.inner } /// Passthrough to an iterator on the underlying vector. - pub fn iter(&self) -> core::slice::Iter { self.inner.iter() } + pub fn iter(&self) -> core::slice::Iter<'_, T> { self.inner.iter() } } impl Threshold {