diff --git a/.clusterfuzzlite/Dockerfile b/.clusterfuzzlite/Dockerfile new file mode 100644 index 0000000..ff2119d --- /dev/null +++ b/.clusterfuzzlite/Dockerfile @@ -0,0 +1,4 @@ +FROM gcr.io/oss-fuzz-base/base-builder:v1 +COPY . $SRC/ci-workflows +WORKDIR $SRC/ci-workflows +COPY .clusterfuzzlite/build.sh $SRC/ diff --git a/.clusterfuzzlite/build.sh b/.clusterfuzzlite/build.sh new file mode 100755 index 0000000..41da699 --- /dev/null +++ b/.clusterfuzzlite/build.sh @@ -0,0 +1,5 @@ +#!/bin/bash -eu + +"$CXX" $CXXFLAGS -std=c++17 \ + "$SRC/ci-workflows/tests/fixtures/fuzzing/checksum_fuzzer.cpp" \ + -o "$OUT/checksum_fuzzer" $LIB_FUZZING_ENGINE diff --git a/.clusterfuzzlite/project.yaml b/.clusterfuzzlite/project.yaml new file mode 100644 index 0000000..b478801 --- /dev/null +++ b/.clusterfuzzlite/project.yaml @@ -0,0 +1 @@ +language: c++ diff --git a/.github/workflows/runtime-fixtures-fuzzing.yml b/.github/workflows/runtime-fixtures-fuzzing.yml new file mode 100644 index 0000000..bd02fbb --- /dev/null +++ b/.github/workflows/runtime-fixtures-fuzzing.yml @@ -0,0 +1,78 @@ +name: runtime-fixtures-fuzzing + +on: + pull_request: + paths: + - '.clusterfuzzlite/**' + - 'tests/fixtures/fuzzing/**' + - 'tests/fixtures/rust/fuzz/**' + - '.github/workflows/fuzzing.yml' + - '.github/workflows/clusterfuzzlite.yml' + - '.github/workflows/runtime-fixtures-fuzzing.yml' + workflow_dispatch: + +permissions: {} + +concurrency: + group: ${{ github.workflow }}-${{ github.run_id }} + cancel-in-progress: false + +jobs: + cargo-fuzz: + permissions: + contents: read # cargo-fuzz compiles the checked-out fixture tree + uses: ./.github/workflows/fuzzing.yml + with: + runner: ubuntu-latest + working_directory: tests/fixtures/rust + rust_toolchain: nightly + install_command: cargo install cargo-fuzz --locked + fuzz_command: cargo fuzz run checksum -- -runs=1000 + timeout_minutes: 20 + + clusterfuzzlite: + permissions: + actions: read # ClusterFuzzLite discovers prior corpus/build runs + contents: read # build the checked-out C++ harness + security-events: write # upload the real SARIF result requested below + uses: ./.github/workflows/clusterfuzzlite.yml + with: + runner: ubuntu-latest + mode: code-change + language: c++ + sanitizer_matrix: '["address"]' + fuzz_seconds: 60 + output_sarif: true + timeout_minutes: 25 + + evidence: + name: fuzz runtime evidence + if: ${{ always() }} + needs: [cargo-fuzz, clusterfuzzlite] + runs-on: ubuntu-latest + timeout-minutes: 5 + permissions: {} + steps: + - name: Require both real callers + env: + RESULTS: ${{ toJSON(needs) }} + run: | + set -euo pipefail + python3 -I <<'PY' + import json + import os + import sys + + results = json.loads(os.environ["RESULTS"]) + expected = {"cargo-fuzz", "clusterfuzzlite"} + if set(results) != expected: + raise SystemExit(f"unexpected callers: {sorted(results)}") + failed = { + name: payload.get("result") + for name, payload in results.items() + if payload.get("result") != "success" + } + if failed: + raise SystemExit(f"fuzz runtime evidence rejected: {failed}") + print("fuzz runtime evidence accepted: cargo-fuzz + ClusterFuzzLite") + PY diff --git a/.gitignore b/.gitignore index 9cff19a..089f5bc 100644 --- a/.gitignore +++ b/.gitignore @@ -16,6 +16,8 @@ __pycache__/ /.serena/ # Serena MCP local project config (developer tooling, not part of the published catalog) tests/fixtures/rust/target/ +tests/fixtures/rust/fuzz/artifacts/ +tests/fixtures/rust/fuzz/corpus/ tests/fixtures/terraform/.terraform/ tests/fixtures/terraform/.terraform.lock.hcl .venv/ diff --git a/docs/generated/workflow-inventory.md b/docs/generated/workflow-inventory.md index fe205f5..f914010 100644 --- a/docs/generated/workflow-inventory.md +++ b/docs/generated/workflow-inventory.md @@ -51,6 +51,7 @@ | `.github/workflows/release-supply-chain.yml` | `artifact-attestations`, `sbom-generation`, `slsa-build-provenance`, `release-supply-chain` | ga | | `.github/workflows/release.yml` | internal | internal | | `.github/workflows/runtime-fixtures-event-write.yml` | internal | internal | +| `.github/workflows/runtime-fixtures-fuzzing.yml` | internal | internal | | `.github/workflows/runtime-fixtures-languages.yml` | internal | internal | | `.github/workflows/runtime-fixtures.yml` | internal | internal | | `.github/workflows/rust-ci.yml` | `rust-ci` | ga | diff --git a/scripts/_workflow_yaml.py b/scripts/_workflow_yaml.py index 60dfe46..f04e202 100644 --- a/scripts/_workflow_yaml.py +++ b/scripts/_workflow_yaml.py @@ -28,6 +28,7 @@ "runtime-fixtures.yml", "runtime-fixtures-languages.yml", "runtime-fixtures-event-write.yml", + "runtime-fixtures-fuzzing.yml", "scorecard.yml", } diff --git a/tests/fixtures/fuzzing/checksum_fuzzer.cpp b/tests/fixtures/fuzzing/checksum_fuzzer.cpp new file mode 100644 index 0000000..2582fa6 --- /dev/null +++ b/tests/fixtures/fuzzing/checksum_fuzzer.cpp @@ -0,0 +1,13 @@ +#include +#include + +extern "C" int LLVMFuzzerTestOneInput(const std::uint8_t* data, + std::size_t size) { + std::uint8_t checksum = 0; + for (std::size_t index = 0; index < size; ++index) { + checksum = static_cast(checksum + data[index]); + } + volatile std::uint8_t observed = checksum; + (void)observed; + return 0; +} diff --git a/tests/fixtures/rust/fuzz/Cargo.lock b/tests/fixtures/rust/fuzz/Cargo.lock new file mode 100644 index 0000000..0bdc5d4 --- /dev/null +++ b/tests/fixtures/rust/fuzz/Cargo.lock @@ -0,0 +1,94 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "arbitrary" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1" + +[[package]] +name = "cc" +version = "1.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ad534f4357a5264cce5019c989cf66a4f0dc4e0d1b1d15f8aacec0ff7360273" +dependencies = [ + "find-msvc-tools", + "jobserver", + "libc", + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "ciwf-fixture" +version = "0.1.0" + +[[package]] +name = "ciwf-fixture-fuzz" +version = "0.0.0" +dependencies = [ + "ciwf-fixture", + "libfuzzer-sys", +] + +[[package]] +name = "find-msvc-tools" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d45db016d36b838f563236e9193d0ee6ce38f3f68b6c94e914b4929c96bbb890" + +[[package]] +name = "getrandom" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099" +dependencies = [ + "cfg-if", + "libc", + "r-efi", +] + +[[package]] +name = "jobserver" +version = "0.1.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c00acbd29eabad4a2392fa0e921c874934dbbf4194312ad20f04a0ed67a3cb3" +dependencies = [ + "getrandom", + "libc", +] + +[[package]] +name = "libc" +version = "0.2.189" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2" + +[[package]] +name = "libfuzzer-sys" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9fd2f41a1cba099f79a0b6b6c35656cf7c03351a7bae8ff0f28f25270f929d2" +dependencies = [ + "arbitrary", + "cc", +] + +[[package]] +name = "r-efi" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" + +[[package]] +name = "shlex" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" diff --git a/tests/fixtures/rust/fuzz/Cargo.toml b/tests/fixtures/rust/fuzz/Cargo.toml new file mode 100644 index 0000000..34247e3 --- /dev/null +++ b/tests/fixtures/rust/fuzz/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "ciwf-fixture-fuzz" +version = "0.0.0" +publish = false +edition = "2021" + +[package.metadata] +cargo-fuzz = true + +[dependencies] +libfuzzer-sys = "0.4" +ciwf-fixture = { path = ".." } + +[[bin]] +name = "checksum" +path = "fuzz_targets/checksum.rs" +test = false +doc = false +bench = false diff --git a/tests/fixtures/rust/fuzz/fuzz_targets/checksum.rs b/tests/fixtures/rust/fuzz/fuzz_targets/checksum.rs new file mode 100644 index 0000000..3344a9b --- /dev/null +++ b/tests/fixtures/rust/fuzz/fuzz_targets/checksum.rs @@ -0,0 +1,9 @@ +#![no_main] + +use libfuzzer_sys::fuzz_target; + +fuzz_target!(|data: &[u8]| { + if let Ok(text) = std::str::from_utf8(data) { + let _ = ciwf_fixture::checksum(text); + } +});