Repository navigation
Images: refuse a zlib stream that asks for a preset dictionary (FDICT) - #297
Merged
nathanpond merged 1 commit intoAug 30, 2026
Conversation
A zlib header with FDICT set makes zlib ask for a preset dictionary rather than call the stream corrupt, and .NET raises ZLibException for that — an IOException, outside the InvalidDataException family both image inflaters caught and outside every clause of the ImageReader net. Two bytes of a PNG IDAT or a TIFF Deflate strip therefore took the whole conversion down. Caught as IOException, because ZLibException cannot be named: it is public in System.IO.Compression but absent from the net10.0 reference assembly. The width costs nothing on these paths — the only stream is a MemoryStream over a byte array in hand, which performs no I/O, so an IOException there can only have come from the inflater. Tests at both decoders, where the ImageReader net cannot swallow them, plus libFuzzer's minimised unit through TryRead; all three fail without the fix. The unit is seeded into the image corpus, which lives in the Actions cache rather than in git. Found by the scheduled fuzz job, run 33303663302. Refs #296
5 tasks
5 tasks
nathanpond
deleted the
296-zlibexception-escapes-the-image-decoders-on-a-preset-dictionary-fdict-zlib-stream
branch
August 30, 2026 13:59
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What this changes
A zlib header with the
FDICTbit set asks for a preset dictionary rather than being corrupt.zlib answers
Z_NEED_DICTand .NET raisesZLibException— anIOException, outside theInvalidDataExceptionfamily that both image inflaters caught and outside every clause of theImageReader.TryReadnet. Two bytes of a PNGIDATor a TIFF Deflate strip therefore took thewhole conversion down. Both decoders now refuse it as the
ImageFormatExceptioneach alreadyraises for a corrupt stream, and the net gains the type behind them.
The catch says
IOExceptionrather thanZLibExceptionbecause the latter cannot be named: itis public in
System.IO.Compressionbut absent from the net10.0 reference assembly, so it doesnot exist at compile time. The width costs nothing on these paths — the only stream is a
MemoryStreamover a byte array already in hand, which performs no I/O, so anIOExceptionthere can only have come from the inflater. That reasoning is written out at each site.
Found by the scheduled fuzz job:
run 33303663302, target
image.The TIFF half was not in the crash — it was found by reading, and confirmed by a test that fails
without the fix.
Linked issue
Refs #296
How it was verified
dotnet build n8PDF.sln -c Release -warnaserroris clean (warnings are errors)dotnet test n8PDF.sln -c Releasepasses, with the external checkers on(
N8PDF_REQUIRE_QPDF=1 N8PDF_REQUIRE_FONTTOOLS=1 N8PDF_REQUIRE_FRIBIDI=1) — 4833 passed,0 failed, 6 skipped (the 3-D chart instrument probes). Run on the Mac with Word, so all 143
comparison fixtures ran, not CI's 97.
PackageReferenceinsrc/n8PDF; the public surface is unchanged.Each of the three new tests was confirmed to fail without the fix, with
System.IO.Compression.ZLibException— including the TIFF one, so that hole was real rather thanhypothetical.
FUZZ_TARGET=image ... replaypasses over a freshly seeded corpus (6 inputs, 0 escaped).Notes for the reviewer
PngHostileTests,ImageDecoderHostileTests) so theTryReadnetcannot swallow the evidence, per the standing rule from Images/PngDecoder: IDAT is inflated into an unbounded MemoryStream with no cap against the header's expected size #2/Images/PngDecoder: InvalidDataException from ZLibStream on a corrupt IDAT escapes uncaught and aborts the conversion #7/Images/TiffDecoder: malformed Deflate strip data throws InvalidDataException straight out of the decoder #35, plus libFuzzer's minimised
unit asserted through
TryRead.fuzz/Program.csrather than committed:fuzz/corpus/isgitignored, and CI carries the corpus in the Actions cache, seeding only when it is empty. The
issue's acceptance criterion said "commit it to
fuzz/corpus/image/" — that was written beforeI noticed the gitignore, and seeding is the equivalent durable mechanism.
PdfFilters.FlateDecodeshares the shape but is deliberately left alone: it only ever readsbytes we wrote ourselves, so it is not on the attack surface.
🤖 Generated with Claude Code
https://claude.ai/code/session_01W69hdtqDC6ewHMXnZdAysZ