# 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`:

```c
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`**:

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

```sh
./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.
