# DF-2694 VERDICT

## Status: REPRODUCED (unprivileged local kernel panic; race-class UAF)

Two independent panics captured on the stock INVARIANTS guest kernel, both
from an unprivileged user (`maxx`, uid 1001), both exactly at the lines
predicted by source analysis:

1. **TCP path** — `panic: assertion "m" failed in sorecvtcp at
   /usr/src/sys/kern/uipc_socket.c:1852` (see `panic.txt`, `run.log`).
   Trigger: `/tmp/poc_race 40 3000 3000 0`.
2. **AF_UNIX path** (generic `soreceive`) — `panic: assertion "sb->sb_mb ==
   m" failed in sbunlinkmbuf at /usr/src/sys/kern/uipc_sockbuf.c:552`
   (see `panic2.txt`). Trigger: `/tmp/poc_race 60 200 500 1`.

## Root cause (line-accurate)

* `soshutdown()` intentionally skips `ssb_lock` for the read side
  (`sys/kern/uipc_socket.c:1953-1957`) and calls `sorflush()`.
* `sorflush()` (`uipc_socket.c:1963-1991`) takes only `so_rcv.ssb_token`,
  snapshots the sockbuf (`asb = *ssb`), `bzero()`s `ssb->sb`, and frees the
  whole chain: `ssb_release(&asb)` → `sbflush()` (`uipc_socket2.c:754-760`)
  → `sbdrop()` (`uipc_sockbuf.c:473-508`) → `m_freem()`.
* A receive is in one of two states w.r.t. that token:
  * `sorecvtcp()` explicitly drops it around the copy loop
    (`uipc_socket.c:1756/1758`), marking the chain `M_SOLOCKED`
    (`:1746-1753`) — but `M_SOLOCKED` is only honored by `sbcompress()`
    (`uipc_sockbuf.c:376`), **never on the flush/free path**.
  * generic `soreceive()` holds it across the loop (`:1322`).
* **In DragonFly, a thread that blocks releases all its lwkt tokens**
  (`lwkt_switch()` → `lwkt_relalltokens()`, `sys/kern/lwkt_thread.c`).
  The moment either copier sleeps inside `uiomove()` — e.g. a swap-in or
  file-I/O page fault on the user receive buffer — its token protection is
  gone and `sorflush()` frees/zeroes the chain underneath it:
  * the copier then reads freed mbufs (`m->m_next == NULL` after `m_free()`)
    and copies freed-cluster contents into the user buffer (UAF read), and
  * the post-loop bookkeeping trips over the zeroed sockbuf:
    `sorecvtcp` at `KKASSERT(m)` (`:1851-1852`), `soreceive` at
    `sbunlinkmbuf`'s `KKASSERT(sb->sb_mb == m)` (`uipc_sockbuf.c:552`).

## Why the PoC needs the "pig"

The window exists only while the copier is *blocked*. With resident receive
windows the copy never sleeps, the token stays (effectively) held, and
`shutdown(SHUT_RD)` serializes harmlessly behind the copy — hundreds of
sweep rounds produced zero hits until the receive windows were forced out to
swap, making every `uiomove()` fault a sleeping `vm_fault`. This also frames
real-world reachability: any process that `recv()`s into swapped/cold pages
(an HTTP server under memory pressure is the canonical case) races any other
thread's `shutdown(SHUT_RD)`/`SO_RCVSHUTDOWN`-style flush.

## Impact assessment

* **Reproduced**: reliable unprivileged local kernel panic (DoS). Both the
  TCP (`sorecvtcp`) and generic (`soreceive`) receive paths are affected;
  AF_UNIX and UDP sockets use the generic path.
* **UAF read**: after the flush the copier does `uiomove()` out of freed
  clusters. On the INVARIANTS kernel the subsequent assertion fires inside
  the same syscall, so those bytes cannot be harvested from userspace.
  No info leak was demonstrated on this kernel.
* **Non-INVARIANTS (production) kernels**: the generic path has no assert;
  `sbunlinkmbuf()` (`uipc_sockbuf.c:548-578`) then executes
  `sb->sb_mb = m->m_nextpkt` with `m` freed — writing a stale pointer into
  live sockbuf state and doing `sbfree()` accounting on freed memory.
  That is sockbuf corruption with (objcache-reuse) attacker-influenced
  contents — assessed as worse than the panic, though not demonstrated here
  (would require a non-INVARIANTS build).
* No uid=0 escalation chain: the primitive is a free-under-reader of the
  victim's own socket data; the hard blocker on this kernel is the
  in-syscall assertion (INVARIANTS) / NULL-or-stale-pointer store whose
  value the attacker does not control precisely. Documented as a hard
  blocker for escalation on the audited build.

## Fix validation

`fix.diff` makes `sorflush()`, after `socantrcvmore()`, poll (bounded,
1 tick, ≤1000 iterations) until no `M_SOLOCKED` mbufs remain in the sockbuf
before snapshotting/zeroing/freeing it. Copiers finish autonomously
(their marked mbufs are untouched; readers blocked in `ssb_wait` were
already woken by `socantrcvmore()`, so `MSG_WAITALL` loops terminate and
cannot deadlock the flusher).

Baseline (stock kernel): both PoC modes panic the guest.
Patched kernel (`make nativekernel` with `fix.diff` in-guest): both PoC
modes run to completion (`done: N rounds, 0 marker leaks`), guest stays up,
see `fix_build.log` / `fix_run.log`.

## Classification

* severity: High — race (CWE-362) → UAF (CWE-416) reachable unprivileged
  from both TCP and AF_UNIX/UDP; reliable panic; potential sockbuf pointer
  corruption on production builds.
* verdict.json impact: `panic` (that is what was reproduced end-to-end).
