β¬’ DragonFlyBSD Kernel Audit
← triage Β· dashboard
DF-3201

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)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-3201 Β· 11 files
FileTypeDescriptionSize
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).

VERDICT.md
↓ download raw

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

fixed
baseline reproduced→ patch + rebuild →patched clean

same PoC on fixed kernel: SURVIVED, bounded alias creation, guest up

['DF-3201/run.fixed.log']
↓ fix.diffDragonFly dfbsd 6.5-DEVELOPMENT DragonFly 6.5-DEVELOPMENT #1: Tue Sep 8 10:25:07 UTC 2026 root@dfbsd:/usr/obj/usr/src/sys/X86_64_GENERIC x86_64

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