{"id":"CVE-2026-96637","kind":"vulnerability","aliases":["CVE-2026-96637","GHSA-g842-9rc4-69pw"],"related":[],"title":"Pillow: Decompression bomb check bypass in SpiderImagePlugin.seek() leads to unbounded allocation DoS","description":"## Summary\nSpiderImagePlugin's seek() bypasses the decompression bomb check, allowing a tiny (<1KB) crafted SPIDER stack file to trigger a multi-gigabyte allocation on im.load().\n\n## Affected versions\nAll versions up to and including current HEAD (v13.0.0.dev0, commit e7a7ea84)\n\n## Root cause\nImage.open() runs Image._decompression_bomb_check(im.size) exactly once, in _open_core (src/PIL/Image.py:3668), right after the initial _open(). \n\nSPIDER 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:\n\n    self._size = int(h[12]), int(h[2])      # SpiderImagePlugin.py:131\n\nstraight from unvalidated header floats (isSpiderHeader only checks internal labrec*lenbyt==labbyt consistency, never bounds nsam/nrow). No bomb check runs on this new size.\n\nOn the subsequent load(), ImageFile.load_prepare() (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.\n\n## Why this is a real bug\nSibling multi-frame plugins DO re-check on their alternate paths:\n- GifImagePlugin.py:286 - Image._decompression_bomb_check(self._size) (in seek)\n- TiffImagePlugin.py:1309 - Image._decompression_bomb_check(self._tile_size)\n- IcnsImagePlugin.py:121/138, IcoImagePlugin.py:236/240 (sub-image selection)\n\nSpiderImagePlugin 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.\n\n## Reproduction\nPython 3.13, Pillow HEAD e7a7ea84\n\nAttached: pillow_poc_spider_bomb.py\n\nSets Image.MAX_IMAGE_PIXELS=5000, then:\n- CONTROL: Image.open(standalone 100x100 SPIDER) -> DecompressionBombWarning fires correctly.\n- BYPASS: Image.open(stack, frame0=1x1) passes; im.seek(1) sets size (100,100) with NO DecompressionBombWarning/Error -> the check is bypassed.\n\nReal-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.\n\n## Suggested fix\nCall Image._decompression_bomb_check(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().\n\nCredit: Yazan Balawneh - CyStack Security Team \n[pillow_poc_spider_bomb.py](https://github.com/user-attachments/files/30244916/pillow_poc_spider_bomb.py)","summary":"Pillow's SPIDER image plugin skips the decompression bomb size check when seeking to later frames, so a tiny crafted SPIDER stack file can make im.load() allocate multiple gigabytes. An attacker who gets a malicious image processed by an app using Pillow can exhaust memory and cause denial of service.","severity":"high","cvss":7.1,"epss":0.003329999977722764,"exploited":false,"exploited_at":null,"products":[{"name":"pillow","fixed":["13.0.0"],"affected":"< 13.0.0","ecosystem":"pypi"}],"ecosystems":["pypi"],"tags":["cwe-400","cwe-409","image-upload","package","pillow","python","self-hosted"],"not_affected_if":["no-multi-frame-seek","no-untrusted-spider-input","pillow-at-or-above-13.0.0"],"how_to_check":"Run pip show pillow and check whether the version is below 13.0.0.\nFind Pillow in requirements or lockfiles with grep -i pillow requirements*.txt poetry.lock uv.lock Pipfile.lock.\nCheck whether the app opens untrusted SPIDER (.spi, .spider) files with Image.open() and then seeks frames.","fix":"Upgrade Pillow to 13.0.0 or later (pip install -U 'pillow>=13.0.0').\nUntil then, reject or do not process SPIDER uploads from untrusted users.","status":"open","published_at":"2026-10-21T21:20:31+00:00","first_seen_at":"2026-10-23T19:11:32+00:00","triaged_at":"2026-10-23T20:10:03+00:00","updated_at":"2026-10-23T21:57:29+00:00","category":"package","confidence":0.85,"triage":{"at":"2026-10-23T20:10:03Z","model":"claude-haiku-5-5","output":{"fix":"Upgrade Pillow to 13.0.0 or later (pip install -U 'pillow>=13.0.0').\nUntil then, reject or do not process SPIDER uploads from untrusted users.","summary":"Pillow's SPIDER image plugin skips the decompression bomb size check when seeking to later frames, so a tiny crafted SPIDER stack file can make im.load() allocate multiple gigabytes. An attacker who gets a malicious image processed by an app using Pillow can exhaust memory and cause denial of service.","category":"package","who_cares":["python","pillow","image-upload","self-hosted"],"confidence":0.85,"analyst_note":"","how_to_check":"Run pip show pillow and check whether the version is below 13.0.0.\nFind Pillow in requirements or lockfiles with grep -i pillow requirements*.txt poetry.lock uv.lock Pipfile.lock.\nCheck whether the app opens untrusted SPIDER (.spi, .spider) files with Image.open() and then seeks frames.","needs_analyst":false,"not_affected_if":["pillow-at-or-above-13.0.0","no-untrusted-spider-input","no-multi-frame-seek"],"severity_if_unknown":"high"},"cost_usd":0.000538,"input_tokens":3227,"output_tokens":431,"severity_before":"high"},"analyst_status":null,"analyst_note":null,"search":"'-1':386C '-190':155C '-2026':14A,22A '-400':26B '-409':28B '-437':225C '-96637':15A,23A '/138,':278C '/240':281C '/8v84/45hq':315C '/user-attachments/files/30244916/pillow_poc_spider_bomb.py)':446C '1':367C,408C '100':370C,371C '100x100':355C '12':189C '190':168C '1kb':100C '1x1':364C '2':192C '27':178C '3.13':340C '3668':139C '4':242C '40000x40000':246C,389C '5000':350C '5x94':314C '6.4':247C,394C '65pm':307C '69pw':20A '9rc4':19A 'affect':114C 'alloc':11A,64B,111C,317C,393C 'allow':97C 'altern':266C 'alway':240C 'anywher':291C 'app':77B 'attach':344C 'attack':68B,159C,235C,326C 'attacker-chosen':234C 'attacker-influenc':158C 'balawneh':439C 'bomb':3A,46B,95C,211C,289C,336C,400C 'bound':208C 'bug':254C,298C 'bypass':5A,92C,360C,378C,403C 'bytes/pixel':243C 'call':163C,412C 'caus':84B,128C,390C 'check':4A,48B,96C,201C,212C,263C,290C,337C,376C,401C 'chosen':236C 'class':296C,311C 'commit':125C 'comput':156C 'consist':206C 'control':352C 'core':137C 'correct':359C 'craft':57B,101C 'credit':437C 'current':122C 'cve':13A,21A 'cwe':25B,27B 'cystack':440C 'decompress':2A,45B,94C 'decompressionbombwarn':357C 'decompressionbombwarning/error':374C 'denial':85B 'direct':166C 'dos':12A 'e.g':245C,388C 'e7a7ea84':126C,343C 'end':417C 'exact':133C 'execut':226C 'exhaust':81B 'f':241C,397C 'field':328C 'file':60B,104C,147C,406C 'fire':358C 'fix':300C,411C 'float':179C,198C 'fontfil':310C 'frame':53B,151C,153C,258C,385C 'frame0':363C 'fresh':177C 'fulli':402C 'g842':18A 'gb':248C,395C 'gdimagefil':302C 'get':70B 'ghsa':17A,304C,313C 'ghsa-5x94':312C 'ghsa-g842-9rc4-69pw':16A 'ghsa-phj9-mv4w-65pm':303C 'gifimageplugin':426C 'gifimageplugin.py:286':268C 'gigabyt':66B,110C 'github.com':445C 'github.com/user-attachments/files/30244916/pillow_poc_spider_bomb.py)':444C 'h':188C,191C 'head':123C,342C 'header':180C,197C,327C 'host':37B 'icnsimageplugin.py:121':277C 'icnsimageplugin.py:121/138,':276C 'icoimageplugin.py:236':280C 'icoimageplugin.py:236/240':279C 'im.load':63B,113C,391C 'im.seek':366C 'im.size':132C 'imag':30B,41B,73B,284C,348C 'image-upload':29B 'image._decompression_bomb_check':131C,269C,274C,413C 'image.core.new':228C 'image.max':347C 'image.open':129C,171C,353C,361C 'imagefile.load':222C 'imagefile.py:436':224C 'impact':382C 'includ':121C 'independ':329C 'influenc':160C 'initi':143C 'int':187C,190C 'intern':202C 'isspiderhead':199C 'kb':409C 'labbyt':205C 'labrec':203C 'larg':384C 'later':52B 'lead':8A 'lenbyt':204C 'line':167C 'load':221C 'make':62B 'malici':72B 'memori':82B 'mirror':425C 'mode':238C,396C 'multi':109C,150C,257C 'multi-fram':149C,256C 'multi-gigabyt':108C 'multipl':65B 'mv4w':306C 'never':207C 'new':216C 'non-image.open':321C 'nsam/nrow':209C,387C,430C 'offset':183C 'one':333C 'one-tim':332C 'open':136C,144C,172C,424C,436C 'option':428C 'packag':32B 'pars':175C 'pass':365C 'path':267C,322C 'phj9':305C 'pillow':1A,24A,33B,38B,79B,341C 'pillow_poc_spider_bomb.py':345C,443C 'pixel':335C,349C 'plugin':42B,259C 'posit':432C 'prepar':223C 'process':74B 'python':34B,339C 're':174C,262C 're-check':261C 're-pars':173C 'reach':318C 'real':253C,380C 'real-world':379C 'reassign':185C 'reproduct':338C 'right':140C 'root':127C 'run':130C,213C 'sane':434C 'secur':441C 'seek':50B,91C,272C,422C 'select':285C 'self':36B 'self-host':35B 'self._open':164C 'self._size':186C,270C 'self._tile_size':275C 'self.im':227C 'self.mode':229C 'self.size':230C,414C 'servic':87B 'set':346C,368C 'sibl':255C 'size':47B,217C,237C,323C,369C 'skip':43B 'spider':40B,58B,102C,145C,356C 'spiderimagefile._open':419C 'spiderimagefile.seek':152C 'spiderimageplugin':89C,286C 'spiderimageplugin.py:131':193C 'spiderimageplugin.py:179':154C 'spiderimageplugin.seek':7A 'src/pil/image.py':138C 'stack':59B,103C,146C,362C 'standalon':354C 'stkoffset':161C 'straight':194C 'sub':283C 'sub-imag':282C 'subsequ':220C 'suggest':410C 'summari':88C 'team':442C 'tiffimageplugin':427C 'tiffimageplugin.py:1309':273C 'time':334C 'tini':56B,99C 'trigger':106C 'unbound':10A 'uncheck':233C 'unvalid':196C 'upload':31B 'use':78B 'v13.0.0.dev0':124C 'valid':429C 'version':115C,117C 'world':381C 'yazan':438C","nicknames":[],"mitigations":[],"who_affected":null,"tier":"B","verified":{},"analyst_revisit_at":null,"first_public_at":"2026-10-21T21:20:31+00:00","kev":null,"cvss_vector":"CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:P/VC:N/VI:N/VA:H/SC:N/SI:N/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X","cvss_by":"security-advisories@github.com","poc_count":0,"sources":[{"source":"ghsa","external_id":"GHSA-g842-9rc4-69pw","url":"https://github.com/advisories/GHSA-g842-9rc4-69pw","severity":"high","published_at":"2026-10-22T15:57:17+00:00","modified_at":"2026-10-22T15:57:18+00:00","first_seen_at":"2026-10-23T19:11:32+00:00","fetched_at":"2026-10-23T19:11:32+00:00","note":null},{"source":"nvd","external_id":"CVE-2026-96637","url":"https://nvd.nist.gov/vuln/detail/CVE-2026-96637","severity":null,"published_at":"2026-10-21T21:20:31+00:00","modified_at":"2026-10-23T22:17:01+00:00","first_seen_at":"2026-10-23T21:57:29+00:00","fetched_at":"2026-10-23T22:29:02+00:00","note":null}],"documents":[],"notice":"Free to read at a human pace. An API with keys and an MCP server are in preparation; early access: hello@infrastack.ai"}