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

From: ghuicao

Date: Thu Aug 27 2026 - 02:06:55 EST


From: Cao Guanghui <caoguanghui@xxxxxxxxxx>

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@xxxxxxxxxxxxxxx
Signed-off-by: Cao Guanghui <caoguanghui@xxxxxxxxxx>
---

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