How to decode untrusted, deeply-nested MessagePack safely¶
A MessagePack array or map can hold values of its own type, to unbounded
depth. A decoder written the obvious way – one Python function that calls
itself for each nested container – risks RecursionError on maliciously or
accidentally deep input, since each level of nesting consumes another Python
call frame.
rustruct.formats.msgpack.decode()/
decode_from don’t have this problem: they
walk the buffer with an explicit work stack instead of Python recursion, so
nesting depth is bounded only by available memory:
from rustruct.formats.msgpack import decode_from
# 50,000 nested single-element arrays, terminated by nil -- hand-built,
# since a naive recursive encoder couldn't construct this many levels
# either.
depth = 50_000
wire = bytes([0x91]) * depth + b"\xc0"
value, offset = decode_from(wire, 0)
assert offset == len(wire)
seen = 0
while isinstance(value, list):
seen += 1
value = value[0] if value else None
assert seen == depth
encode() is iterative the same way, so building a deeply-nested Python
structure and encoding it doesn’t risk RecursionError either – only
constructing the Python object itself does, which is governed by Python’s
own limits, not rustruct’s.
This doesn’t bound memory or time: an attacker can still send a buffer that decodes into an enormous structure. Apply your own limits (a maximum input size, a maximum element count while walking the decoded value) the same way you would for any other untrusted, dynamically-sized input.