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

91: Stack overflow error caused by jakarta.json parsing of untrusted JSON String #87

Merged
merged 1 commit into from
Nov 7, 2024
Merged
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
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);
}
}
90 changes: 90 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,90 @@
/*
* 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.After;
import org.junit.Before;
import org.junit.Test;

public class JsonParserImplTest {

private String previousValue;

@Before
public void before() {
previousValue = System.getProperty(JsonParserImpl.MAX_DEPTH);
System.getProperties().remove(JsonParserImpl.MAX_DEPTH);
}

@After
public void after() {
if (previousValue != null) {
System.setProperty(JsonParserImpl.MAX_DEPTH, previousValue);
} else {
System.getProperties().remove(JsonParserImpl.MAX_DEPTH);
}
}

@Test
public void undefined() {
System.getProperties().remove(JsonParserImpl.MAX_DEPTH);
int result = JsonParserImpl.propertyStringToInt(JsonParserImpl.MAX_DEPTH, -1);
assertEquals(-1, result);
}

@Test
public void notInteger() {
System.setProperty(JsonParserImpl.MAX_DEPTH, "String");
int result = JsonParserImpl.propertyStringToInt(JsonParserImpl.MAX_DEPTH, -10);
assertEquals(-10, result);
}

@Test
public void integer() {
System.setProperty(JsonParserImpl.MAX_DEPTH, "10");
int result = JsonParserImpl.propertyStringToInt(JsonParserImpl.MAX_DEPTH, -1);
assertEquals(10, result);
}
}
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();
}

}