Skip to main content
VulnerabilityPublished Updated

CVE-2026-112400: Pillow · GIF seek keeps rejected size after DecompressionBombError, allowing DoS

Affects aiderSource GCVE Database

0
Compare

Mentioned vulnerabilities

Authoritative record:CVE-2026-112400· High 8.7

Not known to be exploited · CVSS 8.7 High.

Data as of 30 Oct 2026, 07:30 UTC

CVSS 4.0 base score
8.7High
How severe the flaw is on the 0–10 CVSS scale.
CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:H/SC:N/SI:N/SA:N

Exploitation evidence

  1. Disclosed: evidence found.

    CVE published 19 Oct 2026

  2. Detectable: no evidence on record.

    No public nuclei template indexed

  3. Weaponized: no evidence on record.

    No public exploit code indexed in Metasploit or Exploit-DB

  4. Exploited in the wild: no evidence on record.

    Not in CISA KEV and no source reports exploitation

  5. Observed by OffSeq Mirage: no evidence on record.

    No attack traffic recorded by OffSeq Mirage sensors

A missing step is not evidence of safety. Sources: cve.org (opens in a new tab), the Metasploit, Exploit-DB and nuclei-templates index as of 29 Oct 2026.

What to do

Affected software

ProductAffectedFixed in
Homebrewaiderpkg:brew/aiderOther threatsAffected
  • >=0.86.1_1
Fixed in–

Check your own hosts for these packages with threat-finder (opens in a new tab), our free open-source scanner.

Description

``` ============================================================================ GIF seek keeps rejected size after DecompressionBombError, allowing DoS Location: src/PIL/GifImagePlugin.py:293 in GifImageFile._seek() Commit: a707c6b21c313880d0771e54965d142dd42358b6 ============================================================================ ``` A GIF of about 60 bytes can make Pillow allocate gigabytes of memory. This affects applications that catch the "image too large" error raised while stepping to a later animation frame and then keep using the same image object. Pillow correctly refuses the oversized frame, but it stores the refused dimensions on the image object anyway, and later operations size their memory from them. An attacker who can upload a GIF to such a service can exhaust its memory: the process either fails with an out-of-memory error after committing gigabytes, or is killed by the operating system. There is no memory corruption. The bug is in `GifImageFile._seek()` (`src/PIL/GifImagePlugin.py:292-294`). When a frame's extent enlarges the image, the code assigns the new size to `self._size` and only then calls `Image._decompression_bomb_check()`. Nothing restores the old size when `DecompressionBombError` is raised, because `seek()` only rolls back on `EOFError`. After the caught error, `im.size` reports the rejected size while the pixel buffer `im.im` is still the 1×1 first frame. Because the extent is a 16-bit offset plus a 16-bit size, that rejected size can be as large as 131070×131070. In the attached reproducer, the rejected size is 40000×40000. A retried `seek(1)` followed by `load()` then reaches `load_prepare()`, which calls `Image.core.fill()` with that size to decode a single pixel. Peak memory rises from 17 MiB to 1700 MiB before a 3 GiB address-space cap stops the next allocation. `Image.new("RGB", im.size, ...)` and `rotate(..., expand=True)` also allocate from the stale size. The default paths are unaffected: plain `Image.open()` plus `load()`, `ImageSequence.Iterator`, and `n_frames`/`is_animated`. The ordering was introduced in c9f1b35e9810 ("Added GIF decompression bomb check", 2022-06-30, first released in 9.2.0), the check added for CVE-2022-45198. The two dispose-extent checks later in the same function already validate before using the size, and the attached patch makes this check follow the same order. ## Details `GifImageFile._seek()` lets a later frame enlarge the image, and performs the decompression-bomb check for the new size only *after* storing it on the object: ```python # src/PIL/GifImagePlugin.py:289-294 (GifImageFile._seek) x0, y0 = i16(s, 0), i16(s, 2) x1, y1 = x0 + i16(s, 4), y0 + i16(s, 6) if (x1 > self.size[0] or y1 > self.size[1]) and update_image: self._size = max(x1, self.size[0]), max(y1, self.size[1]) # state mutated Image._decompression_bomb_check(self._size) # ...then validated ``` `_decompression_bomb_check()` (`src/PIL/Image.py:3586`) raises `DecompressionBombError` for anything above `2 * MAX_IMAGE_PIXELS` (178 956 970 px by default), but by then `self._size` already holds the rejected value and nothing restores it: `seek()` (`GifImagePlugin.py:166-179`) only rolls back on `EOFError`, and `_seek(0)` only ever grows `_size`. The object that the application still holds therefore reports `im.size == (40000, 40000)` while `im.im` is the 1×1 buffer of frame 0. Compare the two dispose-extent checks a few lines further down in the same function (`GifImagePlugin.py:383` and `:407`), which call `_decompression_bomb_check(dispose_size)` *before* the corresponding `Image.core.fill()`, and `TiffImagePlugin.load_prepare()` (`src/PIL/TiffImagePlugin.py:1319-1321`), which checks `_tile_size` before allocating from it. With this 60-byte GIF (hex) ``` 47494638396101000100800000000000ffffff GIF89a, 1x1 screen, 2-entry global palette 2c000000000100010000 02 024401 00 frame 0: 1x1 @ (0,0), LZW data 2c00000000409c409c00 frame 1 descriptor: 40000x40000 @ (0,0) 2c000000000100010000 02 024401 00 another 1x1 frame 3b trailer ``` `Image.open()` succeeds (`im.size == (1, 1)`), `im.seek(1)` raises `DecompressionBombError: Image size (1600000000 pixels) exceeds limit of 178956970 pixels`, and afterwards `im.size == (40000, 40000)`, `im.im.size == (1, 1)`, `im.tell() == 0`. What happens next depends on what the application does with the object, but several ordinary operations take their dimensions from `im.size` rather than from the C buffer: - Retrying `im.seek(1)`. The failed `_seek` left `self.__offset == 0` and the file pointer just past the 9-byte descriptor, so the retry parses whatever follows — in the file above, a valid 1×1 frame — and succeeds. `im.load()` then runs `GifImageFile.load_prepare()`: ```python # src/PIL/GifImagePlugin.py:452-456 elif self.mode in ("RGB", "RGBA"): self._prev_im = self.

References

Community reviews

Loading reviews…