DF-5734 / fix.diff
--- a/sys/netbt/hci_link.c 2026-10-10 22:45:15.649479095 +0000 +++ b/sys/netbt/hci_link.c 2026-10-10 22:54:55.077917309 +0000 @@ -723,6 +723,21 @@ return; } + /* + * DF-5734: if the head PDU has no fragments pending at the + * controller but still holds unsent data, no amount of + * completion credit can make progress on it -- without this + * guard the loop iterates forever (num is never decremented + * and the queue never changes), wedging the bluetooth + * NETISR thread permanently while it holds the mplock. + */ + if (pdu->lp_pending == 0) { + kprintf("hci_acl_complete: completion credit for " + "handle %#x with no fragments pending\n", + link->hl_handle); + return; + } + if (num >= pdu->lp_pending) { num -= pdu->lp_pending; pdu->lp_pending = 0; @@ -731,7 +746,8 @@ TAILQ_REMOVE(&link->hl_txq, pdu, lp_next); chan = pdu->lp_chan; if (chan != NULL) { - chan->lc_pending--; + if (chan->lc_pending > 0) + chan->lc_pending--; (*chan->lc_proto->complete) (chan->lc_upper, 1); --- a/sys/netbt/hci_event.c 2026-10-10 22:45:15.649479095 +0000 +++ b/sys/netbt/hci_event.c 2026-10-10 22:45:23.829371871 +0000 @@ -878,6 +878,16 @@ if (rp.status > 0) return; + /* + * DF-5735: a Max ACL Data Packet Length of zero is unusable (and + * unspec-conformant): hci_acl_send() would fragment every outgoing + * PDU into zero-length chunks forever (m_split(m, 0) hands all the + * data to the tail and plen never decreases), draining the mbuf + * pool and blackholing the link. Ignore the broken report. + */ + if (letoh16(rp.max_acl_size) == 0) + return; + if ((unit->hci_flags & BTF_INIT_BUFFER_SIZE) == 0) return; --- a/sys/netbt/hci_misc.c 2026-10-10 22:45:15.649479095 +0000 +++ b/sys/netbt/hci_misc.c 2026-10-10 22:45:31.789267549 +0000 @@ -49,6 +49,11 @@ int hci_memo_expiry = 600; /* + * upper bound on cached inquiry memos per unit [sysctl] + */ +int hci_memo_max = 1024; + +/* * set 'src' address for routing to 'dest' */ int @@ -145,9 +150,25 @@ hci_memo_new(struct hci_unit *unit, bdaddr_t *bdaddr) { struct hci_memo *memo; + int count; memo = hci_memo_find(unit, bdaddr); if (memo == NULL) { + /* + * DF-5736: inquiry/RSSI result events (hci_event.c) can + * manufacture memos without bound; expired entries are + * only reaped inside hci_memo_find lookups. Cap the cache + * so a hostile controller (or forged result flood) cannot + * exhaust kernel memory. + */ + count = 0; + LIST_FOREACH(memo, &unit->hci_memos, next) + count++; + if (count >= hci_memo_max) { + DPRINTFN(0, "memo cache full (%d)\n", count); + return NULL; + } + memo = kmalloc(sizeof(struct hci_memo), M_BLUETOOTH, M_NOWAIT | M_ZERO); |