GHSA-JJ3Q-CWQJ-842R

Vulnerability from github – Published: 2026-10-07 16:18 – Updated: 2026-10-07 16:18
VLAI
Summary
ImageSharp: CCITT fax decompression (T4/Modified Huffman): unbounded WriteBits overflows strip buffer — heap OOB write in SixLabors.ImageSharp
Details

Summary

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.

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.

  1. Build a console project referencing src/ImageSharp/ImageSharp.csproj, run it against either crafted file (or call Image.Load on it).
  2. 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.
  3. 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).

Show details on source website

{
  "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"
}



Log in or create an account to share your comment.




Tags
Taxonomy of the tags.


Loading…

Loading…

Loading…

Forecast uses a logistic model when the trend is rising, or an exponential decay model when the trend is falling. Fitted via linearized least squares.

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.

Loading…

Loading…

Loading…

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.


Loading…