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

[email protected]
Newsgroups gmane.linux.kernel.stable,gmane.linux.raid,gmane.linux.kernel
Message-ID <[email protected]>
From: Cao Guanghui <[email protected]>

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: [email protected]
Signed-off-by: Cao Guanghui <[email protected]>
---

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
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.