[ ezo labs // research ]
lopdf 0.42.0 still overflows its stack on a chain of /Length references
RUSTSEC-2026-0187 calls 0.42.0 patched after adding a nesting-depth limit. The limit covers arrays and dictionaries; a stream whose /Length is another stream recurses without it and still aborts.
contentsSummary
abstract
RUSTSEC-2026-0187 reports unbounded recursion in lopdf’s PDF object parser and calls 0.42.0 patched: commit c755394 adds MAX_NESTING_DEPTH = 100 and threads it through the array, dictionary and direct-object paths. A stream dictionary whose /Length is an indirect reference is legal PDF (ISO 32000-1 7.3.8.2), and stream() resolves that reference through Reader::get_object, which re-enters object() and stream() again with no depth counter. We built a chain of N stream objects, each pointing at the next through /Length, and loaded it with the released crates: on 0.42.0, 1,300 links abort with a stack overflow, exit 134, while 1,200 load; on 0.47.0, the 1,200-link file aborts. Controls isolate the recursion: 1,300 flat stream objects load, and the parser’s own depth limit fires at 100 nested arrays. The defect is the parser’s other recursion path, not a failure of the added limit.
Summary
A PDF can chain one stream’s length to another stream. lopdf follows that chain recursively, and the depth counter added in 0.42.0 does not reach it.
The gap
A chain of stream objects whose /Length is an indirect reference recurses through stream() → Reader::get_object → object() → stream() with no depth limit, so a structurally valid PDF aborts the process with a stack overflow.
src/parser/mod.rs(346 in 0.42.0; 438 in 0.47.0) —stream()resolves a referenced/LengththroughReader::get_object;get_object→read_object→parser::indirect_object→object()→stream()again, with no depth counter.src/reader.rs(456 (MAX_NESTING_DEPTH), 1099 / 1269 (get_object)) — The 0.42.0 fix (commitc755394) addsMAX_NESTING_DEPTH = 100and threads a depth argument througharray,_dictionaryand_direct_objectonly.stream()andReader::get_objectreceive no depth argument.
Reproduction
Build a PDF with N stream objects, each << /Length <next> 0 R >> stream … endstream, and load it with the released crate. Controls: the same 1,300 stream objects with plain-integer /Length; nested arrays at 99 and 100 levels.
0.42.0: a 1,300-link chain (101,888 bytes) aborts with thread '<unknown>' has overflowed its stack / fatal runtime error: stack overflow, aborting, exit 134; 1,200 links (93,888 bytes) loads. 0.47.0: the 93,888-byte 1,200-link chain aborts. Controls on both: the 1,300 plain-/Length stream objects load as 1,300 objects; a nested array loads at 99 levels and is dropped at 100.
Impact
A caller that loads an attacker-supplied PDF aborts the process with a stack overflow. The abort cannot be caught with catch_unwind, so one document can take down a service that parses PDFs in-process.
Affected versions
lopdf 0.42.0 through 0.47.0 (latest at 2026-11-04) carry the unbounded stream() path; the defect exists at least from 0.42.0, where the RUSTSEC record says it is patched. No release threads a depth counter through stream()’s /Length resolution.
Fix status
The fix is to thread the existing depth argument through stream() and Reader::get_object (or pass an explicit depth to the /Length resolution) so the cycle is bounded. No release through 0.47.0 carries it.
The vendor’s record
The maintainer published GHSA-4597-5x94-x3xg at 2026-11-12T01:35:47Z with cve_id: null, 31 minutes after the class-anchored publish ask. The published advisory records the same defect and the range lopdf <= 0.47.0; it carries no CVE and no patched version. RUSTSEC-2026-0187 is the published record that calls 0.42.0 patched; the execution contradicts that range for this input class.
Timeline
- 2026-11-04 — lopdf 0.47.0 released
- 2026-11-09T08:44:05Z — Private report filed (
GHSA-4597-5x94-x3xg) - 2026-11-09T10:45:44Z — Draft accepted (inferred from
updated_at) - 2026-11-11T20:36:08Z — Publication ask posted in the advisory thread
- 2026-11-11T21:25Z — CNA request sent to VulnCheck (
disclosure@vulncheck.com) - 2026-11-11T23:22Z — Class-anchored follow-up sent to VulnCheck
- 2026-11-12T01:04:34Z — Class-anchored publish ask in the vendor advisory thread (369870)
- 2026-11-12T01:35:47Z — The maintainer publishes
GHSA-4597-5x94-x3xgwithcve_id: null - 2026-12-03T01:35:47Z — CVE escalation (three weeks after publication) by email or the CNA; the advisory thread refuses comments once published
References
- Stack overflow in lopdf via deeply nested PDF objects: https://github.com/rustsec/advisory-db/blob/main/crates/lopdf/RUSTSEC-2026-0187.md
- Private report: https://github.com/J-F-Liu/lopdf/security/advisories/GHSA-4597-5x94-x3xg
Disclosure
This note is an independent write-up. It is published on 2026-11-12T18:52:53Z as the public reference for a CVE request. The private advisory and this write-up describe the same defect; the vendor advisory named above is the authority where one exists.
revisionsfirst published , last updated
| date | change |
|---|---|
| First publication. |
cite this
EZO Labs Research. lopdf 0.42.0 still overflows its stack on a chain of /Length references. EZO Labs Inc, research report EZO-R-2026-26, 12 November 2026. https://ezolabs.net/research/lopdf-length-chain-stack-overflow
@techreport{lopdflengthchainstackoverflow,
title = {lopdf 0.42.0 still overflows its stack on a chain of /Length references},
author = {{EZO Labs Research}},
institution = {EZO Labs Inc},
type = {Research report},
number = {EZO-R-2026-26},
year = {2026},
month = nov,
url = {https://ezolabs.net/research/lopdf-length-chain-stack-overflow}
}
licensed under CC BY 4.0.
next
-
How many subagents is too many? We gave ours 12 slots; the median used 1.3.
Slots waited on work. Allowed 12 workers at once, the typical build kept about 1.3 busy (1.31 over 47 builds). In the six biggest, new work came 8 to 12 times too slowly to fill them.
-
Let your lead agent only plan: fewer turns, same score
Fewer turns, same score. In three paired runs of one task, a lead agent that only plans took 9 turns instead of 17 and compacted 4 times, not 5. All 168 tests passed. Total tokens did not fall.
-
How a Chinese-language red-team swarm farms free LLM tokens
Free quota is the target A red-team swarm written in Chinese hunts open model relay panels for keys, default admin passwords and free sign-up quota. Then it tests each key and keeps the ones that work.