# DF-5734 VERDICT — REPRODUCED (permanent kernel livelock / total system hang)

## Bottom line

**Reproduced, deterministic, root-caused, fix validated.** A single
well-formed 8-byte HCI `Num_Compl_Pkts` event — deliverable by any
malicious/compromised Bluetooth controller or HCI-transport injector — drives
`hci_event_num_compl_pkts()` (`sys/netbt/hci_event.c:380-405`) into
`hci_acl_complete(link, num)` (`sys/netbt/hci_link.c:708-748`) whose completion
loop can make **no progress** when the head PDU of the link txq has
`lp_pending == 0` with data still queued (the routine state for a PDU waiting
for free controller buffers). The loop decrements `num` by 0 forever; the
bluetooth NETISR thread (`btintr`, `sys/netbt/bt_input.c:31-40`) spins
permanently **while holding the mplock**, and the guest becomes totally
unresponsive — no panic, no recovery short of reset.

## Why the injected path is the production path

* The KLD compiles `hci_event.c`, `hci_link.c`, `hci_misc.c`, `hci_unit.c`,
  `bt_input.c` copied **verbatim** from `/usr/src/sys/netbt` — md5s identical
  to the audit tree and to the guest's `/usr/src` (baseline:
  hci_event.c `c3e1e3ea…`, hci_link.c `a2ee71c2…`, hci_unit.c `d7f992b1…`,
  bt_input.c `35d12188…`; recorded in `build.log`/`setup.log`).
* The harness attaches the unit through the production `hci_attach()` and
  injects the event through the production entry point
  `hci_input_event()` → `netisr(NETISR_BLUETOOTH)` → `btintr()` → `hci_intr()`
  → `hci_event()` — the exact path a controller driver (ubt/btuart) feeds.
* The stuck precondition (PDU queued, `lp_pending == 0`, data queued) is the
  routine production state created by `hci_acl_send()` whenever
  `unit->hci_num_acl_pkts == 0`: `hci_acl_start()` returns at
  `sys/netbt/hci_link.c:631-632` leaving the PDU queued-but-unsent. The
  harness reproduces it with the production functions
  (`hci_link_alloc` + a PDU enqueued exactly as `l2cap_lower.c` does).

## Observed sequence (run.log + serial console)

1. `hw.hcih_setup=1` — unit + ACL link #5 (OPEN) attached via production
   `hci_attach()`.
2. `hw.hcih_arm_pdu=1` —
   `[harness] armed: 1 PDU on link #5 txq, lp_pending=0, lp_data qlen=1`.
3. `hw.hcih_fire_numcompl=1` —
   `[harness] pre-fire state: head lp_pending=0, head lp_data qlen=1`;
   event `04 13 05 01 05 00 01 00` (Num_Compl_Pkts, handle 5, num 1) handed to
   `hci_input_event()`.
4. Console prints the harness "injected" marker — **then silence**. The
   triggering `sysctl` never completes (`hw.hcih_fire_numcompl: 0` printed,
   the `-> 1` epilogue never appears: the syscall thread is starved too).
5. Post-trigger probes: `Connection timed out during banner exchange` (x2),
   `vm.sh status` ⇒ **down**. No panic signature anywhere on the serial
   console — this is a livelock, not a crash.

## Distinction from the pass-1 family

DF-0558 (unbounded `num_con_handles` parse loop), DF-0559/0560/0561 all
concern **parsing trust** (event length fields / KKASSERT guards) with
short/malformed packets. DF-5734 is a different bug class in a different
loop: the packet here is **well-formed**; the defect is the **completion
accounting state machine** (`hci_acl_complete`) lacking a progress guarantee.
Both bugs are reachable from the same event, but neither subsumes the other;
the fixes are disjoint.

## Impact ceiling — availability, not memory corruption

The primitive is an infinite spin in a softint thread holding the mplock: no
attacker-controlled memory write occurs before the spin (the loop body only
touches `num`, `pdu->lp_pending` and the PDU list head). There is no
slab-groom/forge route from this bug. Adjacent accounting corruption
(`chan->lc_pending` `uint8_t` underflow, `sys/netbt/hci_link.c:734`) only
poisons flow control (channel stall), not memory layout. Impact = permanent,
unauthenticated total-system DoS from the controller side.

## Fix validation

`fix.diff` adds a no-progress guard (`if (pdu->lp_pending == 0) return;`) and
an `lc_pending` underflow guard. Applied to the guest's `/usr/src/sys/netbt`,
harness rebuilt against the patched sources, identical trigger re-run:

* `SYSCTL_RC=0`, `ALIVE_AFTER_3S`, guest fully responsive (`fix_run.log`).
* Link-state transition observed on the fixed build proves the guard path
  executes: after the event, the armed PDU moved from `lp_pending=0, qlen=1`
  to `lp_pending=1, qlen=0` — i.e. `hci_acl_complete()` returned via the new
  guard, `hci_event_num_compl_pkts()` then credited
  `unit->hci_num_acl_pkts += 1` and `hci_acl_start()` transmitted the
  fragment (`sys/netbt/hci_link.c:646-669`).
* Direct proof the guard is reached in netisr context: the first fix-build
  attempt used `device_get_nameunit()` in the guard's diagnostic kprintf and
  **panicked inside that call** evaluating the argument
  (`fix_guard_panic_serial.txt`: `Stopped at device_get_nameunit:
  movq 0x58(%rdi),%rax`) — the guard ran; the print argument was a harness
  artifact (fake device_t). The final guard print (device-independent) does
  not appear in msgbuf — netisr-context kprintf output is dropped by the
  console layer on this guest — but the state transition above is the
  behavioral proof.
* Normal completion still works post-fix: a follow-up Num_Compl_Pkts on a PDU
  with `lp_pending=1, qlen=0` completed it and freed the PDU cleanly
  (guest stayed alive through three more triggers on the same boot).

## Reproduction & stress

Baseline wedge reproduced on the first firing; repeated probes confirmed
permanence (no recovery over ~2 minutes). Post-fix, the same trigger ran
cleanly twice on one boot alongside the DF-5735/DF-5736 triggers.

## 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.
