# DF-0522 — VERDICT

## Verdict: SOURCE-CONFIRMED (3 "XXX broken" sites); NOT DIRECTLY TRIGGERABLE in a simple PoC; root-only reachability

## The bug (confirmed in source)

`ng_ksocket` uses an acknowledged-broken credential fallback at **three** call
sites:

```c
// sys/netgraph/ksocket/ng_ksocket.c:559  (ng_ksocket_newhook -> socreate/sobind)
// sys/netgraph/ksocket/ng_ksocket.c:640  (ng_ksocket_rcvmsg  -> soconnect/sogetopt/...)
// sys/netgraph/ksocket/ng_ksocket.c:880  (ng_ksocket_rcvdata  -> sosend)
struct thread *td = curthread->td_proc ? curthread : &thread0;   /* XXX broken */
```

When a ksocket operation runs in a context where `curthread->td_proc == NULL`
(i.e. a kernel thread / softinterrupt / asynchronous netgraph dispatch — no
owning user process), the code falls back to **`&thread0`**, which carries
**root credentials**.  The `td` is then passed to `socreate` (`:598`),
`sobind` (`:662`), `solisten` (`:674`), `soconnect` (`:726`), `sosend`
(`:912`).  Consequently the privilege checks evaluated inside those socket
operations — `PRIV_NET_RAW`, `PRIV_NET_PRIV_PORT` (bind < 1024), and **jail**
restrictions — are all evaluated against **root**, not the real (or absent)
credential.  The developer's own `/* XXX broken */` comment concedes the
defect.  This is the same defect as the netgraph7 twin DF-0510.

## Why it is not directly triggerable in a simple PoC

The fallback fires **only** when `curthread->td_proc == NULL` — i.e. when the
netgraph hook/data operation is serviced by a **kernel worker thread** rather
than synchronously in the issuer's syscall context.  A synchronous `ngctl`
control message issued by root runs in root's own process context
(`curthread->td_proc != NULL`), so the fallback branch is **NOT** taken and
root's own (legitimate) credential is used.  Verified: `ng_ksocket` node
creation succeeds without panic (`ngctl mkpeer .: ksocket ...` → RC=0, no
crash), unlike `ng_fec`.

Forcing the async-dispatch context where `td_proc == NULL` requires
constructing a netgraph topology whose hook/data callbacks are queued to a
netgraph worker thread rather than executed inline — impractical for a
standalone PoC.  The defect is therefore confirmed at the **source level**
(the broken pattern and the `XXX broken` acknowledgement are unambiguous),
not by a runtime privilege escalation.

## Reachability / privilege model

Netgraph node creation requires the ng_socket control socket, gated by
`caps_priv_check(SYSCAP_RESTRICTEDROOT)` (`sys/netgraph/socket/ng_socket.c:172`)
— **root only**.  Verified: unprivileged `maxx` gets `EPERM`.  So even if the
fallback fired, the ksocket node was created by root; the realistic impact
ceiling is a **jail privilege-bypass**: a root-configured ksocket node inside
a jail, whose hook ops are serviced by a kernel worker thread, would have its
`PRIV_NET_RAW` / bind-<1024 / jail checks evaluated against **host-root**
(`thread0`) instead of the jail credential — a jail privilege-boundary
confusion.

## Impact ceiling

Medium logic / privilege-confusion defect: socket privilege checks
(`PRIV_NET_RAW`, privileged-port bind, jail) evaluated against root when
ksocket ops run in kernel-dispatch context.  Not a memory-corruption
primitive; no `uid=0` chain.

## Exploit chain

`none` — logic/privilege bug, not memory corruption.

## Fix validation

`fix.diff` replaces the `&thread0` fallback with **fail-closed** at all three
sites: if `curthread->td_proc == NULL`, return `EACCES` instead of granting
root credentials.  Compiles cleanly (RC=0, `ng_ksocket.ko` produced; all 3
sites patched).  `fix_status = not_testable` — the fallback only fires under
async dispatch, which a synchronous PoC cannot force; compile-validated only.

(An alternative, more complete fix would capture the creating user's
credential at node/hook creation time and use that stored credential in
rcvmsg/rcvdata — matching the finding's "capture credential at node creation"
recommendation.  The fail-closed fix is the minimal safe version.)

## PoC changes

Wrote `trigger.sh` (loads ng_ksocket, documents the 3 broken sites), `build.sh`/
`run.sh`, `fix.diff` (fail-closed at all 3 sites).  No upstream PoC.
