# DF-5734 — Num_Compl_Pkts → `hci_acl_complete()` no-progress livelock (permanent system hang)

**File:** `sys/netbt/hci_event.c` (pass 2) + sink `sys/netbt/hci_link.c`
**Severity:** High — CVSS:3.1/AV:A/AC:L/PR:N/UI:N/S:C/C:N/I:N/A:H (CWE-835)
**Verdict: REPRODUCED** — one injected 8-byte HCI event permanently wedges the kernel.

## Root cause

`hci_event_num_compl_pkts()` (`sys/netbt/hci_event.c:380-405`) takes the
attacker-controlled `compl_pkts` count from the event and passes it **unvalidated**
to `hci_acl_complete(link, num)` (`sys/netbt/hci_event.c:393`).

`hci_acl_complete()` (`sys/netbt/hci_link.c:708-748`) loops:

```c
while (num > 0) {
        pdu = TAILQ_FIRST(&link->hl_txq);
        if (pdu == NULL) { ...; return; }
        if (num >= pdu->lp_pending) {
                num -= pdu->lp_pending;
                pdu->lp_pending = 0;
                if (IF_QEMPTY(&pdu->lp_data)) { ...remove/complete... }
                /* else: NOTHING — pdu stays, lp_pending now 0 */
        } else { ... }
}
```

If the head PDU has `lp_pending == 0` **with data still queued** — the normal
state whenever a PDU is queued while the controller reported zero free buffers
(`hci_acl_start()` returns early at `sys/netbt/hci_link.c:631-632`) — then
`num -= 0` never decrements `num`, the queue never changes, and the loop **never
terminates**. Any `num >= 1` wedges the bluetooth NETISR thread
(`btintr()` → `hci_intr()` → `hci_event()`, `sys/netbt/bt_input.c:31-40`,
`sys/netbt/hci_unit.c:329-352`) **while it holds the mplock**
(`sys/netbt/bt_input.c:36`), starving the kernel. This is distinct from the
pass-1 parse-loop findings (DF-0558): different function, different root cause
(no-progress iteration vs. unbounded parse).

Also fixed here: `chan->lc_pending--` (`sys/netbt/hci_link.c:734`) is a `uint8_t`
that can underflow to 0xFF when completion credit exceeds outstanding fragments,
permanently stalling the L2CAP channel (`l2cap_lower.c:174`,
`l2cap_upper.c:244` both gate on `lc_pending == 0`).

## Threat model

The event source is the Bluetooth controller (or anything able to inject HCI
events to the host: malicious/compromised controller firmware, hostile USB
device presenting a bt controller, HCI transport injection). No authentication
exists on the HCI transport. Net effect from ONE well-formed 8-byte event:
total system unavailability (hard reset required).

## Build & run (inside the guest, as root)

```
cd /root/hcih && sh build.sh     # copies VERBATIM /usr/src/sys/netbt sources,
                                 # builds hci_event_harness.ko
kldload ./hci_event_harness.ko
sysctl hw.hcih_setup=1           # attach fake unit + ACL link #5 (OPEN)
sysctl hw.hcih_arm_pdu=1         # queue 1 unsent PDU fragment, lp_pending=0
sysctl hw.hcih_fire_numcompl=1   # inject Num_Compl_Pkts(handle=5, num=1)
```

## Expected result (baseline)

Console prints the pre-fire state, the injection marker — then goes silent;
the guest stops responding entirely (ssh banner timeout, `vm.sh status` ⇒
down). No panic: a livelock. See `run.log`.

## Fix validation

Apply `fix.diff` (progress guard in `hci_acl_complete` + `lc_pending`
underflow guard) to `/usr/src/sys/netbt`, rebuild the harness (build.sh copies
the patched sources verbatim), rerun the identical trigger: the sysctl
**returns** (`SYSCTL_RC=0`), the guest stays alive and keeps processing further
triggers (`fix_run.log`). The guard's execution is directly evidenced by the
intermediate fix build (`fix_guard_panic_serial.txt`): its diagnostic
`device_get_nameunit()` call faulted on the harness's fake device_t, proving
the new code path is reached in netisr context. See `VERDICT.md` for the full
narrative.
