diff --git a/json-smart/src/main/java/net/minidev/json/parser/JSONParserBase.java b/json-smart/src/main/java/net/minidev/json/parser/JSONParserBase.java index a7254c7..6ad1e30 100644 --- a/json-smart/src/main/java/net/minidev/json/parser/JSONParserBase.java +++ b/json-smart/src/main/java/net/minidev/json/parser/JSONParserBase.java @@ -39,6 +39,12 @@ */ abstract class JSONParserBase { protected char c; + /** + * hard coded maximal depth for JSON parsing + */ + public static int MAX_DEPTH = 400; + + protected int depth = 0; public final static byte EOI = 0x1A; protected static final char MAX_STOP = 126; // '}' -> 125 // @@ -247,6 +253,9 @@ protected List readArray() throws ParseException, IOException { List obj = containerFactory.createArrayContainer(); if (c != '[') throw new RuntimeException("Internal Error"); + if (++this.depth > MAX_DEPTH) { + throw new ParseException(pos, ERROR_UNEXPECTED_JSON_DEPTH, c); + } read(); boolean needData = false; handler.startArray(); @@ -261,6 +270,7 @@ protected List readArray() throws ParseException, IOException { case ']': if (needData && !acceptUselessComma) throw new ParseException(pos, ERROR_UNEXPECTED_CHAR, (char) c); + this.depth--; read(); /* unstack */ handler.endArray(); return obj; @@ -403,6 +413,9 @@ protected Map readObject() throws ParseException, IOException { Map obj = this.containerFactory.createObjectContainer(); if (c != '{') throw new RuntimeException("Internal Error"); + if (++this.depth > MAX_DEPTH) { + throw new ParseException(pos, ERROR_UNEXPECTED_JSON_DEPTH, c); + } handler.startObject(); boolean needData = false; boolean acceptData = true; @@ -422,6 +435,7 @@ protected Map readObject() throws ParseException, IOException { case '}': if (needData && !acceptUselessComma) throw new ParseException(pos, ERROR_UNEXPECTED_CHAR, (char) c); + this.depth--; read(); /* unstack */ handler.endObject(); return obj; @@ -467,6 +481,7 @@ protected Map readObject() throws ParseException, IOException { skipSpace(); if (c == '}') { read(); /* unstack */ + this.depth--; handler.endObject(); return obj; } diff --git a/json-smart/src/main/java/net/minidev/json/parser/ParseException.java b/json-smart/src/main/java/net/minidev/json/parser/ParseException.java index e652cf2..8ae0944 100644 --- a/json-smart/src/main/java/net/minidev/json/parser/ParseException.java +++ b/json-smart/src/main/java/net/minidev/json/parser/ParseException.java @@ -30,6 +30,7 @@ public class ParseException extends Exception { public static final int ERROR_UNEXPECTED_UNICODE = 4; public static final int ERROR_UNEXPECTED_DUPLICATE_KEY = 5; public static final int ERROR_UNEXPECTED_LEADING_0 = 6; + public static final int ERROR_UNEXPECTED_JSON_DEPTH = 7; private int errorType; private Object unexpectedObject; @@ -114,6 +115,12 @@ private static String toMessage(int position, int errorType, Object unexpectedOb sb.append(" at position "); sb.append(position); sb.append("."); + } else if (errorType == ERROR_UNEXPECTED_JSON_DEPTH) { + sb.append("Malicious payload, having non natural depths, parsing stoped on "); + sb.append(unexpectedObject); + sb.append(" at position "); + sb.append(position); + sb.append("."); } else { sb.append("Unkown error at position "); sb.append(position); diff --git a/json-smart/src/test/java/net/minidev/json/test/TestOverflow.java b/json-smart/src/test/java/net/minidev/json/test/TestOverflow.java new file mode 100644 index 0000000..07912f2 --- /dev/null +++ b/json-smart/src/test/java/net/minidev/json/test/TestOverflow.java @@ -0,0 +1,50 @@ +package net.minidev.json.test; + +import net.minidev.json.JSONArray; +import net.minidev.json.JSONValue; +import net.minidev.json.parser.ParseException; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.Test; + +public class TestOverflow { + @Test + public void stressTest() throws Exception { + int size = 10000; + StringBuilder sb = new StringBuilder(10 + size*4); + for (int i=0; i < size; i++) { + sb.append("{a:"); + } + sb.append("true"); + for (int i=0; i < size; i++) { + sb.append("}"); + } + String s = sb.toString(); + try { + JSONValue.parseWithException(s); + } catch (ParseException e) { + assertEquals(e.getErrorType(), ParseException.ERROR_UNEXPECTED_JSON_DEPTH); + return; + } + assertTrue(false); + } + + @Test + public void shouldNotFailParsingArraysWith400Elements() throws Exception { + int size = 400; + StringBuilder sb = new StringBuilder(); + sb.append("["); + for (int i=0; i < size; i++) { + sb.append("{a:true}"); + if(i+1 < size) { + sb.append(","); + } + } + sb.append("]"); + String s = sb.toString(); + JSONArray array = (JSONArray) JSONValue.parseWithException(s); + assertEquals(array.size(), size); + } +}