DragonFlyBSD Kernel Audit
DF-2453 / fix.diff
← back to finding ↓ download raw
diff --git a/sys/dev/disk/dm/flakey/dm_target_flakey.c b/sys/dev/disk/dm/flakey/dm_target_flakey.c
--- a/sys/dev/disk/dm/flakey/dm_target_flakey.c
+++ b/sys/dev/disk/dm/flakey/dm_target_flakey.c
@@ -34,11 +34,14 @@
 
 #include <sys/param.h>
 #include <sys/malloc.h>
+#include <cpu/atomic.h>
 #include <dev/disk/dm/dm.h>
 
 MALLOC_DEFINE(M_DMFLAKEY, "dm_flakey", "Device Mapper Target Flakey");
 
-/* dm_flakey never updates any field after initialization */
+/* dm_flakey never updates any field after initialization.
+ * ref_cnt protects tfc from being freed while async read I/O
+ * (which stores tfc in bio_caller_info1) is in flight. */
 typedef struct target_flakey_config {
 	dm_pdev_t *pdev;
 	uint64_t offset;
@@ -54,11 +57,32 @@
 	unsigned int corrupt_buf_rw;
 	unsigned int corrupt_buf_value;
 	unsigned int corrupt_buf_flags;  /* for B_XXX flags */
+	int ref_cnt;  /* refcount: 1 for table + 1 per in-flight async read */
 } dm_target_flakey_config_t;
 
 #define FLAKEY_CORRUPT_DIR(tfc) \
 	((tfc)->corrupt_buf_rw == BUF_CMD_READ ? 'r' : 'w')
 
+static __inline void
+tfc_hold(dm_target_flakey_config_t *tfc)
+{
+	atomic_add_int(&tfc->ref_cnt, 1);
+}
+
+/*
+ * Drop a reference on tfc.  When the last reference is released
+ * (either the table destroy or the last in-flight async read iodone),
+ * free the config and its pdev reference.
+ */
+static __inline void
+tfc_release(dm_target_flakey_config_t *tfc)
+{
+	if (atomic_fetchadd_int(&tfc->ref_cnt, -1) == 1) {
+		dm_pdev_decr(tfc->pdev);
+		kfree(tfc, M_DMFLAKEY);
+	}
+}
+
 static int _init_features(dm_target_flakey_config_t*, int, char**);
 static __inline void _submit(dm_target_flakey_config_t*, struct bio*);
 static int _flakey_read(dm_target_flakey_config_t*, struct buf*);
@@ -82,6 +106,7 @@
 	tfc = kmalloc(sizeof(*tfc), M_DMFLAKEY, M_WAITOK | M_ZERO);
 	if (tfc == NULL)
 		return ENOMEM;
+	tfc->ref_cnt = 1;  /* initial reference for the table entry */
 
 	if ((dmp = dm_pdev_insert(argv[0])) == NULL) {
 		err = ENOENT;
@@ -214,9 +239,7 @@
 	if (tfc == NULL)
 		return 0;
 
-	dm_pdev_decr(tfc->pdev);
-
-	kfree(tfc, M_DMFLAKEY);
+	tfc_release(tfc);
 
 	return 0;
 }
@@ -282,6 +305,8 @@
 		_flakey_eio_buf(bio->bio_buf);
 
 	biodone(obio);
+
+	tfc_release(tfc);  /* drop ref acquired in _flakey_read */
 }
 
 static int
@@ -302,6 +327,7 @@
 
 	nbio = push_bio(bio);
 	nbio->bio_done = _flakey_read_iodone;
+	tfc_hold(tfc);  /* released in _flakey_read_iodone */
 	nbio->bio_caller_info1.ptr = tfc;
 	nbio->bio_offset = pop_bio(nbio)->bio_offset;