# DF-5735 — READ_BUFFER_SIZE completion with `max_acl_size=0` poisons the ACL MTU (DF-5733 lens)

**File:** `sys/netbt/hci_event.c` (pass 2) + sink `sys/netbt/hci_link.c`
**Severity:** Medium — CVSS:3.1/AV:A/AC:L/PR:N/UI:N/S:C/C:N/I:N/A:H (CWE-834 + CWE-400)
**Verdict: REPRODUCED** (poison lands via injected event; sink spin + pool drain observed).

## Root cause

`hci_cmd_read_buffer_size()` (`sys/netbt/hci_event.c:869-892`) copies the
controller-reported `max_acl_size` into `unit->hci_max_acl_size`
(`sys/netbt/hci_event.c:884`) with **no lower bound** — the same defect class
as DF-5733 (RFCOMM PN MTU=0 → divide-by-zero), here in the HCI init
completion. During the init window (`BTF_INIT_BUFFER_SIZE`), one injected
14-byte Command_Complete event sets the unit's ACL payload MTU to **0**.

Sink: `hci_acl_send()` (`sys/netbt/hci_link.c:534-600`, the production call
site for every L2CAP transmission, `sys/netbt/l2cap_lower.c:204`) fragments
with `mlen = unit->hci_max_acl_size`:

```c
while (plen > 0) {
        if (plen > mlen) {
                n = m_split(m, mlen, M_NOWAIT);   /* m_split(m, 0): tail
                                                      gets ALL the data,
                                                      m becomes empty */
                ...
        } else { mlen = plen; }
        IF_ENQUEUE(&pdu->lp_data, m);             /* enqueue EMPTY mbuf */
        m = n;
        plen -= mlen;                             /* -= 0: plen unchanged */
}
```

The loop is structurally infinite (`plen` never decreases); it terminates only
when `m_split()`'s `m_gethdr(M_NOWAIT)` fails — after transiently allocating
empty packet-header mbufs into `pdu->lp_data` until the **global mbuf pool is
exhausted**. Observed on the guest: 0.17 s of spinning ending in ENOMEM with
`netstat -m` `1 requests for memory denied`; the `nomem` path then frees its
own queue. Every subsequent transmission on the unit repeats the drain;
between drains, the mbuf starvation is system-wide. Additionally, the
`hci_num_acl_pkts`/`hci_num_sco_pkts` window values from the same event are
accepted unvalidated (0xFFFF window poisoning).

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

```
cd /root/hcih && sh build.sh
kldload ./hci_event_harness.ko
sysctl hw.hcih_setup=1
sysctl hw.hcih_fire_mtupoison=1   # 2 = sane completion (fix-check)
```

## Expected result (baseline)

```
[harness] hci_max_acl_size = 0  <-- POISONED
[harness] calling hci_acl_send(64 bytes): ...
[harness] hci_acl_send returned 12   (only after the mbuf pool was drained)
```
`netstat -m`: `1 requests for memory denied`; sysctl wall time 0.17 s.
See `run.log`.

## Fix validation

`fix.diff` rejects the completion when `max_acl_size == 0` (upper hunk in
`hci_event.c`). On the patched build: poison completion ⇒
`completion NOT applied (flag still set)` — the zero can no longer land via
an event; a sane completion (`hw.hcih_fire_mtupoison=2`) is accepted
(`hci_max_acl_size = 64`). In production the rejected init leaves
`BTF_INIT_BUFFER_SIZE` set so `hci_enable()` never brings the unit up — no
`hci_acl_send` is possible on a zero-MTU unit.
