Skip to content

Images: refuse a zlib stream that asks for a preset dictionary (FDICT) - #297

Merged
nathanpond merged 1 commit into
mainfrom
296-zlibexception-escapes-the-image-decoders-on-a-preset-dictionary-fdict-zlib-stream
Aug 30, 2026
Merged

nathanpond merged 1 commit into
mainfrom
296-zlibexception-escapes-the-image-decoders-on-a-preset-dictionary-fdict-zlib-stream

Conversation

@nathanpond

Copy link
Copy Markdown
Owner

What this changes

A zlib header with the FDICT bit set asks for a preset dictionary rather than being corrupt.
zlib answers Z_NEED_DICT and .NET raises ZLibException — an IOException, outside the
InvalidDataException family that both image inflaters caught and outside every clause of the
ImageReader.TryRead net. Two bytes of a PNG IDAT or a TIFF Deflate strip therefore took the
whole conversion down. Both decoders now refuse it as the ImageFormatException each already
raises for a corrupt stream, and the net gains the type behind them.

The catch says IOException rather than ZLibException because the latter cannot be named: it
is public in System.IO.Compression but absent from the net10.0 reference assembly, so it does
not exist at compile time. The width costs nothing on these paths — the only stream is a
MemoryStream over a byte array already in hand, which performs no I/O, so an IOException
there 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 -warnaserror is clean (warnings are errors)
  • dotnet test n8PDF.sln -c Release passes, 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.
  • No golden trace moved — this changes only which exceptions are refused, never output.
  • No new PackageReference in src/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 than
hypothetical.

FUZZ_TARGET=image ... replay passes over a freshly seeded corpus (6 inputs, 0 escaped).

Notes for the reviewer

🤖 Generated with Claude Code

https://claude.ai/code/session_01W69hdtqDC6ewHMXnZdAysZ

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
@nathanpond
nathanpond merged commit 41e5e85 into main Aug 30, 2026
9 checks passed
@nathanpond
nathanpond deleted the 296-zlibexception-escapes-the-image-decoders-on-a-preset-dictionary-fdict-zlib-stream branch August 30, 2026 13:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ZLibException escapes the image decoders on a preset-dictionary (FDICT) zlib stream

1 participant