Skip to content

Commit 70cdd0f

Browse files
dmitry.radchukAndrei Stryhelski
authored andcommitted
Change array parsing logic to prevent StackOverflowException
DEVSIX-6939
1 parent 5186d00 commit 70cdd0f

File tree

15 files changed

+195
-11
lines changed

15 files changed

+195
-11
lines changed

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

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,8 @@ This file is part of the iText (R) project.
6464
import java.io.IOException;
6565
import java.io.InputStream;
6666
import java.io.Serializable;
67+
import java.util.HashSet;
68+
import java.nio.charset.StandardCharsets;
6769
import java.util.Map;
6870

6971
import com.itextpdf.kernel.xmp.XMPException;
@@ -936,9 +938,11 @@ protected PdfDictionary readDictionary(boolean objStm) throws IOException {
936938
PdfObject obj = readObject(true, objStm);
937939
if (obj == null) {
938940
if (tokens.getTokenType() == PdfTokenizer.TokenType.EndDic)
939-
tokens.throwError(PdfException.UnexpectedGtGt);
941+
tokens.throwError(MessageFormatUtil.
942+
format(KernelExceptionMessageConstant.UNEXPECTED_TOKEN, ">>"));
940943
if (tokens.getTokenType() == PdfTokenizer.TokenType.EndArray)
941-
tokens.throwError(PdfException.UnexpectedCloseBracket);
944+
tokens.throwError(MessageFormatUtil.
945+
format(KernelExceptionMessageConstant.UNEXPECTED_TOKEN, "]"));
942946
}
943947
dic.put(name, obj);
944948
}
@@ -950,10 +954,10 @@ protected PdfArray readArray(boolean objStm) throws IOException {
950954
while (true) {
951955
PdfObject obj = readObject(true, objStm);
952956
if (obj == null) {
953-
if (tokens.getTokenType() == PdfTokenizer.TokenType.EndArray)
954-
break;
955-
if (tokens.getTokenType() == PdfTokenizer.TokenType.EndDic)
956-
tokens.throwError(PdfException.UnexpectedGtGt);
957+
if (tokens.getTokenType() != PdfTokenizer.TokenType.EndArray) {
958+
processArrayReadError();
959+
}
960+
break;
957961
}
958962
array.add(obj);
959963
}
@@ -1288,6 +1292,17 @@ boolean isMemorySavingMode() {
12881292
return memorySavingMode;
12891293
}
12901294

1295+
private void processArrayReadError() {
1296+
final String error = MessageFormatUtil.format(KernelExceptionMessageConstant.UNEXPECTED_TOKEN,
1297+
new String(tokens.getByteContent(), StandardCharsets.UTF_8));
1298+
if (StrictnessLevel.CONSERVATIVE.isStricter(this.getStrictnessLevel())) {
1299+
final Logger logger = LoggerFactory.getLogger(PdfReader.class);
1300+
logger.error(error);
1301+
} else {
1302+
tokens.throwError(error);
1303+
}
1304+
}
1305+
12911306
private void readDecryptObj() {
12921307
if (encrypted)
12931308
return;

kernel/src/main/java/com/itextpdf/kernel/pdf/canvas/parser/util/PdfCanvasParser.java

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,10 @@ This file is part of the iText (R) project.
4343
*/
4444
package com.itextpdf.kernel.pdf.canvas.parser.util;
4545

46+
import com.itextpdf.io.util.MessageFormatUtil;
4647
import com.itextpdf.kernel.PdfException;
4748
import com.itextpdf.io.source.PdfTokenizer;
49+
import com.itextpdf.kernel.exceptions.KernelExceptionMessageConstant;
4850
import com.itextpdf.kernel.pdf.PdfArray;
4951
import com.itextpdf.kernel.pdf.PdfDictionary;
5052
import com.itextpdf.kernel.pdf.PdfLiteral;
@@ -173,10 +175,12 @@ public PdfArray readArray() throws IOException {
173175
PdfArray array = new PdfArray();
174176
while (true) {
175177
PdfObject obj = readObject();
176-
if (!obj.isArray() && tokeniser.getTokenType() == PdfTokenizer.TokenType.EndArray)
178+
if (!obj.isArray() && tokeniser.getTokenType() == PdfTokenizer.TokenType.EndArray) {
177179
break;
178-
if (tokeniser.getTokenType() == PdfTokenizer.TokenType.EndDic && obj.getType() != PdfObject.DICTIONARY)
179-
tokeniser.throwError(PdfException.UnexpectedGtGt);
180+
}
181+
if (tokeniser.getTokenType() == PdfTokenizer.TokenType.EndDic && obj.getType() != PdfObject.DICTIONARY) {
182+
tokeniser.throwError(MessageFormatUtil.format(KernelExceptionMessageConstant.UNEXPECTED_TOKEN, ">>"));
183+
}
180184
array.add(obj);
181185
}
182186
return array;

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

Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2050,6 +2050,150 @@ public void notCloseUserStreamTest() throws IOException {
20502050
}
20512051
}
20522052

2053+
@Test
2054+
@LogMessages(messages = {
2055+
@LogMessage(messageTemplate = KernelExceptionMessageConstant.UNEXPECTED_TOKEN)
2056+
})
2057+
public void endDicInsteadOfArrayClosingBracketTest() throws IOException {
2058+
String fileName = sourceFolder + "invalidArrayEndDictToken.pdf";
2059+
PdfDocument document = new PdfDocument(new PdfReader(fileName));
2060+
PdfArray actual = (PdfArray) document.getPdfObject(4);
2061+
PdfArray expected = new PdfArray(new float[]{5, 10, 15, 20});
2062+
for (int i = 0; i < expected.size(); i++) {
2063+
Assert.assertEquals(expected.get(i), actual.get(i));
2064+
}
2065+
}
2066+
2067+
@Test
2068+
public void endArrayClosingBracketInsteadOfEndDicTest() {
2069+
String fileName = sourceFolder + "endArrayClosingBracketInsteadOfEndDic.pdf";
2070+
Exception exception = Assert.assertThrows(com.itextpdf.io.IOException.class,
2071+
() -> new PdfDocument(new PdfReader(fileName)));
2072+
Assert.assertEquals(MessageFormatUtil.format(KernelExceptionMessageConstant.UNEXPECTED_TOKEN, "]"),
2073+
exception.getCause().getMessage());
2074+
}
2075+
2076+
@Test
2077+
public void endDicClosingBracketInsideTheDicTest() {
2078+
String fileName = sourceFolder + "endDicClosingBracketInsideTheDic.pdf";
2079+
Exception exception = Assert.assertThrows(com.itextpdf.io.IOException.class,
2080+
() -> new PdfDocument(new PdfReader(fileName)));
2081+
Assert.assertEquals(MessageFormatUtil.format(KernelExceptionMessageConstant.UNEXPECTED_TOKEN, ">>"),
2082+
exception.getCause().getMessage());
2083+
}
2084+
2085+
@Test
2086+
@LogMessages(messages = {
2087+
@LogMessage(messageTemplate = KernelExceptionMessageConstant.UNEXPECTED_TOKEN)
2088+
})
2089+
public void eofInsteadOfArrayClosingBracketTest() throws IOException {
2090+
String fileName = sourceFolder + "invalidArrayEOFToken.pdf";
2091+
PdfDocument document = new PdfDocument(new PdfReader(fileName));
2092+
PdfArray actual = (PdfArray) document.getPdfObject(4);
2093+
PdfArray expected = new PdfArray(new float[]{5, 10, 15, 20});
2094+
for (int i = 0; i < expected.size(); i++) {
2095+
Assert.assertEquals(expected.get(i), actual.get(i));
2096+
}
2097+
}
2098+
2099+
@Test
2100+
@LogMessages(messages = {
2101+
@LogMessage(messageTemplate = KernelExceptionMessageConstant.UNEXPECTED_TOKEN)
2102+
})
2103+
public void endObjInsteadOfArrayClosingBracketTest() throws IOException {
2104+
String fileName = sourceFolder + "invalidArrayEndObjToken.pdf";
2105+
PdfDocument document = new PdfDocument(new PdfReader(fileName));
2106+
PdfArray actual = (PdfArray) document.getPdfObject(4);
2107+
PdfArray expected = new PdfArray(new float[]{5, 10, 15, 20});
2108+
for (int i = 0; i < expected.size(); i++) {
2109+
Assert.assertEquals(expected.get(i), actual.get(i));
2110+
}
2111+
}
2112+
2113+
@Test
2114+
@LogMessages(messages = {
2115+
@LogMessage(messageTemplate = KernelExceptionMessageConstant.UNEXPECTED_TOKEN),
2116+
@LogMessage(messageTemplate = LogMessageConstant.XREF_ERROR_WHILE_READING_TABLE_WILL_BE_REBUILT)
2117+
})
2118+
public void nameInsteadOfArrayClosingBracketTest() throws IOException {
2119+
String fileName = sourceFolder + "invalidArrayNameToken.pdf";
2120+
PdfDocument document = new PdfDocument(new PdfReader(fileName));
2121+
PdfArray actual = (PdfArray) document.getPdfObject(4);
2122+
PdfArray expected = new PdfArray(new float[]{5, 10, 15, 20});
2123+
for (int i = 0; i < expected.size(); i++) {
2124+
Assert.assertEquals(expected.get(i), actual.get(i));
2125+
}
2126+
}
2127+
2128+
@Test
2129+
@LogMessages(messages = {
2130+
@LogMessage(messageTemplate = KernelExceptionMessageConstant.UNEXPECTED_TOKEN)
2131+
})
2132+
public void objInsteadOfArrayClosingBracketTest() throws IOException {
2133+
String fileName = sourceFolder + "invalidArrayObjToken.pdf";
2134+
PdfDocument document = new PdfDocument(new PdfReader(fileName));
2135+
PdfArray actual = (PdfArray) document.getPdfObject(4);
2136+
PdfArray expected = new PdfArray(new float[]{5, 10, 15, 20});
2137+
for (int i = 0; i < expected.size(); i++) {
2138+
Assert.assertEquals(expected.get(i), actual.get(i));
2139+
}
2140+
}
2141+
2142+
@Test
2143+
@LogMessages(messages = {
2144+
@LogMessage(messageTemplate = KernelExceptionMessageConstant.UNEXPECTED_TOKEN)
2145+
})
2146+
public void refInsteadOfArrayClosingBracketTest() throws IOException {
2147+
String fileName = sourceFolder + "invalidArrayRefToken.pdf";
2148+
PdfDocument document = new PdfDocument(new PdfReader(fileName));
2149+
PdfArray actual = (PdfArray) document.getPdfObject(4);
2150+
PdfArray expected = new PdfArray(new float[]{5, 10, 15, 20});
2151+
for (int i = 0; i < expected.size(); i++) {
2152+
Assert.assertEquals(expected.get(i), actual.get(i));
2153+
}
2154+
}
2155+
2156+
@Test
2157+
@LogMessages(messages = {
2158+
@LogMessage(messageTemplate = KernelExceptionMessageConstant.UNEXPECTED_TOKEN, count = 2)
2159+
})
2160+
public void startArrayInsteadOfArrayClosingBracketTest() throws IOException {
2161+
String fileName = sourceFolder + "invalidArrayStartArrayToken.pdf";
2162+
PdfDocument document = new PdfDocument(new PdfReader(fileName));
2163+
PdfArray actual = (PdfArray) document.getPdfObject(4);
2164+
PdfArray expected = new PdfArray(new float[]{5, 10, 15, 20});
2165+
for (int i = 0; i < expected.size(); i++) {
2166+
Assert.assertEquals(expected.get(i), actual.get(i));
2167+
}
2168+
}
2169+
2170+
@Test
2171+
@LogMessages(messages = {
2172+
@LogMessage(messageTemplate = KernelExceptionMessageConstant.UNEXPECTED_TOKEN),
2173+
@LogMessage(messageTemplate = LogMessageConstant.XREF_ERROR_WHILE_READING_TABLE_WILL_BE_REBUILT)
2174+
})
2175+
public void stringInsteadOfArrayClosingBracketTest() throws IOException {
2176+
String fileName = sourceFolder + "invalidArrayStringToken.pdf";
2177+
PdfDocument document = new PdfDocument(new PdfReader(fileName));
2178+
PdfArray actual = (PdfArray) document.getPdfObject(4);
2179+
PdfArray expected = new PdfArray(new float[]{5, 10, 15, 20});
2180+
for (int i = 0; i < expected.size(); i++) {
2181+
Assert.assertEquals(expected.get(i), actual.get(i));
2182+
}
2183+
}
2184+
2185+
@Test
2186+
public void closingArrayBracketMissingConservativeTest() throws IOException {
2187+
String fileName = sourceFolder + "invalidArrayObjToken.pdf";
2188+
PdfReader reader = new PdfReader(fileName);
2189+
reader.setStrictnessLevel(StrictnessLevel.CONSERVATIVE);
2190+
PdfDocument document = new PdfDocument(reader);
2191+
Exception exception = Assert.assertThrows(com.itextpdf.io.IOException.class,
2192+
() -> document.getPdfObject(4));
2193+
Assert.assertEquals(MessageFormatUtil.format(KernelExceptionMessageConstant.UNEXPECTED_TOKEN, "obj"),
2194+
exception.getCause().getMessage());
2195+
}
2196+
20532197
private static File copyFileForTest(String fileName, String copiedFileName) throws IOException {
20542198
File copiedFile = new File(copiedFileName);
20552199
Files.copy(Paths.get(fileName), Paths.get(copiedFileName));

kernel/src/test/java/com/itextpdf/kernel/pdf/canvas/parser/util/PdfCanvasParserTest.java

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ This file is part of the iText (R) project.
2525
import com.itextpdf.io.source.PdfTokenizer;
2626
import com.itextpdf.io.source.RandomAccessFileOrArray;
2727
import com.itextpdf.io.source.RandomAccessSourceFactory;
28+
import com.itextpdf.io.util.MessageFormatUtil;
29+
import com.itextpdf.kernel.exceptions.KernelExceptionMessageConstant;
2830
import com.itextpdf.kernel.pdf.PdfArray;
2931
import com.itextpdf.kernel.pdf.PdfDictionary;
3032
import com.itextpdf.kernel.pdf.PdfDocument;
@@ -35,12 +37,11 @@ This file is part of the iText (R) project.
3537
import com.itextpdf.kernel.pdf.PdfString;
3638
import com.itextpdf.kernel.utils.CompareTool;
3739
import com.itextpdf.test.ExtendedITextTest;
40+
import com.itextpdf.test.annotations.type.IntegrationTest;
3841

3942
import java.io.IOException;
4043
import java.util.ArrayList;
4144
import java.util.List;
42-
43-
import com.itextpdf.test.annotations.type.IntegrationTest;
4445
import org.junit.Assert;
4546
import org.junit.Test;
4647
import org.junit.experimental.categories.Category;
@@ -79,4 +80,24 @@ public void innerArraysInContentStreamTest() throws IOException {
7980
Assert.assertTrue(new CompareTool().compareArrays(cmpArray,
8081
(((PdfDictionary) actual.get(1)).getAsArray(new PdfName("ColorantsDef")))));
8182
}
83+
84+
@Test
85+
public void parseArrayTest() throws IOException {
86+
String inputFileName = sourceFolder + "innerArraysInContentStreamWithEndDictToken.pdf";
87+
88+
PdfDocument pdfDocument = new PdfDocument(new PdfReader(inputFileName));
89+
90+
byte[] docInBytes = pdfDocument.getFirstPage().getContentBytes();
91+
92+
RandomAccessSourceFactory factory = new RandomAccessSourceFactory();
93+
94+
PdfTokenizer tokeniser = new PdfTokenizer(new RandomAccessFileOrArray(factory.createSource(docInBytes)));
95+
PdfResources resources = pdfDocument.getPage(1).getResources();
96+
PdfCanvasParser ps = new PdfCanvasParser(tokeniser, resources);
97+
98+
Exception exception = Assert.assertThrows(com.itextpdf.io.IOException.class,
99+
() -> ps.parse(null));
100+
Assert.assertEquals(MessageFormatUtil.format(KernelExceptionMessageConstant.UNEXPECTED_TOKEN, ">>"),
101+
exception.getCause().getMessage());
102+
}
82103
}

0 commit comments

Comments
 (0)