diff --git a/src/main/java/net/woggioni/worth/exception/MaxDepthExceededException.java b/src/main/java/net/woggioni/worth/exception/MaxDepthExceededException.java new file mode 100644 index 0000000..f8ee8de --- /dev/null +++ b/src/main/java/net/woggioni/worth/exception/MaxDepthExceededException.java @@ -0,0 +1,7 @@ +package net.woggioni.worth.exception; + +public class MaxDepthExceededException extends WorthException { + public MaxDepthExceededException(String msg) { + super(msg); + } +} diff --git a/src/main/java/net/woggioni/worth/serialization/ValueParser.java b/src/main/java/net/woggioni/worth/serialization/ValueParser.java index 1800d39..52d61f7 100644 --- a/src/main/java/net/woggioni/worth/serialization/ValueParser.java +++ b/src/main/java/net/woggioni/worth/serialization/ValueParser.java @@ -1,6 +1,7 @@ package net.woggioni.worth.serialization; import lombok.RequiredArgsConstructor; +import net.woggioni.worth.exception.MaxDepthExceededException; import net.woggioni.worth.exception.NotImplementedException; import net.woggioni.worth.utils.WorthUtils; import net.woggioni.worth.value.*; @@ -13,6 +14,8 @@ import java.io.Reader; import java.nio.charset.Charset; import java.util.ArrayDeque; +import static net.woggioni.worth.utils.WorthUtils.newThrowable; + public class ValueParser implements Parser { protected final Value.Configuration cfg; @@ -64,7 +67,16 @@ public class ValueParser implements Parser { protected ValueParser(Value.Configuration cfg) { this.cfg = cfg; - stack = new ArrayDeque<>(); + stack = new ArrayDeque<>() { + @Override + public void push(StackLevel stackLevel) { + if(size() == cfg.maxDepth) { + throw newThrowable(MaxDepthExceededException.class, + "Objects is too deep, max allowed depth is %d", cfg.maxDepth); + } + super.push(stackLevel); + } + }; stack.push(new ArrayStackLevel()); } diff --git a/src/main/java/net/woggioni/worth/xface/Value.java b/src/main/java/net/woggioni/worth/xface/Value.java index 38c1cd8..5a71625 100644 --- a/src/main/java/net/woggioni/worth/xface/Value.java +++ b/src/main/java/net/woggioni/worth/xface/Value.java @@ -1,5 +1,6 @@ package net.woggioni.worth.xface; +import lombok.Builder; import net.woggioni.worth.exception.TypeException; import net.woggioni.worth.value.NullValue; import net.woggioni.worth.value.ObjectValue; @@ -85,11 +86,21 @@ public interface Value { throw new TypeException("Not an object"); } + @Builder class Configuration { - public ObjectValue.Implementation objectValueImplementation = ObjectValue.Implementation.valueOf( + + @Builder.Default + public final ObjectValue.Implementation objectValueImplementation = ObjectValue.Implementation.valueOf( System.getProperty(ObjectValue.class.getName() + ".implementation", "TreeMap")); - public boolean useReferences = Boolean.valueOf( + + @Builder.Default + public final boolean useReferences = Boolean.valueOf( System.getProperty(Value.class.getName() + ".useReferences", "false")); + + @Builder.Default + public final int maxDepth = + Integer.parseInt(System.getProperty(Value.class.getName() + ".maxDepth", "1048576")); } - Configuration configuration = new Configuration(); + + Configuration configuration = Configuration.builder().build(); } diff --git a/src/test/java/net/woggioni/worth/serialization/JsonBombTest.java b/src/test/java/net/woggioni/worth/serialization/JsonBombTest.java new file mode 100644 index 0000000..929598d --- /dev/null +++ b/src/test/java/net/woggioni/worth/serialization/JsonBombTest.java @@ -0,0 +1,59 @@ +package net.woggioni.worth.serialization; + +import com.fasterxml.jackson.databind.ObjectMapper; +import lombok.SneakyThrows; +import net.woggioni.worth.antlr.JSONLexer; +import net.woggioni.worth.antlr.JSONListenerImpl; +import net.woggioni.worth.exception.MaxDepthExceededException; +import net.woggioni.worth.serialization.json.JSONParser; +import net.woggioni.worth.xface.Parser; +import net.woggioni.worth.xface.Value; +import org.antlr.v4.runtime.CharStream; +import org.antlr.v4.runtime.CharStreams; +import org.antlr.v4.runtime.CommonTokenStream; +import org.antlr.v4.runtime.tree.ParseTreeWalker; +import org.junit.Test; + +import java.io.InputStream; +import java.io.InputStreamReader; + +public class JsonBombTest { + + private InputStream infiniteJson() { + return new InputStream() { + int index = 0; + final String monomer = "{\"key\":["; + @Override + public int read() { + return (int) monomer.charAt(index++ % monomer.length()); + } + }; + } + + @Test(expected = StackOverflowError.class) + @SneakyThrows + public void jackson() { + ObjectMapper om = new ObjectMapper(); + om.readTree(infiniteJson()); + } + + @Test(expected = MaxDepthExceededException.class) + @SneakyThrows + public void worth() { + Value.Configuration cfg = Value.Configuration.builder().maxDepth(1024).build(); + Parser parser = JSONParser.newInstance(cfg); + parser.parse(infiniteJson()); + } + + @Test(expected = OutOfMemoryError.class) + @SneakyThrows + public void antlr() { + CharStream inputStream = CharStreams.fromReader(new InputStreamReader(infiniteJson())); + JSONLexer lexer = new JSONLexer(inputStream); + CommonTokenStream commonTokenStream = new CommonTokenStream(lexer); + net.woggioni.worth.antlr.JSONParser parser = new net.woggioni.worth.antlr.JSONParser(commonTokenStream); + JSONListenerImpl listener = new JSONListenerImpl(); + ParseTreeWalker walker = new ParseTreeWalker(); + walker.walk(listener, parser.json()); + } +} diff --git a/src/test/java/net/woggioni/worth/value/ObjectValueImplementationTest.java b/src/test/java/net/woggioni/worth/value/ObjectValueImplementationTest.java index 96d4925..caab28c 100644 --- a/src/test/java/net/woggioni/worth/value/ObjectValueImplementationTest.java +++ b/src/test/java/net/woggioni/worth/value/ObjectValueImplementationTest.java @@ -32,8 +32,9 @@ public class ObjectValueImplementationTest { ObjectValue obj = ObjectValue.newInstance(); Assert.assertEquals(expectedClass, obj.getClass()); mapping.forEach(tuple -> { - Value.Configuration cfg = new Value.Configuration(); - cfg.objectValueImplementation = tuple._1; + Value.Configuration cfg = Value.Configuration.builder() + .objectValueImplementation(tuple._1) + .build(); ObjectValue obj2 = ObjectValue.newInstance(cfg); Assert.assertEquals(tuple._2, obj2.getClass()); });