Pillow CVE-2026-112393: who the TIFF heap overflow actually affects, and the two CVEs that came with it
On 17 October 2026 the Pillow maintainers published GHSA-332p-7hrc-92wf, now CVE-2026-112393: a heap buffer overflow in the code Pillow uses to write TIFF files. Pillow is the standard imaging library for Python. It sits under Django's ImageField, Wagtail, Odoo, matplotlib, torchvision and most things in a Python estate that touch a picture. The bug has been there since Pillow 2.0.0 in 2013 and is fixed in Pillow 13.0.0, released on 16 October.
It is rated 9.2 Critical, and it is real. It is also much narrower than "every Python app", and several early summaries described the narrowing wrongly. This note sets out who is actually exposed, how to check, and what to do this week. Everything is as of 22 October 2026, and every claim links to its source at the end.
The short version
- Affected: Pillow 2.0.0 up to, but not including, 13.0.0. Fixed in 13.0.0 only; there is no 12.x release with the fix.
- The vulnerable pattern: turning an image someone else supplied into a compressed TIFF, written to memory or a stream (io.BytesIO, an HTTP response, a storage upload) rather than to a file on disk.
- Severity: 9.2 Critical under CVSS 4.0, with no login needed, but high attack complexity and attack prerequisites present.
- A crash reproducer is public. The reporters say they reached code execution with help from a separate memory leak, and did not publish that chain. CISA has not listed it as exploited, and we found no report of exploitation in the wild.
- The same release fixes two more 9.2 CVEs. One, CVE-2026-112403, is in JPEG thumbnail(), the code path nearly every upload feature uses.
- Pillow 13.0.0 drops Python 3.10. If you are pinned to 3.10, that is your real blocker.
Free PDF · 15 pagesPillow CVE-2026-112393: Assessment & Action PlanThe full version as a PDF for whoever owns your Python services: the mechanism, every severity score decoded, exposure by application pattern, the two sibling CVEs, version and code checks, tested workarounds, hunting and a definition of done.Download the PDF →What the bug is
Pillow writes compressed TIFFs through libtiff. When the destination is a real file, libtiff writes to the file descriptor itself. When the destination has no usable fileno(), Pillow gives libtiff a growable memory buffer and a write callback, _tiffWriteProc. libtiff does not always write in order: to place a large tag value it seeks past the current end and writes there. The callback grew its buffer from the buffer's previous size instead of from the position being written, so a write after that seek could land past the end of the allocation.
The bytes written out of bounds are the tail of a tag value the attacker chose. On a standard build, glibc notices the corruption and aborts the process, so the obvious result is a crashed worker. The reporters went further: given two addresses leaked by an unrelated bug, they turned the write into running a shell command in a simple upload server, with ASLR, PIE and NX all enabled.
There is a second, independent defect in the same code. Any compressed TIFF over 64 KiB written this way can contain a few bytes of stale heap memory, meaning data that earlier requests freed. It needs no crafted file. It is low-bandwidth, but in a multi-tenant service it hands one customer's leftovers to another.
Who is actually exposed
For the overflow, every one of these has to be true:
- The application accepts images from someone else.
- It produces a TIFF from them. This is rare on the web, but normal in document imaging, OCR, fax, archiving, GIS, and medical and scientific pipelines.
- The TIFF is compressed. Compression is inherited automatically when an uploaded compressed TIFF is re-saved, so the application does not have to ask for it.
- It writes to something that is not a real file: a BytesIO, a response object, a storage stream. Saving to a file path is not affected.
- An attacker-controlled out-of-line tag reaches the save, such as ImageDescription, DocumentName, XMP or an embedded ICC colour profile. The ICC profile survives convert(), resize(), thumbnail() and copy(), so this can happen without the application copying any tags on purpose.
If you make JPEG, PNG or WebP thumbnails, this CVE does not reach you. If you only read TIFFs, it does not reach you either. If you run a document pipeline that normalises everything to compressed TIFF in memory, it is aimed squarely at you, and those pipelines usually take files from outside the organisation by design.
For most web applications this CVE is narrow. For document and imaging pipelines it is direct. Either way the fix is the same release, and that release also fixes a bug on the path everyone uses.
The sibling that probably matters more
CVE-2026-112403 (GHSA-8g5g-8vmr-9782) is a heap overflow in Pillow's JPEG decoder, reached through draft(), which thumbnail() calls by default. The trigger is a valid but unusual JPEG, 8-bit lossless (SOF3), so restricting Image.open() to JPEG does not stop it. It needs libjpeg-turbo 3.x, which the official PyPI wheels bundle. Our check of the 12.3.0 wheel reports libjpeg-turbo 3.1.4.1. Distribution builds linked against libjpeg-turbo 2.x reject the file and are not affected.
The third, CVE-2026-112900 (GHSA-w889-685w-c8fh), is argument injection into Ghostscript when Pillow opens an EPS file whose name an attacker influences. A name beginning with - or @ is read as an option or an argument file, even with -dSAFER. It needs Ghostscript installed and an upload handler that keeps user-supplied filenames.
All three are rated 9.2 and all three are fixed only in 13.0.0. Plan them as one change.
Five things early summaries got wrong
We reviewed several automated and early write-ups while preparing this. These errors recurred, and each one changes a decision:
- "CVSS 9.5." The CNA's score is 9.2 under CVSS 4.0. We found no primary source for 9.5. NVD has not scored it yet, which is different from rating it low.
- "The attacker controls the pixel data." The overflow comes from tag values or an ICC profile. The pixels only shape the layout.
- "Only BytesIO." It affects any target without a usable fileno(), including HTTP responses and storage streams.
- "No public proof of concept." The advisory includes a working reproducer for the crash and the leak. Only the code-execution chain is unpublished.
- "Fixed in 13.0.0 or the next patch release." Only 13.0.0, with no 12.x backport, and it drops Python 3.10.
Step 1 — Find the version you actually run
Check from the interpreter that runs the service, not the one on your path, and check the running container, not the Dockerfile. Any PyPI Pillow below 13.0.0 is affected by all three CVEs. Distribution packages are a separate question: on 22 October, Debian's tracker listed bookworm, trixie and forky as vulnerable and only sid as fixed.
# Version and bundled libraries, from the service's own interpreter
python -c "import PIL; from PIL import features; print(PIL.__version__, 'libjpeg-turbo', features.version('libjpeg_turbo'), 'libtiff', features.version('libtiff'))"
# Lockfiles and requirements across a repository
grep -rni pillow --include='requirements*.txt' --include='poetry.lock' --include='uv.lock' --include='Pipfile.lock' --include='pyproject.toml' .
# Containers: the running image
docker exec <container> python -c "import PIL; print(PIL.__version__)"Step 2 — Find whether your code reaches it
A vulnerable version means the library is exposed. These searches show whether your code goes near the bug. Review every hit whose input can come from outside. They miss indirect calls through helpers and third-party libraries, so treat a clean result as a starting point, not proof:
# CVE-2026-112393: TIFF output
grep -rnE --include='*.py' "save\(.*['\"](TIFF|tiff)['\"]|\.tiff?['\"]" .
# CVE-2026-112403: thumbnail() / draft()
grep -rnE --include='*.py' "\.thumbnail\(|\.draft\(" .
# CVE-2026-112900: EPS, and whether Ghostscript is installed
grep -rnE --include='*.py' "EpsImagePlugin|\.eps['\"]" .
command -v gsStep 3 — Upgrade to 13.0.0
- Confirm the service runs on Python 3.11 or later. Pillow 13.0.0 does not support 3.10, so a 3.10 pin means upgrading Python first, or waiting for your distribution's patched package and using the workarounds below meanwhile.
- Raise the pin to pillow>=13.0.0, rebuild, and run your tests. 13.0.0 removes some deprecated APIs and changes a few behaviours, for example XMP info is no longer a tuple.
- Deploy, then verify the version from the running service.
- Add a dependency check (pip-audit, Dependabot, Renovate or your SCA tool) that fails builds below the fixed baseline.
If you cannot upgrade this week
These remove the vulnerable path in your own code. They do not fix the library, so treat them as cover until the upgrade lands:
- For TIFF output, write to a real temporary file and read the bytes back (code below). A real file descriptor sends libtiff through its own file I/O instead of the vulnerable callback.
- Or save TIFFs uncompressed with compression="raw". Pillow writes uncompressed TIFF with its own Python writer, not libtiff. You must set it explicitly, because compression is otherwise inherited.
- For thumbnails, call im.load() before thumbnail(), or pass reducing_gap=None. This decodes at full size, so it is slower and uses more memory per image. Keep the decompression-bomb limit in place.
- For EPS, store uploads under names your server generates, open them with formats= excluding EPS, and remove Ghostscript from hosts that do not need it.
- Whatever you choose, run image processing in a separate low-privilege worker with no secrets and no outbound network. It limits the damage from all three, and from the next one.
import tempfile
def tiff_bytes(im, **save_options):
# A real file descriptor makes libtiff write through its own file I/O,
# not the vulnerable in-memory callback (CVE-2026-112393).
with tempfile.TemporaryFile() as f:
im.save(f, "TIFF", **save_options)
f.seek(0)
return f.read()We tested the first two against the advisory's harmless leak scenario, with the same image saved to a file path, to BytesIO, to a TemporaryFile and uncompressed. On Pillow 12.3.0, the BytesIO output contained stale heap bytes in 3 of 20 runs. The temporary-file and uncompressed outputs matched the file-path output in all 20. On 13.0.0, BytesIO matched in all 20. The leak is intermittent because stale memory is often zero, so a passing leak test proves nothing. Rely on the version check.
What to look for
- Image-processing workers dying with SIGABRT, or glibc messages such as "malloc(): corrupted top size" or "realloc(): invalid next size". The minimal reproducer aborts the process, so a cluster of crashes on an image endpoint is a security event, not only an availability one.
- TIFF uploads with hundreds of directory entries or very large text tags. The reporters' code-execution file was a 4.7 MB TIFF with a 63,942-byte XMP packet and about 500 private entries.
- Lossless SOF3 JPEGs, which are rare in normal uploads, and upload filenames starting with - or @.
- Image workers starting shells, downloading tools or opening outbound connections.
What we do not know yet
- Whether the code-execution chain is published, or someone finds a way to do it without a second bug. Either would raise the urgency.
- NVD's own score. The record was still "Undergoing Analysis" on 22 October.
- When distributions ship fixes, and which products embedding Pillow are affected.
If you take one thing from this: the CVE that arrives in your feed is not always the one that matters most to you. Read the preconditions, and look at what else the same release fixes. Here the TIFF bug made the news, while the thumbnail bug sits on the path nearly every upload feature uses.
Sources
- Pillow security advisory GHSA-332p-7hrc-92wf — CVE-2026-112393, heap buffer overflow in _tiffWriteProc
- GitHub Advisory Database — GHSA-332p-7hrc-92wf (reviewed entry: CVSS 4.0 9.2, EPSS)
- NVD — CVE-2026-112393
- Pillow security advisory GHSA-8g5g-8vmr-9782 — CVE-2026-112403, heap buffer overflow in ImagingJpegDecode
- Pillow security advisory GHSA-w889-685w-c8fh — CVE-2026-112900, argument injection in the Ghostscript call
- Pillow 13.0.0 release notes
- CISA Known Exploited Vulnerabilities catalog
- Debian security tracker — CVE-2026-112393
- OX Security — Three critical Pillow CVEs (22 October 2026)