Skip to content
OpenAdvisories
High · 7.5 RUSTSEC-2026-0195

Unbounded namespace-declaration allocation in `NsReader` enables memory-exhaustion denial of service

Published Jun 29, 2026 · Updated Jul 2, 2026 · Assigned by RUSTSEC

Description

`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.

Affected packages

EcosystemPackageAffectedFixedRecord
crates.io quick-xml >= 0.0.0-0, < 0.41.0 0.41.0 RUSTSEC-2026-0195

References