{"id":"CVE-2026-96638","kind":"vulnerability","aliases":["CVE-2026-96638","GHSA-24rc-8hj2-v98c"],"related":[],"title":"Pillow: BMP RLE decoder memory-amplification DoS via unbounded delta escape / absolute mode","description":"## Summary\nBmpRleDecoder can be forced to allocate gigabytes of memory from a kilobyte-sized crafted BMP file via unbounded growth in the delta escape and absolute mode branches.\n\n## Affected versions\nAll versions up to and including current HEAD (v13.0.0.dev0, commit e7a7ea84)\n\n## Root cause\nBmpRleDecoder.decode() (BmpImagePlugin.py:327-395) builds a Python bytearray `data` with dest_length = xsize*ysize, looping `while len(data) < dest_length`. The loop condition is only checked at the TOP; individual branches append attacker-controlled amounts UNBOUNDED by dest_length:\n\n- delta escape (line 370): data += b\"\\x00\" * (right + up * self.state.xsize) with right,up in 0..255 -> up to ~255*xsize bytes in ONE iteration.\n- absolute mode (line 385): data += bytes_read (up to 255).\n\nBecause a single delta can push len(data) to ~256 * (row size), a tiny RLE stream combined with a large-but-legal xsize (xsize*ysize just under MAX_IMAGE_PIXELS, ~89M) yields a multi-GB `data` bytearray (then copied again by bytes(data) and set_as_raw). No heap OOB (bytearray/set_as_raw are bounds-safe), but a severe memory-amplification DoS: the attacker file stays tiny.\n\n## Reproduction\nPython 3.13, Pillow HEAD e7a7ea84\n\nAttached: pillow_poc_bmp_rle_dos.py\n\nA 1084-byte BMP (BI_RLE8, width=3,000,000, height=1 -> 3M px, passes bomb check) with one delta escape (00 02 00 150) produces ~429 MB `data`; measured peak RSS 911 MB. Scaling width toward the bomb limit and up=255 produces tens of GB from a ~1 KB file.\n\n## Suggested fix\nCap growth at dest_length in the delta and absolute branches, e.g. clamp the delta fill to (dest_length - len(data)) and stop appending once len(data) >= dest_length; reject deltas that would move the cursor beyond the image, matching the existing per-row clamp at line 341-343.\n\nCredit: Yazan Balawneh - CyStack Security Team (GitHub: sonicnew)\n\n[pillow_poc_bmp_rle_dos.py](https://github.com/user-attachments/files/30244974/pillow_poc_bmp_rle_dos.py)","summary":"Pillow's BMP RLE decoder can be made to allocate gigabytes of memory from a kilobyte-sized crafted BMP file, because delta escape and absolute mode branches append attacker-controlled amounts without bounding growth by the image size. An attacker who can get a process to open a malicious BMP can cause a memory-exhaustion denial of service.","severity":"medium","cvss":6.5,"epss":0.00279999990016222,"exploited":false,"exploited_at":null,"products":[{"name":"pillow","fixed":["13.0.0"],"affected":">= 9.1.0, < 13.0.0","ecosystem":"pypi"}],"ecosystems":["pypi"],"tags":["cwe-400","cwe-770","image-processing","package","pillow","python","self-hosted"],"not_affected_if":["no-untrusted-bmp-input","version-13.0.0-or-later","version-below-9.1.0"],"how_to_check":"Run `python -c \"import PIL; print(PIL.__version__)\"` and check whether the version is >= 9.1.0 and < 13.0.0.\nSearch the codebase for Image.open or PIL usage on untrusted BMP uploads.","fix":"Upgrade pillow to 13.0.0 or later.\nUntil upgraded, reject or limit untrusted BMP inputs before decoding.","status":"open","published_at":"2026-10-21T21:20:32+00:00","first_seen_at":"2026-10-23T19:11:32+00:00","triaged_at":"2026-10-23T19:57:40+00:00","updated_at":"2026-10-23T21:57:29+00:00","category":"package","confidence":0.8,"triage":{"at":"2026-10-23T19:57:40Z","model":"claude-haiku-5-5","output":{"fix":"Upgrade pillow to 13.0.0 or later.\nUntil upgraded, reject or limit untrusted BMP inputs before decoding.","summary":"Pillow's BMP RLE decoder can be made to allocate gigabytes of memory from a kilobyte-sized crafted BMP file, because delta escape and absolute mode branches append attacker-controlled amounts without bounding growth by the image size. An attacker who can get a process to open a malicious BMP can cause a memory-exhaustion denial of service.","category":"package","who_cares":["python","pillow","image-processing","self-hosted"],"confidence":0.8,"analyst_note":"","how_to_check":"Run `python -c \"import PIL; print(PIL.__version__)\"` and check whether the version is >= 9.1.0 and < 13.0.0.\nSearch the codebase for Image.open or PIL usage on untrusted BMP uploads.\nunknown","needs_analyst":false,"not_affected_if":["version-below-9.1.0","version-13.0.0-or-later","no-untrusted-bmp-input"],"severity_if_unknown":"medium"},"cost_usd":0.00048,"input_tokens":2848,"output_tokens":391,"severity_before":"medium"},"analyst_status":null,"analyst_note":null,"search":"'-2026':16A,24A '-343':398C '-395':147C '-400':28B '-770':30B '-96638':17A,25A '/user-attachments/files/30244974/pillow_poc_bmp_rle_dos.py)':410C '0':198C '00':316C,318C '000':303C,304C '02':317C '1':306C,344C '1084':296C '150':319C '24rc':20A '255':199C,202C,217C,337C '256':227C '3':302C '3.13':289C '341':397C '370':187C '385':211C '3m':307C '429':321C '89m':249C '8hj2':21A '911':327C 'absolut':13A,65B,127C,208C,358C 'affect':130C 'alloc':49B,107C 'amount':72B,179C 'amplif':7A,280C 'append':68B,175C,372C 'attach':293C 'attack':70B,81B,177C,283C 'attacker-control':69B,176C 'b':189C 'balawneh':401C 'beyond':385C 'bi':299C 'bmp':2A,42B,59B,91B,117C,298C 'bmpimageplugin.py:327':146C 'bmprledecod':102C 'bmprledecoder.decode':145C 'bomb':310C,333C 'bound':74B,273C 'bounds-saf':272C 'branch':67B,129C,174C,359C 'build':148C 'byte':204C,213C,261C,297C 'bytearray':151C,256C 'bytearray/set_as_raw':270C 'cap':349C 'caus':93B,144C 'check':169C,311C 'clamp':361C,394C 'combin':234C 'commit':141C 'condit':166C 'control':71B,178C 'copi':258C 'craft':58B,116C 'credit':399C 'current':138C 'cursor':384C 'cve':15A,23A 'cwe':27B,29B 'cystack':402C 'data':152C,161C,188C,212C,225C,255C,262C,323C,369C,375C 'decod':4A,44B 'delta':11A,62B,124C,184C,221C,314C,356C,363C,379C 'denial':98B 'dest':154C,162C,182C,352C,366C,376C 'dos':8A,281C 'e.g':360C 'e7a7ea84':142C,292C 'escap':12A,63B,125C,185C,315C 'exhaust':97B 'exist':390C 'file':60B,118C,284C,346C 'fill':364C 'fix':348C 'forc':105C 'gb':254C,341C 'get':84B 'ghsa':19A 'ghsa-24rc-8hj2-v98c':18A 'gigabyt':50B,108C 'github':405C 'github.com':409C 'github.com/user-attachments/files/30244974/pillow_poc_bmp_rle_dos.py)':408C 'growth':75B,121C,350C 'head':139C,291C 'heap':268C 'height':305C 'host':39B 'imag':32B,78B,247C,387C 'image-process':31B 'includ':137C 'individu':173C 'iter':207C 'kb':345C 'kilobyt':56B,114C 'kilobyte-s':55B,113C 'larg':238C 'large-but-leg':237C 'legal':240C 'len':160C,224C,368C,374C 'length':155C,163C,183C,353C,367C,377C 'limit':334C 'line':186C,210C,396C 'loop':158C,165C 'made':47B 'malici':90B 'match':388C 'max':246C 'mb':322C,328C 'measur':324C 'memori':6A,52B,96B,110C,279C 'memory-amplif':5A,278C 'memory-exhaust':95B 'mode':14A,66B,128C,209C 'move':382C 'multi':253C 'multi-gb':252C 'one':206C,313C 'oob':269C 'open':88B 'packag':34B 'pass':309C 'peak':325C 'per':392C 'per-row':391C 'pillow':1A,26A,35B,40B,290C 'pillow_poc_bmp_rle_dos.py':294C,407C 'pixel':248C 'process':33B,86B 'produc':320C,338C 'push':223C 'px':308C 'python':36B,150C,288C 'raw':266C 'read':214C 'reject':378C 'reproduct':287C 'right':191C,195C 'rle':3A,43B,232C 'rle8':300C 'root':143C 'row':228C,393C 'rss':326C 'safe':274C 'scale':329C 'secur':403C 'self':38B 'self-host':37B 'self.state.xsize':193C 'servic':100B 'set':264C 'sever':277C 'singl':220C 'size':57B,79B,115C,229C 'sonicnew':406C 'stay':285C 'stop':371C 'stream':233C 'suggest':347C 'summari':101C 'team':404C 'ten':339C 'tini':231C,286C 'top':172C 'toward':331C 'unbound':10A,120C,180C 'v13.0.0.dev0':140C 'v98c':22A 'version':131C,133C 'via':9A,119C 'width':301C,330C 'without':73B 'would':381C 'x00':190C 'xsize':156C,203C,241C,242C 'yazan':400C 'yield':250C 'ysize':157C,243C","nicknames":[],"mitigations":[],"who_affected":null,"tier":"C","verified":{},"analyst_revisit_at":null,"first_public_at":"2026-10-21T21:20:32+00:00","kev":null,"cvss_vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:N/A:H","cvss_by":"GitHub","poc_count":0,"sources":[{"source":"ghsa","external_id":"GHSA-24rc-8hj2-v98c","url":"https://github.com/advisories/GHSA-24rc-8hj2-v98c","severity":"medium","published_at":"2026-10-22T15:59:35+00:00","modified_at":"2026-10-22T15:59:36+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-96638","url":"https://nvd.nist.gov/vuln/detail/CVE-2026-96638","severity":null,"published_at":"2026-10-21T21:20:32+00:00","modified_at":"2026-10-22T16:46:48+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"}