Skip to content

Commit e7a7258

Browse files
author
Egor Martsynkovsky
committed
Add safeguards to avoid infinite loop in xref structure
DEVSIX-6235
1 parent aa2605b commit e7a7258

File tree

8 files changed

+293
-108
lines changed

8 files changed

+293
-108
lines changed

kernel/src/main/java/com/itextpdf/kernel/exceptions/KernelExceptionMessageConstant.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -328,7 +328,11 @@ public final class KernelExceptionMessageConstant {
328328
public static final String WMF_IMAGE_EXCEPTION = "WMF image exception.";
329329
public static final String WRONG_MEDIA_BOX_SIZE_TOO_FEW_ARGUMENTS = "Wrong media box size: {0}. Need at least 4 "
330330
+ "arguments";
331+
public static final String XREF_TABLE_HAS_CYCLED_REFERENCES =
332+
"Xref table has cycled references. Prev pointer indicates an already visited xref table.";
331333
public static final String XREF_SUBSECTION_NOT_FOUND = "xref subsection not found.";
334+
public static final String XREF_STREAM_HAS_CYCLED_REFERENCES =
335+
"Xref stream has cycled references. Prev pointer indicates an already visited xref stream.";
332336
public static final String YOU_HAVE_TO_DEFINE_A_BOOLEAN_ARRAY_FOR_THIS_COLLECTION_SORT_DICTIONARY = "You have to "
333337
+ "define a boolean array for this collection sort dictionary.";
334338
public static final String YOU_MUST_SET_A_VALUE_BEFORE_ADDING_A_PREFIX = "You must set a value before adding a "
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
/*
2+
This file is part of the iText (R) project.
3+
Copyright (c) 1998-2022 iText Group NV
4+
Authors: iText Software.
5+
6+
This program is offered under a commercial and under the AGPL license.
7+
For commercial licensing, contact us at https://itextpdf.com/sales. For AGPL licensing, see below.
8+
9+
AGPL licensing:
10+
This program is free software: you can redistribute it and/or modify
11+
it under the terms of the GNU Affero General Public License as published by
12+
the Free Software Foundation, either version 3 of the License, or
13+
(at your option) any later version.
14+
15+
This program is distributed in the hope that it will be useful,
16+
but WITHOUT ANY WARRANTY; without even the implied warranty of
17+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18+
GNU Affero General Public License for more details.
19+
20+
You should have received a copy of the GNU Affero General Public License
21+
along with this program. If not, see <https://www.gnu.org/licenses/>.
22+
*/
23+
package com.itextpdf.kernel.exceptions;
24+
25+
/**
26+
* Exception class for infinite loop in xref structure.
27+
*/
28+
public class XrefCycledReferencesException extends PdfException {
29+
30+
/**
31+
* Creates a new instance of XrefInfiniteLoopException.
32+
*
33+
* @param message the detail message.
34+
*/
35+
public XrefCycledReferencesException(String message) {
36+
super(message);
37+
}
38+
}

kernel/src/main/java/com/itextpdf/kernel/pdf/PdfReader.java

Lines changed: 39 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ This file is part of the iText (R) project.
5555
import com.itextpdf.kernel.exceptions.PdfException;
5656
import com.itextpdf.kernel.crypto.securityhandler.UnsupportedSecurityHandlerException;
5757
import com.itextpdf.kernel.exceptions.KernelExceptionMessageConstant;
58+
import com.itextpdf.kernel.exceptions.XrefCycledReferencesException;
5859
import com.itextpdf.kernel.pdf.filters.FilterHandlers;
5960
import com.itextpdf.kernel.pdf.filters.IFilterHandler;
6061

@@ -63,10 +64,13 @@ This file is part of the iText (R) project.
6364
import java.io.FileNotFoundException;
6465
import java.io.IOException;
6566
import java.io.InputStream;
67+
import java.util.HashSet;
6668
import java.util.Map;
6769

6870
import com.itextpdf.kernel.xmp.XMPException;
6971
import com.itextpdf.kernel.xmp.XMPMetaFactory;
72+
73+
import java.util.Set;
7074
import org.slf4j.Logger;
7175
import org.slf4j.LoggerFactory;
7276

@@ -724,6 +728,10 @@ protected void readPdf() throws IOException {
724728
}
725729
try {
726730
readXref();
731+
} catch (XrefCycledReferencesException ex) {
732+
// Throws an exception when xref stream has cycled references(due to lack of opportunity to fix such an
733+
// issue) or xref tables have cycled references and PdfReader.StrictnessLevel set to CONSERVATIVE.
734+
throw ex;
727735
} catch (RuntimeException ex) {
728736
Logger logger = LoggerFactory.getLogger(PdfReader.class);
729737
logger.error(IoLogMessageConstant.XREF_ERROR_WHILE_READING_TABLE_WILL_BE_REBUILT, ex);
@@ -961,11 +969,13 @@ protected PdfArray readArray(boolean objStm) throws IOException {
961969
protected void readXref() throws IOException {
962970
tokens.seek(tokens.getStartxref());
963971
tokens.nextToken();
964-
if (!tokens.tokenValueEqualsTo(PdfTokenizer.Startxref))
972+
if (!tokens.tokenValueEqualsTo(PdfTokenizer.Startxref)) {
965973
throw new PdfException(KernelExceptionMessageConstant.PDF_STARTXREF_NOT_FOUND, tokens);
974+
}
966975
tokens.nextToken();
967-
if (tokens.getTokenType() != PdfTokenizer.TokenType.Number)
976+
if (tokens.getTokenType() != PdfTokenizer.TokenType.Number) {
968977
throw new PdfException(KernelExceptionMessageConstant.PDF_STARTXREF_IS_NOT_FOLLOWED_BY_A_NUMBER, tokens);
978+
}
969979
long startxref = tokens.getLongValue();
970980
lastXref = startxref;
971981
eofPos = tokens.getPosition();
@@ -974,27 +984,41 @@ protected void readXref() throws IOException {
974984
xrefStm = true;
975985
return;
976986
}
987+
} catch (XrefCycledReferencesException cycledReferencesException) {
988+
throw cycledReferencesException;
977989
} catch (Exception ignored) {
990+
// Do nothing.
978991
}
979992
// clear xref because of possible issues at reading xref stream.
980993
pdfDocument.getXref().clear();
981994

982995
tokens.seek(startxref);
983996
trailer = readXrefSection();
984997

985-
// Prev key - integer value
986-
// (Present only if the file has more than one cross-reference section; shall be an indirect reference)
998+
// Prev key - integer value.
999+
// (Present only if the file has more than one cross-reference section; shall be an indirect reference).
9871000
// The byte offset in the decoded stream from the beginning of the file
9881001
// to the beginning of the previous cross-reference section.
9891002
PdfDictionary trailer2 = trailer;
1003+
final Set<Long> alreadyVisitedXrefTables = new HashSet<>();
9901004
while (true) {
1005+
alreadyVisitedXrefTables.add(startxref);
9911006
PdfNumber prev = (PdfNumber) trailer2.get(PdfName.Prev);
992-
if (prev == null)
1007+
if (prev == null) {
9931008
break;
994-
if (prev.longValue() == startxref)
995-
throw new PdfException(KernelExceptionMessageConstant.
996-
TRAILER_PREV_ENTRY_POINTS_TO_ITS_OWN_CROSS_REFERENCE_SECTION);
997-
startxref = prev.longValue();
1009+
}
1010+
long prevXrefOffset = prev.longValue();
1011+
if (alreadyVisitedXrefTables.contains(prevXrefOffset)) {
1012+
if (StrictnessLevel.CONSERVATIVE.isStricter(this.getStrictnessLevel())) {
1013+
// Throw the exception to rebuild xref table, it'll be caught in method above.
1014+
throw new PdfException(KernelExceptionMessageConstant.
1015+
TRAILER_PREV_ENTRY_POINTS_TO_ITS_OWN_CROSS_REFERENCE_SECTION);
1016+
} else {
1017+
throw new XrefCycledReferencesException(
1018+
KernelExceptionMessageConstant.XREF_TABLE_HAS_CYCLED_REFERENCES);
1019+
}
1020+
}
1021+
startxref = prevXrefOffset;
9981022
tokens.seek(startxref);
9991023
trailer2 = readXrefSection();
10001024
}
@@ -1098,6 +1122,7 @@ protected PdfDictionary readXrefSection() throws IOException {
10981122
}
10991123

11001124
protected boolean readXrefStream(long ptr) throws IOException {
1125+
final Set<Long> alreadyVisitedXrefStreams = new HashSet<>();
11011126
while (ptr != -1) {
11021127
tokens.seek(ptr);
11031128
if (!tokens.nextToken()) {
@@ -1112,6 +1137,7 @@ protected boolean readXrefStream(long ptr) throws IOException {
11121137
if (!tokens.nextToken() || !tokens.tokenValueEqualsTo(PdfTokenizer.Obj)) {
11131138
return false;
11141139
}
1140+
alreadyVisitedXrefStreams.add(ptr);
11151141
PdfXrefTable xref = pdfDocument.getXref();
11161142
PdfObject object = readObject(false);
11171143
PdfStream xrefStream;
@@ -1208,6 +1234,10 @@ protected boolean readXrefStream(long ptr) throws IOException {
12081234
}
12091235
}
12101236
ptr = prev;
1237+
if (alreadyVisitedXrefStreams.contains(ptr)) {
1238+
throw new XrefCycledReferencesException(
1239+
KernelExceptionMessageConstant.XREF_STREAM_HAS_CYCLED_REFERENCES);
1240+
}
12111241
}
12121242
return true;
12131243
}

0 commit comments

Comments
 (0)