A single XML start tag with just 256 namespace declarations forces roughly 3× its byte size in hidden heap allocations inside quick-xml's NsReader, before the caller ever sees the event — and on untrusted XML, that's a remote denial-of-service vector.
The Allocation Heist: 3× Input Size Before You Get the Event
NsReader resolves namespaces by calling NamespaceResolver::push for every Start or Empty event before returning it to the consumer. push iterates all xmlns/xmlns:* attributes on the start tag. For each one, it appends the prefix bytes to an internal buffer and pushes a NamespaceBinding (32 bytes on 64-bit) into an internal Vec. No upper bound on the number of declarations. A start tag with N namespace declarations drives roughly 3× the tag's byte size in NamespaceResolver heap, allocated inside quick-xml before the consumer ever gets to inspect or reject the event. If you're bounding your input size, you still can't bound this allocation: an M-byte start tag yields on the order of 3×M bytes of resolver heap the caller never sees.
Real-World Kill: NLnet Labs Routinator OOM-Confirmed
On untrusted XML this lets a remote, unauthenticated attacker force large heap allocations with a single start tag. With several NsReaders running concurrently on independent inputs — a common server pattern — the allocations stack and can exhaust process memory, causing the OS to kill the process via OOM. This was confirmed against a real-world RPKI relying party: NLnet Labs Routinator. Concurrent RRDP validation workers parsing a crafted snapshot.xml exceeded the memory limit and the process was OOM-killed. Not a theoretical exercise.
Fix: Configurable Cap at 256 Declarations
Upgrade to quick-xml >= 0.41.0. NamespaceResolver::push now rejects a start tag that declares more than DEFAULT_MAX_DECLARATIONS_PER_ELEMENT (256) namespace bindings, returning NamespaceError::TooManyDeclarations instead of allocating without limit. The limit is configurable via NamespaceResolver::set_max_declarations_per_element (use usize::MAX to restore the previous unbounded behavior, if you enjoy living dangerously). NsReader::resolver_mut() is provided to reach it. No clean workaround exists for NsReader consumers before 0.41.0 — the allocation happens inside the reader with no knob to cap it.
Every project pulling XML from untrusted sources should bump their quick-xml dependency now. The RustSec advisory (RUSTSEC-0000-0000) carries a CVSS 3.1 score of 7.5 (High) with network attack vector and no privileges required.
Source: Add advisory for quick-xml: unbounded namespace-declaration allocatio...
Domain: github.com
Comments load interactively on the live page.