Skip to content

Commit f0b3cc2

Browse files
committed
Catch circular references in /Form XObjects (issue 19800)
For simplicity we will abort /Form XObject parsing *immediately* when encountering a circular reference, rather than letting it continue up until some limit (as e.g. PDFium appears to do), which should be fine since there are never any guarantees if/how *corrupt* PDF documents will render.
1 parent ff558c8 commit f0b3cc2

File tree

4 files changed

+53
-5
lines changed

4 files changed

+53
-5
lines changed

src/core/evaluator.js

Lines changed: 38 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -462,7 +462,8 @@ class PartialEvaluator {
462462
operatorList,
463463
task,
464464
initialState,
465-
localColorSpaceCache
465+
localColorSpaceCache,
466+
seenRefs
466467
) {
467468
const dict = xobj.dict;
468469
const matrix = lookupMatrix(dict.getArray("Matrix"), null);
@@ -526,6 +527,7 @@ class PartialEvaluator {
526527
resources: dict.get("Resources") || resources,
527528
operatorList,
528529
initialState,
530+
prevRefs: seenRefs,
529531
});
530532
operatorList.addOp(OPS.paintFormXObjectEnd, []);
531533

@@ -850,7 +852,8 @@ class PartialEvaluator {
850852
operatorList,
851853
task,
852854
stateManager,
853-
localColorSpaceCache
855+
localColorSpaceCache,
856+
seenRefs
854857
) {
855858
const smaskContent = smask.get("G");
856859
const smaskOptions = {
@@ -880,7 +883,8 @@ class PartialEvaluator {
880883
operatorList,
881884
task,
882885
stateManager.state.clone({ newPath: true }),
883-
localColorSpaceCache
886+
localColorSpaceCache,
887+
seenRefs
884888
);
885889
}
886890

@@ -1065,6 +1069,7 @@ class PartialEvaluator {
10651069
stateManager,
10661070
localGStateCache,
10671071
localColorSpaceCache,
1072+
seenRefs,
10681073
}) {
10691074
const gStateRef = gState.objId;
10701075
let isSimpleGState = true;
@@ -1127,7 +1132,8 @@ class PartialEvaluator {
11271132
operatorList,
11281133
task,
11291134
stateManager,
1130-
localColorSpaceCache
1135+
localColorSpaceCache,
1136+
seenRefs
11311137
)
11321138
);
11331139
gStateObj.push([key, true]);
@@ -1696,7 +1702,19 @@ class PartialEvaluator {
16961702
operatorList,
16971703
initialState = null,
16981704
fallbackFontDict = null,
1705+
prevRefs = null,
16991706
}) {
1707+
const objId = stream.dict?.objId;
1708+
const seenRefs = new RefSet(prevRefs);
1709+
1710+
if (objId) {
1711+
if (prevRefs?.has(objId)) {
1712+
throw new Error(
1713+
`getOperatorList - ignoring circular reference: ${objId}`
1714+
);
1715+
}
1716+
seenRefs.put(objId);
1717+
}
17001718
// Ensure that `resources`/`initialState` is correctly initialized,
17011719
// even if the provided parameter is e.g. `null`.
17021720
resources ||= Dict.empty;
@@ -1808,7 +1826,8 @@ class PartialEvaluator {
18081826
operatorList,
18091827
task,
18101828
stateManager.state.clone({ newPath: true }),
1811-
localColorSpaceCache
1829+
localColorSpaceCache,
1830+
seenRefs
18121831
)
18131832
.then(function () {
18141833
stateManager.restore();
@@ -2158,6 +2177,7 @@ class PartialEvaluator {
21582177
stateManager,
21592178
localGStateCache,
21602179
localColorSpaceCache,
2180+
seenRefs,
21612181
})
21622182
.then(resolveGState, rejectGState);
21632183
}).catch(function (reason) {
@@ -2339,7 +2359,19 @@ class PartialEvaluator {
23392359
markedContentData = null,
23402360
disableNormalization = false,
23412361
keepWhiteSpace = false,
2362+
prevRefs = null,
23422363
}) {
2364+
const objId = stream.dict?.objId;
2365+
const seenRefs = new RefSet(prevRefs);
2366+
2367+
if (objId) {
2368+
if (prevRefs?.has(objId)) {
2369+
throw new Error(
2370+
`getTextContent - ignoring circular reference: ${objId}`
2371+
);
2372+
}
2373+
seenRefs.put(objId);
2374+
}
23432375
// Ensure that `resources`/`stateManager` is correctly initialized,
23442376
// even if the provided parameter is e.g. `null`.
23452377
resources ||= Dict.empty;
@@ -3326,6 +3358,7 @@ class PartialEvaluator {
33263358
markedContentData,
33273359
disableNormalization,
33283360
keepWhiteSpace,
3361+
prevRefs: seenRefs,
33293362
})
33303363
.then(function () {
33313364
if (!sinkWrapper.enqueueInvoked) {

test/pdfs/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,7 @@
205205
!issue3928.pdf
206206
!issue8565.pdf
207207
!clippath.pdf
208+
!issue19800.pdf
208209
!issue8795_reduced.pdf
209210
!bug1755507.pdf
210211
!close-path-bug.pdf

test/pdfs/issue19800.pdf

1.08 KB
Binary file not shown.

test/test_manifest.json

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6890,6 +6890,20 @@
68906890
"lastPage": 2,
68916891
"type": "eq"
68926892
},
6893+
{
6894+
"id": "issue19800-eq",
6895+
"file": "pdfs/issue19800.pdf",
6896+
"md5": "92825d3178196bdd01096c4081609efd",
6897+
"rounds": 1,
6898+
"type": "eq"
6899+
},
6900+
{
6901+
"id": "issue19800-text",
6902+
"file": "pdfs/issue19800.pdf",
6903+
"md5": "92825d3178196bdd01096c4081609efd",
6904+
"rounds": 1,
6905+
"type": "text"
6906+
},
68936907
{
68946908
"id": "issue3438",
68956909
"file": "pdfs/issue3438.pdf",

0 commit comments

Comments
 (0)