From 321336392695ead6f071cb60240956282d254782 Mon Sep 17 00:00:00 2001 From: Egor Martsynkovsky Date: Sun, 6 Feb 2022 20:13:16 +0300 Subject: [PATCH] Fix OOM on reading raw bytes stream DEVSIX-6244 --- .../KernelExceptionMessageConstant.java | 1 + .../com/itextpdf/kernel/pdf/PdfReader.java | 14 +++--- .../kernel/pdf/PdfReaderDecodeTest.java | 29 ++++++------- .../itextpdf/kernel/pdf/PdfReaderTest.java | 40 ++++++++++++++++++ .../PdfReaderTest/CompressionWrongObjStm.pdf | Bin 3720 -> 3721 bytes .../pdf/PdfReaderTest/NoEndstreamKeyword.pdf | Bin 0 -> 3634 bytes 6 files changed, 64 insertions(+), 20 deletions(-) create mode 100644 kernel/src/test/resources/com/itextpdf/kernel/pdf/PdfReaderTest/NoEndstreamKeyword.pdf diff --git a/kernel/src/main/java/com/itextpdf/kernel/exceptions/KernelExceptionMessageConstant.java b/kernel/src/main/java/com/itextpdf/kernel/exceptions/KernelExceptionMessageConstant.java index 225545561d..cde3efbede 100644 --- a/kernel/src/main/java/com/itextpdf/kernel/exceptions/KernelExceptionMessageConstant.java +++ b/kernel/src/main/java/com/itextpdf/kernel/exceptions/KernelExceptionMessageConstant.java @@ -271,6 +271,7 @@ public final class KernelExceptionMessageConstant { + "do not contain ExtGState entry. Unable to process operator {0}."; public static final String SHADING_TYPE_NOT_FOUND = "Shading type not found."; public static final String STDCF_NOT_FOUND_ENCRYPTION = "/StdCF not found (encryption)"; + public static final String STREAM_SHALL_END_WITH_ENDSTREAM = "Stream shall end with endstream keyword."; public static final String STRUCT_PARENT_INDEX_NOT_FOUND_IN_TAGGED_OBJECT = "StructParent index not found in " + "tagged object."; public static final String STRUCTURE_ELEMENT_IN_STRUCTURE_DESTINATION_SHALL_BE_AN_INDIRECT_OBJECT = "Structure " diff --git a/kernel/src/main/java/com/itextpdf/kernel/pdf/PdfReader.java b/kernel/src/main/java/com/itextpdf/kernel/pdf/PdfReader.java index 25296ef0c7..a5f9d28c8e 100644 --- a/kernel/src/main/java/com/itextpdf/kernel/pdf/PdfReader.java +++ b/kernel/src/main/java/com/itextpdf/kernel/pdf/PdfReader.java @@ -382,8 +382,9 @@ public byte[] readStreamBytes(PdfStream stream, boolean decode) throws IOExcepti */ public byte[] readStreamBytesRaw(PdfStream stream) throws IOException { PdfName type = stream.getAsName(PdfName.Type); - if (!PdfName.XRefStm.equals(type) && !PdfName.ObjStm.equals(type)) + if (!PdfName.XRef.equals(type) && !PdfName.ObjStm.equals(type)) { checkPdfStreamLength(stream); + } long offset = stream.getOffset(); if (offset <= 0) return null; @@ -393,7 +394,7 @@ public byte[] readStreamBytesRaw(PdfStream stream) throws IOException { RandomAccessFileOrArray file = tokens.getSafeFile(); byte[] bytes = null; try { - file.seek(stream.getOffset()); + file.seek(offset); bytes = new byte[length]; file.readFully(bytes); boolean embeddedStream = pdfDocument.doesStreamBelongToEmbeddedFile(stream); @@ -1461,10 +1462,13 @@ private void checkPdfStreamLength(PdfStream pdfStream) throws IOException { line.reset(); // added boolean because of mailing list issue (17 Feb. 2014) - if (!tokens.readLineSegment(line, false)) + if (!tokens.readLineSegment(line, false)) { + if (!StrictnessLevel.CONSERVATIVE.isStricter(this.strictnessLevel)) { + throw new PdfException(KernelExceptionMessageConstant.STREAM_SHALL_END_WITH_ENDSTREAM); + } break; + } if (line.startsWith(endstream)) { - streamLength = (int) (pos - start); break; } else if (line.startsWith(endobj)) { tokens.seek(pos - 16); @@ -1472,10 +1476,10 @@ private void checkPdfStreamLength(PdfStream pdfStream) throws IOException { int index = s.indexOf(endstream1); if (index >= 0) pos = pos - 16 + index; - streamLength = (int) (pos - start); break; } } + streamLength = (int) (pos - start); tokens.seek(pos - 2); if (tokens.read() == 13) { streamLength--; diff --git a/kernel/src/test/java/com/itextpdf/kernel/pdf/PdfReaderDecodeTest.java b/kernel/src/test/java/com/itextpdf/kernel/pdf/PdfReaderDecodeTest.java index 3e5530ae44..38cc17ceb6 100644 --- a/kernel/src/test/java/com/itextpdf/kernel/pdf/PdfReaderDecodeTest.java +++ b/kernel/src/test/java/com/itextpdf/kernel/pdf/PdfReaderDecodeTest.java @@ -60,12 +60,12 @@ This file is part of the iText (R) project. @Category(IntegrationTest.class) public class PdfReaderDecodeTest extends ExtendedITextTest { - public static final String sourceFolder = "./src/test/resources/com/itextpdf/kernel/pdf/PdfReaderDecodeTest/"; + public static final String SOURCE_FOLDER = "./src/test/resources/com/itextpdf/kernel/pdf/PdfReaderDecodeTest/"; @Test public void noMemoryHandlerTest() throws IOException { try (PdfDocument pdfDocument = new PdfDocument(new PdfWriter(new ByteArrayOutputStream())); - FileInputStream is = new FileInputStream(sourceFolder + "stream")) { + FileInputStream is = new FileInputStream(SOURCE_FOLDER + "stream")) { byte[] b = new byte[51]; is.read(b); @@ -98,7 +98,7 @@ public void noMemoryHandlerTest() throws IOException { }) public void defaultMemoryHandlerTest() throws IOException { try (PdfDocument pdfDocument = new PdfDocument( - new PdfReader(sourceFolder + "timing.pdf"), + new PdfReader(SOURCE_FOLDER + "timing.pdf"), new PdfWriter(new ByteArrayOutputStream()))) { PdfStream stream = pdfDocument.getFirstPage().getContentStream(0); byte[] b = stream.getBytes(false); @@ -129,7 +129,7 @@ public void customMemoryHandlerSingleTest() throws IOException { handler.setMaxSizeOfSingleDecompressedPdfStream(1000); try (PdfDocument pdfDocument = new PdfDocument( - new PdfReader(sourceFolder + "timing.pdf", + new PdfReader(SOURCE_FOLDER + "timing.pdf", new ReaderProperties().setMemoryLimitsAwareHandler(handler)), new PdfWriter(new ByteArrayOutputStream()))) { @@ -166,7 +166,7 @@ public void oneFilterCustomMemoryHandlerSingleTest() throws IOException { handler.setMaxSizeOfSingleDecompressedPdfStream(20); try (PdfDocument pdfDocument = new PdfDocument( - new PdfReader(sourceFolder + "timing.pdf", + new PdfReader(SOURCE_FOLDER + "timing.pdf", new ReaderProperties().setMemoryLimitsAwareHandler(handler)), new PdfWriter(new ByteArrayOutputStream()))) { @@ -176,10 +176,10 @@ public void oneFilterCustomMemoryHandlerSingleTest() throws IOException { PdfArray array = new PdfArray(); stream.put(PdfName.Filter, array); - // Limit is reached, but the stream has no filters. Therefore we don't consider ot to be suspicious + // Limit is reached, but the stream has no filters. Therefore, we don't consider it to be suspicious. Assert.assertEquals(51, PdfReader.decodeBytes(b, stream).length); - // Limit is reached, but the stream has only one filter. Therefore we don't consider ot to be suspicious + // Limit is reached, but the stream has only one filter. Therefore, we don't consider it to be suspicious. array.add(PdfName.Fl); Assert.assertEquals(40, PdfReader.decodeBytes(b, stream).length); } @@ -200,7 +200,7 @@ public boolean isMemoryLimitsAwarenessRequiredOnDecompression(PdfArray filters) handler.setMaxSizeOfSingleDecompressedPdfStream(20); try (PdfDocument pdfDocument = new PdfDocument( - new PdfReader(sourceFolder + "timing.pdf", + new PdfReader(SOURCE_FOLDER + "timing.pdf", new ReaderProperties().setMemoryLimitsAwareHandler(handler)), new PdfWriter(new ByteArrayOutputStream()))) { @@ -211,7 +211,7 @@ public boolean isMemoryLimitsAwarenessRequiredOnDecompression(PdfArray filters) stream.put(PdfName.Filter, array); array.add(PdfName.Fl); - // Limit is reached, and the stream with one filter is considered to be suspicious + // Limit is reached, and the stream with one filter is considered to be suspicious. Exception e = Assert.assertThrows(MemoryLimitsAwareException.class, () -> PdfReader.decodeBytes(b, stream) ); @@ -235,7 +235,7 @@ public boolean isMemoryLimitsAwarenessRequiredOnDecompression(PdfArray filters) handler.setMaxSizeOfSingleDecompressedPdfStream(20); try (PdfDocument pdfDocument = new PdfDocument( - new PdfReader(sourceFolder + "timing.pdf", + new PdfReader(SOURCE_FOLDER + "timing.pdf", new ReaderProperties().setMemoryLimitsAwareHandler(handler)), new PdfWriter(new ByteArrayOutputStream()))) { @@ -247,8 +247,7 @@ public boolean isMemoryLimitsAwarenessRequiredOnDecompression(PdfArray filters) array.add(PdfName.Fl); array.add(PdfName.Fl); - // Limit is reached but the stream with several copies of the filter is not considered - // to be suspicious + // Limit is reached but the stream with several copies of the filter is not considered to be suspicious. PdfReader.decodeBytes(b, stream); } } @@ -279,7 +278,7 @@ public void customMemoryHandlerSumTest() throws IOException { handler.setMaxSizeOfDecompressedPdfStreamsSum(100000); try (PdfDocument pdfDocument = new PdfDocument( - new PdfReader(sourceFolder + "timing.pdf", + new PdfReader(SOURCE_FOLDER + "timing.pdf", new ReaderProperties().setMemoryLimitsAwareHandler(handler)), new PdfWriter(new ByteArrayOutputStream()))) { @@ -303,7 +302,7 @@ public void pageSumTest() throws IOException { handler.setMaxSizeOfDecompressedPdfStreamsSum(1500000); try (PdfDocument pdfDocument = new PdfDocument( - new PdfReader(sourceFolder + "timing.pdf", + new PdfReader(SOURCE_FOLDER + "timing.pdf", new ReaderProperties().setMemoryLimitsAwareHandler(handler)), new PdfWriter(new ByteArrayOutputStream()))) { @@ -324,7 +323,7 @@ public void pageAsSingleStreamTest() throws IOException { handler.setMaxSizeOfSingleDecompressedPdfStream(1500000); try (PdfDocument pdfDocument = new PdfDocument( - new PdfReader(sourceFolder + "timing.pdf", + new PdfReader(SOURCE_FOLDER + "timing.pdf", new ReaderProperties().setMemoryLimitsAwareHandler(handler)), new PdfWriter(new ByteArrayOutputStream()))) { diff --git a/kernel/src/test/java/com/itextpdf/kernel/pdf/PdfReaderTest.java b/kernel/src/test/java/com/itextpdf/kernel/pdf/PdfReaderTest.java index e46827b83c..256a4b4616 100644 --- a/kernel/src/test/java/com/itextpdf/kernel/pdf/PdfReaderTest.java +++ b/kernel/src/test/java/com/itextpdf/kernel/pdf/PdfReaderTest.java @@ -57,6 +57,7 @@ This file is part of the iText (R) project. import com.itextpdf.kernel.exceptions.XrefCycledReferencesException; import com.itextpdf.kernel.pdf.PdfReader.StrictnessLevel; import com.itextpdf.kernel.utils.CompareTool; +import com.itextpdf.kernel.xmp.XMPException; import com.itextpdf.test.AssertUtil; import com.itextpdf.test.ExtendedITextTest; import com.itextpdf.test.annotations.LogMessage; @@ -2518,6 +2519,45 @@ public void readPdfInvalidPrevConservativeModeTest() throws IOException { } } + @Test + public void streamWithoutEndstreamKeywordTest() throws IOException, XMPException { + final String fileName = SOURCE_FOLDER + "NoEndstreamKeyword.pdf"; + try (PdfReader reader = new PdfReader(fileName)) { + reader.setStrictnessLevel(StrictnessLevel.LENIENT); + try (PdfDocument document = new PdfDocument(reader)) { + final PdfCatalog catalog = new PdfCatalog((PdfDictionary) reader.trailer + .get(PdfName.Root, true)); + final PdfStream xmpMetadataStream = catalog.getPdfObject().getAsStream(PdfName.Metadata); + final int xmpMetadataStreamLength = ((PdfNumber) xmpMetadataStream.get(PdfName.Length)).intValue(); + + // 27600 is actual invalid length of stream. In reader StrictnessLevel#LENIENT we expect, that this + // length will be fixed. + Assert.assertNotEquals(27600, xmpMetadataStreamLength); + + // 3090 is expected length of the stream after fix. + Assert.assertEquals(3090, xmpMetadataStreamLength); + } + } + } + + @Test + public void streamWithoutEndstreamKeywordConservativeModeTest() throws IOException, XMPException { + final String fileName = SOURCE_FOLDER + "NoEndstreamKeyword.pdf"; + try (PdfReader reader = new PdfReader(fileName)) { + reader.setStrictnessLevel(StrictnessLevel.CONSERVATIVE); + + Exception exception = Assert.assertThrows(PdfException.class, () -> new PdfDocument(reader)); + Assert.assertEquals(KernelExceptionMessageConstant.STREAM_SHALL_END_WITH_ENDSTREAM, exception.getMessage()); + + PdfCatalog catalog = new PdfCatalog((PdfDictionary) reader.trailer.get(PdfName.Root, true)); + PdfStream xmpMetadataStream = catalog.getPdfObject().getAsStream(PdfName.Metadata); + + // 27600 is actual invalid length of stream. In reader StrictnessLevel#CONSERVATIVE we expect, that + // exception would be thrown and length wouldn't be fixed. + Assert.assertEquals(27600, ((PdfNumber) xmpMetadataStream.get(PdfName.Length)).intValue()); + } + } + /** * Returns the current memory use. * diff --git a/kernel/src/test/resources/com/itextpdf/kernel/pdf/PdfReaderTest/CompressionWrongObjStm.pdf b/kernel/src/test/resources/com/itextpdf/kernel/pdf/PdfReaderTest/CompressionWrongObjStm.pdf index cf783c2091d6bdbf5dca2499c623d4549240a4b5..f4bb25d3abf68a88f3d90e43cf795c1f447fa524 100644 GIT binary patch delta 16 XcmeB>?UdbMz{_l8YP8viH;fSgCvgNG delta 15 WcmeB_?U3DIz{_N4u-TY5j1d4MdjsYG diff --git a/kernel/src/test/resources/com/itextpdf/kernel/pdf/PdfReaderTest/NoEndstreamKeyword.pdf b/kernel/src/test/resources/com/itextpdf/kernel/pdf/PdfReaderTest/NoEndstreamKeyword.pdf new file mode 100644 index 0000000000000000000000000000000000000000..2c1847d54eefa5745c254c568426c8ed82fb876b GIT binary patch literal 3634 zcmeHKO^(|(6mHR-PC=+}GlN1RMOl`u$_gfSodg+A4BKb}OlCn#v_~_xC{T2*nX~i) z+2jIQ6vzd-$PLm1^a>?q$(o<0^(H$L0!Tf+?>+K+--53(>JJRdbhXCkzyAEBIRF7R zyV86g4&tSt91fO5&^`@WMByn-7UB}LgCG-}l9iVK{@pine5=tUDot{w0yOJDJ(dWX zhzzLUV>qNyOrEj)9LZm&n^(1Hem zO0H5`b;=Wj~Mv{MB{Zfe3gK)Fim>b(+7Fq<8Ld4PQM8)fvxk z_5{d`UZvKqUg+-Qzw7m$T#v!WFUqxKUGg4soX#}_ zTur{X%K=)BV>@6DDjP$0j(5mz1ma z>t!FD`}3A-Be#t(wU8AxaTHpuS%*^M5`@E!8~ne=1$>#z8Bh(*7>*f}{X(&v#J92^ hkv&4Ac<~vkKQZq2Ct4;57X?wajgi)9{QP>L{R4MC$O-@e literal 0 HcmV?d00001