Jul 21, 2026·8 min read·16 visits
A calculation mismatch in Pillow's TGA RLE encoder allows remote attackers to leak up to 57 KB of process heap memory by saving 1-bit images with RLE compression.
CVE-2026-59198 is a high-severity heap out-of-bounds read vulnerability in Pillow, the Python imaging library, affecting versions from 5.2.0 up to 12.3.0. The vulnerability occurs in the TGA RLE compression path due to a calculation mismatch between the allocated packed 1-bit row buffer and the byte-level pixel stride assumption in the C-level encoder. This mismatch allows an attacker to leak up to approximately 57 KB of adjacent process heap memory directly into generated TGA image files.
The affected component is Pillow's TGA (Truevision Graphics Adapter) image plugin, which manages decoding and encoding operations for TGA-formatted files. Specifically, the vulnerability resides within the Run-Length Encoded (RLE) compression routine. Under standard conditions, applications invoke Pillow to process, transform, and export images, exposing the internal C-based codec libraries to user-supplied input parameters.
The TGA format supports multiple image modes, including 1-bit monochrome (represented as mode "1" in Pillow). When an application saves a mode "1" image using TGA format and specifies RLE compression, control flows from the Python wrapper src/PIL/TgaImagePlugin.py down to the underlying C-extension layer located in src/libImaging/TgaRleEncode.c via the encode.c interface.
The attack surface exists in any application workflow where user-supplied or user-controlled images are saved as TGA files with RLE compression enabled. Since the library fails to validate the compatibility of the bit-packed representation of mode "1" images with the byte-stride assumptions of the RLE encoder, memory layout discrepancies occur during serialization. This leads to a heap-based out-of-bounds read vulnerability, which serializes adjacent process memory directly into the generated output file.
The root cause of CVE-2026-59198 is a mathematical discrepancy between how Pillow structures bit-packed 1-bit monochrome images in memory and how the TGA RLE encoder reads individual pixel values. In a mode "1" image, Pillow packs 8 monochrome pixels into a single byte of memory to optimize storage efficiency. The initial row buffer allocation size is computed in the C-level encoding controller using the calculation Allocation Size = ((state->bits * state->xsize) + 7) / 8.
When saving a TGA image with a maximum width of 65,535 pixels, the allocation formula evaluates to ((1 * 65535) + 7) / 8 = 8192 bytes. This represents the precise buffer size allocated on the heap to contain the packed 1-bit image data.
However, when RLE compression is enabled, execution transitions to ImagingTgaRleEncode within src/libImaging/TgaRleEncode.c. To iterate through the pixels, the encoder calculates the individual pixel stride using the formula bytesPerPixel = ((state->bits + 7) / 8). Because state->bits is 1 for mode "1" images, integer division rounds this calculation up to 1. Consequently, the encoder assumes that each pixel occupies a full byte rather than a single bit. During iteration, the encoder walks the buffer using an offset of x * bytesPerPixel where x runs up to the full image width of 65,535. This causes the encoder to read up to 65,535 bytes from an 8,192-byte buffer, leading to an out-of-bounds read of approximately 57,343 bytes of adjacent heap memory.
To understand the flaw and the subsequent correction, examine the vulnerable logical path in the TGA saving routine. The following diagram illustrates the workflow leading to the out-of-bounds read:
The implementation patch blocks the vulnerability at the Python interface level in src/PIL/TgaImagePlugin.py. The patch intercepts the execution path prior to reaching the vulnerable C-extension layer by raising an OSError if a mode "1" image is saved with TGA RLE compression.
Below is the comparison of the vulnerable Python wrapper code versus the patched version:
# === VULNERABLE CODE (Pre-Patch) ===
def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
# ... configuration and header parsing ...
compression = im.encoderinfo.get("compression", im.info.get("compression"))
rle = compression == "tga_rle"
if rle:
imagetype += 8 # Execution proceeds to C-level RLE encoder with packed bit-buffer
# === PATCHED CODE (Post-Patch) ===
def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
# ... configuration and header parsing ...
compression = im.encoderinfo.get("compression", im.info.get("compression"))
rle = compression == "tga_rle"
if rle:
if im.mode == "1":
# Prevent the creation of incompatible modes with C-level RLE encoder
msg = f"cannot write mode {im.mode} as TGA with run-length encoding"
raise OSError(msg)
imagetype += 8While this wrapper-level validation successfully prevents exploitation via standard Pillow APIs, it is technically an indirect mitigation. The underlying C function ImagingTgaRleEncode remains unaltered. If external extensions bypass the Python plugin layer and interact directly with the C API using custom packed structures, the underlying memory access discrepancy may still occur. Developers using custom C wrappers for Pillow components must verify that packed images are normalized to 8-bit representation prior to invoking TGA compression.
Exploiting CVE-2026-59198 does not require specialized authentication or local privileges but is constrained by the application's implementation details. The threat actor must target an application that exposes image uploading and conversion services, allowing the user to dictate the output file format or force TGA RLE processing. The system must also process binary or monochrome images (mode "1") without automatic color-space translation.
An attack scenario proceeds through the following sequential steps:
The vulnerability behaves reliably because the C encoder uses deterministic loop bounds. The primary limitation to exploitation is the memory layout adjacent to the image row buffer. If the adjacent memory is unmapped or falls outside the process space, the read will trigger a segmentation fault, causing a denial of service. If the adjacent memory contains active heap data, the data is successfully exfiltrated in the exported file.
The primary security consequence of CVE-2026-59198 is unauthorized information disclosure via heap memory exfiltration. Because the heap stores temporary variables, configuration parameters, and buffers from other active application threads, the leaked 57 KB of data may contain sensitive credentials, active session tokens, application keys, or database records. In multi-tenant environments, this leakage could allow one user to intercept the transaction details or credentials of another user processing data concurrently.
Additionally, the leak of heap pointers and internal structural references can bypass Address Space Layout Randomization (ASLR). This provides attackers with the necessary memory layout details to construct subsequent memory corruption exploits. The CVSS v3.1 vector is rated as CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:N/A:L with a base score of 6.5. The attack complexity is rated high because it depends on the target application executing a specific image processing flow with specific format parameters.
According to the Exploit Prediction Scoring System (EPSS), the probability of active exploitation in the wild remains low at 0.31% (percentile 23.42%). The vulnerability is not listed in CISA's Known Exploited Vulnerabilities (KEV) catalog, and there are no reports of active exploitation in ransomware campaigns. The overall exploit maturity is limited to proof-of-concept testing.
The recommended remediation strategy is upgrading the Pillow installation to version 12.3.0 or higher. This version implements strict input verification at the wrapper layer, raising an OSError and preventing execution from reaching the vulnerable C-based TGA RLE encoder.
In scenarios where immediate package upgrades are unfeasible due to dependency pinning or production freeze windows, developers must implement application-level input sanitization. The following code snippet demonstrates how to intercept and reject vulnerable configurations before saving:
def secure_save(image, output_path, **kwargs):
# Replicate the patch logic to block the vulnerable path
format_type = kwargs.get("format", "").upper()
compression_type = kwargs.get("compression", "")
if format_type == "TGA" and compression_type == "tga_rle":
if image.mode == "1":
raise ValueError("Incompatible saving configuration blocked to prevent security risks")
image.save(output_path, **kwargs)Additionally, security teams should adhere to sandboxing principles for high-risk parsing code. Executing image processing operations within temporary, short-lived containers or restricted namespaces limits the impact of potential information leaks. Restricting the list of permitted image formats and modes via white-lists during file ingest also significantly reduces the available attack surface.
CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:N/A:L| Product | Affected Versions | Fixed Version |
|---|---|---|
Pillow python-pillow | >= 5.2.0, < 12.3.0 | 12.3.0 |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-125 |
| Attack Vector | Network |
| CVSS v3.1 | 6.5 (Medium) |
| EPSS Score | 0.00313 |
| Impact | Information Disclosure (Heap Leak up to ~57 KB) |
| Exploit Status | Proof-of-Concept |
| CISA KEV Status | Not Listed |
The software reads data past the end, or before the beginning, of the intended buffer.
A path traversal vulnerability in the optional dashboard server of atomic-agents-stack before version 1.1.0 allows unauthenticated remote attackers to read arbitrary files from the host filesystem.
CVE-2026-9318 is a stored cross-site scripting (XSS) vulnerability affecting Jazzband tablib versions prior to 3.10.0. The flaw is located in the HTML export functionality of multi-sheet Databook objects. Due to raw f-string interpolation, unsanitized sheet titles containing malicious script tags are rendered directly as HTML, allowing arbitrary client-side code execution in a victim's browser.
CVE-2026-54917 is a critical path traversal and authorization bypass vulnerability affecting the S3 and Iceberg REST catalog gateways in SeaweedFS. By explicitly disabling canonical path cleaning in the gorilla/mux routing system, relative path segments such as '..' are allowed to bypass routing constraints and access control checks. When these paths are collapsed server-side by the backend filer, they resolve to folders outside the authorized bucket boundary, allowing unauthorized cross-bucket access.
An out-of-bounds read vulnerability in the SCTP SACK chunk parser of SIPSorcery leads to Denial of Service (DoS) or silent internal state corruption due to lack of boundary validation on incoming chunk elements.
An uncaught exception vulnerability exists in SIPSorcery's TurnServer component, where unauthenticated malformed UDP packets can crash the core UDP receive loop, resulting in a persistent Denial of Service.
A critical use-after-free vulnerability in Microsoft QUIC allows unauthenticated remote attackers to disclose sensitive system memory over the network. The vulnerability is caused by a race condition during rapid connection termination and asynchronous packet retransmission.