CVE Patch Review

CVE-2026-48517 Root-Cause Fix for Nested Typeless Deserialization Blocklist Bypass

CVE-2026-48517 · GHSA-QHMF-XW27-6RQR · Updated 2026-06-26 Root-cause

Summary

The patch addresses a real root cause in MessagePack-CSharp typeless deserialization by extending disallowed-type validation from only the top-level type to recursively inspected array element types and generic type arguments. This closes the documented bypass where dangerous gadget types could be wrapped inside collections and evade the blocklist. The change also introduces a new core virtual hook to preserve backward compatibility while steering custom policy implementations toward safer override semantics.

Analysis

Vulnerability

CVE-2026-48517 describes a remote code execution condition in MessagePack-CSharp typeless deserialization where the library validated only the top-level type against a disallowed-type blocklist. According to the advisory and CVE records, versions prior to 2.5.301 and 3.1.7 could deserialize nested gadget types when those types were wrapped inside arrays or generic collections, allowing a blocklist bypass during unauthenticated deserialization of untrusted payloads. See the vendor advisory at GHSA-QHMF-XW27-6RQR, the upstream patch at the fixing commit, and the public CVE records at CVE.org and NVD.

The vulnerable behavior shown in the patch summary was effectively:

if (type.FullName is string fullName && DisallowedTypes.Contains(fullName))
    throw new MessagePackSerializationException($"Deserialization attempted to create the type {fullName} which is not allowed.");

That logic checks only the immediate type being instantiated. If the payload materializes something like List<DangerousType> or DangerousType[], the outer collection type is not itself blocklisted, so the dangerous inner type could evade the policy.

Patch

The upstream fix in f093bdc1207f6576088d39801fa43e92cec1e5c1 changes the validation model from a flat top-level check to recursive inspection of nested type structure. The patched method now delegates the direct-type decision to a new ThrowIfDeserializingTypeIsDisallowedCore(Type) hook, then recursively validates array element types and constructed generic arguments.

this.ThrowIfDeserializingTypeIsDisallowedCore(type);

if (type.HasElementType && type.GetElementType() is Type elementType)
{
    this.ThrowIfDeserializingTypeIsDisallowed(elementType);
}

if (type.IsConstructedGenericType)
{
    foreach (Type genericTypeArgument in type.GenericTypeArguments)
    {
        this.ThrowIfDeserializingTypeIsDisallowed(genericTypeArgument);
    }
}

The patch also adds tests covering nested disallowed types in both arrays and generic collections, which directly target the bypass class described in the advisory:

yield return new object[] { new MyObject[] { new() { SomeValue = 5 } } };
yield return new object[] { new List<MyObject> { new() { SomeValue = 5 } } };

From an API-design perspective, introducing ThrowIfDeserializingTypeIsDisallowedCore(Type) is significant. Existing overrides of ThrowIfDeserializingTypeIsDisallowed(Type) remain possible for compatibility, while the new core method gives downstream consumers a narrower and safer extension point for direct-type policy without accidentally suppressing recursive traversal.

Review

Pros

  • The patch directly addresses the documented root cause: missing recursive validation of nested types inside arrays and generic collections.
  • The implementation is structurally aligned with the vulnerability description in the vendor advisory, rather than merely adding a special-case gadget signature.
  • The new ThrowIfDeserializingTypeIsDisallowedCore(Type) method improves extensibility by separating direct-type policy from recursive traversal logic.
  • Regression tests cover both major bypass shapes called out by the patch: array element types and generic collection arguments.
  • The exception path remains explicit and fail-closed when a disallowed nested type is encountered.

Cons

  • The default model is still a blocklist. The patch improves coverage, but blocklists remain weaker than strict allowlists for typeless deserialization of untrusted data, as the updated XML comments also imply.
  • Backward compatibility means consumers can still override ThrowIfDeserializingTypeIsDisallowed(Type) directly and potentially reintroduce unsafe behavior if they bypass or replace the recursive logic.
  • The visible tests cover arrays and List<T>, but the patch summary does not show broader regression coverage for more complex nested shapes such as dictionaries, jagged arrays, or deeply nested generic graphs. The recursive implementation should handle them, but the shown test surface is narrow.
  • The fix is scoped to type validation during typeless deserialization; it does not change the broader security posture that typeless deserialization of untrusted input is inherently high risk.

Verdict

Root-cause.

This is a substantive fix, not a superficial filter update. The vulnerable behavior existed because only the outer deserialized type was checked against the disallowed set, while nested generic arguments and array element types were not traversed. The patch corrects that design flaw by recursively validating nested type components before deserialization proceeds. The addition of a core virtual method is also a thoughtful compatibility measure that preserves existing extension points while encouraging safer customization. Engineers should still prefer allowlist-based policies or avoid typeless deserialization for untrusted inputs, but relative to the reported issue, the patch closes the bypass mechanism described in GHSA-QHMF-XW27-6RQR and tracked in CVE-2026-48517.

Sources