[ 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
  1. Summary
  2. The gap
  3. Reproduction
  4. Impact
  5. Affected versions
  6. Fix status
  7. The vendor’s record
  8. Timeline
  9. References
  10. Disclosure

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 /Length through Reader::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 (commit c755394) adds MAX_NESTING_DEPTH = 100 and threads a depth argument through array, _dictionary and _direct_object only. stream() and Reader::get_object receive 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-x3xg with cve_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

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
revisions to this post
datechange
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

  1. 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.

  2. 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.

all research