Skip to content

Commit 5186d00

Browse files
Egor MartsynkovskyAndrei Stryhelski
authored andcommitted
Fix OOM on reading raw bytes stream
DEVSIX-6937
1 parent 14477e9 commit 5186d00

File tree

6 files changed

+423
-21
lines changed

6 files changed

+423
-21
lines changed

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

Lines changed: 355 additions & 0 deletions
Large diffs are not rendered by default.

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

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ This file is part of the iText (R) project.
5454
import com.itextpdf.io.util.MessageFormatUtil;
5555
import com.itextpdf.kernel.PdfException;
5656
import com.itextpdf.kernel.crypto.securityhandler.UnsupportedSecurityHandlerException;
57+
import com.itextpdf.kernel.exceptions.KernelExceptionMessageConstant;
5758
import com.itextpdf.kernel.pdf.filters.FilterHandlers;
5859
import com.itextpdf.kernel.pdf.filters.IFilterHandler;
5960

@@ -381,8 +382,9 @@ public byte[] readStreamBytes(PdfStream stream, boolean decode) throws IOExcepti
381382
*/
382383
public byte[] readStreamBytesRaw(PdfStream stream) throws IOException {
383384
PdfName type = stream.getAsName(PdfName.Type);
384-
if (!PdfName.XRefStm.equals(type) && !PdfName.ObjStm.equals(type))
385+
if (!PdfName.XRef.equals(type) && !PdfName.ObjStm.equals(type)) {
385386
checkPdfStreamLength(stream);
387+
}
386388
long offset = stream.getOffset();
387389
if (offset <= 0)
388390
return null;
@@ -392,7 +394,7 @@ public byte[] readStreamBytesRaw(PdfStream stream) throws IOException {
392394
RandomAccessFileOrArray file = tokens.getSafeFile();
393395
byte[] bytes = null;
394396
try {
395-
file.seek(stream.getOffset());
397+
file.seek(offset);
396398
bytes = new byte[length];
397399
file.readFully(bytes);
398400
boolean embeddedStream = pdfDocument.doesStreamBelongToEmbeddedFile(stream);
@@ -1416,21 +1418,24 @@ private void checkPdfStreamLength(PdfStream pdfStream) throws IOException {
14161418
line.reset();
14171419

14181420
// added boolean because of mailing list issue (17 Feb. 2014)
1419-
if (!tokens.readLineSegment(line, false))
1421+
if (!tokens.readLineSegment(line, false)) {
1422+
if (!StrictnessLevel.CONSERVATIVE.isStricter(this.strictnessLevel)) {
1423+
throw new PdfException(KernelExceptionMessageConstant.STREAM_SHALL_END_WITH_ENDSTREAM);
1424+
}
14201425
break;
1426+
}
14211427
if (line.startsWith(endstream)) {
1422-
streamLength = (int) (pos - start);
14231428
break;
14241429
} else if (line.startsWith(endobj)) {
14251430
tokens.seek(pos - 16);
14261431
String s = tokens.readString(16);
14271432
int index = s.indexOf(endstream1);
14281433
if (index >= 0)
14291434
pos = pos - 16 + index;
1430-
streamLength = (int) (pos - start);
14311435
break;
14321436
}
14331437
}
1438+
streamLength = (int) (pos - start);
14341439
tokens.seek(pos - 2);
14351440
if (tokens.read() == 13) {
14361441
streamLength--;

kernel/src/test/java/com/itextpdf/kernel/pdf/PdfReaderDecodeTest.java

Lines changed: 14 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -59,12 +59,12 @@ This file is part of the iText (R) project.
5959
@Category(IntegrationTest.class)
6060
public class PdfReaderDecodeTest extends ExtendedITextTest {
6161

62-
public static final String sourceFolder = "./src/test/resources/com/itextpdf/kernel/pdf/PdfReaderDecodeTest/";
62+
public static final String SOURCE_FOLDER = "./src/test/resources/com/itextpdf/kernel/pdf/PdfReaderDecodeTest/";
6363

6464
@Test
6565
public void noMemoryHandlerTest() throws IOException {
6666
try (PdfDocument pdfDocument = new PdfDocument(new PdfWriter(new ByteArrayOutputStream()));
67-
FileInputStream is = new FileInputStream(sourceFolder + "stream")) {
67+
FileInputStream is = new FileInputStream(SOURCE_FOLDER + "stream")) {
6868
byte[] b = new byte[51];
6969
is.read(b);
7070

@@ -97,7 +97,7 @@ public void noMemoryHandlerTest() throws IOException {
9797
})
9898
public void defaultMemoryHandlerTest() throws IOException {
9999
try (PdfDocument pdfDocument = new PdfDocument(
100-
new PdfReader(sourceFolder + "timing.pdf"),
100+
new PdfReader(SOURCE_FOLDER + "timing.pdf"),
101101
new PdfWriter(new ByteArrayOutputStream()))) {
102102
PdfStream stream = pdfDocument.getFirstPage().getContentStream(0);
103103
byte[] b = stream.getBytes(false);
@@ -128,7 +128,7 @@ public void customMemoryHandlerSingleTest() throws IOException {
128128
handler.setMaxSizeOfSingleDecompressedPdfStream(1000);
129129

130130
try (PdfDocument pdfDocument = new PdfDocument(
131-
new PdfReader(sourceFolder + "timing.pdf",
131+
new PdfReader(SOURCE_FOLDER + "timing.pdf",
132132
new ReaderProperties().setMemoryLimitsAwareHandler(handler)),
133133
new PdfWriter(new ByteArrayOutputStream()))) {
134134

@@ -165,7 +165,7 @@ public void oneFilterCustomMemoryHandlerSingleTest() throws IOException {
165165
handler.setMaxSizeOfSingleDecompressedPdfStream(20);
166166

167167
try (PdfDocument pdfDocument = new PdfDocument(
168-
new PdfReader(sourceFolder + "timing.pdf",
168+
new PdfReader(SOURCE_FOLDER + "timing.pdf",
169169
new ReaderProperties().setMemoryLimitsAwareHandler(handler)),
170170
new PdfWriter(new ByteArrayOutputStream()))) {
171171

@@ -175,10 +175,10 @@ public void oneFilterCustomMemoryHandlerSingleTest() throws IOException {
175175
PdfArray array = new PdfArray();
176176
stream.put(PdfName.Filter, array);
177177

178-
// Limit is reached, but the stream has no filters. Therefore we don't consider ot to be suspicious
178+
// Limit is reached, but the stream has no filters. Therefore, we don't consider it to be suspicious.
179179
Assert.assertEquals(51, PdfReader.decodeBytes(b, stream).length);
180180

181-
// Limit is reached, but the stream has only one filter. Therefore we don't consider ot to be suspicious
181+
// Limit is reached, but the stream has only one filter. Therefore, we don't consider it to be suspicious.
182182
array.add(PdfName.Fl);
183183
Assert.assertEquals(40, PdfReader.decodeBytes(b, stream).length);
184184
}
@@ -199,7 +199,7 @@ public boolean isMemoryLimitsAwarenessRequiredOnDecompression(PdfArray filters)
199199
handler.setMaxSizeOfSingleDecompressedPdfStream(20);
200200

201201
try (PdfDocument pdfDocument = new PdfDocument(
202-
new PdfReader(sourceFolder + "timing.pdf",
202+
new PdfReader(SOURCE_FOLDER + "timing.pdf",
203203
new ReaderProperties().setMemoryLimitsAwareHandler(handler)),
204204
new PdfWriter(new ByteArrayOutputStream()))) {
205205

@@ -210,7 +210,7 @@ public boolean isMemoryLimitsAwarenessRequiredOnDecompression(PdfArray filters)
210210
stream.put(PdfName.Filter, array);
211211
array.add(PdfName.Fl);
212212

213-
// Limit is reached, and the stream with one filter is considered to be suspicious
213+
// Limit is reached, and the stream with one filter is considered to be suspicious.
214214
Exception e = Assert.assertThrows(MemoryLimitsAwareException.class,
215215
() -> PdfReader.decodeBytes(b, stream)
216216
);
@@ -234,7 +234,7 @@ public boolean isMemoryLimitsAwarenessRequiredOnDecompression(PdfArray filters)
234234
handler.setMaxSizeOfSingleDecompressedPdfStream(20);
235235

236236
try (PdfDocument pdfDocument = new PdfDocument(
237-
new PdfReader(sourceFolder + "timing.pdf",
237+
new PdfReader(SOURCE_FOLDER + "timing.pdf",
238238
new ReaderProperties().setMemoryLimitsAwareHandler(handler)),
239239
new PdfWriter(new ByteArrayOutputStream()))) {
240240

@@ -246,8 +246,7 @@ public boolean isMemoryLimitsAwarenessRequiredOnDecompression(PdfArray filters)
246246
array.add(PdfName.Fl);
247247
array.add(PdfName.Fl);
248248

249-
// Limit is reached but the stream with several copies of the filter is not considered
250-
// to be suspicious
249+
// Limit is reached but the stream with several copies of the filter is not considered to be suspicious.
251250
PdfReader.decodeBytes(b, stream);
252251
}
253252
}
@@ -278,7 +277,7 @@ public void customMemoryHandlerSumTest() throws IOException {
278277
handler.setMaxSizeOfDecompressedPdfStreamsSum(100000);
279278

280279
try (PdfDocument pdfDocument = new PdfDocument(
281-
new PdfReader(sourceFolder + "timing.pdf",
280+
new PdfReader(SOURCE_FOLDER + "timing.pdf",
282281
new ReaderProperties().setMemoryLimitsAwareHandler(handler)),
283282
new PdfWriter(new ByteArrayOutputStream()))) {
284283

@@ -302,7 +301,7 @@ public void pageSumTest() throws IOException {
302301
handler.setMaxSizeOfDecompressedPdfStreamsSum(1500000);
303302

304303
try (PdfDocument pdfDocument = new PdfDocument(
305-
new PdfReader(sourceFolder + "timing.pdf",
304+
new PdfReader(SOURCE_FOLDER + "timing.pdf",
306305
new ReaderProperties().setMemoryLimitsAwareHandler(handler)),
307306
new PdfWriter(new ByteArrayOutputStream()))) {
308307

@@ -323,7 +322,7 @@ public void pageAsSingleStreamTest() throws IOException {
323322
handler.setMaxSizeOfSingleDecompressedPdfStream(1500000);
324323

325324
try (PdfDocument pdfDocument = new PdfDocument(
326-
new PdfReader(sourceFolder + "timing.pdf",
325+
new PdfReader(SOURCE_FOLDER + "timing.pdf",
327326
new ReaderProperties().setMemoryLimitsAwareHandler(handler)),
328327
new PdfWriter(new ByteArrayOutputStream()))) {
329328

kernel/src/test/java/com/itextpdf/kernel/pdf/PdfReaderTest.java

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,11 @@ This file is part of the iText (R) project.
5050
import com.itextpdf.io.util.FileUtil;
5151
import com.itextpdf.io.util.MessageFormatUtil;
5252
import com.itextpdf.kernel.PdfException;
53+
import com.itextpdf.kernel.exceptions.KernelExceptionMessageConstant;
54+
import com.itextpdf.kernel.pdf.PdfReader.StrictnessLevel;
5355
import com.itextpdf.kernel.utils.CompareTool;
56+
import com.itextpdf.kernel.xmp.XMPException;
57+
import com.itextpdf.test.AssertUtil;
5458
import com.itextpdf.test.ExtendedITextTest;
5559
import com.itextpdf.test.annotations.LogMessage;
5660
import com.itextpdf.test.annotations.LogMessages;
@@ -2045,7 +2049,7 @@ public void notCloseUserStreamTest() throws IOException {
20452049
Assert.assertEquals(-1, pdfStream.read());
20462050
}
20472051
}
2048-
2052+
20492053
private static File copyFileForTest(String fileName, String copiedFileName) throws IOException {
20502054
File copiedFile = new File(copiedFileName);
20512055
Files.copy(Paths.get(fileName), Paths.get(copiedFileName));
@@ -2063,6 +2067,45 @@ private void readingNotCompletedTest(PdfReader reader) {
20632067
Assert.assertEquals(PdfException.DocumentHasNotBeenReadYet, e.getMessage());
20642068
}
20652069

2070+
@Test
2071+
public void streamWithoutEndstreamKeywordTest() throws IOException, XMPException {
2072+
final String fileName = sourceFolder + "NoEndstreamKeyword.pdf";
2073+
try (PdfReader reader = new PdfReader(fileName)) {
2074+
reader.setStrictnessLevel(StrictnessLevel.LENIENT);
2075+
try (PdfDocument document = new PdfDocument(reader)) {
2076+
final PdfCatalog catalog = new PdfCatalog((PdfDictionary) reader.trailer
2077+
.get(PdfName.Root, true));
2078+
final PdfStream xmpMetadataStream = catalog.getPdfObject().getAsStream(PdfName.Metadata);
2079+
final int xmpMetadataStreamLength = ((PdfNumber) xmpMetadataStream.get(PdfName.Length)).intValue();
2080+
2081+
// 27600 is actual invalid length of stream. In reader StrictnessLevel#LENIENT we expect, that this
2082+
// length will be fixed.
2083+
Assert.assertNotEquals(27600, xmpMetadataStreamLength);
2084+
2085+
// 3090 is expected length of the stream after fix.
2086+
Assert.assertEquals(3090, xmpMetadataStreamLength);
2087+
}
2088+
}
2089+
}
2090+
2091+
@Test
2092+
public void streamWithoutEndstreamKeywordConservativeModeTest() throws IOException, XMPException {
2093+
final String fileName = sourceFolder + "NoEndstreamKeyword.pdf";
2094+
try (PdfReader reader = new PdfReader(fileName)) {
2095+
reader.setStrictnessLevel(StrictnessLevel.CONSERVATIVE);
2096+
2097+
Exception exception = Assert.assertThrows(PdfException.class, () -> new PdfDocument(reader));
2098+
Assert.assertEquals(KernelExceptionMessageConstant.STREAM_SHALL_END_WITH_ENDSTREAM, exception.getMessage());
2099+
2100+
PdfCatalog catalog = new PdfCatalog((PdfDictionary) reader.trailer.get(PdfName.Root, true));
2101+
PdfStream xmpMetadataStream = catalog.getPdfObject().getAsStream(PdfName.Metadata);
2102+
2103+
// 27600 is actual invalid length of stream. In reader StrictnessLevel#CONSERVATIVE we expect, that
2104+
// exception would be thrown and length wouldn't be fixed.
2105+
Assert.assertEquals(27600, ((PdfNumber) xmpMetadataStream.get(PdfName.Length)).intValue());
2106+
}
2107+
}
2108+
20662109
/**
20672110
* Returns the current memory use.
20682111
*

0 commit comments

Comments
 (0)