Repository navigation
[Security] Stack Overflow DoS via Uncontrolled Recursion in JsonMergePatch Deserialization #168
Description
Security Vulnerability Report
Metadata
| Field | Value |
|---|---|
| Affected Version | 1.13 (latest on Maven Central) |
| Component | JsonMergePatchDeserializer.deserialize() |
| File | src/main/java/com/github/fge/jsonpatch/mergepatch/JsonMergePatchDeserializer.java:87 |
| CWE | CWE-674 (Uncontrolled Recursion) |
| CVSS 3.1 | 7.5 (AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H) |
Summary
JsonMergePatch.fromJson() uses a recursive deserializer (JsonMergePatchDeserializer) that recurses without any depth limit. A deeply nested JSON object (2000+ levels) causes StackOverflowError, killing the processing thread. This is a separate code path from Jackson's own parsing — even if Jackson 2.15+ limits parsing depth to 1000, a programmatically-built JsonNode passed to JsonMergePatch.fromJson() still triggers the unguarded recursion.
Root Cause
// src/main/java/com/github/fge/jsonpatch/mergepatch/JsonMergePatchDeserializer.java:87
@Override
public JsonMergePatch deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
JsonNode node = p.readValueAsTree();
// ...
if (node.isObject()) {
// For each field in the object:
for (Map.Entry<String, JsonNode> entry : ...) {
// RECURSIVE CALL — no depth counter!
JsonMergePatch child = deserialize(entry.getValue(), ctxt); // Line 87
// ...
}
}
// ...
}The recursive call at line 87 has no depth guard — it recurses as deep as the input JSON structure goes.
Proof of Concept
Runtime verified on VM with OpenJDK 21 (-Xss256k):
--- Test 3: Stack Overflow in MergePatch Deserialization ---
VULNERABLE: StackOverflowError in JsonMergePatch.fromJson() at depth=2000
Minimal reproduction:
import com.github.fge.jsonpatch.mergepatch.JsonMergePatch;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
ObjectMapper mapper = new ObjectMapper();
// Build a 2000-level deep nested JSON object programmatically
ObjectNode root = mapper.createObjectNode();
ObjectNode current = root;
for (int i = 0; i < 2000; i++) {
ObjectNode child = mapper.createObjectNode();
current.set("nested", child);
current = child;
}
current.put("leaf", "value");
// This throws StackOverflowError — killing the thread
JsonMergePatch patch = JsonMergePatch.fromJson(root); // CRASH!Attack scenario:
- Attacker sends HTTP request with a deeply nested JSON body (2000+ levels)
- Application calls
JsonMergePatch.fromJson()on the parsed body JsonMergePatchDeserializerrecurses 2000 times without depth checkStackOverflowErrorthrown — thread is killed (unrecoverable)- Repeated requests exhaust thread pool
Why This Is NOT Covered by CVE-2020-36518
CVE-2020-36518 addresses Jackson's ObjectMapper.readTree() parsing depth — fixed in Jackson 2.15+ via StreamReadConstraints (default max depth: 1000).
This vulnerability is different because:
- The unguarded recursion is in
json-patch's ownJsonMergePatchDeserializer, not in Jackson - If the application parses JSON first (subject to Jackson's limit) then passes the
JsonNodetoJsonMergePatch.fromJson(), the json-patch recursion still has no guard - Even with Jackson 2.15+ protecting the parsing layer, programmatically-built deep
JsonNodetrees bypass Jackson's protection entirely
Impact
- Denial of Service: Thread killed by StackOverflowError (unrecoverable — cannot be caught reliably)
- Thread pool exhaustion: Each crashed thread is permanently lost until JVM restart
- Low attack cost: Single HTTP request with ~40KB payload (2000 nested
{"nested":prefixes) - No authentication required: Any endpoint using JsonMergePatch
Suggested Remediation
Add a depth counter parameter to the recursive deserialize() call:
private static final int MAX_DEPTH = 1000;
private JsonMergePatch deserialize(JsonNode node, DeserializationContext ctxt, int depth) throws IOException {
if (depth > MAX_DEPTH) {
throw new JsonProcessingException("JSON Merge Patch exceeds maximum nesting depth: " + MAX_DEPTH);
}
if (node.isObject()) {
for (Map.Entry<String, JsonNode> entry : ...) {
JsonMergePatch child = deserialize(entry.getValue(), ctxt, depth + 1);
// ...
}
}
// ...
}References
- RFC 7386 (JSON Merge Patch): does not mandate accepting unlimited nesting depth
- CWE-674: https://cwe.mitre.org/data/definitions/674.html
- Related but distinct: CVE-2020-36518 (Jackson parsing layer — different code path)
Activity
TonyOlaCodes commentedon Sep 28, 2026
Hi, we're currently using json-patch 1.13 and have been impacted by CVE-2026-86319 (Stack Overflow DoS via uncontrolled recursion in JsonMergePatch deserialization). We've checked and there's no patched release available. Could you share whether a fix is being worked on and if there's an expected timeline for a release? We'd really appreciate any update. Thanks!