Skip to content

fix: NewReader no longer loops forever on a cyclic xref /Prev chain - #96

Closed
Aquitano wants to merge 1 commit into
ledongthuc:masterfrom
Aquitano:xref-prev-loop
Closed

Aquitano wants to merge 1 commit into
ledongthuc:masterfrom
Aquitano:xref-prev-loop

Conversation

@Aquitano

Copy link
Copy Markdown

A file whose cross-reference /Prev chain points back at a section already read makes NewReader loop forever. readPrevXrefs follows /Prev for as long as it is set, so the same sections are parsed again and again. A damaged incremental save produces this, as does a single section whose /Prev is its own offset.

The walk now records the offsets it has read and ends at the first one it meets again. That section's entries are already in the table, so stopping there loses nothing and the file opens normally. pdf.js and MuPDF treat the loop the same way; MuPDF logs "ignoring xref section recursion".

TestXrefPrevLoopTerminates covers a section pointing at itself and two sections pointing at each other. Without the fix, both cases hang past the test's 5 s bound. The second case also checks that an object listed only in the older section still resolves.

#93 does not cover this; its branch hangs on the same test. It rewrites the two lines just below the new check in readPrevXrefs, so whichever of the two lands second needs a small rebase. With both applied, the full suite passes.

A /Prev that points back at a cross-reference section already read, as a
damaged incremental save can leave behind, made readPrevXrefs parse the
same sections forever, so NewReader never returned. The walk now ends at
the first offset it has seen, since that section's entries are already in
the table. pdf.js and MuPDF treat the loop the same way.
@Aquitano Aquitano closed this Oct 12, 2026
Aquitano added a commit to Aquitano/aqt that referenced this pull request Oct 12, 2026
The upstream fix for a looping xref /Prev chain (ledongthuc/pdf#96) was
closed, so the copy no longer carries it. read.go and read_test.go return
to #93's versions, every file now matches upstream master with #93, and
the copy can go once #93 is released.
Aquitano added a commit to Aquitano/aqt that referenced this pull request Oct 12, 2026
…attic (#70)

* fix(extract): one malformed PDF can no longer hang or crash bank and attic

ledongthuc/pdf loops forever on a cyclic xref /Prev chain or page tree,
overflows the stack on an object stream that refers to itself, and
allocates whatever a forged /Size asks for. recover catches none of
these, so a single such file hangs or kills bank and attic. extract now
reads PDFs through a copy of the library at 6c8c28e with upstream #93 and
#96 applied. It lives in internal/pdf, excluded from lint so it stays
identical to upstream until both are released.

* fix(extract): keep the pdf copy to upstream plus #93

The upstream fix for a looping xref /Prev chain (ledongthuc/pdf#96) was
closed, so the copy no longer carries it. read.go and read_test.go return
to #93's versions, every file now matches upstream master with #93, and
the copy can go once #93 is released.

* fix(extract): keep a local guard against looping xref /Prev chains

Bank extracts blobs one after another, so a PDF whose /Prev chain loops
back would hang its extraction run, and every later run would stall on
the same file again. The guard from the closed upstream #96 stays in the
copy as a local patch: readPrevXrefs stops at a section it has already
read, as pdf.js and MuPDF do. The copy's README records it and what
lets the copy go.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant