Skip to content
This repository was archived by the owner on Nov 7, 2024. It is now read-only.

Commit

Permalink
91: Stack overflow error caused by jakarta.json parsing of untrusted …
Browse files Browse the repository at this point in the history
…JSON String

Signed-off-by: Jorge Bescos Gascon <[email protected]>
  • Loading branch information
jbescos committed Oct 25, 2024
1 parent 37876d2 commit 57df6a1
Show file tree
Hide file tree
Showing 3 changed files with 237 additions and 2 deletions.
30 changes: 28 additions & 2 deletions impl/src/main/java/org/glassfish/json/JsonParserImpl.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright (c) 2012-2018 Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2012-2024 Oracle and/or its affiliates. All rights reserved.
*
* The contents of this file are subject to the terms of either the GNU
* General Public License Version 2 only ("GPL") or the Common Development
Expand Down Expand Up @@ -78,27 +78,39 @@
*/
public class JsonParserImpl implements JsonParser {

/**
* Configuration property to limit maximum level of nesting when being parsing JSON string.
* Default value is set to {@code 1000}.
*/
public static String MAX_DEPTH = "org.eclipse.parsson.maxDepth";

/** Default maximum level of nesting. */
private static final int DEFAULT_MAX_DEPTH = 1000;

private final BufferPool bufferPool;
private Context currentContext = new NoneContext();
private Event currentEvent;

private final Stack stack = new Stack();
private final Stack stack;
private final JsonTokenizer tokenizer;

public JsonParserImpl(Reader reader, BufferPool bufferPool) {
this.bufferPool = bufferPool;
tokenizer = new JsonTokenizer(reader, bufferPool);
stack = new Stack(propertyStringToInt(MAX_DEPTH, DEFAULT_MAX_DEPTH));
}

public JsonParserImpl(InputStream in, BufferPool bufferPool) {
this.bufferPool = bufferPool;
UnicodeDetectingInputStream uin = new UnicodeDetectingInputStream(in);
tokenizer = new JsonTokenizer(new InputStreamReader(uin, uin.getCharset()), bufferPool);
stack = new Stack(propertyStringToInt(MAX_DEPTH, DEFAULT_MAX_DEPTH));
}

public JsonParserImpl(InputStream in, Charset encoding, BufferPool bufferPool) {
this.bufferPool = bufferPool;
tokenizer = new JsonTokenizer(new InputStreamReader(in, encoding), bufferPool);
stack = new Stack(propertyStringToInt(MAX_DEPTH, DEFAULT_MAX_DEPTH));
}

@Override
Expand Down Expand Up @@ -388,9 +400,19 @@ public void close() {
// Using the optimized stack impl as we don't require other things
// like iterator etc.
private static final class Stack {
private int size = 0;
private final int limit;

private Stack(int size) {
this.limit = size;
}

private Context head;

private void push(Context context) {
if (++size >= limit) {
throw new RuntimeException("Input is too deeply nested " + size);
}
context.next = head;
head = context;
}
Expand All @@ -399,6 +421,7 @@ private Context pop() {
if (head == null) {
throw new NoSuchElementException();
}
size--;
Context temp = head;
head = head.next;
return temp;
Expand Down Expand Up @@ -590,4 +613,7 @@ void skip() {
}
}

static int propertyStringToInt(String propertyName, int defaultValue) throws JsonException {
return Integer.getInteger(propertyName, defaultValue);
}
}
92 changes: 92 additions & 0 deletions tests/src/test/java/org/glassfish/json/JsonParserImplTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright (c) 2024 Oracle and/or its affiliates. All rights reserved.
*
* The contents of this file are subject to the terms of either the GNU
* General Public License Version 2 only ("GPL") or the Common Development
* and Distribution License("CDDL") (collectively, the "License"). You
* may not use this file except in compliance with the License. You can
* obtain a copy of the License at
* https://oss.oracle.com/licenses/CDDL+GPL-1.1
* or LICENSE.txt. See the License for the specific
* language governing permissions and limitations under the License.
*
* When distributing the software, include this License Header Notice in each
* file and include the License file at LICENSE.txt.
*
* GPL Classpath Exception:
* Oracle designates this particular file as subject to the "Classpath"
* exception as provided by Oracle in the GPL Version 2 section of the License
* file that accompanied this code.
*
* Modifications:
* If applicable, add the following below the License Header, with the fields
* enclosed by brackets [] replaced by your own identifying information:
* "Portions Copyright [year] [name of copyright owner]"
*
* Contributor(s):
* If you wish your version of this file to be governed by only the CDDL or
* only the GPL Version 2, indicate your decision by adding "[Contributor]
* elects to include this software in this distribution under the [CDDL or GPL
* Version 2] license." If you don't indicate a single choice of license, a
* recipient has the option to distribute your version of this file under
* either the CDDL, the GPL Version 2 or to extend the choice of license to
* its licensees as provided above. However, if you add GPL Version 2 code
* and therefore, elected the GPL Version 2 license, then the option applies
* only if the new code is made subject to such option by the copyright
* holder.
*/

package org.glassfish.json;

import static org.junit.Assert.assertEquals;

import javax.json.JsonException;

import org.junit.Test;

public class JsonParserImplTest {

@Test
public void undefined() {
String previousValue = System.getProperty(JsonParserImpl.MAX_DEPTH);
try {
System.getProperties().remove(JsonParserImpl.MAX_DEPTH);
int result = JsonParserImpl.propertyStringToInt(JsonParserImpl.MAX_DEPTH, -1);
assertEquals(-1, result);
} finally {
if (previousValue != null) {
System.setProperty(JsonParserImpl.MAX_DEPTH, previousValue);
}
}
}

@Test
public void notInteger() {
String previousValue = System.getProperty(JsonParserImpl.MAX_DEPTH);
try {
System.setProperty(JsonParserImpl.MAX_DEPTH, "String");
int result = JsonParserImpl.propertyStringToInt(JsonParserImpl.MAX_DEPTH, -10);
assertEquals(-10, result);
} finally {
if (previousValue != null) {
System.setProperty(JsonParserImpl.MAX_DEPTH, previousValue);
}
}
}

@Test
public void integer() {
String previousValue = System.getProperty(JsonParserImpl.MAX_DEPTH);
try {
System.setProperty(JsonParserImpl.MAX_DEPTH, "10");
int result = JsonParserImpl.propertyStringToInt(JsonParserImpl.MAX_DEPTH, -1);
assertEquals(10, result);
} finally {
if (previousValue != null) {
System.setProperty(JsonParserImpl.MAX_DEPTH, previousValue);
}
}
}
}
117 changes: 117 additions & 0 deletions tests/src/test/java/org/glassfish/json/tests/JsonNestingTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright (c) 2024 Oracle and/or its affiliates. All rights reserved.
*
* The contents of this file are subject to the terms of either the GNU
* General Public License Version 2 only ("GPL") or the Common Development
* and Distribution License("CDDL") (collectively, the "License"). You
* may not use this file except in compliance with the License. You can
* obtain a copy of the License at
* https://oss.oracle.com/licenses/CDDL+GPL-1.1
* or LICENSE.txt. See the License for the specific
* language governing permissions and limitations under the License.
*
* When distributing the software, include this License Header Notice in each
* file and include the License file at LICENSE.txt.
*
* GPL Classpath Exception:
* Oracle designates this particular file as subject to the "Classpath"
* exception as provided by Oracle in the GPL Version 2 section of the License
* file that accompanied this code.
*
* Modifications:
* If applicable, add the following below the License Header, with the fields
* enclosed by brackets [] replaced by your own identifying information:
* "Portions Copyright [year] [name of copyright owner]"
*
* Contributor(s):
* If you wish your version of this file to be governed by only the CDDL or
* only the GPL Version 2, indicate your decision by adding "[Contributor]
* elects to include this software in this distribution under the [CDDL or GPL
* Version 2] license." If you don't indicate a single choice of license, a
* recipient has the option to distribute your version of this file under
* either the CDDL, the GPL Version 2 or to extend the choice of license to
* its licensees as provided above. However, if you add GPL Version 2 code
* and therefore, elected the GPL Version 2 license, then the option applies
* only if the new code is made subject to such option by the copyright
* holder.
*/

package org.glassfish.json.tests;

import javax.json.Json;
import javax.json.stream.JsonParser;
import org.junit.Test;

import java.io.StringReader;

public class JsonNestingTest {

@Test(expected = RuntimeException.class)
public void testArrayNestingException() {
String json = createDeepNestedDoc(500);
try (JsonParser parser = Json.createParser(new StringReader(json))) {
while (parser.hasNext()) {
JsonParser.Event ev = parser.next();
if (JsonParser.Event.START_ARRAY == ev) {
parser.getArray();
}
}
}
}

@Test
public void testArrayNesting() {
String json = createDeepNestedDoc(499);
try (JsonParser parser = Json.createParser(new StringReader(json))) {
while (parser.hasNext()) {
JsonParser.Event ev = parser.next();
if (JsonParser.Event.START_ARRAY == ev) {
parser.getArray();
}
}
}
}

@Test(expected = RuntimeException.class)
public void testObjectNestingException() {
String json = createDeepNestedDoc(500);
try (JsonParser parser = Json.createParser(new StringReader(json))) {
while (parser.hasNext()) {
JsonParser.Event ev = parser.next();
if (JsonParser.Event.START_OBJECT == ev) {
parser.getObject();
}
}
}
}

@Test
public void testObjectNesting() {
String json = createDeepNestedDoc(499);
try (JsonParser parser = Json.createParser(new StringReader(json))) {
while (parser.hasNext()) {
JsonParser.Event ev = parser.next();
if (JsonParser.Event.START_OBJECT == ev) {
parser.getObject();
}
}
}
}

private static String createDeepNestedDoc(final int depth) {
StringBuilder sb = new StringBuilder();
sb.append("[");
for (int i = 0; i < depth; i++) {
sb.append("{ \"a\": [");
}
sb.append(" \"val\" ");
for (int i = 0; i < depth; i++) {
sb.append("]}");
}
sb.append("]");
return sb.toString();
}

}

0 comments on commit 57df6a1

Please sign in to comment.