KEVWire

Security intelligence, with context.

Response snapshot Oct 26, 2026, 21:56 UTC

Back to the wire
Permalink
VulnerabilityCVE-2026-112398

Pillow: Heap buffer overflow in FlashPix (FPX) JPEG decoding leads to code execution

**Researcher**: Alex Bouffard (mixbo@pebwalker.com) A heap buffer overflow in Pillow's FlashPix (FPX) JPEG-tile decoder lets an attacker achieve arbitrary code execution by getting an application to open a single crafted file with `Image.open()`.

At a glance

Exploited
Not known
Severity
9.2 Critical (CVSS 4.0)
Chance of exploitation
0.6% in the next 30 days
Sources
2 independent sources

What to do

GitHub lists first patched versions: pillow: 13.0.0. Confirm the affected range and upgrade guidance in the exact advisory.

Read GitHub-reviewed advisory GHSA-5x8w-8jj5-q52h (opens a new tab)

What happened

### Credits

**Researcher**: Alex Bouffard (mixbo@pebwalker.com)

### Summary

A heap buffer overflow in Pillow's FlashPix (FPX) JPEG-tile decoder lets an attacker achieve arbitrary code execution by getting an application to open a single crafted file with `Image.open()`. The file is recognized by content (the OLE2 magic), not by extension, so any service that opens or thumbnails untrusted uploads with the optional `olefile` dependency installed is reachable with no user interaction.

Continue reading

In the PoC below, opening one crafted file executes an attacker-chosen command on stock Pillow 12.2.0 (x86_64 Ubuntu 24.04, glibc 2.39).

### Details

`FpxImagePlugin` decodes FlashPix (FPX) files, which are OLE2/CFBF compound documents.

`Image.open()` auto-detects them from the OLE2 magic `D0CF11E0...` whenever the optional `olefile` package is installed (Pillow's documented dependency for FPX/MIC support), regardless of the file's extension.

For a JPEG-compressed subimage (`compression == 2`), the plugin builds a `"jpeg"` decode tile and derives the `(rawmode, jpegmode)` pair from attacker-controlled FPX color IDs through the `MODES` table ([`src/PIL/FpxImagePlugin.py` L25-L32](https://github.com/python-pillow/Pillow/blob/12.2.0/src/PIL/FpxImagePlugin.py#L25-L32)):

```python MODES = { ... (0x00018000, 0x00017FFE): ("RGBA", "LA"), # rawmode "LA" (2 bytes/pixel) ... (0x00020000, 0x00020001, 0x00020002): ("RGB", "YCC;P"), # rawmode "YCC;P" } ```

The Imaging output buffer for the tile is sized for the rawmode (here `"LA"` = 2 bytes/pixel) ([`src/decode.c` L223-L226](https://github.com/python-pillow/Pillow/blob/12.2.0/src/decode.c#L223-L226)):

```c state->bytes = (state->bits * state->xsize + 7) / 8; // "LA" gives 2 * xsize bytes/row ... state->buffer = (UINT8 *)calloc(1, state->bytes); ```

In `JpegDecode.c` the rawmode string is mapped to a libjpeg `out_color_space`. Recognized modes (`CMYK`, `YCbCr`, `YCbCrK`, and so on) get a concrete colorspace, but `"LA"` and `"YCC;P"` are not recognized, so control reaches the fallback branch ([`src/libImaging/JpegDecode.c` L219-L221](https://github.com/python-pillow/Pillow/blob/12.2.0/src/libImaging/JpegDecode.c#L219-L221)):

```c /* Disable decoder conversions. */ context->cinfo.jpeg_color_space = JCS_UNKNOWN; context->cinfo.out_color_space = JCS_UNKNOWN; ```

Under `JCS_UNKNOWN`, libjpeg performs no color conversion and emits `cinfo.num_components` bytes per pixel, taken from the embedded JPEG's SOF marker, which the attacker sets to 4. So `jpeg_read_scanlines` writes `4 * width` bytes per row into a buffer allocated for only `2 * width` bytes ([`src/libImaging/JpegDecode.c` L252](https://github.com/python-pillow/Pillow/blob/12.2.0/src/libImaging/JpegDecode.c#L252)):

```c ok = jpeg_read_scanlines(&context->cinfo, &state->buffer, 1); // writes 4*W into a 2*W buffer ```

This is a heap out-of-bounds write with attacker-controlled length (the image width, up to tens of thousands of pixels, so about `2 * width` bytes of overflow per row) and attacker-controlled content (the decoded pixel data).

The PoC weaponizes it: the overflow overwrites an adjacent Python `bytes` object's `ob_type` with a pointer to a fake `PyTypeObject` / `PyBufferProcs` vtable that the same overflow plants inside the decode buffer.

When Pillow releases the input buffer, `PyBuffer_Release` walks `ob_type -> tp_as_buffer -> bf_releasebuffer` and executes `call *rax`; the PoC points that at `system@plt` (fixed, the interpreter binary is non-PIE) with `rdi` set to the command string, yielding command execution.

### PoC

To facilitate an exact reproduction on an x86_64 machine, this PoC uses the version-pinned, ASLR-disabled Docker image from [Annex A](#annex-a-docker-setup).

The source code for the payload builder `make_payload.py` is in [Annex B](#annex-b-payload-builder-source).

`vulnerable.py` is the program under test, a one-liner that opens the file it is given:

```python #!/usr/bin/env python3 # The entire vulnerable operation an application performs on an untrusted file. import sys from PIL import Image

Image.open(sys.argv[1]).load() ```

The payload is a standalone `.fpx` file the attacker crafts on their own host; the target only ever opens the resulting file, so building it requires no access to or code execution on the target. We run `make_payload.py` inside the same container here purely for convenience, so its baked-in addresses (`--offset`, `system@plt`) match this pinned image.

The payload redirects the hijacked indirect call to `system@plt` (`0x4206c0`, fixed because the interpreter binary is non-PIE) with a baked-in command:

```console $ python3 make_payload.py --target 0x4206c0 --cmd id --out shell.fpx [control] shell.fpx: S=0xdc8800 target=0x4206c0 cmd='id' craft_iters=0 bad=0 [control] opening it: vtable hijack -> call *0x4206c0 with rdi -> 'id' ```

Then open it with `vulnerable.py`:

```console $ python3 vulnerable.py shell.fpx uid=0(root) gid=0(root) groups=0(root) corrupted size vs. prev_size ```

`Image.open()` of the crafted file runs the baked command via `system("id")`: command execution as the user running the image processing (root in the container).

The trailing `corrupted size vs. prev_size` is glibc aborting during interpreter teardown over the corrupted heap, after `id` has already run.

The builder writes the command inline at the hijacked `rdi`, so `--cmd` is limited to 7 bytes (for example `id`, `id;w`). `--offset` and `system@plt` are constants of the pinned, ASLR-disabled image and hold across runs of it.

### Impact

This is a heap-based buffer overflow (CWE-122) / out-of-bounds write (CWE-787) that leads to code execution. Any application or service that opens attacker-influenced files with Pillow and has the optional `olefile` dependency installed is affected: image upload and thumbnailing services, avatar and media processing, document and image conversion pipelines, and similar. Because detection is by OLE2 magic rather than file extension an

Affected package ranges reported by GitHub: pillow: <= 12.3.0

Scores and how they were measured

CVSS base severity

9.2

Version 4.0As of

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

Measurement source (opens a new tab)

How bad it is if exploited. Not a prediction of exploitation or proof you are exposed.

EPSS probability

0.6%

Scored Percentile 46.9%

Model and measurement source (opens a new tab)

A model's estimate of exploitation in the next 30 days, not certainty or your own risk.

Related technology

Sources

  1. Primary source

    GitHub-reviewed advisory GHSA-5x8w-8jj5-q52h (opens a new tab)

    The public advisory supplies the description, affected package/version ranges, and any listed CVSS metric. GitHub review is not local review.

    Published Checked

  2. Primary source

    GitHub public advisory API page (opens a new tab)

    The API page supplied GHSA-5x8w-8jj5-q52h, its actual identifiers, source dates, and structured metrics.

    Published Date not suppliedChecked

  3. Primary source

    NVD CVE-2026-112398 (opens a new tab)

    NVD record: description, CVSS (the CNA score is preferred), affected products, and publication date. Not evidence of exploitation.

    Published Checked

  4. Primary source

    NVD CVE API 2.0 (opens a new tab)

    The NVD API page that supplied this CVE record.

    Published Date not suppliedChecked

  5. Primary source

    FIRST measured EPSS score (opens a new tab)

    Measured EPSS probability and percentile for CVE-2026-112398, scored 2026-10-26. This is a model estimate, not proof of exposure.

    Published Checked

Cite this record

For factual claims, cite the primary sources above; cite KEVWire for its compilation, dates, and classifications.

APA

KEVWire. (2026, October 23). Pillow: Heap buffer overflow in FlashPix (FPX) JPEG decoding leads to code execution (CVE-2026-112398). https://kevwire.com/record/CVE-2026-112398

MLA

"Pillow: Heap buffer overflow in FlashPix (FPX) JPEG decoding leads to code execution (CVE-2026-112398)." KEVWire, 23 Oct. 2026, kevwire.com/record/CVE-2026-112398.

Chicago

KEVWire. "Pillow: Heap buffer overflow in FlashPix (FPX) JPEG decoding leads to code execution (CVE-2026-112398)." Last modified October 23, 2026. https://kevwire.com/record/CVE-2026-112398.

Evidence summary (plain text)

Pillow: Heap buffer overflow in FlashPix (FPX) JPEG decoding leads to code execution (Oct 23, 2026).
**Researcher**: Alex Bouffard (mixbo@pebwalker.com)

A heap buffer overflow in Pillow's FlashPix (FPX) JPEG-tile decoder lets an attacker achieve arbitrary code execution by getting an application to open a single crafted file with `Image.open()`. The file is recognized by content (the OLE2 magic), not by extension, so any service that opens or thumbnails untrusted uploads with the optional `olefile` dependency installed is reachable with no user interaction. In the PoC below, opening one crafted file executes an attacker-chosen command on stock Pillow 12.2.0 (x86_64 Ubuntu 24.04, glibc 2.39).
CVSS 9.2 (version 4.0; evidence as of Oct 23, 2026; https://github.com/advisories/GHSA-5x8w-8jj5-q52h).
EPSS 0.6% for the following 30 days, scored Oct 26, 2026 (https://api.first.org/data/v1/epss?cve=CVE-2026-112398&date=2026-10-26).
Sources:
GitHub-reviewed advisory GHSA-5x8w-8jj5-q52h: https://github.com/advisories/GHSA-5x8w-8jj5-q52h
GitHub public advisory API page: https://api.github.com/advisories?type=reviewed&per_page=100&sort=updated&direction=desc
NVD CVE-2026-112398: https://nvd.nist.gov/vuln/detail/CVE-2026-112398
NVD CVE API 2.0: https://services.nvd.nist.gov/rest/json/cves/2.0?pubStartDate=2026-10-12T13:35:55.142Z&pubEndDate=2026-10-26T13:35:55.142Z&resultsPerPage=500&startIndex=5500
FIRST measured EPSS score: https://api.first.org/data/v1/epss?cve=CVE-2026-112398&date=2026-10-26
Record ID: CVE-2026-112398.
Record origin: Ingested record.
AI evidence: Not assessed.
Known exploitation: Not assessed.
Zero-day status: Not assessed. KEV membership alone does not establish zero-day status.
About this data

This record was ingested from a data source. Retrieved: Oct 26, 2026, 19:40 UTC. Ingestion is not an independent editorial investigation.

A CISA KEV listing documents known exploitation, not necessarily a zero-day; absence from the catalog does not prove a vulnerability has never been exploited.

Technology tags indicate possible relevance, not verified exposure. Confirm the affected product, version, and configuration against your own inventory; KEVWire does not scan your systems.

A personal reading checklist on this device, not verified remediation.

Record JSON

Your account

Email alerts

Email alerts are not available yet. To follow your stack today, use its RSS feed, which honors your filters, or connect an AI agent through the API.

Suggest an improvement

Ideas, missing sources, corrections, and bugs are all welcome. No account needed. Suggestions are reviewed by a person before anything changes.

Record details

Record detail