# DF-2707 — duplicate transmitted DELETE: double RB_REMOVE + phantom rbtree
# refdrop in kdmsg_state_cleanuptx() → state-tree corruption + premature
# state free (UAF) — peer-driven, production (non-INVARIANTS) kernels

## What this pack contains

- `df2707_trigger.c` — the trigger (modes: `leak` (DF-2708 evidence),
  `wedge` (DF-2710 evidence), `attack` (this finding)).
  It becomes the cluster peer of a kernel hammer2 iocom using the mount
  program's documented `cluster_fd` hook (hammer2_vfsops.c:1350-1357,
  same wire position as the userland hammer2 service daemon / a remote
  cluster node), then:
    1. parks the kernel **write** thread in `fp_write()` (small
       `SO_SNDBUF` on the kernel's socket end + junk SPAN transactions
       that each elicit an immediate 64-byte terminating reply);
    2. opens a victim LNK_SPAN transaction (stays open: `msg_result`);
    3. sends N=24 **duplicate DELETE** frames for the victim msgid while
       the writer is parked — on production kernels the rx-side assert
       (kern_dmsg.c:1076, DF-0018) is compiled out, each absorbed
       duplicate re-runs the callback and `kdmsg_msg_reply()` (its
       `txcmd & DMSGF_DELETE` check at kern_dmsg.c:2057 races the
       writer's cleanuptx) queues *another* terminating reply;
    4. drains → the writer transmits reply#1 (legit close:
       RB_REMOVE + "rbtree" refdrop), then reply#2 →
       `kdmsg_state_msgtx()` had reset `txcmd` (:1571), so
       `kdmsg_state_cleanuptx()` re-runs the close: **RB_REMOVE on the
       already-removed node** (stale-pointer writes into the live state
       tree) **+ a second "state on rbtree" refdrop** (refcount
       underflow → premature `kdmsg_state_free()` while replies #3..#N
       still hold `msg->state`) → use-after-free;
    5. sprays CREATEs concurrent with the drain (reclaim of the freed
       slot) and churns the tree.
- `run_df2707.sh` — guest-side orchestration (image prep, newfs, build,
  run, dmesg capture, unmount).
- `fix.diff` — the verified fix (guard at the RB_REMOVE site keyed on
  the RBINSERTED invariant).
- logs (see VERDICT.md for interpretation):
  - `panic.invariants_baseline.txt` — stock INVARIANTS kernel: same peer
    input panics at the rx-side assert kern_dmsg.c:1076 (DF-0018),
    proving the harness reaches the duplicate-DELETE machinery.
  - `panic.txt` — **non-INVARIANTS (production-equivalent) kernel:
    Fatal trap 12 page fault in `kdmsg_state_tree_RB_REMOVE_COLOR`** —
    the double-remove corrupting the state tree. This is the finding.
  - `run.attack2_ssh.log` / `run.attack2_trigger.out` — second attack
    run on the non-INVARIANTS kernel: no panic, but the iocom reader
    wedges after the drain (alternate manifestation).
  - `run.wedge_probe.txt` — `wedge` mode: single-DELETE reader stall
    (DF-2710), separating the stall from the corruption.
  - `run.fix.log` — same attack on the fix kernel: completes cleanly.

## Build & run (from the repo root)

```
scp -F dfbsd-qemu/config findings/poc/DF-2707/df2707_trigger.c \
                      findings/poc/DF-2707/run_df2707.sh dfbsd:/root/poc/df2707/
dfbsd-qemu/vm.sh run_root 'sh /root/poc/df2707/run_df2707.sh attack 3 24'
```

The attack requires a kernel built without `options INVARIANTS`
(production configuration); on the stock INVARIANTS kernel the same
input panics earlier at kern_dmsg.c:1076 (DF-0018). Kernel build in
guest:

```
sed -i.bak 's/^options \tINVARIANTS/#options \tINVARIANTS/' \
    /usr/src/sys/config/X86_64_GENERIC
cd /usr/src && make -j6 nativekernel KERNCONF=X86_64_GENERIC && \
    make installkernel KERNCONF=X86_64_GENERIC && reboot
```

## Expected output

- baseline non-INVARIANTS: guest dies mid-run; serial console shows
  `Fatal trap 12 ... Stopped at kdmsg_state_tree_RB_REMOVE_COLOR+0x6f`
  (or an equivalent corruption crash / iocom wedge).
- fix kernel (fix.diff applied, still non-INVARIANTS): trigger prints
  all rounds + `TRIGGER_DONE`, `UMOUNT_RC=0`, no new kdmsg errors.
