Skip to content

Commit

Permalink
Limit maximum TOML structure depth
Browse files Browse the repository at this point in the history
This replaces the StackOverflowError with a StreamReadException with a proper message.

Fixes #387
  • Loading branch information
yawkat committed Mar 6, 2023
1 parent 91cdf2b commit 62d08fd
Show file tree
Hide file tree
Showing 2 changed files with 30 additions and 10 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
class Parser {
private static final JsonNodeFactory factory = new JsonNodeFactoryImpl();
private static final int MAX_CHARS_TO_REPORT = 1000;
private static final int MAX_DEPTH = 1000;

private final TomlFactory tomlFactory;

Expand Down Expand Up @@ -115,7 +116,7 @@ public ObjectNode parse() throws IOException {
while (next != null) {
TomlToken token = peek();
if (token == TomlToken.UNQUOTED_KEY || token == TomlToken.STRING) {
parseKeyVal(currentTable, Lexer.EXPECT_EOL);
parseKeyVal(currentTable, Lexer.EXPECT_EOL, 0);
} else if (token == TomlToken.STD_TABLE_OPEN) {
pollExpected(TomlToken.STD_TABLE_OPEN, Lexer.EXPECT_INLINE_KEY);
FieldRef fieldRef = parseAndEnterKey(root, true);
Expand Down Expand Up @@ -201,7 +202,11 @@ private FieldRef parseAndEnterKey(
}
}

private JsonNode parseValue(int nextState) throws IOException {
private JsonNode parseValue(int nextState, int depth) throws IOException {
if (depth > MAX_DEPTH) {
throw errorContext.atPosition(lexer).generic("Nesting too deep");
}

TomlToken firstToken = peek();
switch (firstToken) {
case STRING:
Expand All @@ -224,9 +229,9 @@ private JsonNode parseValue(int nextState) throws IOException {
case INTEGER:
return parseInt(nextState);
case ARRAY_OPEN:
return parseArray(nextState);
return parseArray(nextState, depth);
case INLINE_TABLE_OPEN:
return parseInlineTable(nextState);
return parseInlineTable(nextState, depth);
default:
throw errorContext.atPosition(lexer).unexpectedToken(firstToken, "value");
}
Expand Down Expand Up @@ -397,7 +402,7 @@ private JsonNode parseFloat(int nextState) throws IOException {
}
}

private ObjectNode parseInlineTable(int nextState) throws IOException {
private ObjectNode parseInlineTable(int nextState, int depth) throws IOException {
// inline-table = inline-table-open [ inline-table-keyvals ] inline-table-close
// inline-table-keyvals = keyval [ inline-table-sep inline-table-keyvals ]
pollExpected(TomlToken.INLINE_TABLE_OPEN, Lexer.EXPECT_INLINE_KEY);
Expand All @@ -413,7 +418,7 @@ private ObjectNode parseInlineTable(int nextState) throws IOException {
throw errorContext.atPosition(lexer).generic("Trailing comma not permitted for inline tables");
}
}
parseKeyVal(node, Lexer.EXPECT_TABLE_SEP);
parseKeyVal(node, Lexer.EXPECT_TABLE_SEP, depth + 1);
TomlToken sepToken = peek();
if (sepToken == TomlToken.INLINE_TABLE_CLOSE) {
break;
Expand All @@ -429,7 +434,7 @@ private ObjectNode parseInlineTable(int nextState) throws IOException {
return node;
}

private ArrayNode parseArray(int nextState) throws IOException {
private ArrayNode parseArray(int nextState, int depth) throws IOException {
// array = array-open [ array-values ] ws-comment-newline array-close
// array-values = ws-comment-newline val ws-comment-newline array-sep array-values
// array-values =/ ws-comment-newline val ws-comment-newline [ array-sep ]
Expand All @@ -440,7 +445,7 @@ private ArrayNode parseArray(int nextState) throws IOException {
if (token == TomlToken.ARRAY_CLOSE) {
break;
}
JsonNode value = parseValue(Lexer.EXPECT_ARRAY_SEP);
JsonNode value = parseValue(Lexer.EXPECT_ARRAY_SEP, depth + 1);
node.add(value);
TomlToken sepToken = peek();
if (sepToken == TomlToken.ARRAY_CLOSE) {
Expand All @@ -456,11 +461,11 @@ private ArrayNode parseArray(int nextState) throws IOException {
return node;
}

private void parseKeyVal(TomlObjectNode target, int nextState) throws IOException {
private void parseKeyVal(TomlObjectNode target, int nextState, int depth) throws IOException {
// keyval = key keyval-sep val
FieldRef fieldRef = parseAndEnterKey(target, false);
pollExpected(TomlToken.KEY_VAL_SEP, Lexer.EXPECT_VALUE);
JsonNode value = parseValue(nextState);
JsonNode value = parseValue(nextState, depth);
if (fieldRef.object.has(fieldRef.key)) {
throw errorContext.atPosition(lexer).generic("Duplicate key");
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,21 @@ public void testNumberParsingFail50395() throws Exception
verifyException(e, "Premature end of file");
}
}

@Test
public void testStackOverflow50083() throws Exception
{
StringBuilder input = new StringBuilder();
for (int i = 0; i < 9999; i++) {
input.append("a={");
}
try {
TOML_MAPPER.readTree(input.toString());
Assert.fail("Should not pass");
} catch (StreamReadException e) {
verifyException(e, "Nesting too deep");
}
}

protected void verifyException(Throwable e, String... matches)
{
Expand Down

0 comments on commit 62d08fd

Please sign in to comment.