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

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)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-2246 Β· 9 files
FileTypeDescriptionSize
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
VERDICT.md verdict full analysis: jail-confinement gap + evidence
↓ download 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():

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: 1
  • impact: dos (kernel panic / hard DDB hang of the host; crosses jail boundary)
  • confidence: certain
  • fix_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 is not_testable here.

Fix verification

fixed
baseline reproduced→ patch + rebuild →patched clean

VALIDATED-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).
↓ fix.diffDragonFly 6.5-DEVELOPMENT #1: Sun Aug 9 14:31:09 UTC 2026 (single-fix kernel sha256 ec12226281d5eed4daebf6ab34bd814aa3c9747ac5df6615f92bc750db0944be)

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