CVE-2026-81723: NLTK before 3.10.3 Quadratic CPU Exhaustion via XMLCorpusView

Published Aug 27, 2026
·
Updated

Summary

XMLCorpusView.readxmlfragment() reads a corpus file in 1 KiB blocks, appending each block to a growing fragment string, then calls VALIDXMLRE.match(fragment) on the full accumulated buffer every iteration. Because each iteration rescans the entire accumulated fragment, the total amount of work grows quadratically with input size.

Commit c9c332284 (CWE-1333) made each match() call linear. The quadratic behavior is separate: the loop calls match() once per 1 KiB block, each time on a longer buffer.

On the test system, an 8 MiB malformed XML file consumed approximately 48 CPU-seconds through the public BNCCorpusReader.words() API with no source modification. Absolute timings vary by hardware. readxmlfragment() imposes no limit on fragment size or iteration count.

Details

File: nltk/corpus/reader/xmldocs.py Function: XMLCorpusView.readxmlfragment(), lines 261–308

The relevant loop:

python fragment = "" while True: fragment += stream.read(self.BLOCKSIZE) # grows by 1 KiB per iteration if self.VALIDXMLRE.match(fragment): # rescans full buffer each time return fragment ... lastopenbracket = fragment.rfind("<") if lastopenbracket > 0: # False for single-'<' payload if self.VALIDXMLRE.match(fragment[:lastopenbracket]): return ... # loop continues

For a payload of b'<' + b'a' (N-1):

- For this malformed input, VALIDXMLRE.match(fragment) does not succeed because the unterminated tag prevents the expression from matching before EOF. - fragment.rfind("<") returns 0; the guard lastopenbracket > 0 is False, so the backtrack branch is never taken. - The only exit is EOF, after all N bytes are consumed.

Affected readers -> readers that rely on XMLCorpusView, including BNCCorpusReader, NPSChatCorpusReader, SemcorCorpusReader, MTECorpusReader, NKJPCorpusReader, FrameNetCorpusReader, VerbNetCorpusReader, and direct XMLCorpusView instantiation. XMLCorpusReader.xml() is not affected -> it calls defusedxml.safeparse().

PoC

Requires only pip install nltk. No corpus data needed.

python from pathlib import Path from tempfile import TemporaryDirectory from time import perfcounter from nltk.corpus.reader.bnc import BNCCorpusReader

SIZESKIB = (256, 512, 1024, 2048, 4096, 8192) results = [] with TemporaryDirectory() as directory: root = Path(directory) malformed = root / "unterminated.xml" for kib in SIZESKIB: malformed.writebytes(b"<" + b"a" (kib 1024 - 1)) t = perfcounter() try: list(BNCCorpusReader(str(root), [malformed.name]).words()) except ValueError as e: assert "tag not closed" in str(e) results.append(perfcounter() - t)

print("KiB seconds growth") for i, (kib, elapsed) in enumerate(zip(SIZESKIB, results)): ratio = "-" if i == 0 else f"{elapsed / results[i-1]:.2f}x" print(f"{kib:5d} {elapsed:9.3f} {ratio}")

Runtime should increase by approximately fourfold for each doubling of input size, although absolute timings vary by hardware.

During verification, VALIDXMLRE.match() was instrumented to record the size of each input. For a 256 KiB malformed file it was invoked 257 times on monotonically increasing buffers (1024, 2048, …, 262144 bytes), with the final call occurring after EOF. This confirms that every iteration rescans the accumulated fragment.

Impact

Applications that process attacker-controlled XML corpus files through an affected reader are vulnerable. The attacker needs only write access to a path the reader will open. No NLTK credentials or special privileges required. Offline tools reading only trusted local corpora are not at risk.

Affected versions: Verified in NLTK 3.9.4, 3.10.0, and the current develop branch. Historical inspection indicates the same loop structure has existed since the introduction of XMLCorpusView (2007), but only the listed versions were experimentally verified. No patch exists in any published release.

This issue results in CPU exhaustion and may allow denial of service in applications that process attacker-controlled XML corpus files.

Suggested Fix

Avoid rescanning the accumulated fragment from the beginning after each 1 KiB read. Incremental parsing, bounded fragment accumulation, or another streaming approach would eliminate the quadratic behavior while preserving existing semantics.

A regression test should verify that BNCCorpusReader.words() raises ValueError within a fixed timeout (e.g. 5 seconds) against a 2 MiB malformed input. The existing testxmldocssecurity.py covers only the prior ReDoS payloads and does not exercise this path.

Other sources

NLTK versions before 3.10.3 contain a quadratic CPU exhaustion vulnerability in XMLCorpusView.readxmlfragment() that rescans accumulated XML fragments on every 1 KiB block read. Attackers can provide malformed XML corpus files to cause severe CPU consumption and denial of service through affected readers like BNCCorpusReader.

— MITRE

Affected Software

2 affected componentsFixes available
nltk Natural Language Toolkit<3.10.3
pip/nltk<=3.10.2
3.10.3

Remediation

Recommended actions to resolve this vulnerability, in priority order.

  1. Upgrade

    Upgrade pip/nltk to a version that resolves this vulnerability.

    Fixed in 3.10.3

Event History

Aug 27, 2026
CVE Published
via MITRE·02:51 PM
Data Sourced
via MITRE·02:51 PM
DescriptionSeverityWeakness
Data Sourced
via NVD·05:21 PM
DescriptionSeverityWeakness
Sep 2, 2026
Advisory Published
via GitHub·02:34 PM
Data Sourced
via GitHub·02:34 PM
DescriptionSeverityWeaknessAffected Software

Frequently Asked Questions

1

What must an attacker be able to do to trigger the denial of service?

An attacker must be able to provide a malformed XML corpus file that is processed by an affected NLTK XML reader, such as BNCCorpusReader. No authentication or user interaction is required according to the supplied vector.

2

Which deployments are realistically exposed?

Deployments are exposed when they use NLTK before 3.10.3 and process XML corpus files from untrusted or attacker-influenced sources through XMLCorpusView-based readers. The impact is CPU exhaustion and resulting denial of service; confidentiality and integrity are not affected.

3

What is the available remediation?

Upgrade NLTK to version 3.10.3 or later. The affected range is NLTK versions before 3.10.3.

Contact

SecAlerts Pty Ltd.
132 Wickham Terrace
Fortitude Valley,
QLD 4006, Australia
info@secalerts.co
By using SecAlerts services, you agree to our services end-user license agreement. This website is safeguarded by reCAPTCHA and governed by the Google Privacy Policy and Terms of Service. All names, logos, and brands of products are owned by their respective owners, and any usage of these names, logos, and brands for identification purposes only does not imply endorsement. If you possess any content that requires removal, please get in touch with us.
© 2026 SecAlerts Pty Ltd.
ABN: 70 645 966 203, ACN: 645 966 203