RUSTSEC-2026-0195
Unbounded namespace-declaration allocation in `NsReader` enables memory-exhaustion denial of service
Details
`NsReader` resolves namespaces by calling `NamespaceResolver::push` for every `Start`/`Empty` event *before* the event is returned to the caller. `push` iterated all `xmlns` / `xmlns:*` attributes on the start tag and, for each one, appended the prefix bytes to an internal buffer and pushed a `NamespaceBinding` (32 bytes on 64-bit) to an internal `Vec`, with no upper bound on the number of declarations.
## Impact
A start tag with `N` namespace declarations drove roughly `3×` the tag's byte size in `NamespaceResolver` heap, allocated *inside* `quick-xml` before the `NsReader` consumer ever received the event and could inspect or reject it. A consumer that bounds its *input* size therefore still cannot bound this allocation: an `M`-byte start tag yields on the order of `3 × M` bytes of resolver heap the caller never sees.
On untrusted XML this lets a remote, unauthenticated attacker force large heap allocations with a single start tag. With several `NsReader`s running concurrently on independent inputs (a common server pattern), the allocations stack and can exhaust process memory, causing the operating system to kill the process (OOM). This was confirmed against a real-world RPKI relying party (NLnet Labs Routinator), where concurrent RRDP validation workers parsing a crafted `snapshot.xml` exceeded the memory limit and the process was OOM-killed.
## Affected code paths
Consumers using `NsReader` (which always calls `NamespaceResolver::push` before yielding `Start`/`Empty`), or calling `NamespaceResolver::push` directly. A plain `Reader` that does not perform namespace resolution is not affected.
## Remediation
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 the new `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), and `NsReader::resolver_mut()` is provided to reach it.
There is no clean workaround for `NsReader` consumers before 0.41.0, as the allocation happens inside the reader with no configuration knob to cap it.
Are you affected?
Enter the version of the package you're using.
Affected packages
0.0.0-0 Fixed in: 0.41.0 Upgrade quick-xml to 0.41.0 or newer (ecosystem crates.io).