Aug 7, 2026·7 min read·3 visits
Unconstrained CID font width expansion in pypdf leads to application hang and OOM crashes.
A Denial of Service (DoS) vulnerability exists in pypdf prior to version 6.15.0. When parsing maliciously crafted PDF files containing excessively large CID font width ranges, the library suffers from CPU starvation and memory exhaustion due to unconstrained loop expansion.
The library pypdf is a widely utilized open-source Python library for parsing, manipulating, and extracting content from PDF documents. It serves as a foundational component in numerous document-processing pipelines, search indexing systems, and data extraction backends. Because it processes complex, nested binary structures defined by untrusted external users, its parsing functions represent a significant attack surface.
This vulnerability, tracked as CVE-2026-71852, belongs to the class of CWE-834: Excessive Iteration. It resides specifically within the CID (Character Identifier) font width parser module, located in pypdf/_font.py. The flaw enables local or remote unauthenticated actors to trigger a denial of service condition.
The attack is executed by embedding a custom Font dictionary containing structurally anomalous CID widths. When the application attempts to process the font dictionaries during typical workflows like text extraction, the parser executes an unconstrained loop. This results in execution delays and memory allocation overhead.
The primary flaw stems from the implementation of the static method Font._collect_cid_character_widths() in pypdf/_font.py. This method is designed to parse the /W (Widths) array within a DescendantFont dictionary. According to the PDF specification, the /W array specifies character widths to facilitate accurate typographic rendering and text extraction.
The PDF specification defines two formats for width specifications. Format 1 provides a start character index followed by an array of individual glyph widths. Format 2 defines a starting index, an ending index, and a constant width that is applied uniformly across the entire range. In previous versions, the parser handled both formats by instantly expanding these ranges into a flat Python dictionary mapping each individual character index to its defined width.
The vulnerability is triggered because the parsing logic failed to validate the distance between the starting and ending indices. When encountering a Format 2 entry such as [ 1 200000000 120 ], the parser initializes a generator expression via the Python range() constructor spanning from 1 to 200,000,000. It then processes this range within a dictionary comprehension to populate the internal mapping. This design assumes all inputs are structurally valid and conform to reasonable bounds.
Due to the internal implementation of Python dictionary objects, which use hash tables, allocating millions of keys instantly consumes significant memory resources. The host operating system running the document processing service experiences memory exhaustion, resulting in an OOM termination. If the system has sufficient swap space to prevent an immediate crash, the CPU becomes completely occupied by the iteration, resulting in resource starvation.
The vulnerability was remediated in the py-pdf/pypdf repository via commit 51cb6acf9e8a35b77e90b4d87d28fe3e1416d7d7. The changes isolate the unconstrained range expansion by implementing defensive thresholds based on the limits of standard typography.
Below is a comparison highlighting the critical code paths before and after the remediation was applied.
The following block shows the unvalidated dictionary updates for both format types:
# Format 1: Individual character widths mapping
# If len(width_list) is unchecked, it can trigger high loop counts.
current_widths.update(
{
chr(_cidx): _width
for _cidx, _width in zip(
range(
cast(int, start_idx),
cast(int, start_idx) + len(width_list),
1,
),
width_list,
)
}
)
# Format 2: Constant width range mapping
# High stop_idx values lead to excessive iteration.
current_widths.update(
{
chr(_cidx): const_width
for _cidx in range(
cast(int, start_idx), cast(int, stop_idx + 1), 1
)
}
)The patch defines strict structural limits to restrict range bounds to standard typographic constraints:
# Global constraints added to pypdf/_font.py
MAX_CID_WIDTH_ENTRY_COUNT = 65_536 # Standard limit for 16-bit CID spaces
MAX_WIDTH_ENTRY_COUNT = 100_000
@staticmethod
def __check_range_length(start: int, end: int) -> None:
if end < start:
raise LimitReachedError(
f"Invalid CID width range: {start}..{end}."
)
count = end - start
if count > MAX_CID_WIDTH_ENTRY_COUNT:
raise LimitReachedError(f"CID width range too large: {count} > {MAX_CID_WIDTH_ENTRY_COUNT}.")
@staticmethod
def __check_entry_count(count: int) -> None:
if count > MAX_WIDTH_ENTRY_COUNT:
raise LimitReachedError(f"Too many character widths: {count} > {MAX_WIDTH_ENTRY_COUNT}.")During execution, these functions assert limits prior to dictionary generation:
# Format 1 with range validation
start_idx, width_list = int(w_entry), w_next_entry
stop_idx = start_idx + len(width_list)
Font.__check_range_length(start_idx, stop_idx)
entry_count += (stop_idx - start_idx)
Font.__check_entry_count(entry_count)
# Format 2 with range validation
start_idx, stop_idx, const_width = (
int(w_entry),
int(w_next_entry),
_w[idx + 2].get_object(),
)
Font.__check_range_length(start_idx, stop_idx + 1)
entry_count += (stop_idx - start_idx + 1)
Font.__check_entry_count(entry_count)By asserting these limits, the software prevents the execution of large loops and blocks memory allocations that exceed predefined structural norms.
Exploitation of CVE-2026-71852 relies on creating a structural PDF file containing abnormal CID metrics within a descendant font object. Since the library processes font resources automatically when analyzing PDF pages, an attacker does not require special administrative access.
The attack path is visualized in the diagram below:
To configure a test case, an attacker creates a DescendantFont dictionary containing a /W array containing a large index span:
3 0 obj
<<
/Type /Font
/Subtype /CIDFontType2
/BaseFont /CustomCIDFont
/CIDSystemInfo << /Registry (Adobe) /Ordering (Identity) /Supplement 0 >>
/W [ 0 100000000 1000 ]
>>
endobjWhen this file is parsed via PdfReader('malicious.pdf') and any method attempting to parse page text is called, the library executes _collect_cid_character_widths(). The processing loop triggers CPU utilization and memory expansion, crashing the active process.
The primary impact of CVE-2026-71852 is localized Denial of Service (DoS). When a vulnerable application processes a crafted document, the Python engine allocates memory dynamically to accommodate the generated dictionary. This quickly exhausts system memory, triggering operating system-level process termination via the Out-of-Memory (OOM) killer.
In environments where auto-restart mechanisms are not configured, this termination results in a persistent denial of service state. For backend systems processing documents asynchronously (e.g., through a queue), the crash of worker processes can stall the entire pipeline, as the offending document is repeatedly picked up and re-processed.
The vulnerability is rated at a CVSS score of 4.8. Although the attack vector is local, the actual mechanism of delivery is frequently remote, such as uploading a file to a web application. Consequently, document processing utilities facing public-facing endpoints represent the primary area of exposure.
The primary remediation strategy is upgrading the pypdf dependency to version 6.15.0 or higher. The maintainers resolved the issue by introducing the input size and bounds checks documented in commit 51cb6acf9e8a35b77e90b4d87d28fe3e1416d7d7.
To execute the upgrade via standard package managers, run the following command:
pip install --upgrade pypdf>=6.15.0Where immediate upgrade is unfeasible, implement host-level resource constraints. Configure container limits inside runtime environments to prevent a single parsing thread from affecting surrounding system services:
# Example docker-compose resource limits
services:
pdf-worker:
image: pdf-processor:latest
deploy:
resources:
limits:
cpus: '1.0'
memory: 512MAdditionally, apply maximum payload size constraints on incoming files to minimize resource footprint and restrict document processing execution time through application-level timeouts.
An analysis of the remediation commit reveals potential security boundaries that developers and security administrators should monitor. The patch enforces the limits using a local counter variable, entry_count, which tracking elements during a single call to _collect_cid_character_widths().
Because the counter is initialized to 0 at the start of the function call, it does not maintain state across multiple font dictionary processing operations. If a document defines multiple /DescendantFonts in a single complex font resource, pypdf iterates over each descendant font sequentially. Each descendant font can populate up to 100,000 entries into the shared character_widths dictionary reference.
This design permits an attacker to define dozens of child font dictionaries, each carrying slightly less than the 100,000 limit. When merged, the final character_widths mapping can still reach dimensions that cause significant memory growth. Furthermore, developers should inspect equivalent mapping modules, such as vertical typographic specifications (/W2) and custom CMaps, to confirm that range expansions are subject to similar validations.
CVSS:4.0/AV:L/AC:L/AT:N/PR:N/UI:P/VC:N/VI:N/VA:L/SC:N/SI:N/SA:N| Product | Affected Versions | Fixed Version |
|---|---|---|
pypdf py-pdf | < 6.15.0 | 6.15.0 |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-834 |
| Attack Vector | Local |
| CVSS v4.0 | 4.8 |
| Impact | Denial of Service (DoS) |
| Exploit Status | none |
| KEV Status | Not Listed |
The software does not limit the number of times a loop or iteration can execute, allowing excessive resource consumption.
An uncontrolled resource consumption vulnerability (CWE-400) exists in pypdf prior to version 6.15.0. When extracting text from a specially crafted PDF document, the parser fails to restrict token lengths within /ToUnicode CMap streams, causing unbounded memory allocation and process termination via Out-of-Memory (OOM) crashes.
A vulnerability in Netty's Redis codec allows remote unauthenticated attackers to cause a memory-pinning Denial of Service (DoS) due to the failure to release partial aggregate state when specific error conditions occur in RedisArrayAggregator. When processing Redis Serialization Protocol (RESP) messages, the aggregator fails to clear internal queues and release retained direct byte buffers on exception paths triggered by exceeded maxElements or invalid length properties. If the pipeline does not explicitly tear down the connection upon detecting a decoder error, subsequent elements continue utilizing the stale context, allowing memory blocks to remain indefinitely pinned.
CVE-2026-54164 is a class/type confusion vulnerability (CWE-843) in API Platform Core. When processing relationships via Internationalized Resource Identifiers (IRIs) in write requests, the framework's normalizer fails to verify if the resolved resource matches the expected type. For PHP applications utilizing untyped properties, the mismatched object is silently assigned, breaking domain logic and data integrity.
An unsafe option guard bypass vulnerability exists in GitPython before version 3.1.58. When keyword arguments are passed to Git commands with split_single_char_options=False, GitPython's argument validation helper fails to inspect the combined short-option value. This discrepancy allows short-option token smuggling or clustering manipulation, enabling remote attackers to bypass option blocklists and execute arbitrary system commands.
GHSA-WG23-69C2-GJC8 is a critical security vulnerability discovered in the native Passkey (WebAuthn) login implementation of Craft CMS. The vulnerability allows an attacker to bypass WebAuthn's core cryptographic challenge-response and signature-counter replay protections. By intercepting a single successful passkey login request body, an attacker can replay the identical request payload to generate additional authenticated active sessions, resulting in complete account takeover.
An architectural flaw in the Footnote extension of league/commonmark allows unauthenticated remote attackers to trigger severe denial of service conditions through algorithmic complexity exploitation (O(N^2) CPU and memory exhaustion) and path-delimiter injection leading to unexpected application-level crashes.