Skip to content

Commit 3ccde7d

Browse files
committed
Fix #3328 (improvement to handling of JDK serialization of JsonNode)
1 parent 70ba54f commit 3ccde7d

File tree

3 files changed

+76
-6
lines changed

3 files changed

+76
-6
lines changed

release-notes/VERSION-2.x

+1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ Project: jackson-databind
99
#3280: Can not deserialize json to enum value with Object-/Array-valued input,
1010
`@JsonCreator`
1111
(reported by peteryuanpan@github)
12+
#3328: Possible DoS issue
1213

1314
2.12.5 (27-Aug-2021)
1415

src/main/java/com/fasterxml/jackson/databind/node/NodeSerialization.java

+36-2
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
import java.io.ObjectInput;
55
import java.io.ObjectOutput;
66

7+
import com.fasterxml.jackson.core.util.ByteArrayBuilder;
8+
79
/**
810
* Helper value class only used during JDK serialization: contains JSON as `byte[]`
911
*
@@ -12,6 +14,9 @@
1214
class NodeSerialization implements java.io.Serializable,
1315
java.io.Externalizable
1416
{
17+
// To avoid malicious input only allocate up to 100k
18+
protected final static int LONGEST_EAGER_ALLOC = 100_000;
19+
1520
private static final long serialVersionUID = 1L;
1621

1722
public byte[] json;
@@ -45,7 +50,36 @@ public void writeExternal(ObjectOutput out) throws IOException {
4550
@Override
4651
public void readExternal(ObjectInput in) throws IOException {
4752
final int len = in.readInt();
48-
json = new byte[len];
49-
in.readFully(json, 0, len);
53+
json = _read(in, len);
54+
}
55+
56+
private byte[] _read(ObjectInput in, int expLen) throws IOException {
57+
// Common case, just read directly
58+
if (expLen <= LONGEST_EAGER_ALLOC) {
59+
byte[] result = new byte[expLen];
60+
in.readFully(result, 0, expLen);
61+
return result;
62+
}
63+
// but longer content needs more care to avoid DoS by maliciously crafted data
64+
// (this wrt [databind#3328]
65+
try (final ByteArrayBuilder bb = new ByteArrayBuilder(LONGEST_EAGER_ALLOC)) {
66+
byte[] buffer = bb.resetAndGetFirstSegment();
67+
int outOffset = 0;
68+
while (true) {
69+
int toRead = Math.min(buffer.length - outOffset, expLen);
70+
in.readFully(buffer, 0, toRead);
71+
expLen -= toRead;
72+
outOffset += toRead;
73+
// Did we get everything we needed? If so, we are done
74+
if (expLen == 0) {
75+
return bb.completeAndCoalesce(outOffset);
76+
}
77+
// Or perhaps we filled the current segment? If so, finish, get next
78+
if (outOffset == buffer.length) {
79+
buffer = bb.finishCurrentSegment();
80+
outOffset = 0;
81+
}
82+
}
83+
}
5084
}
5185
}

src/test/java/com/fasterxml/jackson/databind/TestNodeJDKSerialization.java renamed to src/test/java/com/fasterxml/jackson/databind/node/NodeJDKSerializationTest.java

+39-4
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
1-
package com.fasterxml.jackson.databind;
1+
package com.fasterxml.jackson.databind.node;
22

33
import java.io.*;
44

5-
import com.fasterxml.jackson.databind.node.ArrayNode;
6-
import com.fasterxml.jackson.databind.node.ObjectNode;
5+
import com.fasterxml.jackson.core.JsonGenerator;
76

8-
public class TestNodeJDKSerialization extends BaseMapTest
7+
import com.fasterxml.jackson.databind.BaseMapTest;
8+
import com.fasterxml.jackson.databind.JsonNode;
9+
import com.fasterxml.jackson.databind.ObjectMapper;
10+
11+
public class NodeJDKSerializationTest extends BaseMapTest
912
{
1013
private final ObjectMapper MAPPER = newJsonMapper();
1114

@@ -40,6 +43,38 @@ public void testArrayNodeSerialization() throws Exception
4043
testNodeRoundtrip(root);
4144
}
4245

46+
// [databind#3328]
47+
public void testBigArrayNodeSerialization() throws Exception
48+
{
49+
// Try couple of variations just to tease out possible edge cases
50+
_testBigArrayNodeSerialization(NodeSerialization.LONGEST_EAGER_ALLOC - 39);
51+
_testBigArrayNodeSerialization(NodeSerialization.LONGEST_EAGER_ALLOC + 1);
52+
_testBigArrayNodeSerialization(3 * NodeSerialization.LONGEST_EAGER_ALLOC - 1);
53+
_testBigArrayNodeSerialization(9 * NodeSerialization.LONGEST_EAGER_ALLOC);
54+
}
55+
56+
private void _testBigArrayNodeSerialization(int expSize) throws Exception
57+
{
58+
ByteArrayOutputStream out = new ByteArrayOutputStream();
59+
int ix = 0;
60+
try (JsonGenerator g = MAPPER.createGenerator(out)) {
61+
g.writeStartArray();
62+
63+
do {
64+
g.writeStartObject();
65+
g.writeNumberField("index", ix++);
66+
g.writeStringField("extra", "none#"+ix);
67+
g.writeEndObject();
68+
} while (out.size() < expSize);
69+
70+
g.writeEndArray();
71+
}
72+
73+
JsonNode root = MAPPER.readTree(out.toByteArray());
74+
75+
testNodeRoundtrip(root);
76+
}
77+
4378
// and then also some scalar types
4479
public void testScalarSerialization() throws Exception
4580
{

0 commit comments

Comments
 (0)