C# reference: one payload nesting bound (256) on read and write, nil arm slots refused, bounds vectors

CultPayloadDepth.Check walks MessagePack headers iteratively and refuses the first
container past CultMessagePackSecurity.MaximumPayloadDepth, read or skipped, called by
SerializeUntyped (on the bytes it produced) and DeserializeUntyped (before decoding).
UntrustedData's 500 is replaced by the same value as a backstop. CultUnionGuard refuses
[k, nil] arms. New vectors in tests/vectors/union-parity, with bounds-outcomes.msgpack
as their data; the gate now runs every Check-calling test body and fingerprints bytes
and write times. All commits build.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Meta Vens 2026-10-11 08:33:15 +02:00
commit abf624ab04
20 changed files with 465 additions and 21 deletions

View file

@ -83,7 +83,9 @@ public static class CultDocumentMessagePackSerialization
{
if (registry == null) throw new ArgumentNullException(nameof(registry));
if (value != null) registry.GetRequired(type);
return MessagePackSerializer.Serialize(type, value, OptionsFor(type.Assembly));
var payload = MessagePackSerializer.Serialize(type, value, OptionsFor(type.Assembly));
CultPayloadDepth.Check(payload, type, PayloadDirection.Write);
return payload;
}
public static object DeserializeUntyped(Type type, byte[] payload)
@ -95,6 +97,7 @@ public static class CultDocumentMessagePackSerialization
{
if (registry == null) throw new ArgumentNullException(nameof(registry));
registry.GetRequired(type);
CultPayloadDepth.Check(payload, type, PayloadDirection.Read);
return MessagePackSerializer.Deserialize(type, payload, OptionsFor(type.Assembly))
?? throw new InvalidOperationException($"MessagePack returned null for Cult document type {type.FullName}.");
}