Skip to main content
VulnerabilityPublished Updated

CVE-2026-112403: Pillow · Heap buffer overflow in ImagingJpegDecode

Affects aiderSource GCVE Database

0
Compare
Authoritative record:CVE-2026-112403· Critical 9.2

Not known to be exploited · CVSS 9.2 Critical.

Data as of 30 Oct 2026, 07:30 UTC

CVSS 4.0 base score
9.2Critical
How severe the flaw is on the 0–10 CVSS scale.

Thin evidence

One scorer or none
CVSS scores by source
CNA (GitHub_M)CVSS 4.09.2
CVSS:4.0/AV:N/AC:H/AT:N/PR:N/UI:N/VC:H/VI:H/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

## Summary `JpegImageFile.draft()` — which `Image.thumbnail()` calls automatically — requests 1/2, 1/4 or 1/8 scaled JPEG output and shrinks `self._size` and the tile extents to match, so `_setimage()` allocates a single-row decode buffer of `ceil(W/s)` samples. `ImagingJpegDecode()` sets `cinfo.scale_num`/`scale_denom`, calls `jpeg_start_decompress()`, and then reads scanlines into that buffer without ever comparing `cinfo.output_width`, `cinfo.output_height` or `cinfo.out_color_components` against `state->xsize`, `state->ysize` and `state->bits`. libjpeg-turbo 3.x decodes lossless (SOF3) JPEG, but hardwires lossless streams to no scaling — the requested `scale_denom` is silently discarded. It therefore returns full-width `W`-sample rows into a buffer sized for `ceil(W/s)`. Each `jpeg_read_scanlines()` call writes `(W - ceil(W/s)) x components` bytes past the end of the heap allocation, and the overflowing bytes are the decoded pixel values, which are byte-exact for an 8-bit grayscale lossless image. The input is a standards-compliant JPEG, so `Image.open(..., formats=["JPEG"])` does not prevent it. Pillow's Python SOF parser already accepts marker `0xFFC3` ("SOF3", "Spatial lossless") at 8-bit precision, so the file opens normally as a `JPEG` / mode `L` image at its full declared size. ## Affected code At `e41083f383c9cd3db95de52564cc0b6452313d4a` (`main`, 2026-08-29): - `src/libImaging/JpegDecode.c:224-262` — `ImagingJpegDecode()`; `jpeg_start_decompress()` at :240, `jpeg_read_scanlines()` at :252, with no geometry check between them. - `src/decode.c:222-231` — `_setimage()`; `state->buffer = calloc(1, state->bytes)` with `state->bytes = (state->bits * state->xsize + 7) / 8`. - `src/PIL/JpegImagePlugin.py:428-464` — `JpegImageFile.draft()`; computes `s` and shrinks `_size`, the tile extents and `decoderconfig` with no notion of lossless coding. - `src/PIL/JpegImagePlugin.py:271` — `0xFFC3: ("SOF3", "Spatial lossless", SOF)`. ```c // src/libImaging/JpegDecode.c if (context->scale > 1) { context->cinfo.scale_num = 1; context->cinfo.scale_denom = context->scale; } ... case 2: if (!jpeg_start_decompress(&context->cinfo)) { break; } state->state++; /* fall through */ case 3: ok = 1; while (state->y < state->ysize) { ok = jpeg_read_scanlines(&context->cinfo, &state->buffer, 1); ``` Because `state->ysize` (`ceil(H/s)`) can never equal libjpeg's `output_height` when `s >= 2`, the decode always ends in an error after the rows have already been written: the caller sees an exception and gets no pixels back. The `OSError` is a consequence of the overflow, not the issue being reported. ## Primitive Per scanline: a contiguous write of `(W - ceil(W/s)) x c` bytes immediately past a `calloc(ceil(W/s) x c)` block, where `c = 1` for mode `L` and 4 for RGB (RGBX) and CMYK; repeated for `ceil(H/s)` rows over the same region. The buffer size class is chosen by the attacker through `W`, and for grayscale the overflowing content is an arbitrary attacker-chosen byte string. Measured in the reproducer below: 1200x1100 mode `L`, `s=2` — a 600-byte allocation, 600 bytes written past it, ASan reporting the whole 1200-byte row write as one `memcpy`. Threshold: `draft()` must select `s >= 2`, i.e. `W // requested_w >= 2` and `H // requested_h >= 2`. For `thumbnail((256,256))` with the default `reducing_gap=2.0` that means `W, H >= 1024`. ## Reproduction Self-contained Docker reproducer (Ubuntu 24.04, clang 20, libjpeg-turbo 3.1.4 and Pillow both built from source with `-fsanitize=address`; Pillow checked out at `${TARGET_COMMIT}`, default `e41083f383c9cd3db95de52564cc0b6452313d4a`). Save the three files below as `Dockerfile`, `make_sof3.py` and `trigger.py` in an empty directory, then: ```bash docker build -t pillow-sof3-oob . && docker run --rm pillow-sof3-oob ``` `make_sof3.py` is a self-contained encoder that emits a standards-compliant 8-bit grayscale lossless-Huffman JPEG (SOI / DHT / SOF3 `P=8, Y=1100, X=1200, Nf=1` / SOS `Ss=1` predictor 1, `Se=0`, `Ah=Al=0` / entropy-coded row differences / EOI), whose rows carry a repeating marker pattern. `trigger.py` opens it and calls `thumbnail()`, whose implicit `draft()` picks `s=2` and allocates a 600-byte row buffer. ### `Dockerfile` ```dockerfile FROM ubuntu:24.04 ENV DEBIAN_FRONTEND=noninteractive ARG TARGET_COMMIT=e41083f383c9cd3db95de52564cc0b6452313d4a ARG CLANG_VERSION=20 ARG LIBJPEG_TURBO_TAG=3.1.4 RUN apt-get update && apt-get install -y --no-install-recommends \ ca-certificates git make cmake nasm pkg-config libc6-dev gcc \ wget gnupg lsb-release software-properties-common \ python3 python3-dev python3-pip python3-venv zlib1g-dev \ && wget -qO /tmp/llvm.sh https://apt.llvm.org/llvm.sh && chmod +x /tmp/llvm.sh && /tmp/llvm.sh ${CLANG_VERSION} \

References

Community reviews

Loading reviews…