⬢ DragonFlyBSD Kernel Audit
DF-5739 / fix.diff
← back to finding ↓ download raw
--- a/sys/netbt/hci.h	2026-10-11 00:20:51.387137328 +0000
+++ b/sys/netbt/hci.h	2026-10-11 00:21:02.026999604 +0000
@@ -2403,6 +2403,9 @@
 	uint16_t		 hl_clock;	/* remote clock offset */
 
 	TAILQ_HEAD(,l2cap_pdu)	 hl_txq;	/* queue of outgoing PDUs */
+	unsigned int		 hl_teardown;	/* exactly-once free guard
+						 * (atomic test-and-set;
+						 * see hci_link_free) */
 	int			 hl_txqlen;	/* number of fragments */
 	struct mbuf		*hl_rxp;	/* incoming PDU (accumulating)*/
 	struct callout		 hl_expire;	/* connection expiry timer */
--- a/sys/netbt/hci_link.c	2026-10-11 00:20:51.387137328 +0000
+++ b/sys/netbt/hci_link.c	2026-10-11 00:22:08.890134116 +0000
@@ -203,6 +203,13 @@
 	hci_discon_cp cp;
 	int err;
 
+	/*
+	 * Exactly-once teardown: if any other context is already
+	 * freeing this link, its hci_link_free() will finish the job.
+	 */
+	if (link->hl_teardown)
+		return;
+
 	crit_enter();
 
 	if (link->hl_refcnt > 0) 
@@ -936,12 +943,42 @@
 
 	KKASSERT(link != NULL);
 
+	/*
+	 * Exactly-once teardown guard (DF-5739): hci_acl_timeout() in
+	 * the softclock thread and event handlers in the netisr thread
+	 * can reach this function on the same link concurrently.  The
+	 * old `callout_stop(); if (callout_active()) return;` deferral
+	 * never triggers on DragonFly callouts because callout_stop()
+	 * clears CALLOUT_ACTIVE unconditionally.  The loser of this
+	 * test-and-set returns without touching anything else.
+	 */
+	if (atomic_testandset_int(&link->hl_teardown, 0))
+		return;
+
 	DPRINTF("(%s) #%d, type = %d, state = %d, refcnt = %d\n",
 	    device_get_nameunit(link->hl_unit->hci_dev), link->hl_handle,
 	    link->hl_type, link->hl_state, link->hl_refcnt);
 
 	/* ACL reference count */
 	if (link->hl_refcnt > 0) {
+		/*
+		 * DF-5738: SCO links hold an anchor reference via
+		 * hl_link (hci_acl_open() from hci_sco_newconn() or
+		 * sco_connect()).  Release them first so the refcount
+		 * reaches zero -- hci_cmd_reset() does the same dance.
+		 */
+		if (link->hl_type == HCI_LINK_ACL) {
+			struct hci_link *sco, *snext;
+
+			sco = TAILQ_FIRST(&link->hl_unit->hci_links);
+			while (sco != NULL) {
+				snext = TAILQ_NEXT(sco, hl_next);
+				if (sco->hl_type == HCI_LINK_SCO &&
+				    sco->hl_link == link)
+					hci_link_free(sco, err);
+				sco = snext;
+			}
+		}
 		next = LIST_FIRST(&l2cap_active_list);
 		while ((chan = next) != NULL) {
 			next = LIST_NEXT(chan, lc_ncid);
@@ -1002,9 +1039,15 @@
 	 * any case.
 	 */
 	link->hl_state = HCI_LINK_CLOSED;
-	callout_stop(&link->hl_expire);
-	if (callout_active(&link->hl_expire))
-		return;
+	/*
+	 * callout_drain() blocks until a running callback has finished
+	 * (recursion from within the callback itself is handled by the
+	 * callout code); callout_terminate() detaches the internal
+	 * _callout so that kfree()ing the embedded callout leaves no
+	 * dangling state behind.
+	 */
+	callout_drain(&link->hl_expire);
+	callout_terminate(&link->hl_expire);
 
 	/*
 	 * If we made a note of clock offset, keep it in a memo