⬢ DragonFlyBSD Kernel Audit
DF-5754 / fix.diff
← back to finding ↓ download raw
diff --git a/sys/netbt/bt_input.c b/sys/netbt/bt_input.c
index 3e580b0..b258c03 100644
--- a/sys/netbt/bt_input.c
+++ b/sys/netbt/bt_input.c
@@ -25,17 +25,36 @@
 #include <net/netisr.h>
 
 #include <sys/mplock2.h>
+#include <sys/spinlock2.h>
 
 #include <netbt/hci.h>
 
 void
 btintr(netmsg_t msg)
 {
-	struct hci_unit *unit;
+	struct hci_unit *unit, *units[16];
+	int i, n = 0;
 
 	get_mplock();
+
+	/*
+	 * Snapshot the (referenced) unit list under hci_unit_lock so
+	 * hci_detach() cannot remove+free a unit out from under the walk.
+	 */
+	spin_lock(&hci_unit_lock);
 	TAILQ_FOREACH(unit, &hci_unit_list, hci_next) {
-		hci_intr(unit);
+		if (n >= (int)NELEM(units))
+			break;
+		unit->hci_refcnt++;
+		units[n++] = unit;
 	}
+	spin_unlock(&hci_unit_lock);
+
+	for (i = 0; i < n; i++)
+		hci_intr(units[i]);
+
+	for (i = 0; i < n; i++)
+		hci_unit_rele(units[i]);
+
 	rel_mplock();
 }
diff --git a/sys/netbt/hci.h b/sys/netbt/hci.h
index f1cb2a7..42a0865 100644
--- a/sys/netbt/hci.h
+++ b/sys/netbt/hci.h
@@ -2503,6 +2503,8 @@ struct hci_unit {
 	struct ifqueue		 hci_cmdwait;	/* pending commands */
 	struct ifqueue		 hci_scodone;	/* SCO done queue */
 
+	int			 hci_refcnt;	/* netisr walk references */
+
 	TAILQ_ENTRY(hci_unit) hci_next;
 };
 
@@ -2563,6 +2565,11 @@ int hci_input_event(struct hci_unit *, struct mbuf *);
 int hci_input_acl(struct hci_unit *, struct mbuf *);
 int hci_input_sco(struct hci_unit *, struct mbuf *);
 int hci_complete_sco(struct hci_unit *, struct mbuf *);
+
+/* unit lifetime (hci_unit.c) */
+extern struct spinlock hci_unit_lock;
+void	hci_unit_ref(struct hci_unit *);
+void	hci_unit_rele(struct hci_unit *);
 void hci_output_cmd(struct hci_unit *, struct mbuf *);
 void hci_output_acl(struct hci_unit *, struct mbuf *);
 void hci_output_sco(struct hci_unit *, struct mbuf *);
diff --git a/sys/netbt/hci_unit.c b/sys/netbt/hci_unit.c
index 8de0c1a..b37defa 100644
--- a/sys/netbt/hci_unit.c
+++ b/sys/netbt/hci_unit.c
@@ -42,6 +42,7 @@
 #include <sys/systm.h>
 #include <sys/endian.h>
 #include <sys/bus.h>
+#include <sys/spinlock2.h>
 
 #include <net/netisr.h>
 
@@ -50,6 +51,9 @@
 
 struct hci_unit_list hci_unit_list = TAILQ_HEAD_INITIALIZER(hci_unit_list);
 
+/* guards hci_unit_list and per-unit hci_refcnt (netisr walk vs detach) */
+struct spinlock hci_unit_lock = SPINLOCK_INITIALIZER(hci_unit_lock, "hci_unit_lock");
+
 /*
  * HCI Input Queue max lengths.
  */
@@ -110,9 +114,11 @@ hci_attach(const struct hci_if *hci_if, device_t dev, uint16_t flags)
 	TAILQ_INIT(&unit->hci_links);
 	LIST_INIT(&unit->hci_memos);
 
-	crit_enter();
+	unit->hci_refcnt = 0;	/* netisr walk references */
+
+	spin_lock(&hci_unit_lock);
 	TAILQ_INSERT_TAIL(&hci_unit_list, unit, hci_next);
-	crit_exit();
+	spin_unlock(&hci_unit_lock);
 
 	return unit;
 }
@@ -120,15 +126,49 @@ hci_attach(const struct hci_if *hci_if, device_t dev, uint16_t flags)
 void
 hci_detach(struct hci_unit *unit)
 {
-	crit_enter();
 	hci_disable(unit);
 
+	spin_lock(&hci_unit_lock);
 	TAILQ_REMOVE(&hci_unit_list, unit, hci_next);
-	crit_exit();
+	spin_unlock(&hci_unit_lock);
+
+	/*
+	 * Wait until the netisr walker (btintr -> hci_intr) is out of the
+	 * unit before freeing it.  The controller driver remains
+	 * responsible for quiescing its own rx paths before hci_detach().
+	 */
+	while (unit->hci_refcnt != 0)
+		tsleep(unit, 0, "hciunre", 1);
 
 	kfree(unit, M_BLUETOOTH);
 }
 
+/*
+ * Acquire/release a unit reference for the duration of a netisr-side
+ * hci_intr() invocation so hci_detach() cannot free the unit out from
+ * under the walk.  The list is snapshotted under hci_unit_lock in
+ * btintr().
+ */
+void
+hci_unit_ref(struct hci_unit *unit)
+{
+
+	spin_lock(&hci_unit_lock);
+	unit->hci_refcnt++;
+	spin_unlock(&hci_unit_lock);
+}
+
+void
+hci_unit_rele(struct hci_unit *unit)
+{
+
+	spin_lock(&hci_unit_lock);
+	KKASSERT(unit->hci_refcnt > 0);
+	if (--unit->hci_refcnt == 0)
+		wakeup(unit);
+	spin_unlock(&hci_unit_lock);
+}
+
 int
 hci_enable(struct hci_unit *unit)
 {