⬢ DragonFlyBSD Kernel Audit
DF-4797 / fix.diff
← back to finding ↓ download raw
--- a/sys/dev/disk/nata/ata-disk.h	2026-09-23 22:16:40.311576960 +0000
+++ b/sys/dev/disk/nata/ata-disk.h	2026-09-23 22:16:40.335576649 +0000
@@ -44,7 +44,9 @@
 #define         AD_F_32B_ENABLED        0x0004
 #define         AD_F_TAG_ENABLED        0x0008
 #define         AD_F_RAID_SUBDISK       0x0010
+#define         AD_F_DETACHING          0x0020
 
+    int                         refs;           /* in-flight ad_strategy refs */
     struct devstat              stats;          /* devstat entry */
     struct disk                 disk;           /* disklabel/slice stuff */
     cdev_t                      cdev;           /* device placeholder */
--- a/sys/dev/disk/nata/ata-disk.c	2026-09-23 22:16:40.311576960 +0000
+++ b/sys/dev/disk/nata/ata-disk.c	2026-09-23 22:45:57.340784783 +0000
@@ -169,6 +169,7 @@
 {
     struct ad_softc *adp = device_get_ivars(dev);
     struct ata_device *atadev = device_get_softc(dev);
+    struct ata_channel *ch = device_get_softc(device_get_parent(dev));
     device_t *children;
     int nchildren, i;
 
@@ -176,6 +177,32 @@
     if (!adp)
 	return ENXIO;
 
+    /*
+     * Refuse to detach while any disk/slice device is open: the cdevs
+     * outlive disk_destroy() and keep walking the embedded struct disk
+     * (diskclose/dsclose) until their last close.  (DF-4797)
+     */
+    if (adp->disk.d_opencount != 0)
+	return EBUSY;
+
+    /*
+     * Stop the data path: no new bio may pass ad_strategy(), and the
+     * softc must survive until the last in-flight request's ad_done().
+     * (DF-4797)
+     */
+    lockmgr(&ch->queue_mtx, LK_EXCLUSIVE);
+    adp->flags |= AD_F_DETACHING;
+    device_set_ivars(dev, NULL);
+    if (adp->disk.d_opencount != 0) {		/* lost an open race */
+	adp->flags &= ~AD_F_DETACHING;
+	device_set_ivars(dev, adp);
+	lockmgr(&ch->queue_mtx, LK_RELEASE);
+	return EBUSY;
+    }
+    lockmgr(&ch->queue_mtx, LK_RELEASE);
+    while (adp->refs)
+	tsleep(adp, 0, "addetach", hz);
+
     /* destroy the power timeout */
     callout_drain(&atadev->spindown_timer);
 
@@ -187,17 +214,19 @@
 	kfree(children, M_TEMP);
     }
 
+    /*
+     * Fail anything still queued or running on the channel so their
+     * biodone()s happen while the disk layer structures are intact.
+     */
+    ata_fail_requests(dev);
+
     /* detroy disk from the system so we dont get any further requests */
     disk_invalidate(&adp->disk);
     disk_destroy(&adp->disk);
 
-    /* fail requests on the queue and any thats "in flight" for this device */
-    ata_fail_requests(dev);
-
     /* dont leave anything behind */
     /* disk_destroy() already took care of the dev_ops */
     devstat_remove_entry(&adp->stats);
-    device_set_ivars(dev, NULL);
     kfree(adp, M_AD);
     return 0;
 }
@@ -286,6 +315,22 @@
     struct ata_device *atadev = device_get_softc(dev);
     struct ata_request *request;
     struct ad_softc *adp = device_get_ivars(dev);
+    struct ata_channel *ch = device_get_softc(device_get_parent(dev));
+
+    /*
+     * Serialize against ad_detach(): either we obtain a reference and
+     * the softc stays alive until our ad_done() drops it, or the driver
+     * is detaching and the bio is failed immediately.  (DF-4797)
+     */
+    lockmgr(&ch->queue_mtx, LK_EXCLUSIVE);
+    adp = device_get_ivars(dev);
+    if (adp == NULL || (adp->flags & AD_F_DETACHING)) {
+	lockmgr(&ch->queue_mtx, LK_RELEASE);
+	biofinish(bp, NULL, ENXIO);
+	return(0);
+    }
+    atomic_add_int(&adp->refs, 1);
+    lockmgr(&ch->queue_mtx, LK_RELEASE);
 
     if (atadev->spindown)
 	callout_reset(&atadev->spindown_timer, hz * atadev->spindown,
@@ -294,10 +339,13 @@
     if (!(request = ata_alloc_request())) {
 	device_printf(dev, "FAILURE - out of memory in strategy\n");
 	biofinish(bp, NULL, ENOMEM);
+	if (atomic_fetchadd_int(&adp->refs, -1) == 1)
+	    wakeup(adp);
 	return(0);
     }
 
     /* setup request */
+    request->driver = adp;	/* ad_done(): stable softc pointer */
     request->dev = dev;
     request->bio = bp;
     request->callback = ad_done;
@@ -354,6 +402,8 @@
 	device_printf(dev, "FAILURE - unknown BUF operation\n");
 	ata_free_request(request);
 	biofinish(bp, NULL, EIO);
+	if (atomic_fetchadd_int(&adp->refs, -1) == 1)
+	    wakeup(adp);
 	return(0);
     }
     request->flags |= ATA_R_ORDERED;
@@ -365,7 +415,8 @@
 static void
 ad_done(struct ata_request *request)
 {
-    struct ad_softc *adp = device_get_ivars(request->dev);
+    /* ivars are NULL after ad_detach(); use the strategy-time pointer */
+    struct ad_softc *adp = request->driver;
     struct bio *bp = request->bio;
     struct buf *bbp = bp->bio_buf;
 
@@ -376,6 +427,8 @@
     devstat_end_transaction_buf(&adp->stats, bbp);
     biodone(bp);
     ata_free_request(request);
+    if (atomic_fetchadd_int(&adp->refs, -1) == 1)
+	wakeup(adp);
 }
 
 static int
--- a/sys/kern/subr_disk.c	2026-09-23 22:16:40.315576907 +0000
+++ b/sys/kern/subr_disk.c	2026-09-23 22:16:40.339576597 +0000
@@ -1243,6 +1243,13 @@
 	 * returns NULL it will have handled the bio for us (e.g. EOF
 	 * or error due to being beyond the device size).
 	 */
+	if (dp->d_slice == NULL) {
+		/* the disk is being invalidated/detached underneath us */
+		bio->bio_buf->b_error = ENXIO;
+		bio->bio_buf->b_flags |= B_ERROR;
+		biodone(bio);
+		return(0);
+	}
 	if ((nbio = dscheck(dev, bio, dp->d_slice)) != NULL) {
 		dev_dstrategy(dp->d_rawdev, nbio);
 	} else {