JDK deserialization of a `JsonNode` with a negative length prefix throws `NegativeArraySizeException` instead of an `IOException`

Open Beginner friendly
#6,443 0 comments 0 reactions 0 assignees View on GitHub

Maintainers usually reply within 1 day

Nobody has claimed this yet.

Assessment

Difficulty
2/5
Estimated time
1-3 hours
Newbie friendliness
72/100
Issue type
Bug
Clarity
Clearly specified
Activity status
Active
Tech stack
java
Domain
databases

Research direction

The bug is in NodeSerialization.readExternal() and _read() in NodeSerialization.java, where a negative length from in.readInt() reaches new byte[expLen] on the eager path. Start by reading _read() around line 62 and add a check that throws a StreamCorruptedException when expLen is negative. Done when the reproduction in the issue throws an IOException and the existing test suite still passes.

Written by the indexing model from the issue text.

Description

Search before asking
  • I searched in the issues and found nothing similar.
Describe the bug

JsonNode is JDK-serialized through NodeSerialization, which writes a 4-byte length followed by the JSON bytes. On reading, a negative length makes readObject() throw an unchecked NegativeArraySizeException. A length that is too large is reported as an EOFException, and other malformed streams give IOExceptions:

java.lang.NegativeArraySizeException: -1
	at tools.jackson.databind.node.NodeSerialization._read(NodeSerialization.java:63)
	at tools.jackson.databind.node.NodeSerialization.readExternal(NodeSerialization.java:57)
	at java.base/java.io.ObjectInputStream.readExternalData(ObjectInputStream.java:2333)
	...

Cause: readExternal() passes the length read by in.readInt() to _read() (NodeSerialization.java:56-57). _read() takes the eager path for every expLen <= LONGEST_EAGER_ALLOC (:62), including negative values, and calls new byte[expLen] (:63). 2.x has the same code (NodeSerialization.java:52-59). The #3328 hardening only covers large lengths.

The effect is limited to the exception type: the stream is rejected either way, so this is not a security issue. Callers that catch IOException around readObject() do get an unexpected runtime exception, though.

Version Information

3.x (3.3.0-SNAPSHOT, 043e38d), 3.2.4, 2.x (2.23.0-SNAPSHOT, 425299b), 2.22.4.

Reproduction
JsonNode node = new JsonMapper().createObjectNode().put("a", 1);
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
try (ObjectOutputStream out = new ObjectOutputStream(bytes)) {
    out.writeObject(node);
}
byte[] b = bytes.toByteArray();
// overwrite the 4-byte length that precedes the JSON payload {"a":1} with -1
int idx = indexOf(b, "{\"a\":1}".getBytes(StandardCharsets.UTF_8));
b[idx - 4] = b[idx - 3] = b[idx - 2] = b[idx - 1] = (byte) 0xFF;

try (ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(b))) {
    in.readObject();   // NegativeArraySizeException
}
Expected behavior

An IOException, for example StreamCorruptedException("Invalid JsonNode content length: -1") thrown from _read() when expLen < 0. With that change, the full 3.x test suite passes.

Additional context

This issue was found by an LLM-Enhanced Static Analysis Engine and verified manually.

Dominant language
Java
Stars
3.8k
Forks
1.6k
Avg merge
1d 18h
Merged PRs (30d)
59

Getting set up

This project ships no dev container, Dockerfile or contributing guide, so setting up is up to you: start from its README, and see our first-contribution guide for the general steps.

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from FasterXML/jackson-databind

All issues in FasterXML/jackson-databind

Similar issues

More Java issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.