Skip to content

Commit

Permalink
Fix OOM on reading raw bytes stream
Browse files Browse the repository at this point in the history
DEVSIX-6244
  • Loading branch information
Egor Martsynkovsky authored and Ubuntu committed Feb 16, 2022
1 parent a9af64d commit 3213363
Show file tree
Hide file tree
Showing 6 changed files with 64 additions and 20 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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 "
Expand Down
14 changes: 9 additions & 5 deletions kernel/src/main/java/com/itextpdf/kernel/pdf/PdfReader.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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);
Expand Down Expand Up @@ -1461,21 +1462,24 @@ 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);
String s = tokens.readString(16);
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--;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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()))) {

Expand Down Expand Up @@ -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()))) {

Expand All @@ -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);
}
Expand All @@ -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()))) {

Expand All @@ -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)
);
Expand All @@ -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()))) {

Expand All @@ -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);
}
}
Expand Down Expand Up @@ -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()))) {

Expand All @@ -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()))) {

Expand All @@ -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()))) {

Expand Down
40 changes: 40 additions & 0 deletions kernel/src/test/java/com/itextpdf/kernel/pdf/PdfReaderTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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.
*
Expand Down
Binary file not shown.
Binary file not shown.

0 comments on commit 3213363

Please sign in to comment.