Jul 21, 2026·8 min read·3 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.
CVE-2026-50525 is a high-severity Denial of Service (DoS) vulnerability in the Microsoft .NET XML Cryptography stack. The vulnerability resides in the `System.Security.Cryptography.Xml` library, specifically within the `EncryptedXml` processing engine. Unauthenticated remote attackers can exploit this flaw by sending specifically crafted XML documents containing nested or recursive structures, or utilizing resource-intensive transforms. Processing such payloads leads to infinite CPU loops, stack exhaustion, or memory starvation, resulting in application termination.
An improper encoding and escaping vulnerability in the .NET SMTP client component allows network-based attackers to perform SMTP command smuggling and email spoofing by injecting control characters into email fields.
CVE-2026-50651 is a high-severity Denial of Service vulnerability in Microsoft .NET runtimes, SDKs, and Visual Studio installations. It stems from a weakness in System.Net.Http (CWE-770), where the HTTP/2 connection handling state machine fails to throttle server-initiated protocol streams and control frames. An attacker-controlled server can exploit this by returning highly fragmented or infinite control and continuation frame sequences. This forces the client to allocate memory indefinitely on the managed heap, eventually provoking an unhandled Out-of-Memory (OOM) exception and application crash.
A heap out-of-bounds write vulnerability exists in Pillow prior to version 12.3.0 due to an integer overflow during image expansion calculations. The subsequent patch introduced a division-by-zero regression causing denial of service.
A critical signed 32-bit integer overflow vulnerability was identified in Pillow (Python Imaging Library) versions prior to 12.3.0. The vulnerability resides within the native C extension library (libImaging) during coordinate and bounding box calculations in functions like ImagingPaste and ImagingFill2. Exploitation can bypass bounds and clipping safety checks, leading to a controlled heap backward underwrite and application crash.
CVE-2026-59200 is a high-severity uncontrolled resource consumption vulnerability in the Pillow Python Imaging Library. The flaw resides in the PDF stream decoder, allowing remote, unauthenticated attackers to trigger host out-of-memory crashes by submitting malicious PDF decompression bombs.