⬢ DragonFlyBSD Kernel Audit
DF-5761 / fix.diff
← back to finding ↓ download raw
--- /home/maxx/dfbsd/dfbsd/sys/netbt/hci_link.c	2026-06-29 12:51:19.000000000 +0000
+++ hci_link.c	2026-10-11 12:11:04.653039910 +0000
@@ -39,6 +39,7 @@
 #include <sys/queue.h>
 #include <sys/systm.h>
 #include <sys/endian.h>
+#include <sys/spinlock2.h>
 #include <sys/callout.h>
 #include <net/if.h>
 #include <sys/bus.h>
@@ -783,13 +784,41 @@
 	if (acl == NULL || acl->hl_state != HCI_LINK_OPEN)
 		return NULL;
 
-	LIST_FOREACH(pcb, &sco_pcb, sp_next) {
-		if ((pcb->sp_flags & SP_LISTENING) == 0)
-			continue;
+	{
+		/* Snapshot + pin the listening candidates under the lock:
+		 * newconn() can block (sonewconn) so it must run without
+		 * the spinlock, but the pcb must stay allocated across it.
+		 * XXX 16 simultaneous SCO listeners is beyond any use case.
+		 */
+		struct sco_pcb *cand[16];
+		int n = 0, i;
+
+		new = NULL;
+
+		spin_lock(&sco_pcblock);
+		LIST_FOREACH(pcb, &sco_pcb, sp_next) {
+			if ((pcb->sp_flags & SP_LISTENING) == 0)
+				continue;
+			if (n == (int)NELEM(cand))
+				break;
+			sco_pcb_hold(pcb);
+			cand[n++] = pcb;
+		}
+		spin_unlock(&sco_pcblock);
 
-		new = (*pcb->sp_proto->newconn)(pcb->sp_upper, &laddr, &raddr);
+		for (i = 0; i < n; i++) {
+			pcb = cand[i];
+			new = (*pcb->sp_proto->newconn)(pcb->sp_upper,
+			    &laddr, &raddr);
+			if (new != NULL)
+				break;
+			sco_pcb_rele(pcb);
+		}
 		if (new == NULL)
-			continue;
+			return NULL;
+
+		for (; i < n; i++)
+			sco_pcb_rele(cand[i]);
 
 		/*
 		 * Ok, got new pcb so we can start a new link and fill
--- /home/maxx/dfbsd/dfbsd/sys/netbt/sco.h	2026-06-29 12:51:19.000000000 +0000
+++ sco.h	2026-10-11 11:32:46.290598700 +0000
@@ -43,6 +43,7 @@
  */
 struct sco_pcb {
 	struct hci_link		*sp_link;	/* SCO link */
+	int			 sp_refcnt;	/* list ref + walker pins */
 	unsigned int		 sp_flags;	/* flags */
 	bdaddr_t		 sp_laddr;	/* local address */
 	bdaddr_t		 sp_raddr;	/* remote address */
@@ -57,6 +58,9 @@
 
 LIST_HEAD(sco_pcb_list, sco_pcb);
 extern struct sco_pcb_list sco_pcb;
+extern struct spinlock sco_pcblock;
+void	sco_pcb_hold(struct sco_pcb *);
+void	sco_pcb_rele(struct sco_pcb *);
 
 /* sp_flags */
 #define SP_LISTENING		(1<<0)		/* is listening pcb */
--- /home/maxx/dfbsd/dfbsd/sys/netbt/sco_upper.c	2026-06-29 12:51:19.000000000 +0000
+++ sco_upper.c	2026-10-11 11:58:19.242892084 +0000
@@ -40,6 +40,7 @@
 #include <sys/systm.h>
 #include <sys/endian.h>
 #include <sys/bus.h>
+#include <sys/spinlock2.h>
 
 #include <netbt/bluetooth.h>
 #include <netbt/hci.h>
@@ -51,6 +52,21 @@
  */
 
 struct sco_pcb_list sco_pcb = LIST_HEAD_INITIALIZER(sco_pcb);
+struct spinlock sco_pcblock = SPINLOCK_INITIALIZER(sco_pcblock, "sco_pcblock");
+
+void
+sco_pcb_hold(struct sco_pcb *pcb)
+{
+	/* atomic: races rele() which runs without the list lock */
+	atomic_add_int(&pcb->sp_refcnt, 1);
+}
+
+void
+sco_pcb_rele(struct sco_pcb *pcb)
+{
+	if (atomic_fetchadd_int(&pcb->sp_refcnt, -1) == 1)
+		kfree(pcb, M_BLUETOOTH);
+}
 
 /*
  * sco_attach(handle, proto, upper)
@@ -71,10 +87,13 @@
 	if (pcb == NULL)
 		return ENOMEM;
 
+	pcb->sp_refcnt = 1;			/* the list's reference */
 	pcb->sp_proto = proto;
 	pcb->sp_upper = upper;
 
+	spin_lock(&sco_pcblock);
 	LIST_INSERT_HEAD(&sco_pcb, pcb, sp_next);
+	spin_unlock(&sco_pcblock);
 
 	*handle = pcb;
 	return 0;
@@ -242,8 +261,10 @@
 		pcb->sp_link = NULL;
 	}
 
+	spin_lock(&sco_pcblock);
 	LIST_REMOVE(pcb, sp_next);
-	kfree(pcb, M_BLUETOOTH);
+	spin_unlock(&sco_pcblock);
+	sco_pcb_rele(pcb);	/* may defer the kfree to the last walker */
 	return 0;
 }