# DF-3553 — VERDICT

**status: reproduced / impact: panic / confidence: certain**
**fix_status: fixed (baseline panic reproduced; patched core survives)**

## What was verified, step by step

Guest: DragonFly 6.5-DEVELOPMENT #0 (X86_64_GENERIC, stock INVARIANTS,
2026-07-02 build), QEMU/KVM single-tenant, fresh `vm.sh reset with-src`
before each phase.

### 1. The graph is constructible (pure topology)

`./tee_cycle7` (root), output captured over ssh before the machine died:

    tee_cycle: uid=0 core=ng7
    STEP mkpeer1 ok        (C.tap <-> T1.left2right   — ENTRY EDGE)
    STEP mkpeer2 ok        (T1.right  <-> T2.right)
    STEP connect ok        (T1.left   <-> T2.left     — CYCLE)
    STEP graph built (tee/tee cycle + tap entry), injecting 1 byte ...

ng7 hook-add order on T1 is left2right, right, left (and right, left on
T2), which resolves `left.dest=&right / right.dest=&left` on both nodes —
i.e. the canonical bidirectional tee. One `sendto()` of a single byte is
the whole trigger. No node-specific control message is used (contrast
DF-3540, which needed three ng_bpf SET_PROGRAMs).

### 2. Stock kernel: deterministic panic

The guest died during launch; the serial console (console_full.txt,
panic.txt) shows:

    panic: assertion "ref < &td->td_toks_end" failed in lwkt_gettoken_shared
           at /usr/src/sys/kern/lwkt_token.c:732
    cpuid = 0
    Trace beginning at frame 0xfffff80117ee87f0
    lwkt_gettoken_shared() at lwkt_gettoken_shared+0x34c
    lwkt_gettoken_shared() at lwkt_gettoken_shared+0x34c
    ng_apply_item() at ng_apply_item+0xa9
    ng_apply_item() at ng_apply_item+0x105   (x ~30 more)
    Debugger("panic")
    CPU0 stopping CPUs: 0x0000003e
    db>

Captured live via the QEMU monitor as well (`chardev-change` of the serial
to a socket, then DDB `trace` over that socket — ddb_trace.txt):

    Debugger() at Debugger+0x7c
    panic() at panic+0x24e
    lwkt_gettoken_shared() at lwkt_gettoken_shared+0x34c
    ng_apply_item() at ng_apply_item+0xa9
    ng_apply_item() at ng_apply_item+0x105    x 31
    ngthread() at ngthread+0x18

Identical signature to DF-3540 (ng_bpf instance): each synchronous forward
pushes a tokref; LWKT_MAXTOKENS=32; KKASSERT trips. The recursion rides
the cpu0 ngthread (ng_snd_item queues first-entry items to ng_cpuport(0),
ng_base.c:1948-1956).

### 3. Source trace

- sys/netgraph7/tee/ng_tee.c:293-331 — `ng_tee_rcvdata`: in→dest(+dup)
  forwarding; no cycle knowledge. T1: left2right-in → out left (via
  newhook-wired dest, ng_tee.c:186); left-in → out right; right-in → out
  left.
- sys/netgraph7/tee/ng_tee.c:169-205 — `ng_tee_newhook` precalcutes
  dest/dup; with hook-add order left2right,right,left (T1) and right,left
  (T2) both tees end up left↔right bidirectional.
- sys/netgraph7/netgraph/ng_base.c:1908-1965 — `ng_snd_item`: forwarded
  items (refs>1) applied synchronously → unbounded recursion.
- sys/netgraph7/netgraph/ng_base.c:1996-1999 — `ng_acquire_read` →
  `lwkt_gettoken_shared` → td_toks[] overflow at 32 (sys/kern/lwkt_token.c:732).

### 4. Fix validation (fix.diff)

fix.diff = the ng7 CORE portion of DF-3540's validated two-layer fix
(an `el_hops` counter on the item + `NG_HOPS_MAX 16` bound in
`ng_snd_item`), because the correct layer for a pure-topology cycle is the
core: ng_tee itself cannot detect the cycle at newhook/connect time
without graph-wide knowledge.

Applied to the guest's /usr/src copy only; ng7 modules rebuilt with
`-Werror` (clean); never applied to the audit tree.

| run | stock | patched core |
|---|---|---|
| tee cycle + 1-byte inject | panic (token KKASSERT), DDB, guest dead | `STEP sendto ok`, `RESULT no-panic survived 30s`, `RUN_RC=0`, guest healthy (fix1_cycle.log) |

The patched run also re-executed all three graph-construction control
messages through the patched core (mkpeer/mkpeer/connect all OK), so
normal message handling is intact; only the unbounded recursion is gone
(cycle unwinds at depth 16, item dropped with ELOOP on the ngthread).

## Exploit ceiling

None beyond denial of service: root-gated configuration, deterministic
panic behind an always-on KKASSERT tripwire; no memory-safety violation
and no controlled overwrite occurs before the panic. Same ceiling as
DF-3540/DF-3546. (netgraph socket creation is caps_priv_check-gated;
uid 1001 cannot even open the control socket — verified for the identical
gate in DF-3546's unpriv_check.txt.)

## Notes

- The stock-run trigger's own stdout log was lost with the guest (death
  was immediate); the ssh-side transcript (kldstat + LAUNCH + step output
  shown above) and the console capture are the decisive records.
- Terminal state of the ng7 instance is a clean panic + DDB (unlike the
  v1 twin DF-3554, whose terminal state varies — see that pack).

## Fix status (batch validation)
Applied with the 36 sibling fix.diffs of this batch in findings/poc/_batch41/all_fixes.patch; single in-guest `make -j6 nativekernel KERNCONF=X86_64_GENERIC` completed RC=0 with zero errors under -Werror (full log: findings/poc/_batch41/fix_build8.log). Behavior A/B intentionally skipped for this Info-tier item.
