RUSTSEC-2026-0194 json
Quadratic run time when checking a start tag for duplicate attribute names
published 4mo ago modified 4mo ago
- cvss
- 7.5
- version 3.1, OSV
- cisa kev
- Not listed
- not in CISA's catalog
- epss
- —
- no score, EPSS covers CVEs only
- public exploits
- 0
- none known
Summary
`BytesStart::attributes()` returns an `Attributes` iterator which, by default (`with_checks(true)`), rejects a start tag that repeats an attribute name. For each attribute yielded, the iterator compared the new name against every name seen so far in the same tag using a linear scan, so a start tag with `N` distinct attribute names cost `O(N²)` byte comparisons. There was no bound on `N` other than the size of the buffered start tag.
## Impact
Any code that parses untrusted XML and iterates a start tag's attributes with the default duplicate check enabled can be made to spend CPU time quadratic in the number of attributes on a single tag. Because the check is pure computation with no `.await`/I/O, an I/O-based timeout on the consumer (for example a read or request timeout) cannot interrupt it while it runs.
Measured cost of a single start tag, release build:
| Attributes on one tag | Time | |---|---| | 80,000 | ~6 s | | 800,000 | ~10 min |
The cost grows with the square of the attribute count, so a start tag of a few tens of megabytes can stall a parsing thread for hours. No memory is exhausted and the parser does not crash; the effect is CPU exhaustion on the thread doing the parsing: a single crafted start tag can pin a CPU core for minutes to hours, denying service to that worker. A deployment that places a wall-clock bound on parsing, or confines it to a non-critical thread, may consider the availability impact lower.
## Affected code paths
* `BytesStart::attributes()` / `Attributes` iterated with checks enabled (the default), and `BytesStart::try_get_attribute`. * `NsReader`, which resolves namespaces by iterating a tag's attributes and so reaches the same check internally.
Consumers that iterate attributes with `.attributes().with_checks(false)` and do not use `NsReader` are not affected.
This was reported as reachable by a remote, unauthenticated attacker in a real-world RPKI relying party (NLnet Labs Routinator) via a crafted RRDP `snapshot.xml`.
## Remediation
Upgrade to `quick-xml >= 0.41.0`, where the duplicate check keeps the linear scan for start tags with a small number of attributes and switches to an `O(1)` hash pre-filter above a threshold, making the whole tag `O(N)`. The reported `AttrError::Duplicated` positions are unchanged.
If upgrading is not possible and duplicate-name detection is not required, disable it with `.attributes().with_checks(false)` (this does not help `NsReader` consumers, which have no equivalent opt-out before 0.41.0).
Patch / fix
- crates.io quick-xml 0.41.0
- github.com/tafia/quick-xml@07f3db8 commit
- github.com/tafia/quick-xml#971 pull request
Is RUSTSEC-2026-0194 in your dependencies? Fencer shows which of your repositories are affected as soon as an advisory lands, and opens the fix as a pull request.
Check with FencerCVSS scores
| assigned by | version | score | vector |
|---|---|---|---|
| OSV | 3.1 | 7.5 | CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H |
Affected packages
| ecosystem | package | affected | fixed in |
|---|---|---|---|
| crates.io | quick-xml | >=0.0.0-0, <0.41.0 |
0.41.0 |
References
Sources
Fencer Intel keeps one entry per vulnerability: the CVE id wins, then GHSA, and records that alias each other are merged, except that two CVEs always keep separate entries. The same data is available as JSON.