Adds FuzzNewManifestFromReader and its seed corpus in mfer/testdata/fuzz/FuzzNewManifestFromReader/. make test runs the seeds as ordinary tests; make fuzz (script/fuzz) fuzzes for one minute with two workers and is never run by CI.
The target fails when the parser returns both or neither of a manifest and an error, or allocates more than sixteen times its input plus MaxDecompressedSize, measured with the runtime's allocation counter.
Parser bug fixed.MaxDecompressedSize did not bound decompression. The zstd decoder kept its own 64 GiB limit: it set aside whatever content size a frame header claimed, and it decoded a payload under 128 KiB in full before the LimitReader read any of it. A 92-byte manifest whose frame claims 8 GiB made the parser allocate 8 GiB. The decoder now has MaxDecompressedSize as its limit; that manifest is the seed zstd-frame-claims-8gib.
Not visible in the diff:
The seeds were written once by a throwaway test using the builder and the gpg test helper; that test is not committed.
The target empties PATH and points TMPDIR at its own directory, so the signed seed ends in a gpg-not-found error: signature verification itself is not fuzzed.
The bomb states its size in the frame header, so the decoder refuses it before decoding. A bomb without that size is stopped after MaxDecompressedSize bytes, which is too heavy under the race detector for make test, so the corpus has none.
Mutations cannot get past the payload hash check; the decoder and the inner message are reached only through seeds.
Judgement call: the sixteen-times ceiling leaves room for buffer growth.
The issue says zlib; the format uses zstd, so the seeds do too.
Model: opus-5-5
Adds `FuzzNewManifestFromReader` and its seed corpus in `mfer/testdata/fuzz/FuzzNewManifestFromReader/`. `make test` runs the seeds as ordinary tests; `make fuzz` (`script/fuzz`) fuzzes for one minute with two workers and is never run by CI.
The target fails when the parser returns both or neither of a manifest and an error, or allocates more than sixteen times its input plus `MaxDecompressedSize`, measured with the runtime's allocation counter.
**Parser bug fixed.** `MaxDecompressedSize` did not bound decompression. The zstd decoder kept its own 64 GiB limit: it set aside whatever content size a frame header claimed, and it decoded a payload under 128 KiB in full before the `LimitReader` read any of it. A 92-byte manifest whose frame claims 8 GiB made the parser allocate 8 GiB. The decoder now has `MaxDecompressedSize` as its limit; that manifest is the seed `zstd-frame-claims-8gib`.
Not visible in the diff:
- The seeds were written once by a throwaway test using the builder and the gpg test helper; that test is not committed.
- The target empties `PATH` and points `TMPDIR` at its own directory, so the signed seed ends in a gpg-not-found error: signature verification itself is not fuzzed.
- The bomb states its size in the frame header, so the decoder refuses it before decoding. A bomb without that size is stopped after `MaxDecompressedSize` bytes, which is too heavy under the race detector for `make test`, so the corpus has none.
- Mutations cannot get past the payload hash check; the decoder and the inner message are reached only through seeds.
- Judgement call: the sixteen-times ceiling leaves room for buffer growth.
- The issue says zlib; the format uses zstd, so the seeds do too.
Model: opus-5-5
FuzzNewManifestFromReader fails when the parser returns both or neither
of a manifest and an error, or allocates more than sixteen times its
input plus MaxDecompressedSize. make test runs the committed seed
corpus; make fuzz fuzzes for one minute, by hand only.
Parser bug: MaxDecompressedSize did not bound decompression. The zstd
decoder kept its own 64 GiB limit, set aside whatever size a frame
header claimed, and decoded payloads under 128 KiB in full before the
LimitReader read any of it, so a 92-byte manifest claiming 8 GiB made
the parser allocate 8 GiB. The decoder now has MaxDecompressedSize as
its limit; that manifest is a regression seed.
Model: opus-5-5
mfer/deserialize.go lines 114-119 (decompressInner): MaxDecompressedSize still does not bound decompression when the payload holds several zstd frames that declare no content size. The decoder applies the limit to each frame separately, so a payload under 128 KiB with two such frames is decoded to twice MaxDecompressedSize before the LimitReader reads any of it, whatever size the manifest declares. The PR body's "a bomb without that size is stopped after MaxDecompressedSize bytes" holds for one frame only. Acceptable: decompression stops once all frames together reach MaxDecompressedSize (or the declared size plus one, when that is smaller), with a seed of two frames that are each under that limit and together over it.
mfer/deserialize_fuzz_test.go lines 41-46: the comment says the sixteen-times ceiling leaves no room for a decoder that sets aside whatever size its input claims. For any small input the ceiling is about 4 GiB, so it lets through a decoder that sets aside any claimed size below that; only claims above about 4 GiB fail. Acceptable: the comment says what the ceiling actually catches.
PR body: about 280 words, over the 250-word limit. Acceptable: 250 words or fewer, disclosures kept one line each.
Gated on next at a3749e9; the branch needed no rebase.
Judgement call: mutated inputs stopping at the payload hash check, as the PR body says, is not counted as a defect, since the issue names the seeds as the way to reach the decoder.
Judgement call: the unchecked fuzzing item in the README's 1.0 task list is not counted; that whole list is out of date pending #76.
Model: opus-5-5
Review failed.
1. `mfer/deserialize.go` lines 114-119 (`decompressInner`): `MaxDecompressedSize` still does not bound decompression when the payload holds several zstd frames that declare no content size. The decoder applies the limit to each frame separately, so a payload under 128 KiB with two such frames is decoded to twice `MaxDecompressedSize` before the `LimitReader` reads any of it, whatever size the manifest declares. The PR body's "a bomb without that size is stopped after `MaxDecompressedSize` bytes" holds for one frame only. Acceptable: decompression stops once all frames together reach `MaxDecompressedSize` (or the declared size plus one, when that is smaller), with a seed of two frames that are each under that limit and together over it.
2. `mfer/deserialize_fuzz_test.go` lines 41-46: the comment says the sixteen-times ceiling leaves no room for a decoder that sets aside whatever size its input claims. For any small input the ceiling is about 4 GiB, so it lets through a decoder that sets aside any claimed size below that; only claims above about 4 GiB fail. Acceptable: the comment says what the ceiling actually catches.
3. PR body: about 280 words, over the 250-word limit. Acceptable: 250 words or fewer, disclosures kept one line each.
- Gated on `next` at `a3749e9`; the branch needed no rebase.
- Judgement call: mutated inputs stopping at the payload hash check, as the PR body says, is not counted as a defect, since the issue names the seeds as the way to reach the decoder.
- Judgement call: the unchecked fuzzing item in the README's 1.0 task list is not counted; that whole list is out of date pending https://git.eeqj.de/sneak/mfer/issues/76.
Model: opus-5-5
State for the next manager: branch issue-65-fuzz-manifest-reader, last pushed 99c1b93, which sits on a3749e9 and now conflicts with next in TODO.md only. Left: rework for the three findings above (finding 1, the bound across several frames, is the critical part), rebase onto next, a fresh review, then squash-merge. No worker is running on it.
Model: opus-5-5
State for the next manager: branch `issue-65-fuzz-manifest-reader`, last pushed `99c1b93`, which sits on `a3749e9` and now conflicts with `next` in `TODO.md` only. Left: rework for the three findings above (finding 1, the bound across several frames, is the critical part), rebase onto `next`, a fresh review, then squash-merge. No worker is running on it.
Model: opus-5-5
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Adds
FuzzNewManifestFromReaderand its seed corpus inmfer/testdata/fuzz/FuzzNewManifestFromReader/.make testruns the seeds as ordinary tests;make fuzz(script/fuzz) fuzzes for one minute with two workers and is never run by CI.The target fails when the parser returns both or neither of a manifest and an error, or allocates more than sixteen times its input plus
MaxDecompressedSize, measured with the runtime's allocation counter.Parser bug fixed.
MaxDecompressedSizedid not bound decompression. The zstd decoder kept its own 64 GiB limit: it set aside whatever content size a frame header claimed, and it decoded a payload under 128 KiB in full before theLimitReaderread any of it. A 92-byte manifest whose frame claims 8 GiB made the parser allocate 8 GiB. The decoder now hasMaxDecompressedSizeas its limit; that manifest is the seedzstd-frame-claims-8gib.Not visible in the diff:
PATHand pointsTMPDIRat its own directory, so the signed seed ends in a gpg-not-found error: signature verification itself is not fuzzed.MaxDecompressedSizebytes, which is too heavy under the race detector formake test, so the corpus has none.Model: opus-5-5
Review failed.
mfer/deserialize.golines 114-119 (decompressInner):MaxDecompressedSizestill does not bound decompression when the payload holds several zstd frames that declare no content size. The decoder applies the limit to each frame separately, so a payload under 128 KiB with two such frames is decoded to twiceMaxDecompressedSizebefore theLimitReaderreads any of it, whatever size the manifest declares. The PR body's "a bomb without that size is stopped afterMaxDecompressedSizebytes" holds for one frame only. Acceptable: decompression stops once all frames together reachMaxDecompressedSize(or the declared size plus one, when that is smaller), with a seed of two frames that are each under that limit and together over it.mfer/deserialize_fuzz_test.golines 41-46: the comment says the sixteen-times ceiling leaves no room for a decoder that sets aside whatever size its input claims. For any small input the ceiling is about 4 GiB, so it lets through a decoder that sets aside any claimed size below that; only claims above about 4 GiB fail. Acceptable: the comment says what the ceiling actually catches.PR body: about 280 words, over the 250-word limit. Acceptable: 250 words or fewer, disclosures kept one line each.
nextata3749e9; the branch needed no rebase.Model: opus-5-5
State for the next manager: branch
issue-65-fuzz-manifest-reader, last pushed99c1b93, which sits ona3749e9and now conflicts withnextinTODO.mdonly. Left: rework for the three findings above (finding 1, the bound across several frames, is the critical part), rebase ontonext, a fresh review, then squash-merge. No worker is running on it.Model: opus-5-5
View command line instructions
Checkout
From your project repository, check out a new branch and test the changes.