ImageSharp: Tiled fax TIFF: tile buffer sized by TileWidth but fax decompressor writes scanlines of ImageWidth — heap OOB write
Summary
When decoding a tiled TIFF with fax compression (T4/T6/MH), DecodeTilesChunky allocates each tile buffer from TileWidth (ceil(TileWidth*bpp/8)*TileLength bytes) but constructs the fax decompressor with frame.Width: TiffDecompressorsFactory ignores the isTiled/tileWidth/tileHeight parameters entirely. The T4/T6/MH decompressors treat the full image width as the scanline length and advance (and really write, via read-modify-write bit ops) frame.Width bits per row, with no bounds check against the tile buffer. The very first tile therefore writes linearly out of bounds — about ImageWidth/8 bytes per row × TileLength rows into a TileWidth-sized buffer. With ImageWidth=4,000,000, TileWidth=16, TileLength=16 this writes ~2 MB past a 32-byte buffer and kills the process deterministically; a T6 all-white variant advances the bit offset by >512 MB silently, showing an alarm-free heap-corruption window for the same defect. A crafted file fully controls the OOB length per tile and works with perfectly legal per-row run codes (no overlong runs needed).
Verified at commit 5cd4d0d26a82a9549f297a237aea9cf665bddff8 (main; latest release v4.1.0, the supported major).
Details
Root cause: a size mismatch between tile buffer allocation and decompressor width, because the factory drops the tile parameters.
- Allocation: TiffDecoderCore.cs#L792-L794 —
bytesPerTileRow = RoundUpToMultipleOfEight(tileWidth*bitsPerPixel); tile buffer =bytesPerTileRow*tileLengthbytes (32 bytes in the PoC) - Mismatch: TiffDecoderCore.cs#L797 —
CreateDecompressor<TPixel>(frame.Width, ..., isTiled: true, tileWidth, tileLength)passes the full frame width - Factory drops tile params: TiffDecompressorsFactory.cs#L56-L64 — T4/T6/MH decompressors receive only
width(= frame.Width);isTiled/tileWidth/tileHeightignored - OOB write sink: T4TiffCompression.cs#L69-L119,
T6TiffCompression.cs#L76-L108— per-row advance ofthis.widthbits viaBitWriterUtils.WriteBitswith no buffer-length check (BitWriterUtils.cs#L51); noteTiffDecoderCore.cs:829-831later reads the buffer inbytesPerTileRowstrides, confirming the protocol expects tile-width rows
Attack surface: Image.Load(stream) on an attacker-supplied tiled TIFF (TiffDecoderCore.DecodeImageWithTiles → DecodeTilesChunky). Default configuration; only requirement is a standard tiled TIFF header (TileWidth=16, TileLength=16) + Compression=3 (T4; T6 also constructible).
Suggested remediation:
- Short-term: when
isTiled, construct the T4/T6/MH decompressor withtileWidth(not frame width), or clip row writes to the caller-provided buffer length. - Root fix: bound the write side of
BitWriterUtils(pass remaining bits), and validate every fax row advance against buffer capacity on both tiled and strip paths. - Regression tests: Compression=2/3/4 × tiled with TileWidth < ImageWidth, including a T6 black-pixel row (forces real
WriteBit).
PoC
Full PoC posted as the first comment below: Program.cs (driver), poc-tiled-t4.tif (crafted file, ~9.8 KB, base64 inline), README.
-
Build a small console project referencing
src/ImageSharp/ImageSharp.csprojand run it against the crafted file (or callImage.Loadon it from any host). -
Observed with ImageWidth=4,000,000, TileWidth=16, TileLength=16, T4 with 400 makeup codes + EOL per row:
tile payload 9642 bytes; rows write ~2,048,000 bytes into a 32-byte buffer Fatal error. System.AccessViolationException: Attempted to read or write protected memory. at SixLabors.ImageSharp.Formats.Tiff.Compression.BitWriterUtils.WriteBits(Span`1<Byte>, IntPtr, IntPtr, Byte) at ...T4TiffCompression.WritePixelRun(...) at ...T4TiffCompression.Decompress(...) Aborted (core dumped); exit=134 -
Controls: the same file with Compression=None decodes normally (container is fine); a T6 all-white-rows variant advances >512 MB of bit offset without a real write (silent corruption window) before tripping on a directory-level TileOffsets count check.
Impact
- What it is: out-of-bounds write (CWE-787). For any service decoding untrusted tiled TIFFs: remote, default-configuration, deterministic process crash (DoS), plus a heap OOB write whose per-tile length and row width are attacker-tunable — a potential code-execution surface. This is a vulnerability in the library itself, in scope of your SECURITY.md.
- Who is impacted: applications decoding untrusted TIFF with SixLabors.ImageSharp at the current major (verified on main past v4.1.0); tiled + fax-compressed files are the trigger, which ordinary TIFF writers can produce.
Reported by Kimi Security Team (bug-report@moonshot.ai).
Affected software
Click a technology to see all vulnerabilities affecting it.
| Platform | Package | Vulnerable versions | Fixed in |
|---|---|---|---|
| .NET (NuGet) | ImageSharp | >= 3.0.0, < 4.1.1 | 4.1.1 |
References
- https://github.com/SixLabors/ImageSharp/security/advisories/GHSA-v76p-62qx-wwq2
- https://nvd.nist.gov/vuln/detail/CVE-2026-106118
- https://github.com/SixLabors/ImageSharp/pull/3176
- https://github.com/SixLabors/ImageSharp/commit/9ee7d1dd5b62d8c9f16cc755e76828386bc2191f
- https://github.com/SixLabors/ImageSharp
- https://github.com/SixLabors/ImageSharp/releases/tag/v4.1.1
- CVSS vector
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H- Weakness (CWE)
CWE-787- Platform
- .NET (NuGet)
- Package
ImageSharp- Disclosed
- 07.10.2026
- Updated
- 11.10.2026 11:23
Sources and licenses
OSV / GitHub Advisory Database - GHSA-v76p-62qx-wwq2 - CC BY 4.0
Vulnerability history: ImageSharp
All published vulnerabilities of this package (.NET (NuGet)), newest first. Shows whether this is a one-off slip or a package that needs watching.
- Total entries
- 2
- First → latest
- 10.2026 10.2026
- Without a fix
- 0
- Exploited in attacks
- 0
Severity breakdown
- High 2
Disclosures per year
- 2026: 2
-
ImageSharp: Tiled fax TIFF: tile buffer sized by TileWidth but fax decompressor writes scanlines of ImageWidth — heap OOB write this entry
Know which vulnerabilities affect your application
With the DockRay integration and software inventory enabled, DockRay checks reported component versions against known vulnerabilities and notifies you about confirmed matches. Coverage depends on the supported technology and available advisory data.