Jul 23, 2026·7 min read·69 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.
A path traversal vulnerability (CWE-22) in Contao CMS allows unauthenticated remote attackers to bypass directory boundary restrictions in ImagesController and access files within the project directory.
Contao Open Source CMS versions 4.0.0 through 5.3.49 and 5.4.0-RC1 through 5.7.11 contain a Cross-Site Request Forgery (CSRF) vulnerability in backend parameter handling. The `RequestTokenListener` component validates anti-CSRF tokens solely for HTTP POST requests, while GET-based declarative guards run only when an `act` parameter is present in the query string. Consequently, custom backend actions dispatched via alternative parameters such as `key=` can execute without CSRF token verification when triggered by an authenticated user.
Contao CMS versions 4.1.0 through 5.3.49 and 5.4.0-RC1 through 5.7.11 fail to validate form submission tokens and enforce rate limiting when processing activation email resend requests via HTTP POST, enabling resource exhaustion and account state enumeration.
An information disclosure vulnerability in Contao CMS allows unauthenticated site visitors to view protected page titles, URLs, and text excerpts through search queries when protected page indexing is disabled after previously being enabled.
In Vikunja prior to version 2.6.0, relation creation via the CalDAV endpoint fails to invoke the TaskRelation.CanCreate authorization check. This missing access control allows an authenticated user to establish unauthorized relationships and perform write operations against any task, provided its unique identifier (UID) is known.
A cross-project information disclosure vulnerability in Vikunja allows authenticated users with read access to one project to view private task details from unauthorized projects via subtask expansion parameters.