CVE-2026-128067: Pillow: Heap buffer overflow saving TIFF with libtiff when tags override image size
============================================================================
ANT-2026-CS7N494P [high] CWE-787 memory-safety
Heap buffer overflow saving TIFF with libtiff when tags override image size
Location: src/libImaging/TiffDecode.c:1035 in ImagingLibTiffEncode()
Commit: a707c6b21c313880d0771e54965d142dd42358b6
============================================================================
When Pillow saves an image as a compressed TIFF, size information taken from the image’s metadata can make the encoder read past the end of a heap buffer. Someone who uploads an image can use this to get the server process’s memory, including pointers, copied into the saved file, or to crash the process. When the metadata also enables TIFF “Predictor=2” compression, the encoder writes past the buffer instead, corrupting the heap and aborting the process. This affects applications that write TIFF through libtiff (any compression= other than raw) and either carry the source metadata across with exif=im.getexif() / tiffinfo=, or simply re-save a crafted palette TIFF with Image.open(f).save(out, "TIFF"), which inherits its compression and predictor from the upload.
In TiffImagePlugin._save’s libtiff branch, only BitsPerSample is pinned to the real image when the tag list for the encoder is built (src/PIL/TiffImagePlugin.py:1955). ImageWidth, ImageLength and PlanarConfiguration are set from im.size but are then overwritten by the exif=/tiffinfo= merge loop at :1768-1772. SamplesPerPixel is never set for single-band modes, so a value from the caller’s tags or from the source TIFF’s own IFD reaches TIFFSetField. ImagingLibTiffEncode then passes a row buffer sized from the actual image (src/encode.c:291) to TIFFWriteScanline (src/libImaging/TiffDecode.c:1035) with no check against TIFFScanlineSize(), and libtiff consumes a full scanline of the tag-described width from it.
The attached reproducer demonstrates the read case. A 64x4 greyscale JPEG whose EXIF says SamplesPerPixel=3, Photometric=RGB is converted into a valid RGB TIFF in which 128 bytes of each row are live heap, including pointers that change from run to run. The same JPEG with EXIF ImageWidth=2**27 kills the process with SIGSEGV. For the write case, the values written are byte-wise differences of existing heap contents rather than attacker-chosen data, and nothing beyond the observed glibc abort is shown. The tiffinfo= route dates back to Pillow 2.3.0 (dbf47837ce99), and the exif= route to 8.4.0 (c0f619384cbc).
References
- github.com/advisories/GHSA-xcvg-w56q-r89m
- github.com/python-pillow/Pillow/commit/53318b50e8e9e85a57d033d4d879e1398d97aeb1
- github.com/python-pillow/Pillow/commit/ec5f1cbcf67275641abaad5c065ffb64a0a804a9
- github.com/python-pillow/Pillow/releases/tag/13.0.0
- github.com/python-pillow/Pillow/security/advisories/GHSA-xcvg-w56q-r89m
- nvd.nist.gov/vuln/detail/CVE-2026-128067
Code Behaviors & Features
Detect and mitigate CVE-2026-128067 with GitLab Dependency Scanning
Secure your software supply chain by verifying that all open source dependencies used in your projects contain no disclosed vulnerabilities. Dependency Scanning using SBOM also detects vulnerabilities from proprietary advisories that this site does not include. Learn more about Dependency Scanning using SBOM →