Skip to content

Commit 75b2364

Browse files
Egor MartsynkovskyUbuntu
authored andcommitted
Add safe guards to avoid OOM on parsing xref structures
Add ability to set max size of xref array Ignore flaky tests in LtvVerificationTest class DEVSIX-6247
1 parent cd2ca4e commit 75b2364

File tree

12 files changed

+316
-16
lines changed

12 files changed

+316
-16
lines changed

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -336,6 +336,9 @@ public final class KernelExceptionMessageConstant {
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.";
339+
public static final String XREF_STRUCTURE_SIZE_EXCEEDED_THE_LIMIT = "Xref structure contains too many elements "
340+
+ "and may cause OOM exception. You can increase number of elements by setting custom "
341+
+ "MemoryLimitsAwareHandler.";
339342
public static final String YOU_HAVE_TO_DEFINE_A_BOOLEAN_ARRAY_FOR_THIS_COLLECTION_SORT_DICTIONARY = "You have to "
340343
+ "define a boolean array for this collection sort dictionary.";
341344
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/MemoryLimitsAwareHandler.java

Lines changed: 46 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -62,11 +62,13 @@ public class MemoryLimitsAwareHandler {
6262
private static final int SINGLE_SCALE_COEFFICIENT = 100;
6363
private static final int SUM_SCALE_COEFFICIENT = 500;
6464

65+
private static final int MAX_NUMBER_OF_ELEMENTS_IN_XREF_STRUCTURE = 50000000;
6566
private static final int SINGLE_DECOMPRESSED_PDF_STREAM_MIN_SIZE = Integer.MAX_VALUE / 100;
66-
private static final long SUM_OF_DECOMPRESSED_PDF_STREAMW_MIN_SIZE = Integer.MAX_VALUE / 20;
67+
private static final long SUM_OF_DECOMPRESSED_PDF_STREAMS_MIN_SIZE = Integer.MAX_VALUE / 20;
6768

6869
private int maxSizeOfSingleDecompressedPdfStream;
6970
private long maxSizeOfDecompressedPdfStreamsSum;
71+
private int maxNumberOfElementsInXrefStructure;
7072

7173
private long allMemoryUsedForDecompression = 0;
7274
private long memoryUsedForCurrentPdfStreamDecompression = 0;
@@ -78,8 +80,8 @@ public class MemoryLimitsAwareHandler {
7880
* The max allowed memory limits will be generated by default.
7981
*/
8082
public MemoryLimitsAwareHandler() {
81-
maxSizeOfSingleDecompressedPdfStream = SINGLE_DECOMPRESSED_PDF_STREAM_MIN_SIZE;
82-
maxSizeOfDecompressedPdfStreamsSum = SUM_OF_DECOMPRESSED_PDF_STREAMW_MIN_SIZE;
83+
this(SINGLE_DECOMPRESSED_PDF_STREAM_MIN_SIZE, SUM_OF_DECOMPRESSED_PDF_STREAMS_MIN_SIZE,
84+
MAX_NUMBER_OF_ELEMENTS_IN_XREF_STRUCTURE);
8385
}
8486

8587
/**
@@ -89,10 +91,16 @@ public MemoryLimitsAwareHandler() {
8991
* @param documentSize the size of the document, which is going to be handled by iText.
9092
*/
9193
public MemoryLimitsAwareHandler(long documentSize) {
92-
maxSizeOfSingleDecompressedPdfStream = (int) calculateDefaultParameter(documentSize, SINGLE_SCALE_COEFFICIENT,
93-
SINGLE_DECOMPRESSED_PDF_STREAM_MIN_SIZE);
94-
maxSizeOfDecompressedPdfStreamsSum = calculateDefaultParameter(documentSize, SUM_SCALE_COEFFICIENT,
95-
SUM_OF_DECOMPRESSED_PDF_STREAMW_MIN_SIZE);
94+
this((int) calculateDefaultParameter(documentSize, SINGLE_SCALE_COEFFICIENT,
95+
SINGLE_DECOMPRESSED_PDF_STREAM_MIN_SIZE), calculateDefaultParameter(documentSize, SUM_SCALE_COEFFICIENT,
96+
SUM_OF_DECOMPRESSED_PDF_STREAMS_MIN_SIZE), MAX_NUMBER_OF_ELEMENTS_IN_XREF_STRUCTURE);
97+
}
98+
99+
private MemoryLimitsAwareHandler(int maxSizeOfSingleDecompressedPdfStream, long maxSizeOfDecompressedPdfStreamsSum,
100+
int maxNumberOfElementsInXrefStructure) {
101+
this.maxSizeOfSingleDecompressedPdfStream = maxSizeOfSingleDecompressedPdfStream;
102+
this.maxSizeOfDecompressedPdfStreamsSum = maxSizeOfDecompressedPdfStreamsSum;
103+
this.maxNumberOfElementsInXrefStructure = maxNumberOfElementsInXrefStructure;
96104
}
97105

98106
/**
@@ -167,6 +175,37 @@ public boolean isMemoryLimitsAwarenessRequiredOnDecompression(PdfArray filters)
167175
return false;
168176
}
169177

178+
/**
179+
* Gets maximum number of elements in xref structure.
180+
*
181+
* @return maximum number of elements in xref structure.
182+
*/
183+
public int getMaxNumberOfElementsInXrefStructure() {
184+
return maxNumberOfElementsInXrefStructure;
185+
}
186+
187+
/**
188+
* Sets maximum number of elements in xref structure.
189+
*
190+
* @param maxNumberOfElementsInXrefStructure maximum number of elements in xref structure.
191+
*/
192+
public void setMaxNumberOfElementsInXrefStructure(int maxNumberOfElementsInXrefStructure) {
193+
this.maxNumberOfElementsInXrefStructure = maxNumberOfElementsInXrefStructure;
194+
}
195+
196+
/**
197+
* Performs a check of possible extension of xref structure.
198+
*
199+
* @param requestedCapacity capacity to which we need to expand xref array.
200+
*/
201+
public void checkIfXrefStructureExceedsTheLimit(int requestedCapacity) {
202+
// Objects in xref structures are using 1-based indexes, so to store maxNumberOfElementsInXrefStructure
203+
// amount of elements we need maxNumberOfElementsInXrefStructure + 1 capacity.
204+
if (requestedCapacity - 1 > maxNumberOfElementsInXrefStructure) {
205+
throw new MemoryLimitsAwareException(KernelExceptionMessageConstant.XREF_STRUCTURE_SIZE_EXCEEDED_THE_LIMIT);
206+
}
207+
}
208+
170209
/**
171210
* Considers the number of bytes which are occupied by the decompressed pdf stream.
172211
* If memory limits have not been faced, throws an exception.

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1958,6 +1958,7 @@ protected void open(PdfVersion newPdfVersion) {
19581958
if (null == memoryLimitsAwareHandler) {
19591959
memoryLimitsAwareHandler = new MemoryLimitsAwareHandler(reader.tokens.getSafeFile().length());
19601960
}
1961+
xref.setMemoryLimitsAwareHandler(memoryLimitsAwareHandler);
19611962
reader.readPdf();
19621963
if (reader.decrypt != null && reader.decrypt.isEmbeddedFilesOnly()) {
19631964
encryptedEmbeddedStreamsHandler.storeAllEmbeddedStreams();

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

Lines changed: 75 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,22 +47,49 @@ This file is part of the iText (R) project.
4747

4848
import java.nio.charset.StandardCharsets;
4949

50+
/**
51+
* A {@code PdfNumber}-class is the PDF-equivalent of a {@code Double}-object.
52+
*
53+
* <p>
54+
* PDF provides two types of numeric objects: integer and real. Integer objects represent mathematical integers. Real
55+
* objects represent mathematical real numbers. The range and precision of numbers may be limited by the internal
56+
* representations used in the computer on which the PDF processor is running.
57+
* An integer shall be written as one or more decimal digits optionally preceded by a sign. The value shall be
58+
* interpreted as a signed decimal integer and shall be converted to an integer object.
59+
* A real value shall be written as one or more decimal digits with an optional sign and a leading, trailing, or
60+
* embedded period (decimal point).
61+
*/
5062
public class PdfNumber extends PdfPrimitiveObject {
5163

5264

5365
private double value;
5466
private boolean isDouble;
5567

68+
/**
69+
* Creates an instance of {@link PdfNumber} and sets value.
70+
*
71+
* @param value double value to set
72+
*/
5673
public PdfNumber(double value) {
5774
super();
5875
setValue(value);
5976
}
6077

78+
/**
79+
* Creates an instance of {@link PdfNumber} and sets value.
80+
*
81+
* @param value int value to set
82+
*/
6183
public PdfNumber(int value) {
6284
super();
6385
setValue(value);
6486
}
6587

88+
/**
89+
* Creates an instance of {@link PdfNumber} with provided content.
90+
*
91+
* @param content byte array content to set
92+
*/
6693
public PdfNumber(byte[] content) {
6794
super(content);
6895
this.isDouble = true;
@@ -78,44 +105,90 @@ public byte getType() {
78105
return NUMBER;
79106
}
80107

108+
/**
109+
* Returns value of current instance of {@link PdfNumber}.
110+
*
111+
* @return value of {@link PdfNumber} instance
112+
*/
81113
public double getValue() {
82114
if (java.lang.Double.isNaN(value))
83115
generateValue();
84116
return value;
85117
}
86118

119+
/**
120+
* Returns double value of current instance of {@link PdfNumber}.
121+
*
122+
* @return double value of {@link PdfNumber} instance
123+
*/
87124
public double doubleValue() {
88125
return getValue();
89126
}
90127

128+
/**
129+
* Returns value and converts it to float.
130+
*
131+
* @return value converted to float
132+
*/
91133
public float floatValue() {
92134
return (float) getValue();
93135
}
94136

137+
/**
138+
* Returns value and converts it to long.
139+
*
140+
* @return value converted to long
141+
*/
95142
public long longValue() {
96143
return (long) getValue();
97144
}
98145

146+
/**
147+
* Returns value and converts it to an int. If value surpasses {@link Integer#MAX_VALUE}, {@link Integer#MAX_VALUE}
148+
* would be return.
149+
*
150+
* @return value converted to int
151+
*/
99152
public int intValue() {
100-
return (int) getValue();
153+
if (getValue() > (double) Integer.MAX_VALUE) {
154+
return Integer.MAX_VALUE;
155+
} else {
156+
return (int) getValue();
157+
}
101158
}
102159

160+
/**
161+
* Sets value and convert it to double.
162+
*
163+
* @param value to set
164+
*/
103165
public void setValue(int value) {
104166
this.value = value;
105167
this.isDouble = false;
106168
this.content = null;
107169
}
108170

171+
/**
172+
* Sets value.
173+
*
174+
* @param value to set
175+
*/
109176
public void setValue(double value) {
110177
this.value = value;
111178
this.isDouble = true;
112179
this.content = null;
113180
}
114181

182+
/**
183+
* Increments current value.
184+
*/
115185
public void increment() {
116186
setValue(++value);
117187
}
118188

189+
/**
190+
* Decrements current value.
191+
*/
119192
public void decrement() {
120193
setValue(--value);
121194
}
@@ -144,6 +217,7 @@ public boolean equals(Object o) {
144217

145218
/**
146219
* Checks if string representation of the value contains decimal point.
220+
*
147221
* @return true if contains so the number must be real not integer
148222
*/
149223
public boolean hasDecimalPoint() {

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

Lines changed: 5 additions & 3 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.MemoryLimitsAwareException;
5556
import com.itextpdf.kernel.exceptions.PdfException;
5657
import com.itextpdf.kernel.crypto.securityhandler.UnsupportedSecurityHandlerException;
5758
import com.itextpdf.kernel.exceptions.KernelExceptionMessageConstant;
@@ -729,9 +730,10 @@ protected void readPdf() throws IOException {
729730
}
730731
try {
731732
readXref();
732-
} catch (XrefCycledReferencesException ex) {
733+
} catch (XrefCycledReferencesException | MemoryLimitsAwareException ex) {
733734
// Throws an exception when xref stream has cycled references(due to lack of opportunity to fix such an
734735
// issue) or xref tables have cycled references and PdfReader.StrictnessLevel set to CONSERVATIVE.
736+
// Also throw an exception when xref structure size exceeds jvm memory limit.
735737
throw ex;
736738
} catch (RuntimeException ex) {
737739
Logger logger = LoggerFactory.getLogger(PdfReader.class);
@@ -987,8 +989,8 @@ protected void readXref() throws IOException {
987989
xrefStm = true;
988990
return;
989991
}
990-
} catch (XrefCycledReferencesException cycledReferencesException) {
991-
throw cycledReferencesException;
992+
} catch (XrefCycledReferencesException | MemoryLimitsAwareException exceptionWhileReadingXrefStream) {
993+
throw exceptionWhileReadingXrefStream;
992994
} catch (Exception ignored) {
993995
// Do nothing.
994996
}

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

Lines changed: 60 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ public class PdfXrefTable {
7575
private PdfIndirectReference[] xref;
7676
private int count = 0;
7777
private boolean readingCompleted;
78+
private MemoryLimitsAwareHandler memoryLimitsAwareHandler;
7879

7980
/**
8081
* Free references linked list is stored in a form of a map, where:
@@ -83,19 +84,61 @@ public class PdfXrefTable {
8384
*/
8485
private final TreeMap<Integer, PdfIndirectReference> freeReferencesLinkedList;
8586

87+
/**
88+
* Creates a {@link PdfXrefTable} which will be used to store xref structure of the pdf document.
89+
* Capacity and {@link MemoryLimitsAwareHandler} instance would be set by default values.
90+
*/
8691
public PdfXrefTable() {
8792
this(INITIAL_CAPACITY);
8893
}
8994

95+
/**
96+
* Creates a {@link PdfXrefTable} which will be used to store xref structure of the pdf document.
97+
*
98+
* @param capacity initial capacity of xref table.
99+
*/
90100
public PdfXrefTable(int capacity) {
101+
this(capacity, null);
102+
}
103+
104+
/**
105+
* Creates a {@link PdfXrefTable} which will be used to store xref structure of the pdf document.
106+
*
107+
* @param memoryLimitsAwareHandler custom {@link MemoryLimitsAwareHandler} to set.
108+
*/
109+
public PdfXrefTable(MemoryLimitsAwareHandler memoryLimitsAwareHandler) {
110+
this(INITIAL_CAPACITY, memoryLimitsAwareHandler);
111+
}
112+
113+
/**
114+
* Creates a {@link PdfXrefTable} which will be used to store xref structure of the pdf document.
115+
*
116+
* @param capacity initial capacity of xref table.
117+
* @param memoryLimitsAwareHandler memoryLimitsAwareHandler custom {@link MemoryLimitsAwareHandler} to set.
118+
*/
119+
public PdfXrefTable(int capacity, MemoryLimitsAwareHandler memoryLimitsAwareHandler) {
91120
if (capacity < 1) {
92-
capacity = INITIAL_CAPACITY;
121+
capacity = memoryLimitsAwareHandler == null ? INITIAL_CAPACITY
122+
: Math.min(INITIAL_CAPACITY, memoryLimitsAwareHandler.getMaxNumberOfElementsInXrefStructure());
123+
}
124+
this.memoryLimitsAwareHandler = memoryLimitsAwareHandler;
125+
if (this.memoryLimitsAwareHandler != null) {
126+
this.memoryLimitsAwareHandler.checkIfXrefStructureExceedsTheLimit(capacity);
93127
}
94-
xref = new PdfIndirectReference[capacity];
95-
freeReferencesLinkedList = new TreeMap<>();
128+
this.xref = new PdfIndirectReference[capacity];
129+
this.freeReferencesLinkedList = new TreeMap<>();
96130
add((PdfIndirectReference) new PdfIndirectReference(null, 0, MAX_GENERATION, 0).setState(PdfObject.FREE));
97131
}
98132

133+
/**
134+
* Sets custom {@link MemoryLimitsAwareHandler}.
135+
*
136+
* @param memoryLimitsAwareHandler instance to set.
137+
*/
138+
public void setMemoryLimitsAwareHandler(MemoryLimitsAwareHandler memoryLimitsAwareHandler) {
139+
this.memoryLimitsAwareHandler = memoryLimitsAwareHandler;
140+
}
141+
99142
/**
100143
* Adds indirect reference to list of indirect objects.
101144
*
@@ -216,6 +259,15 @@ protected void freeReference(PdfIndirectReference reference) {
216259

217260
}
218261

262+
/**
263+
* Gets the capacity of xref stream.
264+
*
265+
* @return the capacity of xref stream.
266+
*/
267+
protected int getCapacity() {
268+
return xref.length;
269+
}
270+
219271
/**
220272
* Increase capacity of the array of indirect references.
221273
*
@@ -592,8 +644,11 @@ private void ensureCount(int count) {
592644
}
593645

594646
private void extendXref(int capacity) {
647+
if (this.memoryLimitsAwareHandler != null) {
648+
this.memoryLimitsAwareHandler.checkIfXrefStructureExceedsTheLimit(capacity);
649+
}
595650
PdfIndirectReference[] newXref = new PdfIndirectReference[capacity];
596-
System.arraycopy(xref, 0, newXref, 0, xref.length);
597-
xref = newXref;
651+
System.arraycopy(this.xref, 0, newXref, 0, this.xref.length);
652+
this.xref = newXref;
598653
}
599654
}

0 commit comments

Comments
 (0)