Skip to content

Commit 88c9cb7

Browse files
dmitry.radchukEvgeniy Prudnikov
authored andcommitted
Change array parsing logic to prevent StackOverflowException
DEVSIX-6259
1 parent 59598cd commit 88c9cb7

16 files changed

+197
-11
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
@@ -309,10 +309,13 @@ public final class KernelExceptionMessageConstant {
309309
public static final String UNBALANCED_SAVE_RESTORE_STATE_OPERATORS = "Unbalanced save restore state operators.";
310310
public static final String UNEXPECTED_CHARACTER_FOUND_AFTER_ID_IN_INLINE_IMAGE = "Unexpected character {0} "
311311
+ "found after ID in inline image.";
312+
@Deprecated
312313
public static final String UNEXPECTED_CLOSE_BRACKET = "Unexpected close bracket.";
313314
public static final String UNEXPECTED_COLOR_SPACE = "Unexpected ColorSpace: {0}.";
314315
public static final String UNEXPECTED_END_OF_FILE = "Unexpected end of file.";
316+
@Deprecated
315317
public static final String UNEXPECTED_GT_GT = "unexpected >>.";
318+
public static final String UNEXPECTED_TOKEN = "unexpected {0} was encountered.";
316319
public static final String UNEXPECTED_SHADING_TYPE = "Unexpected shading type.";
317320
public static final String UNKNOWN_ENCRYPTION_TYPE_R = "Unknown encryption type R == {0}.";
318321
public static final String UNKNOWN_ENCRYPTION_TYPE_V = "Unknown encryption type V == {0}.";

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

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ This file is part of the iText (R) project.
6565
import java.io.IOException;
6666
import java.io.InputStream;
6767
import java.util.HashSet;
68+
import java.nio.charset.StandardCharsets;
6869
import java.util.Map;
6970

7071
import com.itextpdf.kernel.xmp.XMPException;
@@ -942,9 +943,11 @@ protected PdfDictionary readDictionary(boolean objStm) throws IOException {
942943
PdfObject obj = readObject(true, objStm);
943944
if (obj == null) {
944945
if (tokens.getTokenType() == PdfTokenizer.TokenType.EndDic)
945-
tokens.throwError(KernelExceptionMessageConstant.UNEXPECTED_GT_GT);
946+
tokens.throwError(MessageFormatUtil.
947+
format(KernelExceptionMessageConstant.UNEXPECTED_TOKEN, ">>"));
946948
if (tokens.getTokenType() == PdfTokenizer.TokenType.EndArray)
947-
tokens.throwError(KernelExceptionMessageConstant.UNEXPECTED_CLOSE_BRACKET);
949+
tokens.throwError(MessageFormatUtil.
950+
format(KernelExceptionMessageConstant.UNEXPECTED_TOKEN, "]"));
948951
}
949952
dic.put(name, obj);
950953
}
@@ -956,10 +959,10 @@ protected PdfArray readArray(boolean objStm) throws IOException {
956959
while (true) {
957960
PdfObject obj = readObject(true, objStm);
958961
if (obj == null) {
959-
if (tokens.getTokenType() == PdfTokenizer.TokenType.EndArray)
960-
break;
961-
if (tokens.getTokenType() == PdfTokenizer.TokenType.EndDic)
962-
tokens.throwError(KernelExceptionMessageConstant.UNEXPECTED_GT_GT);
962+
if (tokens.getTokenType() != PdfTokenizer.TokenType.EndArray) {
963+
processArrayReadError();
964+
}
965+
break;
963966
}
964967
array.add(obj);
965968
}
@@ -1320,6 +1323,17 @@ boolean isMemorySavingMode() {
13201323
return memorySavingMode;
13211324
}
13221325

1326+
private void processArrayReadError() {
1327+
final String error = MessageFormatUtil.format(KernelExceptionMessageConstant.UNEXPECTED_TOKEN,
1328+
new String(tokens.getByteContent(), StandardCharsets.UTF_8));
1329+
if (StrictnessLevel.CONSERVATIVE.isStricter(this.getStrictnessLevel())) {
1330+
final Logger logger = LoggerFactory.getLogger(PdfReader.class);
1331+
logger.error(error);
1332+
} else {
1333+
tokens.throwError(error);
1334+
}
1335+
}
1336+
13231337
private void readDecryptObj() {
13241338
if (encrypted)
13251339
return;

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

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

46+
import com.itextpdf.commons.utils.MessageFormatUtil;
4647
import com.itextpdf.kernel.exceptions.PdfException;
4748
import com.itextpdf.io.source.PdfTokenizer;
4849
import com.itextpdf.kernel.exceptions.KernelExceptionMessageConstant;
@@ -175,10 +176,12 @@ public PdfArray readArray() throws IOException {
175176
PdfArray array = new PdfArray();
176177
while (true) {
177178
PdfObject obj = readObject();
178-
if (!obj.isArray() && tokeniser.getTokenType() == PdfTokenizer.TokenType.EndArray)
179+
if (!obj.isArray() && tokeniser.getTokenType() == PdfTokenizer.TokenType.EndArray) {
179180
break;
180-
if (tokeniser.getTokenType() == PdfTokenizer.TokenType.EndDic && obj.getType() != PdfObject.DICTIONARY)
181-
tokeniser.throwError(KernelExceptionMessageConstant.UNEXPECTED_GT_GT);
181+
}
182+
if (tokeniser.getTokenType() == PdfTokenizer.TokenType.EndDic && obj.getType() != PdfObject.DICTIONARY) {
183+
tokeniser.throwError(MessageFormatUtil.format(KernelExceptionMessageConstant.UNEXPECTED_TOKEN, ">>"));
184+
}
182185
array.add(obj);
183186
}
184187
return array;

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

Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2073,6 +2073,151 @@ public void notCloseUserStreamTest() throws IOException {
20732073
Assert.assertEquals(-1, pdfStream.read());
20742074
}
20752075
}
2076+
2077+
@Test
2078+
@LogMessages(messages = {
2079+
@LogMessage(messageTemplate = KernelExceptionMessageConstant.UNEXPECTED_TOKEN)
2080+
})
2081+
public void endDicInsteadOfArrayClosingBracketTest() throws IOException {
2082+
String fileName = SOURCE_FOLDER + "invalidArrayEndDictToken.pdf";
2083+
PdfDocument document = new PdfDocument(new PdfReader(fileName));
2084+
PdfArray actual = (PdfArray) document.getPdfObject(4);
2085+
PdfArray expected = new PdfArray(new float[]{5, 10, 15, 20});
2086+
for (int i = 0; i < expected.size(); i++) {
2087+
Assert.assertEquals(expected.get(i), actual.get(i));
2088+
}
2089+
}
2090+
2091+
@Test
2092+
public void endArrayClosingBracketInsteadOfEndDicTest() {
2093+
String fileName = SOURCE_FOLDER + "endArrayClosingBracketInsteadOfEndDic.pdf";
2094+
Exception exception = Assert.assertThrows(com.itextpdf.io.exceptions.IOException.class,
2095+
() -> new PdfDocument(new PdfReader(fileName)));
2096+
Assert.assertEquals(MessageFormatUtil.format(KernelExceptionMessageConstant.UNEXPECTED_TOKEN, "]"),
2097+
exception.getCause().getMessage());
2098+
}
2099+
2100+
@Test
2101+
public void endDicClosingBracketInsideTheDicTest() {
2102+
String fileName = SOURCE_FOLDER + "endDicClosingBracketInsideTheDic.pdf";
2103+
Exception exception = Assert.assertThrows(com.itextpdf.io.exceptions.IOException.class,
2104+
() -> new PdfDocument(new PdfReader(fileName)));
2105+
Assert.assertEquals(MessageFormatUtil.format(KernelExceptionMessageConstant.UNEXPECTED_TOKEN, ">>"),
2106+
exception.getCause().getMessage());
2107+
}
2108+
2109+
@Test
2110+
@LogMessages(messages = {
2111+
@LogMessage(messageTemplate = KernelExceptionMessageConstant.UNEXPECTED_TOKEN)
2112+
})
2113+
public void eofInsteadOfArrayClosingBracketTest() throws IOException {
2114+
String fileName = SOURCE_FOLDER + "invalidArrayEOFToken.pdf";
2115+
PdfDocument document = new PdfDocument(new PdfReader(fileName));
2116+
PdfArray actual = (PdfArray) document.getPdfObject(4);
2117+
PdfArray expected = new PdfArray(new float[]{5, 10, 15, 20});
2118+
for (int i = 0; i < expected.size(); i++) {
2119+
Assert.assertEquals(expected.get(i), actual.get(i));
2120+
}
2121+
}
2122+
2123+
@Test
2124+
@LogMessages(messages = {
2125+
@LogMessage(messageTemplate = KernelExceptionMessageConstant.UNEXPECTED_TOKEN)
2126+
})
2127+
public void endObjInsteadOfArrayClosingBracketTest() throws IOException {
2128+
String fileName = SOURCE_FOLDER + "invalidArrayEndObjToken.pdf";
2129+
PdfDocument document = new PdfDocument(new PdfReader(fileName));
2130+
PdfArray actual = (PdfArray) document.getPdfObject(4);
2131+
PdfArray expected = new PdfArray(new float[]{5, 10, 15, 20});
2132+
for (int i = 0; i < expected.size(); i++) {
2133+
Assert.assertEquals(expected.get(i), actual.get(i));
2134+
}
2135+
}
2136+
2137+
@Test
2138+
@LogMessages(messages = {
2139+
@LogMessage(messageTemplate = KernelExceptionMessageConstant.UNEXPECTED_TOKEN),
2140+
@LogMessage(messageTemplate = IoLogMessageConstant.XREF_ERROR_WHILE_READING_TABLE_WILL_BE_REBUILT)
2141+
})
2142+
public void nameInsteadOfArrayClosingBracketTest() throws IOException {
2143+
String fileName = SOURCE_FOLDER + "invalidArrayNameToken.pdf";
2144+
PdfDocument document = new PdfDocument(new PdfReader(fileName));
2145+
PdfArray actual = (PdfArray) document.getPdfObject(4);
2146+
PdfArray expected = new PdfArray(new float[]{5, 10, 15, 20});
2147+
for (int i = 0; i < expected.size(); i++) {
2148+
Assert.assertEquals(expected.get(i), actual.get(i));
2149+
}
2150+
}
2151+
2152+
@Test
2153+
@LogMessages(messages = {
2154+
@LogMessage(messageTemplate = KernelExceptionMessageConstant.UNEXPECTED_TOKEN)
2155+
})
2156+
public void objInsteadOfArrayClosingBracketTest() throws IOException {
2157+
String fileName = SOURCE_FOLDER + "invalidArrayObjToken.pdf";
2158+
PdfDocument document = new PdfDocument(new PdfReader(fileName));
2159+
PdfArray actual = (PdfArray) document.getPdfObject(4);
2160+
PdfArray expected = new PdfArray(new float[]{5, 10, 15, 20});
2161+
for (int i = 0; i < expected.size(); i++) {
2162+
Assert.assertEquals(expected.get(i), actual.get(i));
2163+
}
2164+
}
2165+
2166+
@Test
2167+
@LogMessages(messages = {
2168+
@LogMessage(messageTemplate = KernelExceptionMessageConstant.UNEXPECTED_TOKEN)
2169+
})
2170+
public void refInsteadOfArrayClosingBracketTest() throws IOException {
2171+
String fileName = SOURCE_FOLDER + "invalidArrayRefToken.pdf";
2172+
PdfDocument document = new PdfDocument(new PdfReader(fileName));
2173+
PdfArray actual = (PdfArray) document.getPdfObject(4);
2174+
PdfArray expected = new PdfArray(new float[]{5, 10, 15, 20});
2175+
for (int i = 0; i < expected.size(); i++) {
2176+
Assert.assertEquals(expected.get(i), actual.get(i));
2177+
}
2178+
}
2179+
2180+
@Test
2181+
@LogMessages(messages = {
2182+
@LogMessage(messageTemplate = KernelExceptionMessageConstant.UNEXPECTED_TOKEN, count = 2)
2183+
})
2184+
public void startArrayInsteadOfArrayClosingBracketTest() throws IOException {
2185+
String fileName = SOURCE_FOLDER + "invalidArrayStartArrayToken.pdf";
2186+
PdfDocument document = new PdfDocument(new PdfReader(fileName));
2187+
PdfArray actual = (PdfArray) document.getPdfObject(4);
2188+
PdfArray expected = new PdfArray(new float[]{5, 10, 15, 20});
2189+
for (int i = 0; i < expected.size(); i++) {
2190+
Assert.assertEquals(expected.get(i), actual.get(i));
2191+
}
2192+
}
2193+
2194+
@Test
2195+
@LogMessages(messages = {
2196+
@LogMessage(messageTemplate = KernelExceptionMessageConstant.UNEXPECTED_TOKEN),
2197+
@LogMessage(messageTemplate = IoLogMessageConstant.XREF_ERROR_WHILE_READING_TABLE_WILL_BE_REBUILT)
2198+
})
2199+
public void stringInsteadOfArrayClosingBracketTest() throws IOException {
2200+
String fileName = SOURCE_FOLDER + "invalidArrayStringToken.pdf";
2201+
PdfDocument document = new PdfDocument(new PdfReader(fileName));
2202+
PdfArray actual = (PdfArray) document.getPdfObject(4);
2203+
PdfArray expected = new PdfArray(new float[]{5, 10, 15, 20});
2204+
for (int i = 0; i < expected.size(); i++) {
2205+
Assert.assertEquals(expected.get(i), actual.get(i));
2206+
}
2207+
}
2208+
2209+
@Test
2210+
public void closingArrayBracketMissingConservativeTest() throws IOException {
2211+
String fileName = SOURCE_FOLDER + "invalidArrayObjToken.pdf";
2212+
PdfReader reader = new PdfReader(fileName);
2213+
reader.setStrictnessLevel(StrictnessLevel.CONSERVATIVE);
2214+
PdfDocument document = new PdfDocument(reader);
2215+
Exception exception = Assert.assertThrows(com.itextpdf.io.exceptions.IOException.class,
2216+
() -> document.getPdfObject(4));
2217+
Assert.assertEquals(MessageFormatUtil.format(KernelExceptionMessageConstant.UNEXPECTED_TOKEN, "obj"),
2218+
exception.getCause().getMessage());
2219+
}
2220+
20762221
private static File copyFileForTest(String fileName, String copiedFileName) throws IOException {
20772222
File copiedFile = new File(copiedFileName);
20782223
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
@@ -22,9 +22,11 @@ This file is part of the iText (R) project.
2222
*/
2323
package com.itextpdf.kernel.pdf.canvas.parser.util;
2424

25+
import com.itextpdf.commons.utils.MessageFormatUtil;
2526
import com.itextpdf.io.source.PdfTokenizer;
2627
import com.itextpdf.io.source.RandomAccessFileOrArray;
2728
import com.itextpdf.io.source.RandomAccessSourceFactory;
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.exceptions.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)