# DF-2262 — XPT_DEV_MATCH trusts user-supplied EDT cookie (cam_xpt.c)

## Verdict: REPRODUCED at root level (panic confirmed) — NOT an unprivileged escalation; fix.diff compiles.

**Classification:** `reproduced` (the source bug is real and **panics the kernel**
when triggered) / impact=**panic** (root→kernel DoS; the finding's own attacker model
is "root on host or root inside jail given /dev/xpt0") / the **unprivileged**
(`maxx`) path is **permission-gated** (`/dev/xpt0` is `0600 root:operator`,
`maxx` not in `operator`). The defense-in-depth `fix.diff` **compiles cleanly**
(`nativekernel rc=0`, `-Werror`, `-DINVARIANTS`).

### What was reproduced (root-level live confirmation)

The trigger harness `xpt_devmatch_poison.c` opens `/dev/xpt0` and issues
`CAMIOCOMMAND` with `func_code = XPT_DEV_MATCH`, `position_type = EDT|BUS`,
`generations[CAM_BUS_GENERATION] = 0` (bypassing the generation check), and
`cookie.bus = 0x4141414141414141`. The kernel traps immediately in `xptbustraverse`:

```
Fatal trap 9: general protection fault while in kernel mode
instruction pointer = 0x8:0xffffffff802acbf1
current process       = 996
Stopped at      xptbustraverse+0x61:    movq    0x10(%r15),%r13
```
(see `panic.txt`). The faulting instruction dereferences the fake `struct cam_eb *`
the kernel took verbatim from the user CCB.

Build (in-guest, root): `cc -I/usr/src/sys -o xpt_devmatch_poison xpt_devmatch_poison.c`
→ builds clean (9120-byte binary). Run as root panics the kernel (ssh dies,
`vm.sh status` ⇒ down); run as `maxx` fails with `Permission denied` on `/dev/xpt0`.

### The source bug (cited `path:line`)

`sys/bus/cam/cam_xpt.c`:
- `xptioctl` `CAMIOCOMMAND` accepts `XPT_DEV_MATCH` from userspace, maps the user
  buffers, and calls `xpt_action(inccb)` (`cam_xpt.c:1125-1179`).
- `xpt_action` `XPT_DEV_MATCH` (`:3262-3323`) picks the EDT path and calls
  `xptedtmatch(cdm)` (`:3307`).
- `xptedtmatch` (`:2427-2461`): the generation guard at `:2437-2439` is bypassable —
  it only fires when `generations[CAM_BUS_GENERATION] != 0` AND differs from
  `xsoftc.bus_generation`, so setting `generations[0] = 0` skips it entirely. Then,
  if `position_type & CAM_DEV_POS_BUS` and `cookie.bus != NULL` (`:2444-2445`), it
  passes the **raw user pointer** to `xptbustraverse((struct cam_eb *)cookie.bus, …)`
  (`:2446`) with no validation that it names a live `cam_eb`.
- `xptbustraverse` (`:2631+`) dereferences it: `TAILQ_NEXT(bus, links)`,
  `CAM_SIM_LOCK(bus->sim)` (`:2642`, `:2645`) → controlled deref → trap. This
  confirms finding claim (1): reliable kernel panic DoS.

The other two claims (info leak of kernel heap pointers via legitimate
`CAM_DEV_MATCH_MORE` returns; UAF via captured-valid-cookie + bus deregister) were
not separately exercised — claim (1) is sufficient to confirm the bug is live and
the trust-the-cookie defect is real.

### Why this is a root→kernel finding, NOT an unprivileged escalation

`/dev/xpt0` is `crw------- root operator` (mode 0600). `maxx` (uid 1001,
`groups=1001(maxx)`, not in `operator`) cannot open it:
```
$ id maxx                    -> uid=1001(maxx) ... groups=1001(maxx)
$ su -m maxx -c "camcontrol devlist"
camcontrol: couldn't open /dev/xpt0: Permission denied
```
The finding's own stated attacker is "root on host or root inside jail given
/dev/xpt0". Root→kernel is game-over by definition, so the **unprivileged-escalation
impact is `none` (gated)** while the **source bug is `reproduced` (panic)** and the
fix is warranted as defense-in-depth.

### Exploit chain

No unprivileged chain: the device is 0600 root-only, so `maxx` has no entry point.
The root-level panic is the demonstrated effect (claim 1 of the finding). A fuller
exploit (claims 2/3: KASLR-bypass info leak, UAF via bus-deregister) would also be
root-only and is not pursued — they add no unprivileged impact.

### Fix (defense-in-depth, VALIDATED — built, booted, re-tested)

`fix.diff` makes the generation check **mandatory** whenever `cookie.bus != NULL`:
a caller that wants to resume from a cookie must supply a current
`generations[CAM_BUS_GENERATION]`, closing the trivial
`generations[0]=0`-plus-arbitrary-cookie bypass. (Full hardening would additionally
re-validate that `cookie.bus` names a live `cam_eb`; the shipped fix closes the
trivial controlled-deref.) Validated in the combined `nativekernel` build
(`fix_build.log`).

**Phase 8 before/after (single-fix cam_xpt.c kernel, built + booted):**
- baseline `#0` (unpatched): `./xpt_devmatch_poison` → `Fatal trap 9 ... Stopped at
  xptbustraverse+0x61`, guest down (`panic.txt`).
- patched `#1`: `./xpt_devmatch_poison` → `ioctl returned 0, cdm.status=2`
  (`CAM_DEV_MATCH_LIST_CHANGED` — the fix's safe return), guest **stays up**
  (`fix_run.log`). The controlled-deref panic is gone.

### Reproduce (root only)

```
cc -I/usr/src/sys -o xpt_devmatch_poison xpt_devmatch_poison.c   # builds clean
./xpt_devmatch_poison                                            # as root -> panic
```
As `maxx`: `Permission denied` on `/dev/xpt0` (gate).
