Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 32 additions & 21 deletions .github/workflows/fuzz-smoke.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,11 @@ jobs:
fuzz-smoke:
name: Fuzz Smoke Test (60s per target)
runs-on: cachekit
timeout-minutes: 20
# Budget: ~2 min rustup + ~10 min cargo-fuzz install (CARGO_HOME=/tmp/cargo,
# so no cross-run cache) + ~10 min one-shot ASAN build of all targets
# + ~1 min per fuzz target (count derived from `cargo fuzz list`, ~14 min
# today) + slack.
timeout-minutes: 45

steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
Expand All @@ -40,7 +44,16 @@ jobs:
rustup default nightly-2026-04-27

- name: Install cargo-fuzz
run: cargo install --locked cargo-fuzz
# Version-pinned: a floating `cargo install` hands a hijacked cargo-fuzz
# release immediate code exec on the self-hosted runner.
run: cargo install --locked cargo-fuzz --version 0.13.2

- name: Build all fuzz targets
run: |
cd rust/fuzz
# One cargo invocation builds every [[bin]] target, sharing dependency
# compilation. A target that fails to compile fails the job here.
cargo +nightly-2026-04-27 fuzz build

- name: Run fuzzing smoke tests
id: fuzz
Expand All @@ -50,28 +63,26 @@ jobs:
# Create artifacts directory
mkdir -p artifacts

# Fuzz targets that compile against cachekit-core 0.1.1.
# 9 encryption/advanced targets are disabled — cachekit-core API
# changed (encrypt_aes_gcm → encrypt_with_keys etc.) and the
# fuzz targets haven't been updated. See #114.
FUZZ_TARGETS=(
byte_storage_compress
byte_storage_decompress
byte_storage_format_injection
encryption_key_derivation
)

for target in "${FUZZ_TARGETS[@]}"; do
echo "Fuzzing $target..."
# Every [[bin]] target in fuzz/Cargo.toml runs — the list is derived,
# not hand-maintained, so new targets can't silently go dark.
# Resolve the list in a standalone assignment: `for t in $(cmd)` does
# not propagate a failing $(cmd) under `set -e`, and an empty list
# would loop zero times and go green having fuzzed nothing.
TARGETS=$(cargo +nightly-2026-04-27 fuzz list)
if [ -z "$TARGETS" ]; then
echo "::error::cargo fuzz list returned no targets"
exit 1
fi

if ! cargo +nightly-2026-04-27 fuzz run "$target" -- -max_total_time=60; then
echo "::warning::Fuzz target '$target' found potential issues"
# Continue to test other targets even if one fails
touch artifacts/.fuzz_failures
fi
# A non-zero exit (crash found, or target failed to run) fails the
# job immediately: green must mean "fuzzed and clean".
for target in $TARGETS; do
echo "Fuzzing $target..."
cargo +nightly-2026-04-27 fuzz run "$target" -- -max_total_time=60
done

# Check if any crashes were found
# Belt-and-braces: fail on any crash artifact even if the runs above
# all exited zero.
if find artifacts -name 'crash-*' -o -name 'timeout-*' -o -name 'oom-*' | grep -q .; then
echo "::error::Fuzzing discovered crashes or errors. See artifacts for details."
exit 1
Expand Down
48 changes: 32 additions & 16 deletions .github/workflows/security-deep.yml
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,10 @@ jobs:
fuzzing:
name: Extended Fuzzing (3 targets × 1h)
runs-on: cachekit
timeout-minutes: 200
# Budget: ~2 min rustup + ~10 min cargo-fuzz install (CARGO_HOME=/tmp/cargo,
# no cross-run cache) + ~10 min ASAN builds + 3 x 60 min fuzz time
# (-max_total_time is pure fuzz time, excluding builds) + slack.
timeout-minutes: 240
env:
# Avoid EXDEV "cross-device link" errors when rustup stages a nightly
# toolchain across overlay/hostPath boundaries on the ARC runner pod
Expand All @@ -68,25 +71,35 @@ jobs:
rustup default nightly-2026-04-27

- name: Install cargo-fuzz
run: cargo install --locked cargo-fuzz

# Version-pinned: a floating `cargo install` hands a hijacked cargo-fuzz
# release immediate code exec on the self-hosted runner. Same pattern as
# kani-verifier above and fuzz-smoke.yml.
run: cargo install --locked cargo-fuzz --version 0.13.2

# The fuzz crate (cachekit-storage-fuzz) declares no [features] table — its
# cachekit-core feature set is fixed in rust/fuzz/Cargo.toml. Passing
# --features here makes cargo error out before any fuzzing happens, and the
# old `|| true` swallowed exactly that for three core versions. A non-zero
# exit (build failure or crash) must fail the job; a clean hour exits 0 via
# -max_total_time.
#
# Deliberate 3-of-14 subset: time budget. These three cover the highest-value
# attack surfaces (compression bombs, envelope decode, key derivation); the
# full target set gets 60 s each on every PR via fuzz-smoke.yml.
- name: Fuzz byte_storage_compress (1 hour)
run: |
cd rust
timeout 3600 cargo fuzz run byte_storage_compress --no-default-features --features compression,checksum || true
cargo fuzz run byte_storage_compress -- -max_total_time=3600

- name: Fuzz byte_storage_decompress (1 hour)
run: |
cd rust
timeout 3600 cargo fuzz run byte_storage_decompress --no-default-features --features compression,checksum || true
cargo fuzz run byte_storage_decompress -- -max_total_time=3600

# NOTE: encryption_roundtrip and 8 other encryption targets are stale against
# cachekit-core 0.1.1 (encrypt_aes_gcm → encrypt_with_keys). See #114. Using
# encryption_key_derivation, which compiles, until those targets are migrated.
- name: Fuzz encryption_key_derivation (1 hour)
run: |
cd rust
timeout 3600 cargo fuzz run encryption_key_derivation --no-default-features --features encryption || true
cargo fuzz run encryption_key_derivation -- -max_total_time=3600

- name: Check for crashes
run: |
Expand All @@ -99,13 +112,16 @@ jobs:
fi
echo "✅ No crashes found during fuzzing"

- name: Generate coverage report
run: |
cd rust
for target in byte_storage_compress byte_storage_decompress encryption_key_derivation; do
echo "=== Coverage for $target ==="
cargo fuzz coverage $target || true
done
# A crash now fails the fuzz step itself, killing the job before the check
# above — without this upload the reproducer dies with the ephemeral runner.
- name: Upload crash artifacts
if: failure()
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7
with:
name: extended-fuzz-crash-artifacts
path: rust/fuzz/artifacts/
retention-days: 30
if-no-files-found: warn

atheris-fuzzing:
name: Atheris Python-Rust Fuzzing
Expand Down
1 change: 0 additions & 1 deletion rust/fuzz/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ coverage/

# Build artifacts
target/
Cargo.lock

# Crash reports and triage output
crash-*.txt
Expand Down
Loading
Loading