⬢ DragonFlyBSD Kernel Audit
DF-4646 / fix.diff
← back to finding ↓ download raw
diff -ruN a/sys/dev/disk/nvme/nvme_admin.c b/sys/dev/disk/nvme/nvme_admin.c
--- a/sys/dev/disk/nvme/nvme_admin.c	2026-09-22 13:06:19.531148522 +0000
+++ b/sys/dev/disk/nvme/nvme_admin.c	2026-09-22 13:06:41.770859207 +0000
@@ -120,6 +120,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;
 		}
@@ -186,10 +193,23 @@
 	/*
 	 * Cleanup state.
 	 *
+	 * Poll every completion queue once more before the queues are
+	 * deleted and freed, so in-flight callbacks observe valid softcs
+	 * and their bios complete before the namespace memory goes away.
+	 *
 	 * Note that we actually issue delete queue commands here.  The NVME
 	 * spec says that for a normal shutdown the I/O queues should be
 	 * deleted prior to issuing the shutdown in the CONFIG register.
 	 */
+	for (i = 1; i <= sc->niocomqs; ++i) {
+		nvme_comqueue_t *comq = &sc->comqueues[i];
+
+		if (comq->nqe == 0)	/* not configured */
+			continue;
+		lockmgr(&comq->lk, LK_EXCLUSIVE);
+		nvme_poll_completions(comq, &comq->lk);
+		lockmgr(&comq->lk, LK_RELEASE);
+	}
 	for (i = 1; i <= sc->niosubqs; ++i) {
 		nvme_delete_subqueue(sc, i);
 		nvme_free_subqueue(sc, i);
diff -ruN a/sys/dev/disk/nvme/nvme_attach.c b/sys/dev/disk/nvme/nvme_attach.c
--- a/sys/dev/disk/nvme/nvme_attach.c	2026-09-22 13:06:19.527148574 +0000
+++ b/sys/dev/disk/nvme/nvme_attach.c	2026-09-22 13:21:22.835431041 +0000
@@ -112,6 +112,7 @@
 #endif
 
 	sc->dev = dev;
+	sc->flags &= ~NVME_SC_UNLOADING;
 
 	/*
 	 * Map the register window
@@ -473,6 +474,13 @@
 	int i;
 
 	/*
+	 * Block new data-path and admin-ioctl work before tearing down
+	 * the queues.  (nvme_open() has always checked this flag; it was
+	 * never actually set anywhere.)
+	 */
+	sc->flags |= NVME_SC_UNLOADING;
+
+	/*
 	 * Stop the admin thread
 	 */
 	nvme_stop_admin_thread(sc);
diff -ruN a/sys/dev/disk/nvme/nvme_disk.c b/sys/dev/disk/nvme/nvme_disk.c
--- a/sys/dev/disk/nvme/nvme_disk.c	2026-09-22 13:06:19.527148574 +0000
+++ b/sys/dev/disk/nvme/nvme_disk.c	2026-09-22 13:21:22.835431041 +0000
@@ -116,6 +116,28 @@
 nvme_disk_detach(nvme_softns_t *nsc)
 {
 	if (nsc->cdev) {
+		/*
+		 * Fail parked bios before the softc goes away.  The admin
+		 * thread would otherwise re-dispatch them after kfree().
+		 */
+		lockmgr(&nsc->lk, LK_EXCLUSIVE);
+		while (bioq_first(&nsc->bioq) != NULL) {
+			struct bio *bio = bioq_first(&nsc->bioq);
+			struct buf *bp = bio->bio_buf;
+
+			bioq_remove(&nsc->bioq, bio);
+			bp->b_error = ENXIO;
+			bp->b_flags |= B_ERROR;
+			biodone(bio);
+		}
+		lockmgr(&nsc->lk, LK_RELEASE);
+
+		/*
+		 * devfs keeps dispatching through open descriptors until
+		 * the cdev is fully released; fail them cleanly instead
+		 * of dereferencing the about-to-be-freed softns.
+		 */
+		nsc->cdev->si_drv1 = NULL;
 		disk_destroy(&nsc->disk);
 		devstat_remove_entry(&nsc->stats);
 	}
@@ -127,7 +149,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 +169,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,9 +185,13 @@
 {
 	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:
 		error = nvme_getlog_ioctl(sc, (void *)ap->a_data);
@@ -175,6 +209,16 @@
 	cdev_t dev = ap->a_head.a_dev;
 	nvme_softns_t *nsc = dev->si_drv1;
 
+	if (nsc == NULL || nsc->sc == NULL) {
+		struct bio *bio = ap->a_bio;
+		struct buf *bp = bio->bio_buf;
+
+		bp->b_error = ENXIO;
+		bp->b_flags |= B_ERROR;
+		biodone(bio);
+		return 0;
+	}
+
 	nvme_strategy_core(nsc, ap->a_bio, nvme_sync_delay);
 
 	return 0;
@@ -192,6 +236,9 @@
 	struct bio *bio;
 	int i;
 
+	if (sc->flags & NVME_SC_UNLOADING)
+		return;
+
 	for (i = 0; i < sc->nscmax; ++i) {
 		nsc = sc->nscary[i];
 		if (nsc == NULL || nsc->sc == NULL)
@@ -238,6 +285,19 @@
 
 	devstat_start_transaction(&nsc->stats);
 
+	if (sc->flags & NVME_SC_UNLOADING) {
+		/*
+		 * Device teardown in progress (queues may already be
+		 * freed/bzero'd).  Fail the bio; do NOT enter the
+		 * request machinery.
+		 */
+		bp->b_error = ENXIO;
+		bp->b_flags |= B_ERROR;
+		devstat_end_transaction_buf(&nsc->stats, bp);
+		biodone(bio);
+		return 0;
+	}
+
 	subq = NULL;
 	req = NULL;
 	nobytes = 0;