From 62d08fd3fee2dc82826ea11374fa10866654a1b1 Mon Sep 17 00:00:00 2001 From: yawkat Date: Mon, 6 Mar 2023 12:11:57 +0100 Subject: [PATCH] Limit maximum TOML structure depth This replaces the StackOverflowError with a StreamReadException with a proper message. Fixes #387 --- .../jackson/dataformat/toml/Parser.java | 25 +++++++++++-------- .../dataformat/toml/FuzzTomlReadTest.java | 15 +++++++++++ 2 files changed, 30 insertions(+), 10 deletions(-) diff --git a/toml/src/main/java/com/fasterxml/jackson/dataformat/toml/Parser.java b/toml/src/main/java/com/fasterxml/jackson/dataformat/toml/Parser.java index 77646961..9c807c0d 100644 --- a/toml/src/main/java/com/fasterxml/jackson/dataformat/toml/Parser.java +++ b/toml/src/main/java/com/fasterxml/jackson/dataformat/toml/Parser.java @@ -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; @@ -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); @@ -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: @@ -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"); } @@ -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); @@ -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; @@ -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 ] @@ -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) { @@ -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"); } diff --git a/toml/src/test/java/com/fasterxml/jackson/dataformat/toml/FuzzTomlReadTest.java b/toml/src/test/java/com/fasterxml/jackson/dataformat/toml/FuzzTomlReadTest.java index a8bd1397..d3a012ae 100644 --- a/toml/src/test/java/com/fasterxml/jackson/dataformat/toml/FuzzTomlReadTest.java +++ b/toml/src/test/java/com/fasterxml/jackson/dataformat/toml/FuzzTomlReadTest.java @@ -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) {