Jul 23, 2026·7 min read·4 visits
A denial-of-service vulnerability in the pypdf library prior to version 6.14.2 allows remote attackers to trigger 100% CPU exhaustion. By uploading a crafted PDF containing a non-terminated inline image using ASCII85 or ASCIIHex filters, the library enters an infinite loop during parsing operations.
An infinite loop vulnerability exists in the pypdf library prior to version 6.14.2 when processing malformed PDF inline images that utilize ASCII85 or ASCIIHex decoders. Attackers can exploit this vulnerability by submitting crafted PDF files containing incomplete streams, causing the parsing thread to consume 100% CPU resource indefinitely and leading to Denial of Service.
The pure-Python PDF manipulation library pypdf is widely integrated into web applications, server-side processing pipelines, and document-parsing services to extract text, manage metadata, and modify PDF pages. A critical vulnerability, tracked as CVE-2026-59935 (GHSA-g867-7843-wf8q), exposes these systems to remote Denial of Service (DoS) attacks. The vulnerability resides specifically within the parsing modules responsible for handling inline image streams inside PDF page content streams.
An inline image block in a PDF file is embedded directly within a page's content stream, surrounded by specialized operators. The parser isolates the image properties and processes the raw stream using designated filter decoders, such as /ASCIIHexDecode (abbreviated /AHx) or /ASCII85Decode (abbreviated /A85). When parsing libraries process user-provided documents, they assume structural conformity, specifically the presence of designated End-Of-Data (EOD) delimiters within these encoded streams.
Because pypdf is implemented in pure Python, any execution thread that enters an infinite loop will lock up that execution context completely. In multi-threaded or synchronous single-process web workers, a single malicious PDF payload can exhaust the application server's CPU allocation, leading to unresponsive API endpoints and total system unavailability. The attack surface is broad, as any feature that extracts text, loads images, or rewires pages is vulnerable if it processes untrusted PDF files.
The root cause of this vulnerability lies in the stream reader loops within the extract_inline__ascii_hex_decode and extract_inline__ascii85_decode functions defined in pypdf/generic/_image_inline.py. When decoding inline image streams, the parser reads data block-by-block using a buffer of a predetermined size until it identifies the corresponding End-Of-Data (EOD) marker, which is > for ASCIIHex or ~> for ASCII85.
If an attacker generates a PDF content stream where the EOD marker is deliberately omitted, the parser eventually consumes the entirety of the stream and reaches the End-Of-File (EOF) state. At this point, the core read operations within the loop, structured as stream.read(BUFFER_SIZE), begin to return empty byte sequences (b""). In normal operations, an empty buffer triggers an EOF validation check that throws a PdfReadError and exits the processing logic safely.
However, the vulnerable parsing logic combines the bulk stream read with a preliminary execution of read_non_whitespace(stream). If the malformed stream ends with one or more trailing non-whitespace characters (such as dangling ASCII noise), read_non_whitespace reads and returns this single byte. Consequently, the variable data_buffered becomes a non-empty sequence, bypassing the validation check if not data_buffered:. Since the buffer contains data but lacks the expected EOD or End-Image (EI) sequence, the loop executes a stream rewind using stream.seek(-2, 1) to handle possible split boundaries. Because the stream position is rewound by two bytes from the absolute end-of-file, the next iteration reads the exact same trailing byte, encounters EOF on the bulk read, bypasses the check, and seeks backward again. This cycle repeats indefinitely, consuming 100% of the CPU core resources.
The control flow failure can be traced to how the data buffer was structured prior to version 6.14.2. Below is the vulnerable stream reading logic from pypdf/generic/_image_inline.py:
# Vulnerable implementation in pypdf/generic/_image_inline.py
while True:
# read_non_whitespace extracts trailing data; stream.read() returns empty bytes at EOF
data_buffered = read_non_whitespace(stream) + stream.read(BUFFER_SIZE)
if not data_buffered:
# This check is bypassed because data_buffered is non-empty due to the trailing character
raise PdfReadError("Unexpected end of stream")
pos_tok = data_buffered.find(b">")
# ... processing tokens ...
if len(data_buffered) == 2:
data_out += data_buffered
raise PdfReadError("Unexpected end of stream")
# The parser appends slice and seeks backward, creating a permanent pointer loop
data_out += data_buffered[:-2]
stream.seek(-2, 1) In version 6.14.2, the fix introduces an explicit validation of the bulk stream read status using a Python assignment expression (the walrus operator :=). If stream.read(BUFFER_SIZE) yields an empty sequence, the parser detects that no new data has been read and immediately triggers a termination:
# Patched implementation in pypdf/generic/_image_inline.py
while True:
# Capture bulk stream read explicitly in read_bytes
data_buffered = read_non_whitespace(stream) + (read_bytes := stream.read(BUFFER_SIZE))
# Exit immediately if either no data exists or no new bytes are returned from stream.read
if not data_buffered or not read_bytes:
raise PdfReadError("Unexpected end of stream.")
pos_tok = data_buffered.find(b">")
# ... downstream processing ...
if len(data_buffered) == 2:
data_out += data_buffered
raise PdfReadError("Unexpected end of stream.")
data_out += data_buffered[:-2]
stream.seek(-2, 1)This control flow can be visualized using the following flow diagram, showing how the loop gets trapped under the vulnerable conditions versus how it terminates safely after the patch:
To exploit this vulnerability, an attacker must construct a PDF file containing a malformed inline image within a page content stream. The vulnerability does not require authentication or specific administrative privileges. The target application simply needs to parse the uploaded document programmatically.
The attack vector involves injecting an inline image dictionary that declares the use of /ASCII85Decode or /ASCIIHexDecode filters, but whose image data section abruptly terminates at EOF without the necessary termination sequence. For an ASCII85-encoded object, this means omitting the ~> sequence. The following content structure showcases a raw malformed block:
BI
/W 16
/H 16
/BPC 8
/CS /RGB
/F [/A85]
ID
Gar8O(o6*i%*56~
e L
e L9/ LL9/ LWhen a vulnerable pypdf instance processes this object via common text extraction routines or metadata collection runs, the execution flow enters the unpatched decoding loops. The CPU core execution thread immediately spikes to 100% capacity. If multiple concurrent malformed documents are uploaded, they can consume all available processor cores within seconds, creating a total application freeze.
The concrete security impact of CVE-2026-59935 is restricted to the availability domain, classified as a high-severity Denial of Service (DoS) flaw. There is no direct mechanism for arbitrary code execution (RCE) or sensitive data extraction within this vulnerability. However, the operational consequences of thread exhaustion are severe for enterprise web architectures.
In typical web architectures, multi-worker servers (e.g., Gunicorn or uWSGI processes) handle synchronous parsing tasks. When an unauthenticated attacker transmits several concurrent malformed PDF streams, they can block all active worker threads. This halts processing for all legitimate incoming traffic, presenting as an unresolvable server-side hang that requires administrative intervention to kill the looping processes.
The vulnerability is assessed with a CVSS v4.0 Base Score of 8.7 (High), reflecting an attack path requiring zero privileges and zero user interaction. The EPSS score is currently recorded at 0.00352 (0.352% probability of active exploitation in a 30-day window), and the vulnerability is not listed in the CISA KEV catalog.
The definitive remediation for CVE-2026-59935 is upgrading the pypdf dependency to version 6.14.2 or higher, which contains the complete EOF check patch. Organizations using older major versions are highly advised to pull the latest packages via standard package management utilities.
In environments where upgrading dependencies requires extensive verification cycles, defensive mitigations can be deployed at the application layer. One effective pattern is to segregate intensive parsing workloads into isolated, non-blocking worker pools (such as Celery or Redis Queue) configured with strict runtime limits. Setting a hard timeout (e.g., 5 seconds) on processing tasks guarantees that any thread trapped in an infinite loop is killed before causing host-wide exhaustion.
Additionally, implementing input validations or deploying Web Application Firewalls (WAF) to inspect multi-part file uploads for anomalous PDF stream properties can help block malformed payloads. However, these checks should be treated as temporary measures until the library can be updated to a patched version.
CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:H/SC:N/SI:N/SA:N| Product | Affected Versions | Fixed Version |
|---|---|---|
pypdf pypdf_project | < 6.14.2 | 6.14.2 |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-835 |
| Attack Vector | Network |
| CVSS v4.0 Score | 8.7 |
| EPSS Score | 0.00352 (0.352%) |
| Impact | Denial of Service (Availability) |
| Exploit Status | Proof of Concept (PoC) available |
| KEV Status | Not Listed |
The program contains a loop that can never exit, or that can only exit under extremely rare conditions, due to a bug or an unexpected state.
An infinite loop vulnerability exists in the pure-python PDF library pypdf prior to version 6.14.1. This vulnerability occurs during the processing of malformed or truncated page content streams containing inline images. Specifically, the parser fails to validate the End-of-Stream condition during inline image dictionary parsing. This results in continuous backward stream-seeking and an infinite loop that consumes 100% CPU on the processing thread.
A critical CPU exhaustion vulnerability exists in the pypdf library before version 6.14.0. When parsing PDF files with malformed or corrupt standard cross-reference (xref) table entries, the library falls back to sequentially scanning the entire file buffer via regular expression search. An attacker can exploit this algorithmic bottleneck by supplying a crafted PDF with numerous malformed xref entries, leading to denial of service.
In Auth.js (specifically next-auth version 5), server-side configuration errors or backend exceptions can cause existence-based authentication and authorization checks to fail open. When the underlying core engine (@auth/core) encounters a configuration error or runtime exception, it returns an HTTP 500 Internal Server Error response containing a JSON-formatted error description payload. Prior to the fix, the next-auth wrappers read this response body using response.json() without validating the HTTP status code. Because any parsed JavaScript object evaluates to truthy, common existence-based checks incorrectly evaluated to true, letting unauthenticated requests pass.
A host/authority desynchronization vulnerability exists in Eclipse Jetty's handling of HTTP/2 and HTTP/3 requests. When both an ':authority' pseudo-header and a standard 'Host' header are present in a request, Jetty fails to validate that they represent the same target host. This desynchronization creates a host-confusion or 'split-brain' state, enabling attackers to bypass access control lists, escape virtual host isolation, poison web caches, or manipulate redirect targets in backend applications.
A path traversal vulnerability exists in Eclipse Jetty due to a state-desynchronization defect in the URI parsing state machine inside URIUtil.java. This defect allows unauthenticated remote attackers to bypass path-based security constraints enforced by downstream filters, application gateways, or authorization modules by crafting URIs with path parameter delimiters and parent directory traversal sequences.
An improper access control vulnerability in JupyterLab allows programmatic installation of blocked or non-allowed extensions. The vulnerability is caused by a missing await keyword when invoking the asynchronous checking method, leading Python to evaluate the returned coroutine object as truthy. Furthermore, the check lacks proper canonicalization of package names, enabling an attacker to bypass blocklists using casing or character variants.