[PATCH] md/raid5: reject a per-device size smaller than one chunk

Zizhi Wo posted 1 patch 1 week, 1 day ago
drivers/md/raid5.c | 8 ++++++++
1 file changed, 8 insertions(+)
[PATCH] md/raid5: reject a per-device size smaller than one chunk
Posted by Zizhi Wo 1 week, 1 day ago
From: Zizhi Wo <wozizhi@huawei.com>

Both raid5_run() and raid5_resize() align the per-device size down to a
whole multiple of the chunk size:

	mddev->dev_sectors &= ~(chunk_sectors - 1);

Neither checks the result, so a size smaller than one chunk silently
becomes zero.

In raid5_run() that is harmless: the array size is derived from the same
zero, so the disk just ends up with no capacity.

In raid5_resize() it leaves the array inconsistent -- mddev->dev_sectors
becomes zero, while mddev->array_sectors and the gendisk capacity keep the
previous. A later raid4/raid5 -> raid0 takeover copies the zero into every
rdev->sectors (raid0_takeover_raid45()), so create_strip_zones() builds a
strip zone table whose zone_end is zero. Any read then passes
bio_check_eod() against the stale capacity and hits the BUG() in
find_zone():

 kernel BUG at drivers/md/raid0.c:318!
 Oops: invalid opcode: 0000 [#1] SMP KASAN NOPTI
 CPU: 45 UID: 0 PID: 1300 Comm: mdadm Not tainted 7.3.0-rc3+ #106 PREEMPT(full)
 Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS 1.17.0-4.fc41 04/01/2014
 RIP: 0010:raid0_make_request+0x10cb/0x16a0
 Call Trace:
   <TASK>
   md_handle_request+0x566/0xb40
   __submit_bio+0x2b2/0x600
   submit_bio_noacct_nocheck+0x509/0xb30
   block_read_full_folio+0x364/0x6d0
   filemap_read_folio+0xa2/0x200
   do_read_cache_folio+0x1b6/0x330
   read_part_sector+0xb6/0x2a0
   read_lba+0x17d/0x280
   efi_partition+0x2a6/0x2520
   bdev_disk_changed+0x6e0/0xfa0
   ......
   bdev_open+0x214/0xc40

Reject a size smaller than one chunk in both functions, so that a running
raid4/raid5 array always has mddev->dev_sectors >= one chunk.

The check is done where the value takes effect, not where it is assigned.
mddev->dev_sectors is written from several places, and none of them can
tell whether the value is usable: the chunk size may still change later.
raid5_run() and raid5_resize() are where every input is final, so one check
in each covers them all.

Fixes: eea136d69f9f ("md: fix buglet in RAID5 -> RAID0 conversion.")
Signed-off-by: Zizhi Wo <wozizhi@huawei.com>
---
 drivers/md/raid5.c | 8 ++++++++
 1 file changed, 8 insertions(+)

diff --git a/drivers/md/raid5.c b/drivers/md/raid5.c
index b91545ce090d..e0f1a8e7bd3a 100644
--- a/drivers/md/raid5.c
+++ b/drivers/md/raid5.c
@@ -8201,10 +8201,16 @@ static int raid5_run(struct mddev *mddev)
 		goto abort;
 	}
 
 	/* device size must be a multiple of chunk size */
 	mddev->dev_sectors &= ~((sector_t)mddev->chunk_sectors - 1);
+	if (!mddev->dev_sectors) {
+		pr_warn("md/raid:%s: device size is smaller than one chunk\n",
+			mdname(mddev));
+		ret = -EINVAL;
+		goto abort;
+	}
 	mddev->resync_max_sectors = mddev->dev_sectors;
 
 	if (mddev->degraded > dirty_parity_disks &&
 	    mddev->resync_offset != MaxSector) {
 		if (test_bit(MD_HAS_PPL, &mddev->flags))
@@ -8534,10 +8540,12 @@ static int raid5_resize(struct mddev *mddev, sector_t sectors)
 	struct r5conf *conf = mddev->private;
 
 	if (raid5_has_log(conf) || raid5_has_ppl(conf))
 		return -EINVAL;
 	sectors &= ~((sector_t)conf->chunk_sectors - 1);
+	if (!sectors)
+		return -EINVAL;
 	newsize = raid5_size(mddev, sectors, mddev->raid_disks);
 	if (mddev->external_size &&
 	    mddev->array_sectors > newsize)
 		return -EINVAL;
 
-- 
2.52.0
Re: [PATCH] md/raid5: reject a per-device size smaller than one chunk
Posted by yu kuai 1 week ago
在 2026/9/16 17:51, Zizhi Wo 写道:

> From: Zizhi Wo<wozizhi@huawei.com>
>
> Both raid5_run() and raid5_resize() align the per-device size down to a
> whole multiple of the chunk size:
>
> 	mddev->dev_sectors &= ~(chunk_sectors - 1);
>
> Neither checks the result, so a size smaller than one chunk silently
> becomes zero.
>
> In raid5_run() that is harmless: the array size is derived from the same
> zero, so the disk just ends up with no capacity.
>
> In raid5_resize() it leaves the array inconsistent -- mddev->dev_sectors
> becomes zero, while mddev->array_sectors and the gendisk capacity keep the
> previous. A later raid4/raid5 -> raid0 takeover copies the zero into every
> rdev->sectors (raid0_takeover_raid45()), so create_strip_zones() builds a
> strip zone table whose zone_end is zero. Any read then passes
> bio_check_eod() against the stale capacity and hits the BUG() in
> find_zone():
>
>   kernel BUG at drivers/md/raid0.c:318!
>   Oops: invalid opcode: 0000 [#1] SMP KASAN NOPTI
>   CPU: 45 UID: 0 PID: 1300 Comm: mdadm Not tainted 7.3.0-rc3+ #106 PREEMPT(full)
>   Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS 1.17.0-4.fc41 04/01/2014
>   RIP: 0010:raid0_make_request+0x10cb/0x16a0
>   Call Trace:
>     <TASK>
>     md_handle_request+0x566/0xb40
>     __submit_bio+0x2b2/0x600
>     submit_bio_noacct_nocheck+0x509/0xb30
>     block_read_full_folio+0x364/0x6d0
>     filemap_read_folio+0xa2/0x200
>     do_read_cache_folio+0x1b6/0x330
>     read_part_sector+0xb6/0x2a0
>     read_lba+0x17d/0x280
>     efi_partition+0x2a6/0x2520
>     bdev_disk_changed+0x6e0/0xfa0
>     ......
>     bdev_open+0x214/0xc40
>
> Reject a size smaller than one chunk in both functions, so that a running
> raid4/raid5 array always has mddev->dev_sectors >= one chunk.
>
> The check is done where the value takes effect, not where it is assigned.
> mddev->dev_sectors is written from several places, and none of them can
> tell whether the value is usable: the chunk size may still change later.
> raid5_run() and raid5_resize() are where every input is final, so one check
> in each covers them all.
>
> Fixes: eea136d69f9f ("md: fix buglet in RAID5 -> RAID0 conversion.")
> Signed-off-by: Zizhi Wo<wozizhi@huawei.com>
> ---
>   drivers/md/raid5.c | 8 ++++++++
>   1 file changed, 8 insertions(+)
Applied to md-7.4

-- 
Thanks,
Kuai