From 68c51a7b1b808e37900726d2cb3d077a71a2ac27 Mon Sep 17 00:00:00 2001 From: Zizhi Wo Date: Mon, 17 Aug 2026 11:33:39 +0800 Subject: [PATCH 01/25] md/raid10: annotate lockless access to array_freeze_pending conf->array_freeze_pending is updated under conf->resync_lock in freeze_array(), but read locklessly in allow_barrier(). Annotate these accesses with READ_ONCE()/WRITE_ONCE() to document the intentional lockless access and to prevent load/store tearing or fusing by the compiler. No functional change. Signed-off-by: Zizhi Wo Link: https://patch.msgid.link/20260817033340.418119-2-wozizhi@huawei.com Signed-off-by: Yu Kuai --- drivers/md/raid10.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/md/raid10.c b/drivers/md/raid10.c index 1093c798d9dd..65acc240f7ca 100644 --- a/drivers/md/raid10.c +++ b/drivers/md/raid10.c @@ -1037,7 +1037,7 @@ static void wait_barrier(struct r10conf *conf) static void allow_barrier(struct r10conf *conf) { if ((atomic_dec_and_test(&conf->nr_pending)) || - (conf->array_freeze_pending)) + READ_ONCE(conf->array_freeze_pending)) wake_up_barrier(conf); } @@ -1056,12 +1056,12 @@ static void freeze_array(struct r10conf *conf, int extra) * we continue. */ write_seqlock_irq(&conf->resync_lock); - conf->array_freeze_pending++; + WRITE_ONCE(conf->array_freeze_pending, conf->array_freeze_pending + 1); WRITE_ONCE(conf->barrier, conf->barrier + 1); conf->nr_waiting++; wait_event_barrier_cmd(conf, atomic_read(&conf->nr_pending) == conf->nr_queued + extra, flush_pending_writes(conf)); - conf->array_freeze_pending--; + WRITE_ONCE(conf->array_freeze_pending, conf->array_freeze_pending - 1); write_sequnlock_irq(&conf->resync_lock); } From f00eb0ff45793383d5a62db2d2aea63ada0ccdbd Mon Sep 17 00:00:00 2001 From: Zizhi Wo Date: Mon, 17 Aug 2026 11:33:40 +0800 Subject: [PATCH 02/25] md/raid10: fix missing wakeup in wait_barrier_nolock Fuzz testing triggered a hung raid10d thread: INFO: task md0_raid10 blocked for more than 120 seconds. Call Trace: raid10d md_thread kthread wait_barrier_nolock() speculatively increments nr_pending. If a concurrent writer changes resync_lock's sequence, it rolls the count back with atomic_dec_and_test(). The rollback currently wakes wait_barrier only when nr_pending reaches zero. freeze_array() can be waiting for nr_pending == nr_queued + extra while array_freeze_pending is set. If the rollback changes nr_pending from 2 to 1, the condition becomes true but no wakeup is sent, leaving raid10d blocked indefinitely. Also wake wait_barrier when array_freeze_pending is set, matching allow_barrier(). Fixes: b9b083f9044a ("md/raid10: convert resync_lock to use seqlock") Signed-off-by: Zizhi Wo Reviewed-by: Abd-Alrhman Masalkhi Link: https://patch.msgid.link/20260817033340.418119-3-wozizhi@huawei.com Signed-off-by: Yu Kuai --- drivers/md/raid10.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/md/raid10.c b/drivers/md/raid10.c index 65acc240f7ca..560be6338519 100644 --- a/drivers/md/raid10.c +++ b/drivers/md/raid10.c @@ -1010,7 +1010,8 @@ static bool wait_barrier_nolock(struct r10conf *conf) if (!read_seqretry(&conf->resync_lock, seq)) return true; - if (atomic_dec_and_test(&conf->nr_pending)) + if (atomic_dec_and_test(&conf->nr_pending) || + READ_ONCE(conf->array_freeze_pending)) wake_up_barrier(conf); return false; From 1a33a379e67853d6d778e6e80b214bdf1208f6f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Jean?= Date: Thu, 20 Aug 2026 21:07:03 +0000 Subject: [PATCH 03/25] md: reject v0.90 arrays with too many devices MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The v0.90 superblock has room for MD_SB_DISKS device descriptors, but super_90_load() only rejects nonpositive raid_disks values. A crafted superblock can therefore copy a larger value into mddev->raid_disks. When metadata is next updated, super_90_sync() walks sb->disks[] up to that count and reads and writes beyond the 4 KiB superblock buffer. A checksum-valid RAID1 member advertising 64 devices can be assembled by mdadm --incremental --run. The first write to the array then produces out-of-bounds writes from super_90_sync() under KASAN. Reject device counts that cannot be represented by the v0.90 format while loading the superblock. Assisted-by: Codex:gpt-5 Signed-off-by: Jérémy Jean Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Link: https://patch.msgid.link/20260820210703.3035608-2-Jeremy.Jean@oss.cyber.gouv.fr Signed-off-by: Yu Kuai --- drivers/md/md.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/md/md.c b/drivers/md/md.c index 680b34a63cb3..154ee5a65cb7 100644 --- a/drivers/md/md.c +++ b/drivers/md/md.c @@ -1393,7 +1393,7 @@ static int super_90_load(struct md_rdev *rdev, struct md_rdev *refdev, int minor goto abort; } - if (sb->raid_disks <= 0) + if (sb->raid_disks <= 0 || sb->raid_disks > MD_SB_DISKS) goto abort; if (md_csum_fold(calc_sb_csum(sb)) != md_csum_fold(sb->sb_csum)) { From 5028ceee173bfcf844469cfb0d1c151d3f6d0296 Mon Sep 17 00:00:00 2001 From: Cao Guanghui Date: Thu, 27 Aug 2026 16:03:40 +0800 Subject: [PATCH 04/25] md/raid5: track disks array size to fix extra_page leak on error paths free_conf() iterates conf->pool_size entries to free extra_page allocations, but pool_size may not reflect the actual size of the conf->disks array. Two scenarios cause a mismatch: 1. setup_conf() early abort: pool_size is 0 (not yet set by grow_stripes) but conf->disks has max_disks entries with extra_page allocated. The loop iterates 0 times, leaking all pages. 2. resize_stripes() Step 4 failure: conf->disks was replaced with a newsize-entry array in Step 3, but pool_size is only updated on success. The loop iterates pool_size (old, smaller value) times, leaking (newsize - pool_size) pages. Add a dedicated disks_cnt field to track the actual number of entries in conf->disks. Set it immediately after each allocation or replacement (in setup_conf and resize_stripes Step 3, where the array is safely stalled with no concurrent access), and use it in free_conf() instead of pool_size. This leaves pool_size untouched, preserving the check_reshape() retry behavior that depends on pool_size only being updated on full success. Fixes: d7bd398e97f2 ("md/r5cache: handle alloc_page failure") Cc: stable@vger.kernel.org Signed-off-by: Cao Guanghui Link: https://patch.msgid.link/20260827080341.112296-1-ghuicao@163.com Signed-off-by: Yu Kuai --- drivers/md/raid5.c | 5 +++-- drivers/md/raid5.h | 1 + 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/drivers/md/raid5.c b/drivers/md/raid5.c index b91545ce090d..e5838bf516e3 100644 --- a/drivers/md/raid5.c +++ b/drivers/md/raid5.c @@ -2642,6 +2642,7 @@ static int resize_stripes(struct r5conf *conf, int newsize) } else { kfree(conf->disks); conf->disks = ndisks; + conf->disks_cnt = newsize; } } else err = -ENOMEM; @@ -7552,7 +7553,7 @@ static void free_conf(struct r5conf *conf) free_thread_groups(conf); shrink_stripes(conf); raid5_free_percpu(conf); - for (i = 0; i < conf->pool_size; i++) + for (i = 0; i < conf->disks_cnt; i++) if (conf->disks[i].extra_page) put_page(conf->disks[i].extra_page); kfree(conf->disks); @@ -7733,7 +7734,7 @@ static struct r5conf *setup_conf(struct mddev *mddev) if (!conf->disks) goto abort; - + conf->disks_cnt = max_disks; for (i = 0; i < max_disks; i++) { conf->disks[i].extra_page = alloc_page(GFP_KERNEL); if (!conf->disks[i].extra_page) diff --git a/drivers/md/raid5.h b/drivers/md/raid5.h index cb5feae04db2..94b3e2c947ba 100644 --- a/drivers/md/raid5.h +++ b/drivers/md/raid5.h @@ -667,6 +667,7 @@ struct r5conf { unsigned long cache_state; struct shrinker *shrinker; int pool_size; /* number of disks in stripeheads in pool */ + int disks_cnt; /* number of entries in disks[] array */ spinlock_t device_lock; struct disk_info *disks; struct bio_set bio_split; From 41742e35268d6a45161b187f51f42bc2ade14d0d Mon Sep 17 00:00:00 2001 From: Cao Guanghui Date: Thu, 27 Aug 2026 16:03:41 +0800 Subject: [PATCH 05/25] md/raid5: fix NULL pointer dereference in raid5_free_percpu If cpuhp_state_add_instance() fails in raid5_alloc_percpu() (e.g., the startup callback raid456_cpu_up_prepare fails due to an allocation failure), conf->node is never added to the cpuhp instance list and its pprev remains NULL (from kzalloc initialization). When setup_conf() then jumps to abort, free_conf() calls raid5_free_percpu(), which checks conf->percpu (non-NULL, since it was allocated before the cpuhp failure) and proceeds to call cpuhp_state_remove_instance(). This calls hlist_del() on the unhashed node, which dereferences node->pprev (NULL), causing a kernel panic. Guard the removal with hlist_unhashed_lockless() so that the cpuhp instance is only removed if it was actually added. The lockless variant uses READ_ONCE() for the pprev read, which is safe here because the actual removal is synchronized by cpuhp_state_mutex inside cpuhp_state_remove_instance(). Fixes: 29c6d1bbd7a2 ("md/raid5: Convert to hotplug state machine") Cc: stable@vger.kernel.org Signed-off-by: Cao Guanghui Link: https://patch.msgid.link/20260827080341.112296-2-ghuicao@163.com Signed-off-by: Yu Kuai --- drivers/md/raid5.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/md/raid5.c b/drivers/md/raid5.c index e5838bf516e3..bc5f4b048eed 100644 --- a/drivers/md/raid5.c +++ b/drivers/md/raid5.c @@ -7539,7 +7539,8 @@ static void raid5_free_percpu(struct r5conf *conf) if (!conf->percpu) return; - cpuhp_state_remove_instance(CPUHP_MD_RAID5_PREPARE, &conf->node); + if (!hlist_unhashed_lockless(&conf->node)) + cpuhp_state_remove_instance(CPUHP_MD_RAID5_PREPARE, &conf->node); free_percpu(conf->percpu); } From cf5963d6781ddee4c59a61debc459fcc7580f7b6 Mon Sep 17 00:00:00 2001 From: Chandradhar Kumar Date: Mon, 7 Sep 2026 18:59:36 +0530 Subject: [PATCH 06/25] md/raid0: validate device count before allocating devlist create_strip_zones() allocates conf->devlist based on mddev->raid_disks before verifying that number of devices matches raid_disks. Move the existing device count validation before allocation to reject invalid configurations before attempting a potentially excessive allocation. Fixes: 078d1d8e688d ("md/raid0: use kvzalloc/kvfree for strip_zone and devlist allocations") Reported-by: syzbot+a32ff75e417c0f49a8e9@syzkaller.appspotmail.com Closes: https://syzbot.org/bug?extid=a32ff75e417c0f49a8e9 Signed-off-by: Chandradhar Kumar Link: https://patch.msgid.link/20260907132936.6677-1-chandradhar.2003@gmail.com Signed-off-by: Yu Kuai --- drivers/md/raid0.c | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/drivers/md/raid0.c b/drivers/md/raid0.c index 35e103f0c2c3..453e8abda22b 100644 --- a/drivers/md/raid0.c +++ b/drivers/md/raid0.c @@ -79,7 +79,10 @@ static int create_strip_zones(struct mddev *mddev, struct r0conf **private_conf) *private_conf = ERR_PTR(-ENOMEM); if (!conf) return -ENOMEM; + + cnt = 0; rdev_for_each(rdev1, mddev) { + cnt++; pr_debug("md/raid0:%s: looking at %pg\n", mdname(mddev), rdev1->bdev); @@ -144,6 +147,14 @@ static int create_strip_zones(struct mddev *mddev, struct r0conf **private_conf) } err = -ENOMEM; + + if (cnt != mddev->raid_disks) { + pr_warn("md/raid0:%s: too few disks (%d of %d) - aborting!\n", + mdname(mddev), cnt, mddev->raid_disks); + err = -EINVAL; + goto abort; + } + conf->strip_zone = kvzalloc_objs(struct strip_zone, conf->nr_strip_zones); if (!conf->strip_zone) goto abort; @@ -200,11 +211,6 @@ static int create_strip_zones(struct mddev *mddev, struct r0conf **private_conf) smallest = rdev1; cnt++; } - if (cnt != mddev->raid_disks) { - pr_warn("md/raid0:%s: too few disks (%d of %d) - aborting!\n", - mdname(mddev), cnt, mddev->raid_disks); - goto abort; - } zone->nb_dev = cnt; zone->zone_end = smallest->sectors * cnt; From 38c795105992bbaa3418dd39cbff7348ad17537c Mon Sep 17 00:00:00 2001 From: Martin Wilck Date: Mon, 7 Sep 2026 17:51:03 +0200 Subject: [PATCH 07/25] md/raid5: validate payload size before calculating payload_len Commit b0cc3ae97e89 ("md/raid5: validate payload size before accessing journal metadata") introduced a bounds check to verify that a given payload fits into the current metadata block. In order to calculate the payload size, it has to access the members payload->header.type and payload->size, the offset of which may already be past the end of the metadata block, because the loop condition only checks that the first byte of the payload is inside, and the payload entries have variable sizes. Later on, payload->checksum[0] and payload->checksum[1] are accessed without verifying that payload->size is large enough. Add another check to make sure that payload->size can be safely accessed, and make sure that the checksum fields are accessible. This issue has been found by AI (gemma4, Gemini) during a backport review. Cc: Junrui Luo Cc: stable@vger.kernel.org Fixes: b0cc3ae97e89 ("md/raid5: validate payload size before accessing journal metadata") Signed-off-by: Martin Wilck Link: https://patch.msgid.link/20260907155103.2325511-1-mwilck@suse.com Signed-off-by: Yu Kuai --- drivers/md/raid5-cache.c | 34 ++++++++++++++++++++++++++-------- 1 file changed, 26 insertions(+), 8 deletions(-) diff --git a/drivers/md/raid5-cache.c b/drivers/md/raid5-cache.c index 7b7546bfa21f..62460e231f02 100644 --- a/drivers/md/raid5-cache.c +++ b/drivers/md/raid5-cache.c @@ -2001,27 +2001,36 @@ r5l_recovery_verify_data_checksum_for_mb(struct r5l_log *log, if (!page) return -ENOMEM; - while (mb_offset < le32_to_cpu(mb->meta_size)) { + while (mb_offset + sizeof(struct r5l_payload_header) <= le32_to_cpu(mb->meta_size)) { + u32 payload_size; sector_t payload_len; payload = (void *)mb + mb_offset; payload_flush = (void *)mb + mb_offset; if (le16_to_cpu(payload->header.type) == R5LOG_PAYLOAD_DATA) { + if (mb_offset + sizeof(struct r5l_payload_data_parity) + > le32_to_cpu(mb->meta_size)) + goto mismatch; + payload_size = le32_to_cpu(payload->size) >> (PAGE_SHIFT - 9); payload_len = sizeof(struct r5l_payload_data_parity) + - (sector_t)sizeof(__le32) * - (le32_to_cpu(payload->size) >> (PAGE_SHIFT - 9)); - if (mb_offset + payload_len > le32_to_cpu(mb->meta_size)) + (sector_t)sizeof(__le32) * payload_size; + if (mb_offset + payload_len > le32_to_cpu(mb->meta_size) || + payload_size < 1) goto mismatch; if (r5l_recovery_verify_data_checksum( log, ctx, page, log_offset, payload->checksum[0]) < 0) goto mismatch; } else if (le16_to_cpu(payload->header.type) == R5LOG_PAYLOAD_PARITY) { + if (mb_offset + sizeof(struct r5l_payload_data_parity) + > le32_to_cpu(mb->meta_size)) + goto mismatch; + payload_size = le32_to_cpu(payload->size) >> (PAGE_SHIFT - 9); payload_len = sizeof(struct r5l_payload_data_parity) + - (sector_t)sizeof(__le32) * - (le32_to_cpu(payload->size) >> (PAGE_SHIFT - 9)); - if (mb_offset + payload_len > le32_to_cpu(mb->meta_size)) + (sector_t)sizeof(__le32) * payload_size; + if (mb_offset + payload_len > le32_to_cpu(mb->meta_size) || + payload_size < conf->max_degraded) goto mismatch; if (r5l_recovery_verify_data_checksum( log, ctx, page, log_offset, @@ -2035,6 +2044,9 @@ r5l_recovery_verify_data_checksum_for_mb(struct r5l_log *log, payload->checksum[1]) < 0) goto mismatch; } else if (le16_to_cpu(payload->header.type) == R5LOG_PAYLOAD_FLUSH) { + if (mb_offset + sizeof(struct r5l_payload_flush) + > le32_to_cpu(mb->meta_size)) + goto mismatch; payload_len = sizeof(struct r5l_payload_flush) + (sector_t)le32_to_cpu(payload_flush->size); if (mb_offset + payload_len > le32_to_cpu(mb->meta_size)) @@ -2096,7 +2108,7 @@ r5c_recovery_analyze_meta_block(struct r5l_log *log, mb_offset = sizeof(struct r5l_meta_block); log_offset = r5l_ring_add(log, ctx->pos, BLOCK_SECTORS); - while (mb_offset < le32_to_cpu(mb->meta_size)) { + while (mb_offset + sizeof(struct r5l_payload_header) < le32_to_cpu(mb->meta_size)) { sector_t payload_len; int dd; @@ -2106,6 +2118,9 @@ r5c_recovery_analyze_meta_block(struct r5l_log *log, if (le16_to_cpu(payload->header.type) == R5LOG_PAYLOAD_FLUSH) { int i, count; + if (mb_offset + sizeof(struct r5l_payload_flush) > + le32_to_cpu(mb->meta_size)) + return -EINVAL; payload_len = sizeof(struct r5l_payload_flush) + (sector_t)le32_to_cpu(payload_flush->size); if (mb_offset + payload_len > @@ -2130,6 +2145,9 @@ r5c_recovery_analyze_meta_block(struct r5l_log *log, } /* DATA or PARITY payload */ + if (mb_offset + sizeof(struct r5l_payload_data_parity) > + le32_to_cpu(mb->meta_size)) + return -EINVAL; payload_len = sizeof(struct r5l_payload_data_parity) + (sector_t)sizeof(__le32) * (le32_to_cpu(payload->size) >> (PAGE_SHIFT - 9)); From 0669c7dbd1d375f279e607a24addc2f1dae2d4f3 Mon Sep 17 00:00:00 2001 From: Yogesh Gaur Date: Thu, 10 Sep 2026 18:41:03 +0530 Subject: [PATCH 08/25] md/raid5: don't BUG() on an inconsistent reshape state from the superblock raid5_run() branches on mddev->reshape_position. When it is MaxSector -- no reshape in progress -- the else branch asserts that nothing else describes one: BUG_ON(mddev->level != mddev->new_level); BUG_ON(mddev->layout != mddev->new_layout); BUG_ON(mddev->chunk_sectors != mddev->new_chunk_sectors); BUG_ON(mddev->delta_disks != 0); Nothing enforces that invariant. Both superblock validators copy the reshape fields straight off disk without cross-checking them against each other: super_1_validate() takes reshape_position, delta_disks, new_level, new_layout and new_chunk from the superblock whenever MD_FEATURE_RESHAPE_ACTIVE is set, and super_90_validate() does the same for minor version 91. A superblock that sets the reshape feature while leaving reshape_position at the MaxSector sentinel therefore reaches the else branch with a non-zero delta_disks, and assembling the array takes the machine down: kernel BUG at drivers/md/raid5.c:8117! Oops: invalid opcode: 0000 [#1] SMP KASAN NOPTI RIP: 0010:raid5_run+0x11a7/0x1670 drivers/md/raid5.c:8117 Call Trace: md_run+0xc2f/0x2510 drivers/md/md.c:6779 do_md_run+0x36/0x660 drivers/md/md.c:6880 array_state_store+0x9c5/0xcf0 drivers/md/md.c:4765 md_attr_store+0x1c5/0x330 drivers/md/md.c:6158 sysfs_kf_write+0xf2/0x150 fs/sysfs/file.c:145 kernfs_fop_write_iter+0x3e0/0x5f0 fs/kernfs/file.c:345 This is reachable by anyone who can present an md superblock, so BUG() is the wrong response. Refuse to start the array instead, the way the reshape_position != MaxSector branch a few lines above already refuses a reshape it cannot resume. Nothing has been allocated at this point -- the journal-and-bitmap check just above returns -EINVAL the same way -- so there is nothing to unwind. Reported-by: syzbot+1f5a7de91d547763f4c8@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=1f5a7de91d547763f4c8 Fixes: 91adb56473fe ("md/raid5: refactor raid5 "run"") Assisted-by: LLM Signed-off-by: Yogesh Gaur Link: https://patch.msgid.link/20260910131103.988-1-yogeshgaur.83@gmail.com Signed-off-by: Yu Kuai --- drivers/md/raid5.c | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/drivers/md/raid5.c b/drivers/md/raid5.c index bc5f4b048eed..63e2bc9f329e 100644 --- a/drivers/md/raid5.c +++ b/drivers/md/raid5.c @@ -8112,11 +8112,17 @@ static int raid5_run(struct mddev *mddev) } pr_debug("md/raid:%s: reshape will continue\n", mdname(mddev)); /* OK, we should be able to continue; */ - } else { - BUG_ON(mddev->level != mddev->new_level); - BUG_ON(mddev->layout != mddev->new_layout); - BUG_ON(mddev->chunk_sectors != mddev->new_chunk_sectors); - BUG_ON(mddev->delta_disks != 0); + } else if (mddev->level != mddev->new_level || + mddev->layout != mddev->new_layout || + mddev->chunk_sectors != mddev->new_chunk_sectors || + mddev->delta_disks != 0) { + /* No reshape is in progress, but the array describes one. + * The superblock validators do not cross-check these against + * reshape_position, so this is reachable from disk. + */ + pr_warn("md/raid:%s: inconsistent reshape state - aborting.\n", + mdname(mddev)); + return -EINVAL; } if (test_bit(MD_HAS_JOURNAL, &mddev->flags) && From 0d9bbbb6fafa22659ccd4d7ab94329a43c2632d0 Mon Sep 17 00:00:00 2001 From: Chen Cheng Date: Fri, 11 Sep 2026 12:15:21 +0800 Subject: [PATCH 09/25] md/raid5: reject raid4/5 arrays with too few disks raid4 and raid5 require at least two disks, while raid6 requires at least four disks. setup_conf() only rejects raid6 arrays with fewer than four disks. As a result, an invalid raid4 or raid5 array can be assembled with no data disks and later reach raid5_set_limits(). Reject such arrays before the r5conf is created. This prevents the invalid geometry from reaching queue limit setup, where roundup_pow_of_two() can be called with a zero stripe size. Reported-by: syzbot+de94ddbfff0c9e6fe030@syzkaller.appspotmail.com Closes: https://lore.kernel.org/r/6a778c82.01d0871a.3a0d52.006a.GAE@google.com/ Fixes: f63f17350e53 ("md/raid5: use the atomic queue limit update APIs") Signed-off-by: Chen Cheng Link: https://patch.msgid.link/20260911041521.2433222-1-chencheng@fnnas.com Signed-off-by: Yu Kuai --- drivers/md/raid5.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/drivers/md/raid5.c b/drivers/md/raid5.c index 63e2bc9f329e..d5a8428778e3 100644 --- a/drivers/md/raid5.c +++ b/drivers/md/raid5.c @@ -7659,9 +7659,11 @@ static struct r5conf *setup_conf(struct mddev *mddev) mdname(mddev), mddev->new_layout); return ERR_PTR(-EIO); } - if (mddev->new_level == 6 && mddev->raid_disks < 4) { - pr_warn("md/raid:%s: not enough configured devices (%d, minimum 4)\n", - mdname(mddev), mddev->raid_disks); + if ((mddev->new_level != 6 && mddev->raid_disks < 2) || + (mddev->new_level == 6 && mddev->raid_disks < 4)) { + pr_warn("md/raid:%s: not enough configured devices (%d, minimum %d)\n", + mdname(mddev), mddev->raid_disks, + mddev->new_level == 6 ? 4 : 2); return ERR_PTR(-EINVAL); } From eba5f518701b1c7f2d883504f89c41611bd2de46 Mon Sep 17 00:00:00 2001 From: Zhihao Cheng Date: Tue, 15 Sep 2026 16:17:31 +0800 Subject: [PATCH 10/25] md/raid5: setup_conf: Fix null-ptr-def in error handling path For raid5 setup_conf, if an error(eg. bioset_init fails) happens before 'conf->mddev = mddev', following error handling path could trigger a null-ptr-def problem: free_conf log_exit raid5_has_ppl test_bit(MD_HAS_PPL, &conf->mddev->flags) // conf->mddev is NULL ! Fix it by initializing conf->mddev in advance. Fixes: 3418d036c81d ("raid5-ppl: Partial Parity Log write logging implementation") Signed-off-by: Zhihao Cheng Link: https://patch.msgid.link/20260915081731.122933-1-chengzhihao1@huawei.com Signed-off-by: Yu Kuai --- drivers/md/raid5.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/md/raid5.c b/drivers/md/raid5.c index d5a8428778e3..45b00c990421 100644 --- a/drivers/md/raid5.c +++ b/drivers/md/raid5.c @@ -7679,6 +7679,7 @@ static struct r5conf *setup_conf(struct mddev *mddev) if (conf == NULL) goto abort; + conf->mddev = mddev; #if PAGE_SIZE != DEFAULT_STRIPE_SIZE conf->stripe_size = DEFAULT_STRIPE_SIZE; conf->stripe_shift = ilog2(DEFAULT_STRIPE_SIZE) - 9; @@ -7747,7 +7748,6 @@ static struct r5conf *setup_conf(struct mddev *mddev) ret = bioset_init(&conf->bio_split, BIO_POOL_SIZE, 0, 0); if (ret) goto abort; - conf->mddev = mddev; ret = -ENOMEM; conf->stripe_hashtbl = kzalloc(PAGE_SIZE, GFP_KERNEL); From 370ca0760b18c64592a258bcb057ff0a2531aabf Mon Sep 17 00:00:00 2001 From: Zizhi Wo Date: Wed, 16 Sep 2026 17:51:36 +0800 Subject: [PATCH 11/25] md/raid5: reject a per-device size smaller than one chunk 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 Call Trace: raid0_make_request md_handle_request submit_bio_noacct_nocheck block_read_full_folio read_part_sector bdev_disk_changed bdev_open 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.") Reported-by: syzbot+50f9bc7ab8eb224b7629@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=50f9bc7ab8eb224b7629 Signed-off-by: Zizhi Wo Link: https://patch.msgid.link/20260916095137.1157801-1-wozizhi@huaweicloud.com Signed-off-by: Yu Kuai --- drivers/md/raid5.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/drivers/md/raid5.c b/drivers/md/raid5.c index 45b00c990421..fbc356e44245 100644 --- a/drivers/md/raid5.c +++ b/drivers/md/raid5.c @@ -8213,6 +8213,12 @@ static int raid5_run(struct mddev *mddev) /* 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 && @@ -8546,6 +8552,8 @@ static int raid5_resize(struct mddev *mddev, sector_t sectors) 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) From 0a7465dcb43fbf31631ca2c346401adce4cd85d9 Mon Sep 17 00:00:00 2001 From: Li Youhong Date: Thu, 27 Aug 2026 15:56:18 +0800 Subject: [PATCH 12/25] md: add rdev_record_write_error() helper The WriteErrorSeen + WantReplacement + MD_RECOVERY_NEEDED sequence is duplicated across raid1, raid10 and raid5. Factor it into a small inline helper for readability. No functional change. Signed-off-by: Li Youhong Link: https://patch.msgid.link/20260827075620.3599227-2-dayou5941@163.com Signed-off-by: Yu Kuai --- drivers/md/md.h | 7 +++++++ drivers/md/raid1.c | 16 +++------------- drivers/md/raid10.c | 21 ++++----------------- drivers/md/raid5.c | 5 +---- 4 files changed, 15 insertions(+), 34 deletions(-) diff --git a/drivers/md/md.h b/drivers/md/md.h index b6d2e8929a0f..6440da292105 100644 --- a/drivers/md/md.h +++ b/drivers/md/md.h @@ -989,6 +989,13 @@ static inline void rdev_dec_pending(struct md_rdev *rdev, struct mddev *mddev) } } +static inline void rdev_record_write_error(struct md_rdev *rdev) +{ + set_bit(WriteErrorSeen, &rdev->flags); + if (!test_and_set_bit(WantReplacement, &rdev->flags)) + set_bit(MD_RECOVERY_NEEDED, &rdev->mddev->recovery); +} + static inline int mddev_is_clustered(struct mddev *mddev) { return mddev->cluster_info && mddev->bitmap_info.nodes > 1; diff --git a/drivers/md/raid1.c b/drivers/md/raid1.c index f0646fb24371..9d8441348f14 100644 --- a/drivers/md/raid1.c +++ b/drivers/md/raid1.c @@ -483,10 +483,7 @@ static void raid1_end_write_request(struct bio *bio) * 'one mirror IO has finished' event handler: */ if (bio->bi_status && !ignore_error) { - set_bit(WriteErrorSeen, &rdev->flags); - if (!test_and_set_bit(WantReplacement, &rdev->flags)) - set_bit(MD_RECOVERY_NEEDED, & - conf->mddev->recovery); + rdev_record_write_error(rdev); if (test_bit(FailFast, &rdev->flags) && (bio->bi_opf & MD_FAILFAST) && @@ -2067,10 +2064,7 @@ static void end_sync_write(struct bio *bio) if (bio->bi_status) { abort_sync_write(mddev, r1_bio); - set_bit(WriteErrorSeen, &rdev->flags); - if (!test_and_set_bit(WantReplacement, &rdev->flags)) - set_bit(MD_RECOVERY_NEEDED, & - mddev->recovery); + rdev_record_write_error(rdev); set_bit(R1BIO_WriteError, &r1_bio->state); } else if (rdev_has_badblock(rdev, r1_bio->sector, r1_bio->sectors) && !rdev_has_badblock(conf->mirrors[r1_bio->read_disk].rdev, @@ -2088,11 +2082,7 @@ static int r1_sync_page_io(struct md_rdev *rdev, sector_t sector, /* success */ return 1; if (rw == REQ_OP_WRITE) { - set_bit(WriteErrorSeen, &rdev->flags); - if (!test_and_set_bit(WantReplacement, - &rdev->flags)) - set_bit(MD_RECOVERY_NEEDED, & - rdev->mddev->recovery); + rdev_record_write_error(rdev); } /* need to record an error - either for the block or the device */ rdev_set_badblocks(rdev, sector, sectors, 0); diff --git a/drivers/md/raid10.c b/drivers/md/raid10.c index 560be6338519..a0ce86ea5b34 100644 --- a/drivers/md/raid10.c +++ b/drivers/md/raid10.c @@ -496,10 +496,7 @@ static void raid10_end_write_request(struct bio *bio) */ md_error(rdev->mddev, rdev); else { - set_bit(WriteErrorSeen, &rdev->flags); - if (!test_and_set_bit(WantReplacement, &rdev->flags)) - set_bit(MD_RECOVERY_NEEDED, - &rdev->mddev->recovery); + rdev_record_write_error(rdev); dec_rdev = 0; if (test_bit(FailFast, &rdev->flags) && @@ -2300,10 +2297,7 @@ static void end_sync_write(struct bio *bio) if (repl) md_error(mddev, rdev); else { - set_bit(WriteErrorSeen, &rdev->flags); - if (!test_and_set_bit(WantReplacement, &rdev->flags)) - set_bit(MD_RECOVERY_NEEDED, - &rdev->mddev->recovery); + rdev_record_write_error(rdev); set_bit(R10BIO_WriteError, &r10_bio->state); } } else if (rdev_has_badblock(rdev, r10_bio->devs[slot].addr, @@ -2501,11 +2495,7 @@ static void fix_recovery_read_error(struct r10bio *r10_bio) pages[idx], REQ_OP_WRITE, false); if (!ok) { - set_bit(WriteErrorSeen, &rdev->flags); - if (!test_and_set_bit(WantReplacement, - &rdev->flags)) - set_bit(MD_RECOVERY_NEEDED, - &rdev->mddev->recovery); + rdev_record_write_error(rdev); } } if (!ok) { @@ -2586,10 +2576,7 @@ static int r10_sync_page_io(struct md_rdev *rdev, sector_t sector, /* success */ return 1; if (op == REQ_OP_WRITE) { - set_bit(WriteErrorSeen, &rdev->flags); - if (!test_and_set_bit(WantReplacement, &rdev->flags)) - set_bit(MD_RECOVERY_NEEDED, - &rdev->mddev->recovery); + rdev_record_write_error(rdev); } /* need to record an error - either for the block or the device */ rdev_set_badblocks(rdev, sector, sectors, 0); diff --git a/drivers/md/raid5.c b/drivers/md/raid5.c index fbc356e44245..c315e99d0f30 100644 --- a/drivers/md/raid5.c +++ b/drivers/md/raid5.c @@ -2889,11 +2889,8 @@ static void raid5_end_write_request(struct bio *bi) set_bit(R5_MadeGoodRepl, &sh->dev[i].flags); } else { if (bi->bi_status) { - set_bit(WriteErrorSeen, &rdev->flags); + rdev_record_write_error(rdev); set_bit(R5_WriteError, &sh->dev[i].flags); - if (!test_and_set_bit(WantReplacement, &rdev->flags)) - set_bit(MD_RECOVERY_NEEDED, - &rdev->mddev->recovery); } else if (rdev_has_badblock(rdev, sh->sector, RAID5_STRIPE_SECTORS(conf))) { set_bit(R5_MadeGood, &sh->dev[i].flags); From c43d40eec432f9cc58c29a18bf00fe6b3abd5e18 Mon Sep 17 00:00:00 2001 From: Li Youhong Date: Thu, 27 Aug 2026 15:56:19 +0800 Subject: [PATCH 13/25] md/raid1-10: add rdev_bb_block_sectors() helper raid1 and raid10 narrow_write_error() use the same logic to compute the badblocks-aligned block size. Factor it into an inline helper in raid1-10.c. No functional change. Signed-off-by: Li Youhong Link: https://patch.msgid.link/20260827075620.3599227-3-dayou5941@163.com Signed-off-by: Yu Kuai --- drivers/md/raid1-10.c | 10 ++++++++++ drivers/md/raid1.c | 7 +------ drivers/md/raid10.c | 7 +------ 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/drivers/md/raid1-10.c b/drivers/md/raid1-10.c index 3b0e230692ba..c7ddf136771d 100644 --- a/drivers/md/raid1-10.c +++ b/drivers/md/raid1-10.c @@ -155,6 +155,16 @@ static inline bool raid1_add_bio_to_plug(struct mddev *mddev, struct bio *bio, return true; } +/* Block size used when narrowing a write error down to badblocks granularity. */ +static inline int rdev_bb_block_sectors(struct md_rdev *rdev) +{ + int lbs = bdev_logical_block_size(rdev->bdev) >> 9; + + if (rdev->badblocks.shift < 0) + return lbs; + return roundup(1 << rdev->badblocks.shift, lbs); +} + /* * current->bio_list will be set under submit_bio() context, in this case bitmap * io will be added to the list and wait for current io submission to finish, diff --git a/drivers/md/raid1.c b/drivers/md/raid1.c index 9d8441348f14..8a40c69d70a3 100644 --- a/drivers/md/raid1.c +++ b/drivers/md/raid1.c @@ -2465,16 +2465,11 @@ static void narrow_write_error(struct r1bio *r1_bio, int i) * We currently own a reference on the rdev. */ - int block_sectors, lbs = bdev_logical_block_size(rdev->bdev) >> 9; + int block_sectors = rdev_bb_block_sectors(rdev); sector_t sector; int sectors; int sect_to_write = r1_bio->sectors; - if (rdev->badblocks.shift < 0) - block_sectors = lbs; - else - block_sectors = roundup(1 << rdev->badblocks.shift, lbs); - sector = r1_bio->sector; sectors = ((sector + block_sectors) & ~(sector_t)(block_sectors - 1)) diff --git a/drivers/md/raid10.c b/drivers/md/raid10.c index a0ce86ea5b34..4af09e8f91e0 100644 --- a/drivers/md/raid10.c +++ b/drivers/md/raid10.c @@ -2763,16 +2763,11 @@ static void narrow_write_error(struct r10bio *r10_bio, int i) * We currently own a reference to the rdev. */ - int block_sectors, lbs = bdev_logical_block_size(rdev->bdev) >> 9; + int block_sectors = rdev_bb_block_sectors(rdev); sector_t sector; int sectors; int sect_to_write = r10_bio->sectors; - if (rdev->badblocks.shift < 0) - block_sectors = lbs; - else - block_sectors = roundup(1 << rdev->badblocks.shift, lbs); - sector = r10_bio->sector; sectors = ((r10_bio->sector + block_sectors) & ~(sector_t)(block_sectors - 1)) From 5c2f4115051d064ad79b0b4edac70a2b62528d9d Mon Sep 17 00:00:00 2001 From: Li Youhong Date: Thu, 27 Aug 2026 15:56:20 +0800 Subject: [PATCH 14/25] md/raid1-10: share flush_bio_list between raid1 and raid10 raid1 already had flush_bio_list(); raid10 open-coded the same prepare/wake/submit loop in flush_pending_writes() and raid10_unplug(). Move a shared helper into raid1-10.c. The shared helper uses wq_has_sleeper() before wake_up(), matching raid1's wake_up_barrier() and raid10_unplug(). No intentional functional change. Signed-off-by: Li Youhong Link: https://patch.msgid.link/20260827075620.3599227-4-dayou5941@163.com Signed-off-by: Yu Kuai --- drivers/md/raid1-10.c | 17 +++++++++++++++++ drivers/md/raid1.c | 19 ++----------------- drivers/md/raid10.c | 22 ++-------------------- 3 files changed, 21 insertions(+), 37 deletions(-) diff --git a/drivers/md/raid1-10.c b/drivers/md/raid1-10.c index c7ddf136771d..341a9785e3f2 100644 --- a/drivers/md/raid1-10.c +++ b/drivers/md/raid1-10.c @@ -176,6 +176,23 @@ static inline void raid1_prepare_flush_writes(struct mddev *mddev) mddev->bitmap_ops->unplug(mddev, current->bio_list == NULL); } +static inline void flush_bio_list(struct mddev *mddev, struct bio *bio, + wait_queue_head_t *wait) +{ + /* flush any pending bitmap writes to disk before proceeding w/ I/O */ + raid1_prepare_flush_writes(mddev); + if (wq_has_sleeper(wait)) + wake_up(wait); + + while (bio) { /* submit pending writes */ + struct bio *next = bio->bi_next; + + raid1_submit_write(bio); + bio = next; + cond_resched(); + } +} + /* * Used by fix_read_error() to decay the per rdev read_errors. * We halve the read error count for every hour that has elapsed diff --git a/drivers/md/raid1.c b/drivers/md/raid1.c index 8a40c69d70a3..894efb50c6d5 100644 --- a/drivers/md/raid1.c +++ b/drivers/md/raid1.c @@ -919,21 +919,6 @@ static void wake_up_barrier(struct r1conf *conf) wake_up(&conf->wait_barrier); } -static void flush_bio_list(struct r1conf *conf, struct bio *bio) -{ - /* flush any pending bitmap writes to disk before proceeding w/ I/O */ - raid1_prepare_flush_writes(conf->mddev); - wake_up_barrier(conf); - - while (bio) { /* submit pending writes */ - struct bio *next = bio->bi_next; - - raid1_submit_write(bio); - bio = next; - cond_resched(); - } -} - static void flush_pending_writes(struct r1conf *conf) { /* Any writes that have been queued but are awaiting @@ -959,7 +944,7 @@ static void flush_pending_writes(struct r1conf *conf) */ __set_current_state(TASK_RUNNING); blk_start_plug(&plug); - flush_bio_list(conf, bio); + flush_bio_list(conf->mddev, bio, &conf->wait_barrier); blk_finish_plug(&plug); } else spin_unlock_irq(&conf->device_lock); @@ -1289,7 +1274,7 @@ static void raid1_unplug(struct blk_plug_cb *cb, bool from_schedule) /* we aren't scheduling, so we can do the write-out directly. */ bio = bio_list_get(&plug->pending); - flush_bio_list(conf, bio); + flush_bio_list(conf->mddev, bio, &conf->wait_barrier); kfree(plug); } diff --git a/drivers/md/raid10.c b/drivers/md/raid10.c index 4af09e8f91e0..066c43154497 100644 --- a/drivers/md/raid10.c +++ b/drivers/md/raid10.c @@ -895,16 +895,7 @@ static void flush_pending_writes(struct r10conf *conf) __set_current_state(TASK_RUNNING); blk_start_plug(&plug); - raid1_prepare_flush_writes(conf->mddev); - wake_up(&conf->wait_barrier); - - while (bio) { /* submit pending writes */ - struct bio *next = bio->bi_next; - - raid1_submit_write(bio); - bio = next; - cond_resched(); - } + flush_bio_list(conf->mddev, bio, &conf->wait_barrier); blk_finish_plug(&plug); } else spin_unlock_irq(&conf->device_lock); @@ -1102,16 +1093,7 @@ static void raid10_unplug(struct blk_plug_cb *cb, bool from_schedule) /* we aren't scheduling, so we can do the write-out directly. */ bio = bio_list_get(&plug->pending); - raid1_prepare_flush_writes(mddev); - wake_up_barrier(conf); - - while (bio) { /* submit pending writes */ - struct bio *next = bio->bi_next; - - raid1_submit_write(bio); - bio = next; - cond_resched(); - } + flush_bio_list(mddev, bio, &conf->wait_barrier); kfree(plug); } From 68dab5c14aaff17686df22ba1f1eea3b3433797a Mon Sep 17 00:00:00 2001 From: Jack Wang Date: Sun, 20 Sep 2026 12:37:05 +0200 Subject: [PATCH 15/25] md: fix mddev_unlock() called without holding lock in lbs_store() lbs_store() jumped to the unlock label on mddev_lock() failure, calling mddev_unlock() on a mutex that was never acquired. Return directly instead. Fixes: 62ed1b582246 ("md: allow configuring logical block size") Signed-off-by: Jack Wang Link: https://patch.msgid.link/20260920103705.23191-1-jinpu.wang@ionos.com Signed-off-by: Yu Kuai --- drivers/md/md.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/md/md.c b/drivers/md/md.c index 154ee5a65cb7..1ae310e7fca5 100644 --- a/drivers/md/md.c +++ b/drivers/md/md.c @@ -6048,11 +6048,10 @@ lbs_store(struct mddev *mddev, const char *buf, size_t len) err = mddev_lock(mddev); if (err) - goto unlock; + return err; err = mddev_set_logical_block_size(mddev, lbs); -unlock: mddev_unlock(mddev); return err ?: len; } From 96858e2308431ff5678e0e6fab1a06e19f3d95da Mon Sep 17 00:00:00 2001 From: Yogesh Gaur Date: Sun, 20 Sep 2026 14:45:33 +0530 Subject: [PATCH 16/25] md/raid5: give the io_unit slab caches per-array names Both raid5 log backends create their io_unit slab cache per array but name it after the struct: ppl_conf->io_kc = KMEM_CACHE(ppl_io_unit, 0); log->io_kc = KMEM_CACHE(r5l_io_unit, 0); KMEM_CACHE() passes the stringified type as the cache name, so every array that enables PPL or attaches a journal asks for a cache called "ppl_io_unit" or "r5l_io_unit". With two such arrays live at the same time the second one trips kmem_cache_sanity_check(): kmem_cache of name 'ppl_io_unit' already exists WARNING: mm/slab_common.c:112 at __kmem_cache_create_args+0xae/0x460 ppl_init_log+0x285/0x1310 drivers/md/raid5-ppl.c:1363 log_init drivers/md/raid5-log.h:138 [inline] raid5_change_consistency_policy+0x255/0x740 drivers/md/raid5.c:9141 consistency_policy_store+0x7f/0x230 drivers/md/md.c:5883 md_attr_store+0x3b7/0x640 drivers/md/md.c:6158 which is reachable by any user that can write "ppl" to md/consistency_policy on two arrays, and equally by assembling two journalled arrays. The cache is created and destroyed correctly on every path, so this is a name collision only - duplicate names confuse slabtop and /proc/slabinfo, which is what the check is there to catch. Build the name from the array instead, the way grow_stripes() already does for the stripe_head cache in this driver, and fall back to the mddev pointer for dm-raid where mdname() is the constant "mdX". The buffer is sized like conf->cache_name[] because mdname() can return up to DISK_NAME_LEN bytes. The explicit size/align arguments keep what KMEM_CACHE() expanded to, so object layout does not change. Fixes: 3418d036c81d ("raid5-ppl: Partial Parity Log write logging implementation") Fixes: f6bed0ef0a80 ("raid5: add basic stripe log") Reported-by: syzbot+2ef75c54d0b2ac5d00ba@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=2ef75c54d0b2ac5d00ba Assisted-by: LLM Signed-off-by: Yogesh Gaur Link: https://patch.msgid.link/20260920091533.413-1-yogeshgaur.83@gmail.com Signed-off-by: Yu Kuai --- drivers/md/raid5-cache.c | 12 +++++++++++- drivers/md/raid5-ppl.c | 12 +++++++++++- 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/drivers/md/raid5-cache.c b/drivers/md/raid5-cache.c index 62460e231f02..2600cb5ac393 100644 --- a/drivers/md/raid5-cache.c +++ b/drivers/md/raid5-cache.c @@ -3084,6 +3084,7 @@ int r5l_init_log(struct r5conf *conf, struct md_rdev *rdev) { struct r5l_log *log; struct md_thread *thread; + char cache_name[48]; int ret; pr_debug("md/raid:%s: using device %pg as journal\n", @@ -3123,7 +3124,16 @@ int r5l_init_log(struct r5conf *conf, struct md_rdev *rdev) INIT_LIST_HEAD(&log->flushing_ios); INIT_LIST_HEAD(&log->finished_ios); - log->io_kc = KMEM_CACHE(r5l_io_unit, 0); + if (mddev_is_dm(conf->mddev)) + snprintf(cache_name, sizeof(cache_name), "r5l_io_unit-%p", + conf->mddev); + else + snprintf(cache_name, sizeof(cache_name), "r5l_io_unit-%s", + mdname(conf->mddev)); + + log->io_kc = kmem_cache_create(cache_name, sizeof(struct r5l_io_unit), + __alignof__(struct r5l_io_unit), + 0, NULL); if (!log->io_kc) goto io_kc; diff --git a/drivers/md/raid5-ppl.c b/drivers/md/raid5-ppl.c index 7f8a9d3fd578..0530632f9d2a 100644 --- a/drivers/md/raid5-ppl.c +++ b/drivers/md/raid5-ppl.c @@ -1318,6 +1318,7 @@ int ppl_init_log(struct r5conf *conf) { struct ppl_conf *ppl_conf; struct mddev *mddev = conf->mddev; + char cache_name[48]; int ret = 0; int max_disks; int i; @@ -1360,7 +1361,16 @@ int ppl_init_log(struct r5conf *conf) ppl_conf->mddev = mddev; - ppl_conf->io_kc = KMEM_CACHE(ppl_io_unit, 0); + if (mddev_is_dm(mddev)) + snprintf(cache_name, sizeof(cache_name), "ppl_io_unit-%p", mddev); + else + snprintf(cache_name, sizeof(cache_name), "ppl_io_unit-%s", + mdname(mddev)); + + ppl_conf->io_kc = kmem_cache_create(cache_name, + sizeof(struct ppl_io_unit), + __alignof__(struct ppl_io_unit), + 0, NULL); if (!ppl_conf->io_kc) { ret = -ENOMEM; goto err; From 08c8e27535f2688fbe2d8f965477eafd5826f556 Mon Sep 17 00:00:00 2001 From: Chen Cheng Date: Mon, 21 Sep 2026 15:16:52 +0800 Subject: [PATCH 17/25] md/raid5: extract min_raid_disks() Reuse the minimum disk count in setup_conf() and check_reshape(). Signed-off-by: Chen Cheng Link: https://patch.msgid.link/1dc59914e6935a0d6cdafaa1e73f682935f13abf.1789974245.git.chencheng@fnnas.com Signed-off-by: Yu Kuai --- drivers/md/raid5.c | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/drivers/md/raid5.c b/drivers/md/raid5.c index c315e99d0f30..f418b6aa1d37 100644 --- a/drivers/md/raid5.c +++ b/drivers/md/raid5.c @@ -7629,10 +7629,15 @@ static unsigned long raid5_cache_count(struct shrinker *shrink, return max_stripes - min_stripes; } +static int min_raid_disks(int level) +{ + return level == 6 ? 4 : 2; +} + static struct r5conf *setup_conf(struct mddev *mddev) { struct r5conf *conf; - int raid_disk, memory, max_disks; + int raid_disk, memory, max_disks, min_disks; struct md_rdev *rdev; struct disk_info *disk; char pers_name[6]; @@ -7656,11 +7661,11 @@ static struct r5conf *setup_conf(struct mddev *mddev) mdname(mddev), mddev->new_layout); return ERR_PTR(-EIO); } - if ((mddev->new_level != 6 && mddev->raid_disks < 2) || - (mddev->new_level == 6 && mddev->raid_disks < 4)) { + min_disks = min_raid_disks(mddev->new_level); + if (mddev->raid_disks < min_disks) { pr_warn("md/raid:%s: not enough configured devices (%d, minimum %d)\n", mdname(mddev), mddev->raid_disks, - mddev->new_level == 6 ? 4 : 2); + min_disks); return ERR_PTR(-EINVAL); } @@ -8615,13 +8620,9 @@ static int check_reshape(struct mddev *mddev) if (mddev->delta_disks < 0 && mddev->reshape_position == MaxSector) { /* We might be able to shrink, but the devices must * be made bigger first. - * For raid6, 4 is the minimum size. - * Otherwise 2 is the minimum */ - int min = 2; - if (mddev->level == 6) - min = 4; - if (mddev->raid_disks + mddev->delta_disks < min) + if (mddev->raid_disks + mddev->delta_disks < + min_raid_disks(mddev->level)) return -EINVAL; } From f064aa93d84b730f2f0fe795305579dc71e41392 Mon Sep 17 00:00:00 2001 From: Chen Cheng Date: Mon, 21 Sep 2026 15:16:53 +0800 Subject: [PATCH 18/25] md/raid5: reject invalid reshape disks in raid5_run() Check disk count before dividing by data disks while assembling a reshape, otherwise too few disks can cause division by zero. Reported-by: sashiko-bot Link: https://lore.kernel.org/r/20260911042717.487AC1F00898@smtp.kernel.org/ Signed-off-by: Chen Cheng Link: https://patch.msgid.link/acf8254263d4d40bd65c5b39058d4527cfb44cd9.1789974245.git.chencheng@fnnas.com Signed-off-by: Yu Kuai --- drivers/md/raid5.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/drivers/md/raid5.c b/drivers/md/raid5.c index f418b6aa1d37..6be9c5432918 100644 --- a/drivers/md/raid5.c +++ b/drivers/md/raid5.c @@ -8052,6 +8052,7 @@ static int raid5_run(struct mddev *mddev) sector_t here_new, here_old; int old_disks; int max_degraded = (mddev->level == 6 ? 2 : 1); + int min_disks = min_raid_disks(mddev->level); int chunk_sectors; int new_data_disks; @@ -8067,6 +8068,12 @@ static int raid5_run(struct mddev *mddev) return -EINVAL; } old_disks = mddev->raid_disks - mddev->delta_disks; + if (mddev->raid_disks < min_disks || old_disks < min_disks) { + pr_warn("md/raid:%s: invalid reshape disks (%d -> %d, min %d)\n", + mdname(mddev), old_disks, mddev->raid_disks, + min_disks); + return -EINVAL; + } /* reshape_position must be on a new-stripe boundary, and one * further up in new geometry must map after here in old * geometry. From 7bcc031d49d285045bd640bfcb0f24c65e3bd589 Mon Sep 17 00:00:00 2001 From: Peng Fan Date: Mon, 21 Sep 2026 16:10:08 +0800 Subject: [PATCH 19/25] md/raid: use assign_bit() where applicable Convert open-coded if/else with set_bit/clear_bit to the assign_bit API. Done with Coccinelle semantic patch: // set_bit -> clear_bit => assign_bit @@ expression cond, bit, addr; @@ -if (cond) - set_bit(bit, addr); -else - clear_bit(bit, addr); +assign_bit(bit, addr, cond); // clear_bit -> set_bit => assign_bit @@ expression cond, bit, addr; @@ -if (cond) - clear_bit(bit, addr); -else - set_bit(bit, addr); +assign_bit(bit, addr, !cond); Signed-off-by: Peng Fan Link: https://patch.msgid.link/20260921081009.278532-2-peng.fan@oss.nxp.com Signed-off-by: Yu Kuai --- drivers/md/md.c | 22 ++++++---------------- drivers/md/raid5-cache.c | 6 ++---- drivers/md/raid5.c | 6 ++---- 3 files changed, 10 insertions(+), 24 deletions(-) diff --git a/drivers/md/md.c b/drivers/md/md.c index 1ae310e7fca5..f443dd927ac3 100644 --- a/drivers/md/md.c +++ b/drivers/md/md.c @@ -3586,10 +3586,7 @@ static ssize_t recovery_start_store(struct md_rdev *rdev, const char *buf, size_ return -EBUSY; rdev->recovery_offset = recovery_start; - if (recovery_start == MaxSector) - set_bit(In_sync, &rdev->flags); - else - clear_bit(In_sync, &rdev->flags); + assign_bit(In_sync, &rdev->flags, recovery_start == MaxSector); return len; } @@ -5915,10 +5912,7 @@ fail_last_dev_store(struct mddev *mddev, const char *buf, size_t len) if (ret) return ret; - if (value) - set_bit(MD_FAILLAST_DEV, &mddev->flags); - else - clear_bit(MD_FAILLAST_DEV, &mddev->flags); + assign_bit(MD_FAILLAST_DEV, &mddev->flags, value); return len; } @@ -7573,14 +7567,10 @@ int md_add_new_disk(struct mddev *mddev, struct mdu_disk_info_s *info) } clear_bit(In_sync, &rdev->flags); /* just to be sure */ - if (info->state & (1<flags); - else - clear_bit(WriteMostly, &rdev->flags); - if (info->state & (1<flags); - else - clear_bit(FailFast, &rdev->flags); + assign_bit(WriteMostly, &rdev->flags, + info->state & (1 << MD_DISK_WRITEMOSTLY)); + assign_bit(FailFast, &rdev->flags, + info->state & (1 << MD_DISK_FAILFAST)); if (info->state & (1<cache_state); } - if (free_space < 3 * reclaim_space) - set_bit(R5C_LOG_TIGHT, &conf->cache_state); - else - clear_bit(R5C_LOG_TIGHT, &conf->cache_state); + assign_bit(R5C_LOG_TIGHT, &conf->cache_state, + free_space < 3 * reclaim_space); if (wake_reclaim) r5l_wake_reclaim(log, 0); diff --git a/drivers/md/raid5.c b/drivers/md/raid5.c index 6be9c5432918..c8ea9ba6829e 100644 --- a/drivers/md/raid5.c +++ b/drivers/md/raid5.c @@ -4746,10 +4746,8 @@ static void analyse_stripe(struct stripe_head *sh, struct stripe_head_state *s) RAID5_STRIPE_SECTORS(conf))) set_bit(R5_ReadRepl, &dev->flags); else { - if (rdev && !test_bit(Faulty, &rdev->flags)) - set_bit(R5_NeedReplace, &dev->flags); - else - clear_bit(R5_NeedReplace, &dev->flags); + assign_bit(R5_NeedReplace, &dev->flags, + rdev && !test_bit(Faulty, &rdev->flags)); rdev = conf->disks[i].rdev; clear_bit(R5_ReadRepl, &dev->flags); } From ae5f89b23cb88667cfa1273b42d6a551db9c651e Mon Sep 17 00:00:00 2001 From: Yuchao Zhang Date: Tue, 22 Sep 2026 12:46:06 +0800 Subject: [PATCH 20/25] md: validate bblog_size when loading v1.x badblocks metadata In super_1_load(), when the on-disk feature_map does not have MD_FEATURE_BAD_BLOCKS set but sb->bblog_offset is non-zero, rdev->badblocks.shift is initialized to 0 without validating sb->bblog_size. The same field is validated in the MD_FEATURE_BAD_BLOCKS branch ("sectors > (PAGE_SIZE / 512)"), but this branch bypasses that check. A forged on-disk superblock (malicious storage device or crafted image) can therefore carry an oversized sb->bblog_size. When bad blocks are later recorded, super_1_sync() sets bb->size from sb->bblog_size and md_write_metadata() issues a bio larger than the single rdev->bb_page, reading beyond the page and leaking kernel memory contents to disk. sb->bblog_size is never modified by the kernel between load and sync, and the MD_FEATURE_BAD_BLOCKS branch already validates it, so checking it in this branch is sufficient to cover both paths. Fix this by rejecting an oversized sb->bblog_size with -EINVAL, matching the existing check in the MD_FEATURE_BAD_BLOCKS branch. Fixes: 2699b67223ac ("md: load/store badblock list from v1.x metadata") Cc: stable@vger.kernel.org Signed-off-by: Yuchao Zhang Link: https://patch.msgid.link/20260922044606.30774-1-ndaugoing@gmail.com Signed-off-by: Yu Kuai --- drivers/md/md.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/md/md.c b/drivers/md/md.c index f443dd927ac3..3cb961036637 100644 --- a/drivers/md/md.c +++ b/drivers/md/md.c @@ -1934,8 +1934,11 @@ static int super_1_load(struct md_rdev *rdev, struct md_rdev *refdev, int minor_ if (!badblocks_set(&rdev->badblocks, sector, count, 1)) return -EINVAL; } - } else if (sb->bblog_offset != 0) + } else if (sb->bblog_offset != 0) { + if (le16_to_cpu(sb->bblog_size) > (PAGE_SIZE / 512)) + return -EINVAL; rdev->badblocks.shift = 0; + } if ((le32_to_cpu(sb->feature_map) & (MD_FEATURE_PPL | MD_FEATURE_MULTIPLE_PPLS))) { From 900bf42e0ff8009f66ffd4a679c56264957019dd Mon Sep 17 00:00:00 2001 From: Nguyen Ngoc Thang Date: Fri, 25 Sep 2026 00:13:45 +0700 Subject: [PATCH 21/25] md: take reconfig_mutex when enabling PPL on an inactive array consistency_policy_store() sets MD_HAS_PPL for an external-metadata array that is not running yet, without holding reconfig_mutex. But ->pers is only assigned after pers->run() returns, so "not running" also covers an md_run() that is in progress under the lock. If the write lands inside raid5_run() after log_init() has decided not to set up PPL, or after a failed ppl_init_log() has cleared the flag, the flag ends up set with conf->log_private still NULL. The next idle pass of raid5d() then does: raid5d() -> handle_active_stripes() -> log_flush_stripe_to_raid() -> ppl_write_stripe_run() and dereferences the NULL ppl_conf: Oops: general protection fault, probably for non-canonical address 0xdffffc0000000002 KASAN: null-ptr-deref in range [0x0000000000000010-0x0000000000000017] RIP: 0010:ppl_write_stripe_run+0x60/0x1320 drivers/md/raid5-ppl.c:538 Take reconfig_mutex around the update and refuse it with -EBUSY once the array is running, in which case ->change_consistency_policy() is the right interface. Fixes: 664aed04446c ("md: add sysfs entries for PPL") Reported-by: syzbot+75d7e96ad03ac2dbbd9f@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=75d7e96ad03ac2dbbd9f Signed-off-by: Nguyen Ngoc Thang Link: https://patch.msgid.link/20260924171345.75894-1-ngocthang2710.1999@gmail.com Signed-off-by: Yu Kuai --- drivers/md/md.c | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/drivers/md/md.c b/drivers/md/md.c index 3cb961036637..0f7115b31ca3 100644 --- a/drivers/md/md.c +++ b/drivers/md/md.c @@ -5884,7 +5884,15 @@ consistency_policy_store(struct mddev *mddev, const char *buf, size_t len) else err = -EBUSY; } else if (mddev->external && strncmp(buf, "ppl", 3) == 0) { - set_bit(MD_HAS_PPL, &mddev->flags); + /* md_run() holds the lock until ->pers is set */ + err = mddev_lock(mddev); + if (err) + return err; + if (mddev->pers) + err = -EBUSY; + else + set_bit(MD_HAS_PPL, &mddev->flags); + mddev_unlock(mddev); } else { err = -EINVAL; } From af560e357a81964544be40c549e241b032ef7565 Mon Sep 17 00:00:00 2001 From: Li Youhong Date: Tue, 29 Sep 2026 16:39:16 +0800 Subject: [PATCH 22/25] md/raid5: drain released_stripes before destroying the cache grow_one_stripe() calls raid5_release_stripe() for each new stripe. When mddev->thread is already set, that queues the stripe on conf->released_stripes and wakes the thread. Only raid5d moves that list onto inactive_list. Takeover calls setup_conf() while mddev->thread still belongs to the previous personality, so those stripes stay on released_stripes. If setup_conf() then fails, shrink_stripes() only frees inactive_list and kmem_cache_destroy() warns that objects remain. Move stripes still queued on released_stripes onto inactive_list before shrink_stripes() frees them. Fixes: 773ca82fa1ee ("raid5: make release_stripe lockless") Reported-by: syzbot+6a132745caefcf42cb0d@syzkaller.appspotmail.com Closes: https://lore.kernel.org/linux-raid/6ab6b743.7eec07f4.c25c.0006.GAE@google.com/T/#t Signed-off-by: Li Youhong Link: https://patch.msgid.link/20260929083916.50620-1-dayou5941@163.com Signed-off-by: Yu Kuai --- drivers/md/raid5.c | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/drivers/md/raid5.c b/drivers/md/raid5.c index c8ea9ba6829e..d52adf81065a 100644 --- a/drivers/md/raid5.c +++ b/drivers/md/raid5.c @@ -2713,6 +2713,37 @@ static int drop_one_stripe(struct r5conf *conf) static void shrink_stripes(struct r5conf *conf) { + struct list_head temp[NR_STRIPE_HASH_LOCKS]; + int i; + + /* + * grow_one_stripe() parks new stripes on released_stripes when + * mddev->thread is already set. Takeover leaves the previous + * personality's thread there, so raid5d never moves them to + * inactive_list. Drain them before the cache is destroyed. + * slab_cache is assigned only after device_lock and the hash + * lists exist; earlier abort paths have nothing to drain. + */ + if (conf->slab_cache) { + for (i = 0; i < NR_STRIPE_HASH_LOCKS; i++) + INIT_LIST_HEAD(&temp[i]); + + spin_lock_irq(&conf->device_lock); + release_stripe_list(conf, temp); + spin_unlock_irq(&conf->device_lock); + + for (i = 0; i < NR_STRIPE_HASH_LOCKS; i++) { + if (list_empty(&temp[i])) + continue; + spin_lock_irq(conf->hash_locks + i); + if (list_empty(conf->inactive_list + i)) + atomic_dec(&conf->empty_inactive_list_nr); + list_splice_tail_init(&temp[i], + conf->inactive_list + i); + spin_unlock_irq(conf->hash_locks + i); + } + } + while (conf->max_nr_stripes && drop_one_stripe(conf)) ; From 73b0df929494522acf9ccc8dafea4f721166965b Mon Sep 17 00:00:00 2001 From: Sang-Hoon Choi Date: Wed, 30 Sep 2026 02:37:38 +0900 Subject: [PATCH 23/25] md/raid5: protect big_stripe_tree lookup with RCU big_stripe_tree is updated under tree_lock, and deleted radix-tree nodes are freed after an RCU grace period. r5c_big_stripe_cached() used to run under an RCU read-side critical section, but that protection was removed together with unrelated rdev lookup protection. The lookup can now be preempted while walking the tree, allowing a concurrent deletion and grace period to complete before the walk resumes. Protect the lookup locally with rcu_read_lock(). Only the presence of the entry is used after unlocking. Fixes: ad8606702f26 ("md/raid5: remove rcu protection to access rdev from conf") Assisted-by: LLM Signed-off-by: Sang-Hoon Choi Link: https://patch.msgid.link/179070265341.12871.12495446593114993799.idr-bug-88@gmail.com Signed-off-by: Yu Kuai --- drivers/md/raid5-cache.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/md/raid5-cache.c b/drivers/md/raid5-cache.c index 656c22b8e07e..785ddcb17e9e 100644 --- a/drivers/md/raid5-cache.c +++ b/drivers/md/raid5-cache.c @@ -2968,7 +2968,9 @@ bool r5c_big_stripe_cached(struct r5conf *conf, sector_t sect) return false; tree_index = r5c_tree_index(conf, sect); + rcu_read_lock(); slot = radix_tree_lookup(&log->big_stripe_tree, tree_index); + rcu_read_unlock(); return slot != NULL; } From 4c90b76e0198deb89376988daa8d96b48bea7fcf Mon Sep 17 00:00:00 2001 From: Zhihao Cheng Date: Tue, 22 Sep 2026 11:05:38 +0800 Subject: [PATCH 24/25] md: Fix the null-ptr-deref of 'mddev->private' while submitting IO Concurrent processes md_stop and IO submission can trigger a NULL pointer dereference of mddev->private: BUG: kernel NULL pointer dereference, address: 0000000000000070 RIP: 0010:_wait_barrier+0x2f/0x250 Call Trace: raid1_make_request+0x150/0xf50 md_handle_request+0x104/0x530 md_submit_bio+0x76/0x130 submit_bio+0xdd/0x250 submit_bio_wait+0x1f/0x40 __blkdev_direct_IO_simple+0x1f6/0x370 blkdev_write_iter+0x3b2/0x520 ksys_write+0x7d/0x190 One process can submit I/O through an open file while a forked process uses the same file to issue STOP_ARRAY. The opener check permits this because md_open() was called only once. do_md_stop() can then clear mddev->private while the I/O is entering the personality make_request callback. This affects raid0, raid1, raid10, and raid5. Suspend STOP_ARRAY while holding the reconfiguration lock so teardown waits for active_io references to drain. Fail I/O in md_handle_request() if mddev->pers is NULL after the array resumes. The sysfs stopping paths are protected by mddev_set_closing_and_sync_blockdev(). Apply that protection to both clear and inactive transitions even when the array is read-only. dm-raid teardown is already invoked with the device suspended. Fetch a reproducer in https://bugzilla.kernel.org/show_bug.cgi?id=222020 Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Reported-by: syzbot+3fe892ea5fc292e1353f@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=3fe892ea5fc292e1353f Signed-off-by: Zhihao Cheng Link: https://patch.msgid.link/20260922030538.1634904-1-chengzhihao1@huawei.com Signed-off-by: Yu Kuai --- drivers/md/md.c | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/drivers/md/md.c b/drivers/md/md.c index 0f7115b31ca3..118de338266a 100644 --- a/drivers/md/md.c +++ b/drivers/md/md.c @@ -414,6 +414,20 @@ bool md_handle_request(struct mddev *mddev, struct bio *bio) if (!percpu_ref_tryget_live(&mddev->active_io)) goto check_suspended; } + if (!mddev->pers) { + /* + * The __md_stop() sets 'mddev->private' to NULL during + * the IO submitting, check 'mddev->pers' before the IO + * being processed by specific driver to avoid the + * null-ptr-deref of 'mddev->'. The check is + * safe because the IO has got the 'mddev->active_io' + * reference, and all __md_stop() callers will wait for + * the reference to be zero. + */ + bio_io_error(bio); + percpu_ref_put(&mddev->active_io); + return true; + } if (!mddev->pers->make_request(mddev, bio)) { percpu_ref_put(&mddev->active_io); if (mddev_is_dm(mddev) && mddev->pers->prepare_suspend) @@ -4670,7 +4684,8 @@ array_state_store(struct mddev *mddev, const char *buf, size_t len) case readonly: case inactive: case read_auto: - if (!mddev->pers || !md_is_rdwr(mddev)) + if (!mddev->pers || + (st != clear && st != inactive && !md_is_rdwr(mddev))) break; /* write sysfs will not open mddev and opener should be 0 */ err = mddev_set_closing_and_sync_blockdev(mddev, 0); @@ -8299,6 +8314,7 @@ static bool md_ioctl_need_suspend(unsigned int cmd) case HOT_REMOVE_DISK: case SET_BITMAP_FILE: case SET_ARRAY_INFO: + case STOP_ARRAY: return true; default: return false; From d1472ca2ca5de8ecd2b358411eb14c031404ecf0 Mon Sep 17 00:00:00 2001 From: Yogesh Gaur Date: Tue, 6 Oct 2026 17:59:04 +0530 Subject: [PATCH 25/25] md: don't hand out the array before md_alloc() has added mddev->kobj md_alloc() publishes the gendisk before it is done setting the mddev up: disk->private_data = mddev; ... error = add_disk(disk); if (error) goto out_put_disk; kobject_init(&mddev->kobj, &md_ktype); error = kobject_add(&mddev->kobj, &disk_to_dev(disk)->kobj, "%s", "md"); add_disk() makes /dev/mdN openable. mddev comes from mddev_alloc() and is zeroed, so anything that gets in between add_disk() and kobject_add() sees an mddev->kobj that has never been through kobject_init() - no ktype, no kref, state_initialized clear. syzbot opens the array in that window and issues ADD_NEW_DISK: kobject: '(null)' (ffff8880120640f0): is not initialized, yet kobject_get() is being called. WARNING: lib/kobject.c:642 at kobject_add_internal+0xea/0xcd0 lib/kobject.c:225 kobject_add_varg lib/kobject.c:374 [inline] kobject_add+0x163/0x240 lib/kobject.c:426 bind_rdev_to_array+0x80c/0xdd0 drivers/md/md.c:2621 md_add_new_disk+0xe3b/0x1850 drivers/md/md.c:7684 md_ioctl+0x200a/0x2610 drivers/md/md.c:8499 bind_rdev_to_array() is not the only way in. md_run() calls sysfs_create_group(&mddev->kobj, &md_redundancy_group), and internal_create_group() has its own WARN_ON(!kobj->sd), so RUN_ARRAY in the same window warns too. Every ioctl that can reach mddev->kobj runs under reconfig_mutex, so hold it across add_disk() and kobject_add(). An ioctl issued in the window now waits for md_alloc() to finish instead of seeing a half-built mddev, and opening the array still succeeds. The ioctls md_ioctl() serves without the mutex only read array state and return -ENODEV on an array with no disks. Hoisting kobject_init() above add_disk() is not an option: commit ca39f7502425 ("md: fix mddev->kobj lifetime") moved it below add_disk() so that md_alloc()'s error paths, which free the mddev directly, never see an initialised kobject. Fixes: ca39f7502425 ("md: fix mddev->kobj lifetime") Reported-by: syzbot+95eeb4ada2349a2170ea@syzkaller.appspotmail.com Closes: https://lore.kernel.org/all/6aaf3284.514bccd6.255447.0003.GAE@google.com/ Suggested-by: Yu Kuai Cc: stable@vger.kernel.org Assisted-by: LLM Signed-off-by: Yogesh Gaur Link: https://patch.msgid.link/20261006122904.1538-1-yogeshgaur.83@gmail.com Signed-off-by: Yu Kuai --- drivers/md/md.c | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/drivers/md/md.c b/drivers/md/md.c index 118de338266a..03e35386a802 100644 --- a/drivers/md/md.c +++ b/drivers/md/md.c @@ -6404,12 +6404,22 @@ struct mddev *md_alloc(dev_t dev, char *name) disk->events |= DISK_EVENT_MEDIA_CHANGE; mddev->gendisk = disk; + + /* + * add_disk() makes the array openable before mddev->kobj exists. + * Hold reconfig_mutex until kobject_add() is done so that ioctls + * issued in between wait instead of using an uninitialised kobj. + */ + mddev_lock_nointr(mddev); error = add_disk(disk); - if (error) + if (error) { + mddev_unlock(mddev); goto out_put_disk; + } kobject_init(&mddev->kobj, &md_ktype); error = kobject_add(&mddev->kobj, &disk_to_dev(disk)->kobj, "%s", "md"); + mddev_unlock(mddev); if (error) { /* * The disk is already live at this point. Clear the hold flag