⬢ DragonFlyBSD Kernel Audit
DF-4529 / fix.diff
← back to finding ↓ download raw
--- a/sys/dev/disk/nvme/nvme_attach.c	2026-09-21 04:58:29.815217063 +0000
+++ b/sys/dev/disk/nvme/nvme_attach.c	2026-09-21 04:58:58.746843828 +0000
@@ -112,6 +112,7 @@
 #endif
 
 	sc->dev = dev;
+	sc->flags &= ~NVME_SC_UNLOADING;
 
 	/*
 	 * Map the register window
@@ -473,6 +474,22 @@
 	int i;
 
 	/*
+	 * Block new NVMEIOCGETLOG ioctls and drain any in-flight ones
+	 * before the admin queue is torn down.  Every getlog ioctl holds
+	 * ioctl_lk across its entire get_request -> submit -> wait ->
+	 * put_request sequence, so once we hold ioctl_lk exclusively no
+	 * user thread is inside the admin request machinery.
+	 *
+	 * Without this, nvme_free_subqueue(0) kfree()s reqary and bzero()s
+	 * the queue (nqe = 0) while an ioctl can still be inside
+	 * nvme_get_request(), whose ring-full check computes
+	 * (tail + unsubmitted + 1) % nqe -> integer divide-by-zero panic
+	 * (observed: nvme_get_request+0x34 divl 0x40(%r13),%eax).
+	 */
+	sc->flags |= NVME_SC_UNLOADING;
+	lockmgr(&sc->ioctl_lk, LK_EXCLUSIVE);
+
+	/*
 	 * Stop the admin thread
 	 */
 	nvme_stop_admin_thread(sc);
@@ -492,6 +509,7 @@
 	 */
 	nvme_free_subqueue(sc, 0);
 	nvme_free_comqueue(sc, 0);
+	lockmgr(&sc->ioctl_lk, LK_RELEASE);
 
 	/*
 	 * Release related resources.
--- a/sys/dev/disk/nvme/nvme_ioctl.c	2026-09-21 04:58:29.815217063 +0000
+++ b/sys/dev/disk/nvme/nvme_ioctl.c	2026-09-21 04:58:40.347081192 +0000
@@ -46,6 +46,16 @@
 		return EINVAL;
 
 	lockmgr(&sc->ioctl_lk, LK_EXCLUSIVE);
+
+	/*
+	 * Serialize against device teardown: detach sets NVME_SC_UNLOADING
+	 * and then holds this lock across the admin queue teardown, so we
+	 * never touch a freed/bzero'd queue.
+	 */
+	if (sc->flags & NVME_SC_UNLOADING) {
+		lockmgr(&sc->ioctl_lk, LK_RELEASE);
+		return ENXIO;
+	}
 	req = nvme_get_admin_request(sc, NVME_OP_GET_LOG_PAGE);
 	req->cmd.head.nsid = -1;
 	req->cmd.getlog.lid = ioc->lid;
--- a/sys/dev/disk/nvme/nvme_admin.c
+++ b/sys/dev/disk/nvme/nvme_admin.c	2026-09-21 04:58:40.355081088 +0000
@@ -119,7 +119,13 @@
 
 		if ((nsc = sc->nscary[i]) != NULL) {
 			nvme_disk_detach(nsc);
-
+			/*
+			 * The cdev may still be referenced by open descriptors
+			 * after disk_destroy(); make any late dispatch (which
+			 * reads nsc->sc through si_drv1) fail cleanly instead
+			 * of touching freed memory.
+			 */
+			nsc->sc = NULL;
 			kfree(nsc, M_NVME);
 			sc->nscary[i] = NULL;
 		}
--- a/sys/dev/disk/nvme/nvme_disk.c	2026-09-21 04:58:29.815217063 +0000
+++ b/sys/dev/disk/nvme/nvme_disk.c	2026-09-21 05:53:26.808935656 +0000
@@ -116,6 +116,13 @@
 nvme_disk_detach(nvme_softns_t *nsc)
 {
 	if (nsc->cdev) {
+		/*
+		 * devfs keeps dispatching through open
+		 * descriptors/nodes until the cdev is fully
+		 * released; fail them cleanly instead of
+		 * dereferencing a freed softns.
+		 */
+		nsc->cdev->si_drv1 = NULL;
 		disk_destroy(&nsc->disk);
 		devstat_remove_entry(&nsc->stats);
 	}
@@ -127,7 +134,11 @@
 {
 	cdev_t dev = ap->a_head.a_dev;
 	nvme_softns_t *nsc = dev->si_drv1;
-	nvme_softc_t *sc = nsc->sc;
+	nvme_softc_t *sc;
+
+	if (nsc == NULL || nsc->sc == NULL)
+		return ENXIO;
+	sc = nsc->sc;
 
 	if (sc->flags & NVME_SC_UNLOADING)
 		return ENXIO;
@@ -143,7 +154,11 @@
 {
 	cdev_t dev = ap->a_head.a_dev;
 	nvme_softns_t *nsc = dev->si_drv1;
-	nvme_softc_t *sc = nsc->sc;
+	nvme_softc_t *sc;
+
+	if (nsc == NULL || nsc->sc == NULL)
+		return ENXIO;
+	sc = nsc->sc;
 
 	atomic_add_long(&sc->opencnt, -1);
 
@@ -155,11 +170,17 @@
 {
 	cdev_t dev = ap->a_head.a_dev;
 	nvme_softns_t *nsc = dev->si_drv1;
-	nvme_softc_t *sc = nsc->sc;
+	nvme_softc_t *sc;
 	int error;
 
+	if (nsc == NULL || nsc->sc == NULL)
+		return ENXIO;
+	sc = nsc->sc;
+
 	switch(ap->a_cmd) {
 	case NVMEIOCGETLOG:
+		if (nsc->sc == NULL)
+			return ENXIO;
 		error = nvme_getlog_ioctl(sc, (void *)ap->a_data);
 		break;
 	default:
@@ -175,6 +196,9 @@
 	cdev_t dev = ap->a_head.a_dev;
 	nvme_softns_t *nsc = dev->si_drv1;
 
+	if (nsc == NULL || nsc->sc == NULL)
+		return 0;	/* fail silently, bio is gone with the disk */
+
 	nvme_strategy_core(nsc, ap->a_bio, nvme_sync_delay);
 
 	return 0;