GHSA-5gjj-6r7v-ph3x pillow-heif: Integer Overflow in Encode Path Buffer Validation Leads to Heap Out-of-Bounds Read
SummaryAn 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.DetailsThe 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: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 bypassedAfter the check is bypassed, the subsequent loop reads beyond the input buffer:Additionally, `real_stride = width * n_channels` (e.g., line 148: `real_stride = width * 3`) is also computed as `int * int`, which can independently overflow for large `width` values.This vulnerability exists in the **encode path**, which is distinct from the decode path:The decode path is partially guarded by libheif's built-in security limitsThe encode path has **no such guards** — `DISABLE_SECURITY_LIMITS` is irrelevantThe encode path is reachable whenever an application calls `pillow_heif.encode()` or saves an image via Pillow with `format="HEIF"` / `format="AVIF"`**Affected functions** (all in `_pillow_heif.c`):`_CtxWriteImage_add_plane()` — line 158`_CtxWriteImage_add_plane_la()` — line 344`_CtxWriteImage_add_plane_l()` — line 449**CWE**: CWE-190 (Integer Overflow or Wraparound) → CWE-125 (Out-of-bounds Read)PoCPrerequisitesFor ASAN confirmation:Test 1: Crash without ASAN (process killed by SIGSEGV/SIGBUS)Without ASAN, this crashes the process with **exit code 138 (SIGBUS)** or **139 (SIGSEGV)**.Test 2: Explicit stride — small image, immediate crashWithout ASAN, this crashes with **exit code 139 (SIGSEGV)**.ASAN confirmationWith an ASAN-enabled build of pillow-heif 1.2.1 on macOS (Apple Clang 17, arm64, Python 3.14), Test 1 produces: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/AVIFImage 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 dataPython 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:**Prior art**:CVE-2024-5197 (libvpx): integer overflow in `vpx_img_alloc()`, CVSS 7.5CVE-2024-5171 (libaom): integer overflow in `aom_img_alloc()`, CVSS 9.8
CVSS v4.0 score: 5.5