GHSA-JJ3Q-CWQJ-842R
Vulnerability from github – Published: 2026-10-07 16:18 – Updated: 2026-10-07 16:18Summary
When decoding a fax-compressed strip TIFF (Compression=3 / Group 3 1D, or Compression=2 / Modified Huffman), the CCITT decompressors write decoded runs through BitWriterUtils.WriteBits/WriteBit/WriteZeroBit, which advance and write bits via Unsafe.Add with read-modify-write semantics — without ever comparing the write position against the target buffer length. The strip buffer is sized ImageWidth × RowsPerStrip (8 bytes in the PoC), but two independent defects let an attacker write tens of millions of bits past it: (a) T4 only increments rowsWritten when an EOL code is read, and one ReadNextRun accumulates unlimited makeup codes (+2560 px per 12-bit code) into a single RunLength that WritePixelRun then writes in one shot; (b) Modified Huffman writes before validating (pixelsWritten > Width is checked at :90-93, after the write at :56-63), so the overflow completes even though an exception is thrown later. One crafted ~90 KB file writes ~19.2 MB linearly past an 8-byte buffer and deterministically kills the process; the write offset and length are fully attacker-controlled (classic heap-corruption primitive on the managed heap).
Verified at commit 5cd4d0d26a82a9549f297a237aea9cf665bddff8 (main; latest release v4.1.0, the supported major). A related tiled-path variant (tile-buffer width mismatch) was reported separately as GHSA-v76p-62qx-wwq2 — this report covers the distinct strip-path root cause.
Details
Root cause: the bit-writing sink has no bounds check, and neither decompressor constrains run lengths against the strip buffer.
- Unchecked write primitive: BitWriterUtils.cs#L51 (
WriteBit), :58 (WriteZeroBit— also read-modify-write, so even all-white runs really write), :11 (WriteBits) - T4 trigger: T4TiffCompression.cs#L75 and L109-L119 —
rowsWrittenonly increments on EOL; consecutive makeup codes accumulate into oneRunLength, thenWritePixelRunwrites it in full - MH trigger: ModifiedHuffmanTiffCompression.cs#L56-L63 — write happens before the width check at L90-L93
- Buffer size: TiffDecoderCore.cs#L932-L967 —
CalculateStripBufferSize= width × bpp/8 × rowsPerStrip
Attack surface: Image.Load(stream) on an attacker-supplied strip TIFF (TiffDecoderCore.DecodeStripsChunky → TiffDecompressorsFactory.Create → Decompress). Default configuration, no authentication, no user interaction — a plain strip TIFF (far more common than the tiled variant) with Compression=2 or 3 and a chain of CCITT makeup codes.
Relationship to GHSA-v76p-62qx-wwq2: that report is the tiled variant — a caller-side width mismatch (TiffDecompressorsFactory drops tile parameters) that overflows with perfectly legal run codes. This report is the strip variant — the callee-side missing bounds check plus T4's EOL-only row accounting and MH's write-before-validate. Fixing the caller mismatch does not address this vector; bounding BitWriterUtils addresses both (see remediation).
Suggested remediation:
1. Bound BitWriterUtils.WriteBits/WriteBit/WriteZeroBit against buffer.Length*8 (return bool / throw ImageFormatException) — do not rely on caller discipline.
2. In the T4/MH decompress loops, validate (bitsWritten + RunLength) <= buffer.Length*8 before writing; abort T4 when accumulated rows exceed stripHeight instead of waiting for the loop to end naturally.
3. In Modified Huffman, move the pixelsWritten > Width check before the actual write.
4. Regression fuzz cases: Compression=2/3, EOL-less oversized makeup chains, edge widths; strip and tiled paths share the same constraint.
PoC
Full PoC posted as the first comment below: Program.cs (driver), poc-tiff-t4.tif + poc-tiff-mh.tif (crafted files, ~90 KB each, base64 inline), README.
- Build a console project referencing
src/ImageSharp/ImageSharp.csproj, run it against either crafted file (or callImage.Loadon it). - PoC layout: TIFF (II, 42), ImageWidth=64, ImageLength=1, BitsPerSample=1, Photometric=WhiteIsZero, RowsPerStrip=1; strip data =
EOL(12bit)+ 60000×white makeup 2560 (000000011111)+white terminating code (000111)— declared run ≈ 153,600,001 px ≈ 19.2 MB into an 8-byte buffer. - Observed (both variants):
strip payload 90003 bytes, claimed run ~153,600,001 px = 19,200,000 bytes into 8-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 ...ModifiedHuffmanTiffCompression.Decompress(...)
at SixLabors.ImageSharp.Formats.Tiff.TiffDecoderCore.DecodeStripsChunky[Rgba32](...)
Aborted (core dumped); exit=134
The T4 variant crashes identically at T4TiffCompression.WritePixelRun → BitWriterUtils.WriteBits.
4. Control: an equivalent file without the makeup chain (legal EOL-delimited rows) decodes normally — the crash comes from the oversized runs, not the container.
Impact
- What it is: out-of-bounds write (CWE-787). For any service decoding untrusted TIFFs (image hosting/transcoding/thumbnails/CMS): reliable remote DoS (uncatchable fatal process crash), plus a heap OOB write with attacker-controlled offset and length — a realistic heap-corruption / potential code-execution surface. In scope of your SECURITY.md as a library vulnerability.
- Who is impacted: applications decoding untrusted TIFF with SixLabors.ImageSharp at the current major (verified on main past v4.1.0); plain strip TIFFs with Compression=2/3 are the trigger, which ordinary TIFF writers can produce.
Reported by Kimi Security Team (bug-report@moonshot.ai).
{
"affected": [
{
"package": {
"ecosystem": "NuGet",
"name": "ImageSharp"
},
"ranges": [
{
"events": [
{
"introduced": "3.0.0"
},
{
"fixed": "4.1.1"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2026-106117"
],
"database_specific": {
"cwe_ids": [
"CWE-787"
],
"github_reviewed": true,
"github_reviewed_at": "2026-10-07T16:18:50Z",
"nvd_published_at": "2026-10-06T18:16:53Z",
"severity": "HIGH"
},
"details": "## Summary\n\nWhen decoding a fax-compressed strip TIFF (Compression=3 / Group 3 1D, or Compression=2 / Modified Huffman), the CCITT decompressors write decoded runs through `BitWriterUtils.WriteBits/WriteBit/WriteZeroBit`, which advance and write bits via `Unsafe.Add` with read-modify-write semantics \u2014 **without ever comparing the write position against the target buffer length**. The strip buffer is sized `ImageWidth \u00d7 RowsPerStrip` (8 bytes in the PoC), but two independent defects let an attacker write tens of millions of bits past it: (a) T4 only increments `rowsWritten` when an EOL code is read, and one `ReadNextRun` accumulates unlimited makeup codes (+2560 px per 12-bit code) into a single `RunLength` that `WritePixelRun` then writes in one shot; (b) Modified Huffman writes **before** validating (`pixelsWritten \u003e Width` is checked at :90-93, after the write at :56-63), so the overflow completes even though an exception is thrown later. One crafted ~90 KB file writes ~19.2 MB linearly past an 8-byte buffer and deterministically kills the process; the write offset and length are fully attacker-controlled (classic heap-corruption primitive on the managed heap).\n\nVerified at commit `5cd4d0d26a82a9549f297a237aea9cf665bddff8` (main; latest release v4.1.0, the supported major). A related tiled-path variant (tile-buffer width mismatch) was reported separately as GHSA-v76p-62qx-wwq2 \u2014 this report covers the distinct strip-path root cause.\n\n## Details\n\nRoot cause: the bit-writing sink has no bounds check, and neither decompressor constrains run lengths against the strip buffer.\n\n- Unchecked write primitive: [BitWriterUtils.cs#L51](https://github.com/SixLabors/ImageSharp/blob/5cd4d0d26a82a9549f297a237aea9cf665bddff8/src/ImageSharp/Formats/Tiff/Compression/BitWriterUtils.cs#L51) (`WriteBit`), :58 (`WriteZeroBit` \u2014 also read-modify-write, so even all-white runs really write), :11 (`WriteBits`)\n- T4 trigger: [T4TiffCompression.cs#L75](https://github.com/SixLabors/ImageSharp/blob/5cd4d0d26a82a9549f297a237aea9cf665bddff8/src/ImageSharp/Formats/Tiff/Compression/Decompressors/T4TiffCompression.cs#L75) and L109-L119 \u2014 `rowsWritten` only increments on EOL; consecutive makeup codes accumulate into one `RunLength`, then `WritePixelRun` writes it in full\n- MH trigger: [ModifiedHuffmanTiffCompression.cs#L56-L63](https://github.com/SixLabors/ImageSharp/blob/5cd4d0d26a82a9549f297a237aea9cf665bddff8/src/ImageSharp/Formats/Tiff/Compression/Decompressors/ModifiedHuffmanTiffCompression.cs#L56-L63) \u2014 write happens before the width check at L90-L93\n- Buffer size: [TiffDecoderCore.cs#L932-L967](https://github.com/SixLabors/ImageSharp/blob/5cd4d0d26a82a9549f297a237aea9cf665bddff8/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs#L932-L967) \u2014 `CalculateStripBufferSize` = width \u00d7 bpp/8 \u00d7 rowsPerStrip\n\nAttack surface: `Image.Load(stream)` on an attacker-supplied strip TIFF (`TiffDecoderCore.DecodeStripsChunky` \u2192 `TiffDecompressorsFactory.Create` \u2192 `Decompress`). Default configuration, no authentication, no user interaction \u2014 a plain strip TIFF (far more common than the tiled variant) with Compression=2 or 3 and a chain of CCITT makeup codes.\n\nRelationship to GHSA-v76p-62qx-wwq2: that report is the **tiled** variant \u2014 a caller-side width mismatch (`TiffDecompressorsFactory` drops tile parameters) that overflows with perfectly legal run codes. This report is the **strip** variant \u2014 the callee-side missing bounds check plus T4\u0027s EOL-only row accounting and MH\u0027s write-before-validate. Fixing the caller mismatch does not address this vector; bounding `BitWriterUtils` addresses both (see remediation).\n\n**Suggested remediation:**\n1. Bound `BitWriterUtils.WriteBits/WriteBit/WriteZeroBit` against `buffer.Length*8` (return bool / throw `ImageFormatException`) \u2014 do not rely on caller discipline.\n2. In the T4/MH decompress loops, validate `(bitsWritten + RunLength) \u003c= buffer.Length*8` **before** writing; abort T4 when accumulated rows exceed stripHeight instead of waiting for the loop to end naturally.\n3. In Modified Huffman, move the `pixelsWritten \u003e Width` check before the actual write.\n4. Regression fuzz cases: Compression=2/3, EOL-less oversized makeup chains, edge widths; strip and tiled paths share the same constraint.\n\n## PoC\n\nFull PoC posted as the first comment below: `Program.cs` (driver), `poc-tiff-t4.tif` + `poc-tiff-mh.tif` (crafted files, ~90 KB each, base64 inline), README.\n\n1. Build a console project referencing `src/ImageSharp/ImageSharp.csproj`, run it against either crafted file (or call `Image.Load` on it).\n2. PoC layout: TIFF (II, 42), ImageWidth=64, ImageLength=1, BitsPerSample=1, Photometric=WhiteIsZero, RowsPerStrip=1; strip data = `EOL(12bit)` + 60000\u00d7 `white makeup 2560 (000000011111)` + `white terminating code (000111)` \u2014 declared run \u2248 153,600,001 px \u2248 19.2 MB into an 8-byte buffer.\n3. Observed (both variants):\n\n ```\n strip payload 90003 bytes, claimed run ~153,600,001 px = 19,200,000 bytes into 8-byte buffer\n Fatal error. System.AccessViolationException: Attempted to read or write protected memory.\n at SixLabors.ImageSharp.Formats.Tiff.Compression.BitWriterUtils.WriteBits(Span`1\u003cByte\u003e, IntPtr, IntPtr, Byte)\n at ...ModifiedHuffmanTiffCompression.Decompress(...)\n at SixLabors.ImageSharp.Formats.Tiff.TiffDecoderCore.DecodeStripsChunky[Rgba32](...)\n Aborted (core dumped); exit=134\n ```\n\n The T4 variant crashes identically at `T4TiffCompression.WritePixelRun \u2192 BitWriterUtils.WriteBits`.\n4. Control: an equivalent file without the makeup chain (legal EOL-delimited rows) decodes normally \u2014 the crash comes from the oversized runs, not the container.\n\n## Impact\n\n- **What it is:** out-of-bounds write (CWE-787). For any service decoding untrusted TIFFs (image hosting/transcoding/thumbnails/CMS): reliable remote DoS (uncatchable fatal process crash), plus a heap OOB write with attacker-controlled offset and length \u2014 a realistic heap-corruption / potential code-execution surface. In scope of your SECURITY.md as a library vulnerability.\n- **Who is impacted:** applications decoding untrusted TIFF with SixLabors.ImageSharp at the current major (verified on main past v4.1.0); plain strip TIFFs with Compression=2/3 are the trigger, which ordinary TIFF writers can produce.\n\n---\n\n---\n\nReported by **Kimi Security Team** (bug-report@moonshot.ai).",
"id": "GHSA-jj3q-cwqj-842r",
"modified": "2026-10-07T16:18:51Z",
"published": "2026-10-07T16:18:50Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/SixLabors/ImageSharp/security/advisories/GHSA-jj3q-cwqj-842r"
},
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2026-106117"
},
{
"type": "WEB",
"url": "https://github.com/SixLabors/ImageSharp/pull/3176"
},
{
"type": "WEB",
"url": "https://github.com/SixLabors/ImageSharp/commit/e8892ecd8a95dee95c4480fda51cfe0b32f3b846"
},
{
"type": "PACKAGE",
"url": "https://github.com/SixLabors/ImageSharp"
},
{
"type": "WEB",
"url": "https://github.com/SixLabors/ImageSharp/releases/tag/v4.1.1"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H",
"type": "CVSS_V3"
}
],
"summary": "ImageSharp: CCITT fax decompression (T4/Modified Huffman): unbounded WriteBits overflows strip buffer \u2014 heap OOB write in SixLabors.ImageSharp"
}
Sightings
| Author | Source | Type | Date | Other |
|---|
Nomenclature
- Seen: The vulnerability was mentioned, discussed, or observed by the user.
- Confirmed: The vulnerability has been validated from an analyst's perspective.
- Published Proof of Concept: A public proof of concept is available for this vulnerability.
- Exploited: The vulnerability was observed as exploited by the user who reported the sighting.
- Patched: The vulnerability was observed as successfully patched by the user who reported the sighting.
- Not exploited: The vulnerability was not observed as exploited by the user who reported the sighting.
- Not confirmed: The user expressed doubt about the validity of the vulnerability.
- Not patched: The vulnerability was not observed as successfully patched by the user who reported the sighting.
The approach is described in our paper Mapping CVEs to MITRE ATT&CK Techniques: A Curated Gold-Set Classifier and the Limits of LLM-Assisted Label Expansion.
Browse all ATT&CK techniques and the vulnerabilities related to each.
Related by attack behaviour
Vulnerabilities whose description is nearest to this one in the vector space of the CIRCL/vulnerability-attack-technique-biencoder model. This is a similarity search over the bi-encoder space (plain cosine), not a classification, and it has no measured accuracy.