GHSA-4rr5-89j8-mwwm: Use After Free
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="tifflzw", tiffinfo={317: 3}) # 317 = Predictor
Run it: $ python 01poc.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 PyImagingLibTiffEncoderNew 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 Software
Remediation
Recommended actions to resolve this vulnerability, in priority order.
- Upgrade
Upgrade
pip/pillowto a version that resolves this vulnerability.Fixed in 13.0.0
Event History
Frequently Asked Questions
Which applications are exposed to this issue?
Applications that re-encode attacker-influenced images as TIFF into an in-memory buffer are exposed. The described impact is a crash, with remote code execution identified as a potential outcome.
What input conditions are needed to trigger the vulnerable path?
The TIFF encode operation must encounter a tag that libtiff rejects during encoding, causing TIFFWriteScanline to fail. The floating-point Predictor tag with value 3 is identified as a convenient trigger when used with integer data.
Why does the failure occur after the initial encoding error?
On the affected in-memory error path, the output buffer is freed while the libtiff handle remains open. Later cleanup closes that handle, which flushes data through a callback that writes to the already freed buffer.