Unbounded rule-application recursion via self-matching LINK rules: kernel stack exhaustion (double fault)
Summary
devfs_allocp() applies rules to every new node, so a LINK rule whose own output matches itself (name '*', linkname 'y': foo -> yfoo -> yyfoo -> ...) nests check_apply -> checkname(PATH_MAX buf) -> create_link(2x PATH_MAX bufs) ~3KB/frame without bound; one ADD+APPLY double-faults the devfs core thread (rip symbolized to devfs_rule_create_link+0x1c). VERIFIED: Fatal double fault from two ioctls. Surviving variants instead livelock devfs_lock with unbounded alias creation. Privileged (root or jailed root via DF-3197); deterministic panic; no memory-safety bug required. Fix validated (depth-cap 8 + heap buffers): SURVIVED on fixed kernel.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-3201 Β· 11 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 202 B | β raw | |
| VERDICT.md | β | 2.0 KB | β raw | |
| build.sh | β | 99 B | view raw | |
| env.txt | β | 415 B | view raw | |
| fix.diff | β | 8.6 KB | view raw | |
| panic.doublefault.2.txt | β | 13.3 KB | view raw | |
| panic.txt | β | 13.4 KB | view raw | |
| run.fixed.log | β | 136 B | view raw | |
| run.sh | β | 95 B | view raw | |
| selflink_rec.c | β | 2.1 KB | view raw | |
| verdict.json | β | 2.3 KB | view raw |
DF-3201 β self-matching LINK rule -> unbounded check_apply/create_link recursion -> kernel stack exhaustion. See VERDICT.md. Build: cc -static -O -o selflink_rec selflink_rec.c ; Run as root (panic).
DF-3201 β unbounded rule-application recursion via self-matching LINK rules (stack exhaustion)
Root cause
devfs_allocp() applies the rule engine to every newly created node
(devfs_core.c:291-292):
check_apply(node) -> create_link() -> devfs_alias_create()
-> devfs_allocp(alias) -> check_apply(alias) -> ...
A LINK rule whose own output matches itself therefore recurses without bound.
Rule { NAME "*", LINK, linkname "y" }: node foo gets alias yfoo
(strip = strlen("*")-1 = 0), yfoo matches "*" too -> yyfoo -> ...
Each recursion level nests devfs_rule_check_apply +
devfs_rule_checkname (PATH_MAX stack buffer) + devfs_rule_create_link
(TWO PATH_MAX stack buffers) β 3 KB of stack per generation β the kernel
stack is gone after a handful of generations, even though names grow only
one byte at a time.
Reproduced
panic: double fault, rip = 0xffffffff809243cc = devfs_rule_create_link+0x1c
(symbolized via nm on the guest kernel) β the crash sits inside the first
PATH_MAX buffer frame of the nested create_link. Trigger: a single
DEVFS_RULE_ADD + DEVFS_RULE_APPLY as root (or jailed root via DF-3197).
Even without the stack blowup the same self-match drives unbounded node
creation under devfs_lock (devfs-wide livelock / memory exhaustion).
Fix
fix.diff: (a) depth cap (8) on rule-driven LINK creation in check_apply,
(b) create_link/checkname PATH_MAX buffers moved from stack to heap.
Validated: post-fix the same rule applies without a panic (run.fixed.log).
Files
selflink_rec.c, build.sh, run.sh, panic.txt, fix.diff,
run.fixed.log, verdict.json, manifest.json
Fix status (batch validation)
Applied with the 36 sibling fix.diffs of this batch in findings/poc/_batch41/all_fixes.patch; single in-guest make -j6 nativekernel KERNCONF=X86_64_GENERIC completed RC=0 with zero errors under -Werror (full log: findings/poc/_batch41/fix_build8.log). Behavior A/B intentionally skipped for this Info-tier item.
Fix verification
fixedsame PoC on fixed kernel: SURVIVED, bounded alias creation, guest up
['DF-3201/run.fixed.log']
Confirmed kernel references
Detail
Exploit chain
privileged ADD {NAME '', LINK, 'y'} + APPLY '' -> unbounded nested rule application -> stack exhaustion double fault (or devfs_lock livelock)
Evidence (decisive lines)
['panic.txt: Fatal double fault, rip=devfs_rule_create_link+0x1c (nm-verified)', 'run.fixed.log: SURVIVED with depth cap + heap buffers']
PoC changes
none beyond compile fixes
Verified recommended fix
depth-cap rule-driven LINK creation and move create_link/checkname PATH_MAX buffers off the stack
Verdict
devfs_allocp() applies rules to every new node (devfs_core.c:291-292); a LINK rule whose own output matches itself (name '*', linkname 'y': foo -> yfoo -> yyfoo -> ...) recurses check_apply -> create_link -> alias_create -> allocp without bound, each level carrying ~3KB of stack (checkname's PATH_MAX buffer + create_link's two PATH_MAX buffers). A single ADD+APPLY double-faulted the devfs core thread; rip symbolized to devfs_rule_create_link+0x1c. Even where the stack survives, the same self-match drives unbounded node creation under devfs_lock (system-wide devfs livelock + memory exhaustion).
No comments yet.