# DF-5825 — kdmsg rxcmd REPLY-direction injection → wrong-tree RB_REMOVE → state-tree cross-corruption + UAF

- **File**: `sys/kern/kern_dmsg.c`
- **Class**: memcorrupt (remote peer on a dmsg/hammer2/xdisk link)
- **Severity**: Critical — peer-triggered kernel use-after-free with a
  demonstrated fully-controlled kernel pointer dereference
- **Confidence**: certain (guest-verified)

## Build (on the DragonFly guest, as root)

```sh
sh /root/build.sh        # compiles trigger/oracle/introspect/escalate + probe
```

## Run

```sh
kldload xdisk
/root/oracle             # pure-peer 3-message proof -> INVARIANTS panic at
                         #   kern_dmsg.c:1082 (assertion msg&REPLY)
/root/escalate           # full chain: UAF root + planted fake rbnode ->
                         #   Fatal trap 9 in kdmsg_state_tree_RB_FIND at a
                         #   fully attacker-chosen address (0x4242424242424242)
```

Root is only needed for rig plumbing (opening /dev/xdisk and passing the
socketpair end into the kernel). The attack itself is pure wire input —
exactly the position of a malicious cluster node / dmaster peer.

## Expected output

oracle (decisive run, serial console):

```
panic: assertion "msg->any.head.cmd & DMSGF_REPLY" failed in kdmsg_state_msgrx at /usr/src/sys/kern/kern_dmsg.c:1082
```

escalate (decisive run, serial console — see run.log / panic.txt):

```
conn_state=fffff80117612060 iocom=fffff80118633910
after attack: staterd.root=fffff80117612100 statewr.root=0000000000000000
dangling root at freed state fffff80117612100 confirmed (refs=0)
fake rbnode planted at freed chunk; sending trigger
Fatal trap 9: general protection fault while in kernel mode
Stopped at kdmsg_state_tree_RB_FIND+0x2a: cmpq 0x58(%rax),%rdx
```

## Attack sequence (peer wire messages; all REVTRANS=0)

| msg | cmd | effect |
|-----|-----|--------|
| 1 | `XX3\|CREATE` (unknown proto cmd) | xdisk default handler replies `kdmsg_msg_reply(NOSUPP)` → staterd state **S** half-closed: txcmd has DELETE, rxcmd has none, S stays RBINSERTED in `staterd_tree` |
| 2 | `XX3\|REPLY\|CREATE` (same msgid) | `kdmsg_state_msgrx()` case `REPLY\|CREATE` accepts it against S (lookup tree selected by attacker-cleared REVTRANS) → `S->rxcmd = XX3\|REPLY\|CREATE` — **REPLY bit injected into a staterd state's rxcmd** (kern_dmsg.c:986) |
| 3 | `XX3\|REPLY\|DELETE` (same msgid) | DELETE post-processing picks the tree by `state->rxcmd & DMSGF_REPLY` (kern_dmsg.c:1080) → `RB_REMOVE(&iocom->statewr_tree, S)` while S is linked in `staterd_tree` — **wrong tree**; S then freed by cleanuprx while `staterd_tree.rbh_root` still points at it |
| 4 | any new transaction | reader's `RB_FIND(staterd_tree)` walks the dangling root into freed memory (UAF read), inserts/unlinks write through it (UAF write); with attacker content in the freed chunk → **fully controlled kernel pointer dereference** |

Kernel-memory forensics (introspect via /dev/kmem, see VERDICT.md) show
after msg 3: `statewr.root = 0` (the LNK_CONN state orphaned — proof the
removal hit statewr_tree) and `staterd.root` = freed S with `refs=0`,
`rxcmd=0xe0300101` (injected REPLY + DELETE bits visible in the freed
object).

## Why the fake-content step uses /dev/kmem in the PoC

Aux allocations are 64-byte aligned (`DMSG_DOALIGN`), so an aux buffer can
only be 128/192/… bytes — never 160 (`sizeof(kdmsg_state)`), and DFly's
slab zones for 160 and 192 are distinct. The freed 160-byte state chunk is
therefore groomed with peer-driven *state* allocations (attacker-controlled
`msgid` field). The escalate rig writes the fake rbnode via /dev/kmem to
demonstrate the worst case deterministically (content control in the freed
chunk ⇒ arbitrary-address dereference); the pure-peer UAF (dangling root,
read+write through freed memory) is proven by introspect without any kmem
writes, and the pure-peer INVARIANTS panic is proven by oracle.

## Fix

`fix.diff` — reject REPLY|CREATE on received-side (OPPOSITE) states in
`kdmsg_state_msgrx()` and select the DELETE-time tree by
`KDMSG_STATE_OPPOSITE` instead of the smearable `rxcmd` bit.
