Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Limit maximum TOML structure depth #395

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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