Repository navigation
Conversation
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
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
A file whose cross-reference
/Prevchain points back at a section already read makesNewReaderloop forever.readPrevXrefsfollows/Prevfor 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/Previs 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".
TestXrefPrevLoopTerminatescovers 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.