⬢ DragonFlyBSD Kernel Audit
DF-5780 / fix.diff
← back to finding ↓ download raw
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) */