test(byte_storage): make the compression_bomb fuzz target and Kani bound proofs able to fail (LAB-5508) - #90
Conversation
…und proofs able to fail (LAB-5508) The three decompression-bound checks in StorageEnvelope::extract move unchanged into a private check_decompression_bound, so the Kani proofs can verify the predicate extract actually runs. - Kani: two proofs check check_decompression_bound over every (compressed length, original_size) pair against limits written as literals, replacing four harnesses that compared a predicate to itself. - compression_bomb: every input gets one expected result, asserted for both extract and retrieve. Size classes build envelopes at the 512 MiB limits that only one check rejects, and a valid-stream class reaches ChecksumMismatch and SizeValidationFailed. - Unit test: extract rejects compressed_data over 512 MiB when nothing else would. - deep-fuzz restores and saves each target's corpus between runs, and runs compression_bomb with a 4096 MB RSS limit. - SECURITY.md: the test-coverage paragraph describes the above.
|
Navigate logical layers of code changes, visualize relationships, and explore their blast radius. Warning Review limit reachedNext included review available in 9 minutes. View limit detailsLimit details: You’ve used the included review currently available. You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository. Review configuration: ⚙️ Run configurationConfiguration used: Repository: cachekit-io/cachekit-core/.coderabbit.yaml Review profile: ASSERTIVE Plan: Advanced Run ID: 📒 Files selected for processing (4)
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Repository: cachekit-io/cachekit-core/.coderabbit.yaml Review profile: ASSERTIVE Plan: Advanced Run ID: 📒 Files selected for processing (5)
Included review availability: This review used your included allowance. Your plan provides up to 1 included review per hour; 0 remain after this review. WalkthroughThe change centralises decompression-bound checks in the extraction path and expands fuzz cases and expected-result checks. The deep-fuzz workflow now restores and saves target-specific corpora, sets memory limits, and passes them to libFuzzer. The security documentation describes test and proof coverage. ChangesDecompression Bound Checks and Verification
Priority: ⬇️ Low Estimated code review effort: 3 (Moderate) | ~25 minutes Change: Refactor Merge Risk: ⚪ Minimal · up to The change strengthens decompression-bound validation without an identified runtime behavior regression. No actionable merge-blocking risk is established; merge after normal checks pass. Security Architecture ReviewSecurity architecture risk: 🔵 Low · up to Existing decompression safeguards and output handling are preserved. The workflow changes affect security testing rather than production deployment. External caller and deployment exposure remain unverified, so the assessment is low rather than minimal. Retained concerns Security review detailsSecurity Blast Radius
Security Findings and Attack Paths
Trust Boundaries and Controls
Resilience and Maintainability Implications
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
Full details: Docstring CoverageExplanation Docstring coverage is 78.57% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 14 functions across 2 files. (3 skipped: 3 unsupported.) ✨ Finishing Touches 💡 1📝 Generate docstrings 💡
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Code Review Completed! 🔥The code review was successfully completed based on your current configurations. Kody Guide: Usage and ConfigurationInteracting with Kody
Providing Context (Files & MCPs)Add these hints in your PR description (or a comment) to unlock deeper checks:
Current Kody ConfigurationReview OptionsThe following review options are enabled or disabled:
Kody Code Review — 1 suggested fix. 🛠️ Open Agent Prompt |
|
The deep-fuzz corpus now carries over between runs. I dispatched two Run 1 (36668548107), Run 2 (36668784168), |
…size classes (LAB-5508) - deep-fuzz: the Actions cache evicts a weekly-read entry long before the next run, so the corpus now travels as a 90-day artifact. Each run restores the newest unexpired fuzz-corpus-<target> artifact from this repository on the same ref, and warns when there is none. - compression_bomb: size-class payloads are zeroed and stay lazily mapped, and retrieve's length check is exercised with zeroed bytes instead of a serialized 512 MiB envelope. Peak RSS falls under libFuzzer's default, so the raised limit is removed. Drop the unused fill byte and a clamp that never applies, and derive the minimum length from the limits. - Correct two comments moved into check_decompression_bound.
Code Review Completed! 🔥The code review was successfully completed based on your current configurations. Kody Guide: Usage and ConfigurationInteracting with Kody
Providing Context (Files & MCPs)Add these hints in your PR description (or a comment) to unlock deeper checks:
Current Kody ConfigurationReview OptionsThe following review options are enabled or disabled:
|
|
At Run 1 (36670689677), Run 2 (36670938699), The zero-filled size classes raised this target from 1,477 runs per 61 s to about 1.1 million. The slow-unit report that the earlier run 2 uploaded no longer appears. |
…ct list (LAB-5508) - Split the artifact listing from the jq selection, so a failed API call fails the step under set -e instead of reading as "no artifact" and starting the fuzz run cold. - Walk every page of fuzz-corpus-<target> artifacts: newer artifacts from other branches could otherwise push this ref's corpus off page one. - SECURITY.md and the fuzz module doc: the oracle expects one result per call, and a run with no prior artifact warns and starts empty.
Code Review Completed! 🔥The code review was successfully completed based on your current configurations. Kody Guide: Usage and ConfigurationInteracting with Kody
Providing Context (Files & MCPs)Add these hints in your PR description (or a comment) to unlock deeper checks:
Current Kody ConfigurationReview OptionsThe following review options are enabled or disabled:
|
|
At Dispatch 36672450347 ( |
The decompression bound in
StorageEnvelope::extractwas sound, but its checks could be deleted without failing anything: thecompression_bombfuzz target could not build an input only a size cap rejects, the Kani size/ratio harnesses compared a predicate to itself, and no merge-time test covered the compressed-length cap. This PR makes each of the three checks fail the fuzz target, a Kani proof and acargo testwhen it is deleted. It does not change behaviour.What changed
src/byte_storage.rs. The three checks move, unchanged and in the same order, into a privatecheck_decompression_bound(compressed_len, original_size).extractcalls it first. The constants and error variants are untouched, and every existing test passes without modification.Kani.
verify_decompression_bound_size_capsandverify_decompression_bound_ratiocallcheck_decompression_boundover every(usize, u32)pair and compare it with limits written as literals. So an inverted comparison or a changed constant fails a proof. The four tautological harnesses (verify_decompression_bomb_protection,verify_input_size_limits,verify_compressed_size_limits,verify_compression_ratio_calculation_safety) are deleted.Unit test.
test_extract_rejects_oversized_compressed_datacallsextractwithcompressed_data.len() == 512 MiB + 1andoriginal_size == 1, and requiresInputTooLarge.fuzz/fuzz_targets/compression_bomb.rsis rewritten as an oracle. Every input gets one expected result, andextractandretrievemust return exactly that. There are no guard-gated asserts and no catch-allErrarm. Classes:CompressedOverCap: length in (512 MiB, 512 MiB + 256]. Only the compressed-length cap rejects it.OriginalOverCap: length ≥ 536,871,original_sizein (512 MiB, 1000 × len]. Only theoriginal_sizecap rejects it.RatioOver: both sizes under their caps, ratio over 1000:1.CompressedAtCap: length exactly 512 MiB.extractgets past the bound and fails at decompression.Valid: a stream fromStorageEnvelope::new, with the declared size replaced and/or the checksum altered. This reachesChecksumMismatch,SizeValidationFailedandDecompressionFailed.Raw: arbitrary bytes, declared size and checksum. The expected result comes fromlz4_flex::decompresspluscachekit_core::checksum.lz4_flexis added to the fuzz crate only, at the versioncachekit-corealready resolves (cargo tree -i lz4_flexshows one copy).The size-class payloads are zeroed, so they stay lazily mapped. For the two 512 MiB classes,
retrievegets zeroed bytes just over its length cap rather than a serialized envelope. It must returnInputTooLarge. Without the length check those bytes decode toDeserializationFailed. So the target runs at about 18k to 25k exec/s, with peak RSS around 400 MB, under libFuzzer's default limit..github/workflows/security.yml(deep-fuzzonly). Each target'sfuzz/corpus/<target>is uploaded after the run as an artifact namedfuzz-corpus-<target>, kept for 90 days, underalways(). Before the run, the job restores the newest unexpired artifact of that name from this repository on the same ref, usingactions/download-artifactwithrun-id. If there is none, it emits a::warning::and starts empty. The job gainsactions: readfor that lookup. An artifact is used because Actions cache entries are evicted under size pressure and after 7 days unused, which a weekly run cannot outlast.SECURITY.md. The "Test coverage" paragraph is rewritten to match the above. It namestests/byte_storage_tests.rsand scopes its Kani statements to the two proofs.Evidence
Every command ran at
0b7ba9d. Each fuzz run starts from a fresh empty corpus directory (cargo fuzz run compression_bomb <empty-dir> -- -max_total_time=120).Unmutated head:
Mutation matrix. One check deleted per row. Line numbers are in
check_decompression_boundon this branch.cargo fuzz run(120 s, empty corpus)cargo kani --all-featurescargo test --all-features:139-141extract: expected Err(InputTooLarge), got Err(DecompressionFailed) (compressed_len=536870953 original_size=31242)verify_decompression_bound_size_capsFAILED:assertion failed: over_cap == matches!(result, Err(ByteStorageError::InputTooLarge)). 8 of 9 verifiedtest_extract_rejects_oversized_compressed_dataFAILED (109 passed, 1 failed)original_sizecap,:143-145extract: expected Err(InputTooLarge), got Err(DecompressionFailed) (compressed_len=587243 original_size=536873669)verify_decompression_bound_size_capsFAILED (same check). 8 of 9 verifiedtest_decompression_bomb_integer_boundary,test_decompression_u32_max_original_sizeFAILED:162-164extract: expected Err(DecompressionBomb), got Err(DecompressionFailed) (compressed_len=65535 original_size=65572129)verify_decompression_bound_ratioFAILED:assertion failed: matches!(result, Err(ByteStorageError::DecompressionBomb)). 8 of 9 verifiedtest_compression_ratio_bomb_protection,test_decompression_bomb_extreme_ratio,test_decompression_just_over_thresholdFAILEDReach checks (same fuzz command, not part of the matrix):
extract: exit 1,expected Err(ChecksumMismatch), got Ok(..).expected Err(SizeValidationFailed), got Ok(..).retrieve's envelope-length check: exit 1,retrieve: expected Err(InputTooLarge), got Err(DeserializationFailed("invalid type: integer0, expected struct StorageEnvelope")).Corpus persistence: two
workflow_dispatchruns withrun_deep_fuzz=trueandfuzz_seconds=60; see the latest comment below.Summary
This PR fixes the fuzz corpus artifact lookup in the security workflow and aligns documentation with how the
compression_bombfuzz target and its corpus handling actually behave. No public APIs are modified.Changes
CI: fuzz corpus artifact lookup (
.github/workflows/security.yml)The step that finds the previous fuzz corpus artifact for the current branch has been restructured:
gh apinow uses--paginate, so every page of artifacts is searched. Previously only the first 100 results were checked, and newer artifacts from other branches could push this branch's corpus out of view.set -e. Previously a failure was silently treated as "no artifact found."Documentation (
SECURITY.md)The "Test coverage" section is revised:
extractandretrieve, rather than one result per input that both must share.Fuzz target docs (
fuzz/fuzz_targets/compression_bomb.rs)The module doc comment is updated to the same per-call wording. The target's code is unchanged.
Public API impact
None. Changes are limited to CI configuration, documentation, and a doc comment.