GHSA-g842-9rc4-69pw: Pip/pillow vulnerability
Summary SpiderImagePlugin's seek() bypasses the decompression bomb check, allowing a tiny (<1KB) crafted SPIDER stack file to trigger a multi-gigabyte allocation on im.load().
Affected versions All versions up to and including current HEAD (v13.0.0.dev0, commit e7a7ea84)
Root cause Image.open() runs Image.decompressionbombcheck(im.size) exactly once, in opencore (src/PIL/Image.py:3668), right after the initial open().
SPIDER stack files are multi-frame: SpiderImageFile.seek(frame) (SpiderImagePlugin.py:179-190) computes an attacker-influenced stkoffset and calls self.open() AGAIN directly (line 190) — NOT through Image.open(). open() re-parses a fresh 27-float header at that offset and reassigns:
self.size = int(h[12]), int(h[2]) # SpiderImagePlugin.py:131
straight from unvalidated header floats (isSpiderHeader only checks internal labreclenbyt==labbyt consistency, never bounds nsam/nrow). No bomb check runs on this new size.
On the subsequent load(), ImageFile.loadprepare() (ImageFile.py:436-437) executes self.im = Image.core.new(self.mode, self.size) with the unchecked, attacker-chosen size. Mode is always "F" (4 bytes/pixel), so e.g. 40000x40000 = 6.4 GB.
Why this is a real bug Sibling multi-frame plugins DO re-check on their alternate paths: - GifImagePlugin.py:286 - Image.decompressionbombcheck(self.size) (in seek) - TiffImagePlugin.py:1309 - Image.decompressionbombcheck(self.tilesize) - IcnsImagePlugin.py:121/138, IcoImagePlugin.py:236/240 (sub-image selection)
SpiderImagePlugin has NO bomb check anywhere. This is the same class of bug just fixed for GdImageFile (GHSA-phj9-mv4w-65pm) and the FontFile classes (GHSA-5x94/8v84/45hq): an allocation reached on a non-Image.open() path sized by an attacker header field, independent of the one-time pixel bomb check.
Reproduction Python 3.13, Pillow HEAD e7a7ea84
Attached: pillowpocspiderbomb.py
Sets Image.MAXIMAGEPIXELS=5000, then: - CONTROL: Image.open(standalone 100x100 SPIDER) -> DecompressionBombWarning fires correctly. - BYPASS: Image.open(stack, frame0=1x1) passes; im.seek(1) sets size (100,100) with NO DecompressionBombWarning/Error -> the check is bypassed.
Real-world impact: a large frame-1 nsam/nrow (e.g. 40000x40000) causes im.load() to allocate ~6.4 GB (mode F) with the bomb check fully bypassed, from a file under 1 KB.
Suggested fix Call Image.decompressionbombcheck(self.size) at the end of SpiderImageFile.open() (or in seek() after open()), mirroring GifImagePlugin / TiffImagePlugin. Optionally validate nsam/nrow are positive and sane in open().
Credit: Yazan Balawneh - CyStack Security Team pillowpocspiderbomb.py
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 - Compensating control
Update SpiderImageFile so Image._decompression_bomb_check(self._size) is called at the end of _open(), or after _open() in seek(), before loading attacker-controlled frame dimensions.
Event History
Frequently Asked Questions
Which applications are realistically exposed?
Applications using Pillow are exposed when they process attacker-controlled SPIDER stack files and access a non-initial frame. The unsafe allocation occurs when the selected frame is loaded.
What must an attacker control to trigger the allocation?
An attacker needs to provide a crafted multi-frame SPIDER stack file with a later frame whose header specifies attacker-chosen dimensions. Seeking to that frame causes Pillow to reparse the header without rerunning the decompression-bomb check.
How can I determine whether my Pillow installation is in scope?
The affected range includes all versions through the reported current development head, v13.0.0.dev0 at commit e7a7ea84. Deployments are particularly relevant if they accept SPIDER files from untrusted sources or iterate through frames in SPIDER stacks.