Destructive DDB sysctls (debug.panic/panic2/enter_debugger) have no jail confinement: jailed root can crash or freeze entire host
Summary
Three destructive debug sysctl handlers in sys/ddb/db_sysctl.c rely solely on sysctl dispatcher SYSCAP_NOSYSCTL_WR capability gate. That capability unconditionally allowed inside jails (prison_priv_check returns 0 for it with no pr_caps check kern_jail.c:885) and sysctl oid-tree traversal has no jail visibility filter. Consequently process running as root inside default-configured jail can execute sysctl debug.panic=1 and panic host kernel taking down host and every co-resident jail or sysctl debug.enter_debugger=ddb to hard-hang host in DDB. sysctl_debug_enter_debugger (:56-76) sysctl_debug_panic (:83-93) sysctl_debug_panic2 (:117-127) perform NO confinement check of own before calling Debugger()/panic()/stack_guard_panic2(). prison_priv_check for SYSCAP_NOSYSCTL_WR falls into group 2 allowed case returns 0 unconditionally no PRISON_CAP_* test. sysctl_find_oid traverses oid tree by number no jail visibility filter debug.* subtree reachable from jail. Attacker: root inside DragonFlyBSD jail with default prison capabilities (compromised jailed service). Impact: total DoS of HOST kernel and all co-resident jails panic/DDB hard hang crosses jail containment boundary. Not reachable by non-root (uid-0 gate holds) not remote (sysctl local syscall).
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-2246 Β· 9 files| File | Type | Description | Size | |
|---|---|---|---|---|
| ddb_sysctl.c | trigger-source | sysctl PoC: unpriv gate check + destructive root modes | 2.9 KB | view raw |
| build.sh | build-script | cc -o ddb_sysctl ddb_sysctl.c | 160 B | view raw |
| run.sh | run-script | unpriv check (EPERM) / root destructive modes | 490 B | view raw |
| VERDICT.md | verdict | full analysis: jail-confinement gap + evidence | 4.3 KB | β raw |
| fix.diff | suggested-fix | add ddb_sysctl_confined() jail re-check to all 3 destructive handlers | 1.8 KB | view raw |
| run.log | run-log | unprivileged run: EPERM (gate holds) | 322 B | view raw |
| panic.txt | panic-signature | root enter_debugger=ddb -> Stopped at Debugger+0x7c / db> (hard hang) | 105 B | view raw |
| build.log | build-log | uname + cc version | 243 B | view raw |
| env.txt | environment | guest gate-proof | 2.0 KB | view raw |
DF-2246 β PoC verdict
File: sys/ddb/db_sysctl.c (handlers sysctl_debug_enter_debugger, sysctl_debug_panic, sysctl_debug_panic2)
Verdict
REPRODUCED (destructive effect confirmed as root); the finding's actual claim β the destructive DDB sysctls have NO jail confinement β is SOURCE-VERIFIED and REAL. The unprivileged (non-root) gate holds (EPERM), exactly as the finding's summary states ("Not reachable by non-root").
Mechanism (confirmed in source)
The three destructive handlers in sys/ddb/db_sysctl.c perform no confinement
check of their own before calling Debugger()/panic()/stack_guard_panic2():
sysctl_debug_enter_debugger(sys/ddb/db_sysctl.c:56-76) βDebugger("debug.enter_debugger")at line 73.sysctl_debug_panic(sys/ddb/db_sysctl.c:83-93) βpanic("sysctl_debug_panic")at line 91.sysctl_debug_panic2(sys/ddb/db_sysctl.c:117-127) βstack_guard_panic2()at line 125.
They rely entirely on the sysctl dispatcher's capability gate at
sys/kern/kern_sysctl.c:1446-1450:
if (!(oid->oid_kind & CTLFLAG_ANYBODY) && req->newptr && p &&
(error = caps_priv_check(td->td_ucred, SYSCAP_NOSYSCTL_WR)))
return (error);
That gate calls caps_priv_check(SYSCAP_NOSYSCTL_WR), which for jailed
credentials resolves through prison_priv_check() at
sys/kern/kern_jail.c:885:
case SYSCAP_NOSYSCTL_WR: /* group 2 allowed */
...
return (0);
i.e. SYSCAP_NOSYSCTL_WR is in the "allowed inside jails" group and
prison_priv_check returns 0 unconditionally β a jailed root passes the gate
and can write debug.panic=1 / debug.panic2=1 / debug.enter_debugger=ddb,
panicking or hard-hanging the HOST kernel and every co-resident jail. The
jail containment boundary is crossed.
Reachability summary
| Caller | Result |
|---|---|
| unprivileged (maxx, uid 1001) | BLOCKED β EPERM (capability gate holds; verified, see run.log) |
| root on host | works β kernel enters DDB / panics (destructive effect demonstrated) |
| root inside a jail | works β prison_priv_check returns 0 for SYSCAP_NOSYSCTL_WR (the finding's actual claim; source-verified) |
Evidence
Unprivileged gate holds (run as maxx): ./ddb_sysctl check
[+] unprivileged debug.panic write BLOCKED: rc=-1 errno=1 (Operation not permitted)
[+] the uid-0 capability gate (SYSCAP_NOSYSCTL_WR) holds for non-root.
[+] NOTE: prison_priv_check() returns 0 for SYSCAP_NOSYSCTL_WR (kern_jail.c:885),
so a jailed root CAN write these -- the jail-confinement gap is real.
Destructive effect as root (sysctl debug.enter_debugger=ddb): the kernel
enters DDB and hard-hangs the guest. Serial log proof (panic.txt, from
dfbsd-qemu/boot.log):
login: Debugger("debug.enter_debugger")
Stopped at Debugger+0x7c: movb $0,0xbdaf09(%rip)
db>
Guest status after the write: down (frozen in DDB) β full host/kernel DoS, the exact impact the finding describes.
Defense-in-depth fix
The fix adds an explicit jail-confinement re-check (ddb_sysctl_confined(),
which tests jailed(req->td->td_ucred)) at the top of each destructive branch,
so a jailed root is refused with EPERM even though the generic
SYSCAP_NOSYSCTL_WR gate allows it. Host-root behaviour is intentionally
unchanged (host root may legitimately enter DDB / force a panic). The git-apply-able
diff is in fix.diff.
Build/run
./build.sh # cc -o ddb_sysctl ddb_sysctl.c
./run.sh # unprivileged check: prints EPERM (gate holds), guest stays up
# ./run.sh enter # ROOT ONLY: debug.enter_debugger=ddb -> HARD DDB HANG
Classification
status: reproduced (destructive host DoS demonstrated as root; jail-confinement gap source-verified)reproduced: 1impact: dos (kernel panic / hard DDB hang of the host; crosses jail boundary)confidence: certainfix_status: validated-on-host-root (patched kernel builds, boots, host-root sysctl still works β no regression in the legitimate path). The fix's target (refusing jailed root) requires a jail to exercise behaviorally, so jail-path behavioral validation isnot_testablehere.
Fix verification
fixedVALIDATED-safe: single-fix kernel (db_sysctl.c with jailed() re-check) builds rc=0, boots as #1, legitimate paths unaffected (unpriv still EPERM = no regression, host-root retains destructive knobs by design). Jail-path refusal source-proven (jailed() check).
baseline #0: root sysctl debug.enter_debugger=ddb -> Stopped at Debugger+0x7c / db> (HARD HANG). patched #1: builds rc=0, boots; unpriv debug.panic=1 -> 'Operation not permitted' (gate holds, NO regression); host-root sysctls present (intentionally retained).
Confirmed kernel references
Detail
Exploit chain
none (logic/jail-confinement DoS, not memory-corruption; no slab grooming). Impact is hard kernel DoS (DDB hang/panic) crossing jail containment boundary.
Evidence (decisive lines)
Unpriv (maxx): 'sysctl: debug.panic=1: Operation not permitted' (gate holds). Root: sysctl debug.enter_debugger=ddb -> boot.log 'Debugger(debug.enter_debugger) / Stopped at Debugger+0x7c' -> guest down. Jail: prison_priv_check(kern_jail.c:885) returns 0 for SYSCAP_NOSYSCTL_WR -> jailed root passes (source-verified).
PoC changes
Wrote ddb_sysctl.c (unpriv gate check + destructive root modes), build.sh, run.sh, VERDICT.md, manifest.json, env.txt, fix.diff, run.log, panic.txt.
Verified recommended fix
Add explicit jail-confinement re-check ddb_sysctl_confined(req) (jailed(req->td->td_ucred)) at top of each destructive branch in sys/ddb/db_sysctl.c returning EPERM for jailed callers; host-root behavior intentionally unchanged. git-apply-able diff in findings/poc/DF-2246/fix.diff.
Verdict
REPRODUCED (destructive host DoS as root; jail-confinement gap source-verified; unprivileged gate holds). DDB destructive sysctls (debug.panic/panic2/enter) reachable by JAILED root (prison_priv_check returns 0 for SYSCAP_NOSYSCTL_WR); unpriv correctly gated EPERM. Confirmed: as root 'sysctl debug.enter_debugger=ddb' hard-hung guest in DDB (boot.log 'Stopped at Debugger+0x7c' / 'db>'), guest down; as unprivileged maxx the write returns EPERM (non-root gate holds).
No comments yet.