Thread (17 messages) flat view 17 messages, 3 authors, 17d ago
COLD17d

[PATCH v2 2/3] md/raid5: fix leak and use-after-free in resize_stripes error path

From: <hidden>
Date: 2026-08-27 06:06:23
Also in: lkml, stable
Subsystem: software raid (multiple disks) support, the rest · Maintainers: Song Liu, Yu Kuai, Linus Torvalds

From: Cao Guanghui <redacted>

resize_stripes() has two issues in how conf->disks is replaced:

1. Memory leak: conf->disks is replaced with ndisks in Step 3, but
   pool_size is only updated at the end with "if (!err)".  If Step 4
   (allocating pages for new stripe slots) fails, pool_size retains the
   old value.  On teardown, free_conf() iterates only pool_size entries,
   leaking (newsize - pool_size) extra_page allocations.

2. Use-after-free: conf->disks is freed and replaced without holding
   mddev->lock, while raid5_status() (called from /proc/mdstat via
   md_seq_show) reads conf->disks[i].rdev under mddev->lock.  The
   freeing and replacement happen under reconfig_mutex and
   cache_size_mutex, which do not exclude mddev->lock holders.

Fix both by deferring the conf->disks replacement until after Step 4
succeeds, and performing the pointer swap under mddev->lock so that
concurrent readers in raid5_status() see either the old or new array,
never a freed one.  If Step 4 fails, ndisks is freed instead.

This also preserves the original retry behavior: pool_size is only
updated on full success, so check_reshape() correctly calls
resize_stripes() again on retry.

Fixes: ad01c9e3752f ("[PATCH] md: Allow stripes to be expanded in preparation for expanding an array")
Cc: stable@vger.kernel.org
Signed-off-by: Cao Guanghui <redacted>
---

Changes in v2:
  - Defer conf->disks replacement to after Step 4 instead of setting
    pool_size early, which would break reshape retry logic (Sashiko)
  - Add spinlock protection around the pointer swap to fix a concurrent
    use-after-free in raid5_status() (Sashiko)

 drivers/md/raid5.c | 19 +++++++++++--------
 1 file changed, 11 insertions(+), 8 deletions(-)
diff --git a/drivers/md/raid5.c b/drivers/md/raid5.c
--- a/drivers/md/raid5.c
+++ b/drivers/md/raid5.c
@@ -2639,9 +2639,7 @@ static int resize_stripes(struct r5conf *conf, int newsize)
 				if (ndisks[i].extra_page)
 					put_page(ndisks[i].extra_page);
 			kfree(ndisks);
-		} else {
-			kfree(conf->disks);
-			conf->disks = ndisks;
+			ndisks = NULL;
 		}
 	} else
 		err = -ENOMEM;
@@ -2685,8 +2683,20 @@ static int resize_stripes(struct r5conf *conf, int newsize)
 	}
 	/* critical section pass, GFP_NOIO no longer needed */
 
-	if (!err)
+	if (!err && ndisks) {
+		struct disk_info *old_disks = conf->disks;
+
+		spin_lock_irq(&conf->mddev->lock);
+		conf->disks = ndisks;
+		spin_unlock_irq(&conf->mddev->lock);
+		kfree(old_disks);
 		conf->pool_size = newsize;
+	} else if (ndisks) {
+		for (i = conf->pool_size; i < newsize; i++)
+			if (ndisks[i].extra_page)
+				put_page(ndisks[i].extra_page);
+		kfree(ndisks);
+	}
 	mutex_unlock(&conf->cache_size_mutex);
 
 	return err;
-- 
2.34.1
Keyboard shortcuts
hback out one level
jnext message in thread
kprevious message in thread
ldrill in
Escclose help / fold thread tree
?toggle this help