Threats tagged 'brew-aider-cve-2026-112393'
1 threat
Most recently published first
| Severity | CVSS | Signals | Threat | Source | Published | Score and star |
|---|---|---|---|---|---|---|
| Critical | 9.2 | EPSS 0.5% | CVE-2026-112393Pillow · Heap-based Buffer Overflow Critical9.2EPSS 0.5%CVE Database V5CVE-2026-112393 ``` ============================================================================ Heap buffer overflow in _tiffWriteProc when saving TIFF to BytesIO Location: src/libImaging/TiffDecode.c:94 in _tiffWriteProc() Commit: a707c6b21c313880d0771e54965d142dd42358b6 ============================================================================ ``` Pillow can write attacker-chosen data past the end of a heap buffer when it saves a TIFF image to an in-memory destination such as `io.BytesIO` rather than to a file. The same code also copies leftover heap memory from the process into the saved file. Anyone who can supply an image to a service that opens it and re-saves it as TIFF this way can trigger this with default settings. The attached reproducer crashes the process, and the overflow could potentially be developed further, but code execution is not demonstrated here. The bug is in `_tiffWriteProc` in `src/libImaging/TiffDecode.c`. This is the write callback for the in-memory sink that Pillow gives libtiff whenever the save target has no `fileno()`. While writing the directory, libtiff seeks past the current end of data and writes tag values there. `_tiffWriteProc` sizes its `realloc()` from `state->size + size` instead of from the write position `state->loc`. As a result, the first out-of-line tag value written after that seek can run past the allocation by up to the size of the directory: 12 bytes per entry, 126 bytes in the minimal case, and 2384 bytes observed with a 331-entry directory. The overflowing bytes are the tail of a tag value taken from the uploaded file. No save options are needed, because compression and tags are inherited from the source image. The overflow does require an attacker-sized out-of-line tag to reach libtiff. That happens when the opened TIFF is saved directly (ImageDescription, DocumentName or XMP), or through an embedded ICC profile, which survives `copy()` in `im.info`. The bytes libtiff skips for alignment are never initialised, so any compressed TIFF over 64 KiB written this way also leaks one stale heap byte per padding hole. That leak needs no crafted tag. The attached reproducer builds a 131 KB PackBits TIFF and runs `Image.open(...).save(io.BytesIO(), "TIFF")`. Saving the same image to a file path succeeds, but the `BytesIO` save aborts with glibc's `malloc(): corrupted top size`. A second part of the script shows uninitialised heap bytes at the padding offsets of a `BytesIO` save that are zero in the file save. Saves to real files are unaffected. The defect dates to e782fe721e01 (2013) and has been present since Pillow 2.0.0. ## Details When `im.save()` writes a TIFF through libtiff to a target that has no usable `fileno()` (`io.BytesIO`, an HTTP response object, a storage backend stream), libtiff is opened with `TIFFClientOpen()` on Pillow's own in-memory sink, `_tiffWriteProc` / `_tiffSeekProc` in `src/libImaging/TiffDecode.c`. libtiff treats that sink like a file: when it writes the directory it seeks *beyond* the current end of data (to `eof + IFD size`, where the out-of-line tag values go, and one byte further after every odd-length tag value to keep offsets even) and writes there. On a real file the kernel grows the file and zero-fills the gap. The memory sink does neither correctly: 1. `_tiffWriteProc` sizes its `realloc()` from `state->size + size` instead of `state->loc + size`, so the first write after such a seek can land past the end of the new allocation — a heap buffer overflow whose content is tag data taken from the image being saved. 2. The bytes that libtiff skips are never written, and the grown region is a bare `realloc()`, so they reach the output TIFF with whatever the heap held before. Both are reachable with default settings by the ordinary "re-encode the upload" sequence `Image.open(upload); im.save(io.BytesIO(), "TIFF")` with no save options: `compression` defaults to the source file's (`src/PIL/TiffImagePlugin.py:1734`), any compression other than raw selects the libtiff writer (`:1746`), the source's `tag_v2` entries are passed through to libtiff (`:1959`), and a target without `fileno()` leaves `_fp = 0` (`:1928-1935`), i.e. the memory sink. The overflow half additionally needs an attacker-controlled out-of-line tag value of the right length to reach libtiff. With the opened `TiffImageFile` saved directly (`tag_v2` passthrough) that can be a ~64 KiB ASCII tag (269/270/271/272) or the XMP packet (tag 700, type BYTE — arbitrary bytes), and the number of tags the upload carries sets the IFD size, i.e. the overrun length (12 bytes per entry; 2384 bytes with a 331-entry IFD observed). After `convert()`/`resize()`/`thumbnail()`/`copy()` the `tag_v2` entries are gone, but `im.info["compression"]` (still selecting libtiff) and `im.info["icc_profile"]` survive and the ICC profile is written as tag 34675 (`:1822-1824`), an equally attacker-controlled out-of-line value: `Image.open(x).copy().save(BytesIO(), "TIFF")` with the payload in the ICC profile | CVE Database V5 |
Showing 1–1 of 1 result