rustsec-2026-0195
Vulnerability from osv_rustsec
Published
2026-06-29 12:00
Modified
2026-07-02 08:52
Summary
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 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 NsReaders 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": [
    {
      "database_specific": {
        "categories": [
          "denial-of-service"
        ],
        "cvss": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H",
        "informational": null
      },
      "ecosystem_specific": {
        "affected_functions": null,
        "affects": {
          "arch": [],
          "functions": [],
          "os": []
        }
      },
      "package": {
        "ecosystem": "crates.io",
        "name": "quick-xml",
        "purl": "pkg:cargo/quick-xml"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0.0.0-0"
            },
            {
              "fixed": "0.41.0"
            }
          ],
          "type": "SEMVER"
        }
      ],
      "versions": []
    }
  ],
  "aliases": [],
  "database_specific": {
    "license": "CC0-1.0"
  },
  "details": "`NsReader` resolves namespaces by calling `NamespaceResolver::push` for every\n`Start`/`Empty` event *before* the event is returned to the caller. `push`\niterated all `xmlns` / `xmlns:*` attributes on the start tag and, for each one,\nappended the prefix bytes to an internal buffer and pushed a `NamespaceBinding`\n(32 bytes on 64-bit) to an internal `Vec`, with no upper bound on the number of\ndeclarations.\n\n## Impact\n\nA start tag with `N` namespace declarations drove roughly `3\u00d7` the tag\u0027s byte\nsize in `NamespaceResolver` heap, allocated *inside* `quick-xml` before the\n`NsReader` consumer ever received the event and could inspect or reject it. A\nconsumer that bounds its *input* size therefore still cannot bound this\nallocation: an `M`-byte start tag yields on the order of `3 \u00d7 M` bytes of\nresolver heap the caller never sees.\n\nOn untrusted XML this lets a remote, unauthenticated attacker force large heap\nallocations with a single start tag. With several `NsReader`s running\nconcurrently on independent inputs (a common server pattern), the allocations\nstack and can exhaust process memory, causing the operating system to kill the\nprocess (OOM). This was confirmed against a real-world RPKI relying party (NLnet\nLabs Routinator), where concurrent RRDP validation workers parsing a crafted\n`snapshot.xml` exceeded the memory limit and the process was OOM-killed.\n\n## Affected code paths\n\nConsumers using `NsReader` (which always calls `NamespaceResolver::push` before\nyielding `Start`/`Empty`), or calling `NamespaceResolver::push` directly. A plain\n`Reader` that does not perform namespace resolution is not affected.\n\n## Remediation\n\nUpgrade to `quick-xml \u003e= 0.41.0`. `NamespaceResolver::push` now rejects a start\ntag that declares more than `DEFAULT_MAX_DECLARATIONS_PER_ELEMENT` (256)\nnamespace bindings, returning the new `NamespaceError::TooManyDeclarations`\ninstead of allocating without limit. The limit is configurable via\n`NamespaceResolver::set_max_declarations_per_element` (use `usize::MAX` to\nrestore the previous unbounded behavior), and `NsReader::resolver_mut()` is\nprovided to reach it.\n\nThere is no clean workaround for `NsReader` consumers before 0.41.0, as the\nallocation happens inside the reader with no configuration knob to cap it.",
  "id": "RUSTSEC-2026-0195",
  "modified": "2026-07-02T08:52:02Z",
  "published": "2026-06-29T12:00:00Z",
  "references": [
    {
      "type": "PACKAGE",
      "url": "https://crates.io/crates/quick-xml"
    },
    {
      "type": "ADVISORY",
      "url": "https://rustsec.org/advisories/RUSTSEC-2026-0195.html"
    },
    {
      "type": "REPORT",
      "url": "https://github.com/tafia/quick-xml/issues/970"
    },
    {
      "type": "WEB",
      "url": "https://github.com/tafia/quick-xml/commit/7ca25266e94987210daa864889ab15c9332c8a2a"
    }
  ],
  "related": [],
  "severity": [
    {
      "score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H",
      "type": "CVSS_V3"
    }
  ],
  "summary": "Unbounded namespace-declaration allocation in `NsReader` enables memory-exhaustion denial of service"
}


Log in or create an account to share your comment.




Tags
Taxonomy of the tags.


Loading…

Loading…

Loading…

Sightings

Author Source Type Date

Nomenclature

  • Seen: The vulnerability was mentioned, discussed, or observed by the user.
  • Confirmed: The vulnerability has been validated from an analyst's perspective.
  • Published Proof of Concept: A public proof of concept is available for this vulnerability.
  • Exploited: The vulnerability was observed as exploited by the user who reported the sighting.
  • Patched: The vulnerability was observed as successfully patched by the user who reported the sighting.
  • Not exploited: The vulnerability was not observed as exploited by the user who reported the sighting.
  • Not confirmed: The user expressed doubt about the validity of the vulnerability.
  • Not patched: The vulnerability was not observed as successfully patched by the user who reported the sighting.


Loading…

Detection rules are retrieved from Rulezet.

Loading…

Loading…