⬢ DragonFlyBSD Kernel Audit
DF-4466 / fix.diff
← back to finding ↓ download raw
--- a/sys/dev/disk/dm/crypt/dm_target_crypt.c
+++ b/sys/dev/disk/dm/crypt/dm_target_crypt.c
@@ -96,8 +96,19 @@
 	struct malloc_pipe	read_mpipe;
 	struct malloc_pipe	write_mpipe;
 
+	/*
+	 * In-flight bios that still reference this config (see
+	 * dm_target_crypt_destroy).
+	 */
+	volatile int		nactive;
+
 } dm_target_crypt_config_t;
 
+#define	DMTC_BIO_DONE(priv)	do {					\
+	if (atomic_fetchadd_int(&(priv)->nactive, -1) == 1)		\
+		wakeup(priv);						\
+} while (0)
+
 struct dmtc_dump_helper {
 	dm_target_crypt_config_t *priv;
 	void *data;
@@ -488,6 +499,9 @@
 
 	priv = kmalloc(sizeof(dm_target_crypt_config_t), M_DMCRYPT, M_WAITOK);
 
+	/* No in-flight bios reference this config yet */
+	priv->nactive = 0;
+
 	/* Insert dmp to global pdev list */
 	if ((priv->pdev = dm_pdev_insert(dev)) == NULL) {
 		kprintf("dm_target_crypt: dm_pdev_insert failed\n");
@@ -617,6 +631,16 @@
 		return 0;
 	dm_pdev_decr(priv->pdev);
 
+	/*
+	 * Wait until every in-flight bio referencing this config has
+	 * completed.  dmstrategy drops its table reference as soon as
+	 * strategy() returns, but crypt completions run asynchronously
+	 * afterwards holding nothing but bio_caller_info1.ptr; freeing
+	 * the config (and the mpipes) underneath them is a use-after-free.
+	 */
+	while (priv->nactive != 0)
+		tsleep(priv, 0, "dmtcdn", 1);
+
 	dmtc_destroy_mpipe(priv);
 
 	/*
@@ -692,6 +716,7 @@
 
 	switch (bp->b_cmd) {
 	case BUF_CMD_READ:
+		atomic_add_int(&priv->nactive, 1);
 		bio = push_bio(&bp->b_bio1);
 		bio->bio_offset = bp->b_bio1.bio_offset +
 		    priv->block_offset * DEV_BSIZE;
@@ -700,6 +725,7 @@
 		vn_strategy(priv->pdev->pdev_vnode, bio);
 		break;
 	case BUF_CMD_WRITE:
+		atomic_add_int(&priv->nactive, 1);
 		bio = push_bio(&bp->b_bio1);
 		bio->bio_offset = bp->b_bio1.bio_offset +
 		    priv->block_offset * DEV_BSIZE;
@@ -729,6 +755,7 @@
 dmtc_bio_read_done(struct bio *bio)
 {
 	struct bio *obio;
+	dm_target_crypt_config_t *priv = bio->bio_caller_info1.ptr;
 
 	/*
 	 * If a read error occurs we shortcut the operation, otherwise
@@ -737,6 +764,7 @@
 	if (bio->bio_buf->b_flags & B_ERROR) {
 		obio = pop_bio(bio);
 		biodone(obio);
+		DMTC_BIO_DONE(priv);
 	} else {
 		dmtc_bio_read_decrypt_start(bio);
 	}
@@ -829,6 +857,8 @@
 
 	if (mpipe)
 		mpipe_free(mpipe, data_buf);
+
+	DMTC_BIO_DONE(priv);
 }
 
 /************************************************************************
@@ -906,6 +936,8 @@
 		bio->bio_buf->b_flags |= B_ERROR;
 		struct bio *obio = pop_bio(bio);
 		biodone(obio);
+
+		DMTC_BIO_DONE(priv);
 	} else {
 		bio->bio_caller_info2.ptr = bio->bio_buf->b_data; /* orig_buf */
 		bio->bio_buf->b_data = data_buf;
@@ -946,6 +978,8 @@
 
 	struct bio *obio = pop_bio(bio);
 	biodone(obio);
+
+	DMTC_BIO_DONE(priv);
 }
 
 /************************************************************************