DF-5780 / fix.diff
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 | 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 <sys/param.h> #include <sys/kernel.h> +#include <sys/lock.h> #include <sys/malloc.h> #include <sys/mbuf.h> #include <sys/proc.h> @@ -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) */ |