Skip to content

Commit 6d6df8f

Browse files
Egor MartsynkovskyUbuntu
authored andcommitted
Add check for prev pointer in xref structure relying on PdfReader#StrictnessLevel
DEVSIX-6290
1 parent ef477b7 commit 6d6df8f

File tree

6 files changed

+282
-42
lines changed

6 files changed

+282
-42
lines changed

io/src/main/java/com/itextpdf/io/source/PdfTokenizer.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -689,8 +689,10 @@ public void throwError(String error, Object... messageParams) {
689689

690690
/**
691691
* Checks whether {@code line} equals to 'trailer'.
692-
* @param line for check.
693-
* @return true, if line is equals tio 'trailer', otherwise false.
692+
*
693+
* @param line for check
694+
*
695+
* @return true, if line is equals to 'trailer', otherwise false
694696
*/
695697
public static boolean checkTrailer(ByteBuffer line) {
696698
if (Trailer.length > line.size())
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 invalid prev pointer in xref structure.
27+
*/
28+
public class InvalidXRefPrevException extends PdfException {
29+
30+
/**
31+
* Creates a new instance of InvalidPrevPointerException.
32+
*
33+
* @param message the detail message.
34+
*/
35+
public InvalidXRefPrevException(String message) {
36+
super(message);
37+
}
38+
}

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

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -255,17 +255,17 @@ public final class KernelExceptionMessageConstant {
255255
public static final String PDF_INDIRECT_OBJECT_BELONGS_TO_OTHER_PDF_DOCUMENT = "Pdf indirect object belongs to "
256256
+ "other PDF document. Copy object to current pdf document.";
257257
public static final String PDF_VERSION_IS_NOT_VALID = "PDF version is not valid.";
258+
public static final String PNG_FILTER_UNKNOWN = "PNG filter unknown.";
259+
public static final String PRINT_SCALING_ENFORCE_ENTRY_INVALID = "/PrintScaling shall may appear in the Enforce "
260+
+ "array only if the corresponding entry in the viewer preferences dictionary specifies a valid value "
261+
+ "other than AppDefault";
258262
public static final String REF_ARRAY_ITEMS_IN_STRUCTURE_ELEMENT_DICTIONARY_SHALL_BE_INDIRECT_OBJECTS = "Ref array "
259263
+ "items in structure element dictionary shall be indirect objects.";
260264
public static final String REQUESTED_PAGE_NUMBER_IS_OUT_OF_BOUNDS = "Requested page number {0} is out of bounds.";
261265
public static final String ROLE_IS_NOT_MAPPED_TO_ANY_STANDARD_ROLE = "Role \"{0}\" is not mapped to any standard "
262266
+ "role.";
263267
public static final String ROLE_IN_NAMESPACE_IS_NOT_MAPPED_TO_ANY_STANDARD_ROLE = "Role \"{0}\" in namespace {1} "
264268
+ "is not mapped to any standard role.";
265-
public static final String PNG_FILTER_UNKNOWN = "PNG filter unknown.";
266-
public static final String PRINT_SCALING_ENFORCE_ENTRY_INVALID = "/PrintScaling shall may appear in the Enforce "
267-
+ "array only if the corresponding entry in the viewer preferences dictionary specifies a valid value "
268-
+ "other than AppDefault";
269269
public static final String RESOURCES_CANNOT_BE_NULL = "Resources cannot be null.";
270270
public static final String RESOURCES_DO_NOT_CONTAIN_EXTGSTATE_ENTRY_UNABLE_TO_PROCESS_THIS_OPERATOR = "Resources "
271271
+ "do not contain ExtGState entry. Unable to process operator {0}.";
@@ -331,14 +331,16 @@ public final class KernelExceptionMessageConstant {
331331
public static final String WMF_IMAGE_EXCEPTION = "WMF image exception.";
332332
public static final String WRONG_MEDIA_BOX_SIZE_TOO_FEW_ARGUMENTS = "Wrong media box size: {0}. Need at least 4 "
333333
+ "arguments";
334-
public static final String XREF_TABLE_HAS_CYCLED_REFERENCES =
335-
"Xref table has cycled references. Prev pointer indicates an already visited xref table.";
334+
public static final String XREF_PREV_SHALL_BE_DIRECT_NUMBER_OBJECT = "Prev pointer in xref structure shall be "
335+
+ "direct number object.";
336336
public static final String XREF_SUBSECTION_NOT_FOUND = "xref subsection not found.";
337337
public static final String XREF_STREAM_HAS_CYCLED_REFERENCES =
338338
"Xref stream has cycled references. Prev pointer indicates an already visited xref stream.";
339339
public static final String XREF_STRUCTURE_SIZE_EXCEEDED_THE_LIMIT = "Xref structure contains too many elements "
340340
+ "and may cause OOM exception. You can increase number of elements by setting custom "
341341
+ "MemoryLimitsAwareHandler.";
342+
public static final String XREF_TABLE_HAS_CYCLED_REFERENCES =
343+
"Xref table has cycled references. Prev pointer indicates an already visited xref table.";
342344
public static final String YOU_HAVE_TO_DEFINE_A_BOOLEAN_ARRAY_FOR_THIS_COLLECTION_SORT_DICTIONARY = "You have to "
343345
+ "define a boolean array for this collection sort dictionary.";
344346
public static final String YOU_MUST_SET_A_VALUE_BEFORE_ADDING_A_PREFIX = "You must set a value before adding a "

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

Lines changed: 61 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ This file is part of the iText (R) project.
5252
import com.itextpdf.io.source.RandomAccessSourceFactory;
5353
import com.itextpdf.io.source.WindowRandomAccessSource;
5454
import com.itextpdf.commons.utils.MessageFormatUtil;
55+
import com.itextpdf.kernel.exceptions.InvalidXRefPrevException;
5556
import com.itextpdf.kernel.exceptions.MemoryLimitsAwareException;
5657
import com.itextpdf.kernel.exceptions.PdfException;
5758
import com.itextpdf.kernel.crypto.securityhandler.UnsupportedSecurityHandlerException;
@@ -730,16 +731,20 @@ protected void readPdf() throws IOException {
730731
}
731732
try {
732733
readXref();
733-
} catch (XrefCycledReferencesException | MemoryLimitsAwareException ex) {
734+
} catch (XrefCycledReferencesException | MemoryLimitsAwareException | InvalidXRefPrevException ex) {
734735
// Throws an exception when xref stream has cycled references(due to lack of opportunity to fix such an
735736
// issue) or xref tables have cycled references and PdfReader.StrictnessLevel set to CONSERVATIVE.
736737
// Also throw an exception when xref structure size exceeds jvm memory limit.
737738
throw ex;
738739
} catch (RuntimeException ex) {
739-
Logger logger = LoggerFactory.getLogger(PdfReader.class);
740-
logger.error(IoLogMessageConstant.XREF_ERROR_WHILE_READING_TABLE_WILL_BE_REBUILT, ex);
740+
if (StrictnessLevel.CONSERVATIVE.isStricter(this.getStrictnessLevel())) {
741+
Logger logger = LoggerFactory.getLogger(PdfReader.class);
742+
logger.error(IoLogMessageConstant.XREF_ERROR_WHILE_READING_TABLE_WILL_BE_REBUILT, ex);
741743

742-
rebuildXref();
744+
rebuildXref();
745+
} else {
746+
throw ex;
747+
}
743748
}
744749
pdfDocument.getXref().markReadingCompleted();
745750
readDecryptObj();
@@ -989,7 +994,9 @@ protected void readXref() throws IOException {
989994
xrefStm = true;
990995
return;
991996
}
992-
} catch (XrefCycledReferencesException | MemoryLimitsAwareException exceptionWhileReadingXrefStream) {
997+
} catch (XrefCycledReferencesException
998+
| MemoryLimitsAwareException
999+
| InvalidXRefPrevException exceptionWhileReadingXrefStream) {
9931000
throw exceptionWhileReadingXrefStream;
9941001
} catch (Exception ignored) {
9951002
// Do nothing.
@@ -1008,7 +1015,7 @@ protected void readXref() throws IOException {
10081015
final Set<Long> alreadyVisitedXrefTables = new HashSet<>();
10091016
while (true) {
10101017
alreadyVisitedXrefTables.add(startxref);
1011-
PdfNumber prev = (PdfNumber) trailer2.get(PdfName.Prev);
1018+
PdfNumber prev = getXrefPrev(trailer2.get(PdfName.Prev, false));
10121019
if (prev == null) {
10131020
break;
10141021
}
@@ -1175,7 +1182,7 @@ protected boolean readXrefStream(long ptr) throws IOException {
11751182
}
11761183
PdfArray w = xrefStream.getAsArray(PdfName.W);
11771184
long prev = -1;
1178-
obj = xrefStream.get(PdfName.Prev);
1185+
obj = getXrefPrev(xrefStream.get(PdfName.Prev, false));
11791186
if (obj != null)
11801187
prev = ((PdfNumber) obj).longValue();
11811188
xref.setCapacity(size);
@@ -1321,6 +1328,26 @@ protected void rebuildXref() throws IOException {
13211328
throw new PdfException(KernelExceptionMessageConstant.TRAILER_NOT_FOUND);
13221329
}
13231330

1331+
protected PdfNumber getXrefPrev(PdfObject prevObjectToCheck) {
1332+
if (prevObjectToCheck == null) {
1333+
return null;
1334+
}
1335+
1336+
if (prevObjectToCheck.getType() == PdfObject.NUMBER) {
1337+
return (PdfNumber) prevObjectToCheck;
1338+
} else {
1339+
if (prevObjectToCheck.getType() == PdfObject.INDIRECT_REFERENCE &&
1340+
StrictnessLevel.CONSERVATIVE.isStricter(this.getStrictnessLevel())) {
1341+
final PdfObject value = ((PdfIndirectReference) prevObjectToCheck).getRefersTo(true);
1342+
if (value != null && value.getType() == PdfObject.NUMBER) {
1343+
return (PdfNumber) value;
1344+
}
1345+
}
1346+
throw new InvalidXRefPrevException(
1347+
KernelExceptionMessageConstant.XREF_PREV_SHALL_BE_DIRECT_NUMBER_OBJECT);
1348+
}
1349+
}
1350+
13241351
boolean isMemorySavingMode() {
13251352
return memorySavingMode;
13261353
}
@@ -1359,33 +1386,6 @@ private void readDecryptObj() {
13591386
}
13601387
}
13611388

1362-
/**
1363-
* Utility method that checks the provided byte source to see if it has junk bytes at the beginning. If junk bytes
1364-
* are found, construct a tokeniser that ignores the junk. Otherwise, construct a tokeniser for the byte source as it is
1365-
*
1366-
* @param byteSource the source to check
1367-
* @return a tokeniser that is guaranteed to start at the PDF header
1368-
* @throws IOException if there is a problem reading the byte source
1369-
*/
1370-
private static PdfTokenizer getOffsetTokeniser(IRandomAccessSource byteSource, boolean closeStream)
1371-
throws IOException {
1372-
PdfTokenizer tok = new PdfTokenizer(new RandomAccessFileOrArray(byteSource));
1373-
int offset;
1374-
try {
1375-
offset = tok.getHeaderOffset();
1376-
} catch (com.itextpdf.io.exceptions.IOException ex) {
1377-
if (closeStream) {
1378-
tok.close();
1379-
}
1380-
throw ex;
1381-
}
1382-
if (offset != 0) {
1383-
IRandomAccessSource offsetSource = new WindowRandomAccessSource(byteSource, offset);
1384-
tok = new PdfTokenizer(new RandomAccessFileOrArray(offsetSource));
1385-
}
1386-
return tok;
1387-
}
1388-
13891389
private PdfObject readObject(PdfIndirectReference reference, boolean fixXref) {
13901390
if (reference == null)
13911391
return null;
@@ -1497,6 +1497,33 @@ private PdfObject createPdfNullInstance(boolean readAsDirect) {
14971497
}
14981498
}
14991499

1500+
/**
1501+
* Utility method that checks the provided byte source to see if it has junk bytes at the beginning. If junk bytes
1502+
* are found, construct a tokeniser that ignores the junk. Otherwise, construct a tokeniser for the byte source as it is
1503+
*
1504+
* @param byteSource the source to check
1505+
* @return a tokeniser that is guaranteed to start at the PDF header
1506+
* @throws IOException if there is a problem reading the byte source
1507+
*/
1508+
private static PdfTokenizer getOffsetTokeniser(IRandomAccessSource byteSource, boolean closeStream)
1509+
throws IOException {
1510+
PdfTokenizer tok = new PdfTokenizer(new RandomAccessFileOrArray(byteSource));
1511+
int offset;
1512+
try {
1513+
offset = tok.getHeaderOffset();
1514+
} catch (com.itextpdf.io.exceptions.IOException ex) {
1515+
if (closeStream) {
1516+
tok.close();
1517+
}
1518+
throw ex;
1519+
}
1520+
if (offset != 0) {
1521+
IRandomAccessSource offsetSource = new WindowRandomAccessSource(byteSource, offset);
1522+
tok = new PdfTokenizer(new RandomAccessFileOrArray(offsetSource));
1523+
}
1524+
return tok;
1525+
}
1526+
15001527
protected static class ReusableRandomAccessSource implements IRandomAccessSource {
15011528
private ByteBuffer buffer;
15021529

0 commit comments

Comments
 (0)