diff -ruN fixorig/sys/netbt/hci.h fixnew/sys/netbt/hci.h --- fixorig/sys/netbt/hci.h 2026-10-12 14:12:01.110512441 +0000 +++ fixnew/sys/netbt/hci.h 2026-10-12 14:12:01.146511974 +0000 @@ -2544,7 +2544,8 @@ int hci_route_lookup(bdaddr_t *, bdaddr_t *); struct hci_memo *hci_memo_find(struct hci_unit *, bdaddr_t *); struct hci_memo *hci_memo_new(struct hci_unit *, bdaddr_t *); -void hci_memo_free(struct hci_memo *); +void hci_memo_free(struct hci_unit *, struct hci_memo *); +void hci_memo_free_locked(struct hci_unit *, struct hci_memo *); /* hci_socket.c */ void hci_drop(void *); diff -ruN fixorig/sys/netbt/hci_misc.c fixnew/sys/netbt/hci_misc.c --- fixorig/sys/netbt/hci_misc.c 2026-10-12 14:12:01.110512441 +0000 +++ fixnew/sys/netbt/hci_misc.c 2026-10-12 14:12:01.122512286 +0000 @@ -33,6 +33,7 @@ #include #include +#include #include #include #include @@ -108,9 +109,20 @@ /* * find unit memo from bdaddr + * + * DF-5780: the memo list is walked and mutated (lazy expiry frees) + * concurrently from several execution contexts with no synchronization: + * user process context (any CPU) via L2CAP/SCO connect() -> + * hci_route_lookup()/hci_acl_open(), netisr-0 via hci_event() -> + * hci_memo_new() (hci_intr() drops hci_devlock before hci_event()), + * and softclock via hci_link_free() -> hci_memo_new(). Two walkers + * that both observe the same expired memo both free it, double-freeing + * the 48-byte struct hci_memo and corrupting the global size-class + * slab freelist (verified: "kuhp < 0" / chunk_mark_free panics). + * All memo list operations are serialized on the unit's device lock. */ -struct hci_memo * -hci_memo_find(struct hci_unit *unit, bdaddr_t *bdaddr) +static struct hci_memo * +hci_memo_find_locked(struct hci_unit *unit, bdaddr_t *bdaddr) { struct hci_memo *memo, *m0; struct timeval now; @@ -123,7 +135,7 @@ if (now.tv_sec > memo->time.tv_sec + hci_memo_expiry) { DPRINTF("memo %p too old (expiring)\n", memo); - hci_memo_free(memo); + hci_memo_free_locked(unit, memo); continue; } @@ -137,6 +149,18 @@ return NULL; } +struct hci_memo * +hci_memo_find(struct hci_unit *unit, bdaddr_t *bdaddr) +{ + struct hci_memo *memo; + + lockmgr(&unit->hci_devlock, LK_EXCLUSIVE); + memo = hci_memo_find_locked(unit, bdaddr); + lockmgr(&unit->hci_devlock, LK_RELEASE); + + return memo; +} + /* * Make a new memo on unit for bdaddr. If a memo exists, just * update the timestamp. @@ -146,13 +170,20 @@ { struct hci_memo *memo; - memo = hci_memo_find(unit, bdaddr); + /* + * one lock covers the expiry walk, the allocation and the list + * insert, so concurrent hci_memo_find() callers can neither free + * the memo we found nor observe a half-inserted entry. + */ + lockmgr(&unit->hci_devlock, LK_EXCLUSIVE); + memo = hci_memo_find_locked(unit, bdaddr); if (memo == NULL) { memo = kmalloc(sizeof(struct hci_memo), M_BLUETOOTH, M_NOWAIT | M_ZERO); if (memo == NULL) { DPRINTFN(0, "no memory for memo!\n"); + lockmgr(&unit->hci_devlock, LK_RELEASE); return NULL; } @@ -169,11 +200,24 @@ bdaddr->b[2], bdaddr->b[1], bdaddr->b[0]); microtime(&memo->time); + + lockmgr(&unit->hci_devlock, LK_RELEASE); + return memo; } void -hci_memo_free(struct hci_memo *memo) +hci_memo_free(struct hci_unit *unit, struct hci_memo *memo) +{ + + lockmgr(&unit->hci_devlock, LK_EXCLUSIVE); + hci_memo_free_locked(unit, memo); + lockmgr(&unit->hci_devlock, LK_RELEASE); +} + +/* caller holds hci_devlock exclusively */ +void +hci_memo_free_locked(struct hci_unit *unit, struct hci_memo *memo) { LIST_REMOVE(memo, next); diff -ruN fixorig/sys/netbt/hci_unit.c fixnew/sys/netbt/hci_unit.c --- fixorig/sys/netbt/hci_unit.c 2026-10-12 14:12:01.110512441 +0000 +++ fixnew/sys/netbt/hci_unit.c 2026-10-12 14:12:01.154511870 +0000 @@ -245,7 +245,7 @@ } while ((memo = LIST_FIRST(&unit->hci_memos)) != NULL) - hci_memo_free(memo); + hci_memo_free(unit, memo); /* (no need to hold hci_devlock, the driver is disabled) */