The raid5 takeover invokes setup_conf and allocates strip heads, but
it wakes up the wrong thread, which lefts strip heads in the list
'conf->released_stripes' and not being processed. If raid5_run fails,
the strip heads won't be released, which triggers the following slab
warnings (CONFIG_SLUB_DEBUG):
BUG raid5-md0 (Not tainted): Objects remaining on __kmem_cache_shutdown()
Object 0x0000000062fad548 @offset=3968
Object 0x000000007f74683c @offset=4960
WARNING: mm/slub.c:1268 at __slab_err+0x31/0x40, CPU#0: bash/865
RIP: 0010:__slab_err+0x31
Call Trace:
__kmem_cache_shutdown.cold+0x15b
kmem_cache_destroy+0x71
free_conf+0xf8
raid5_run.cold+0x463
level_store+0x64e
md_attr_store+0xd7
The detailed triggering process is as follows:
mdadm --create /dev/md0 --level=1 --raid-devices=2 /dev/sda /dev/sdb
--force --assume-clean # create raid1, mddev->thread is raid1d
echo 5 > /sys/block/md0/md/level
level_store
raid5_takeover_raid1
setup_conf
grow_stripes
grow_one_stripe
sh = alloc_stripe
raid5_release_stripe
md_wakeup_thread(conf->mddev->thread) // wakeup raid1d
raid5_run
ENOMEM = raid5_create_ctx_pool
free_conf
shrink_stripes
drop_one_stripe // no strips found from the conf->inactive_list
kmem_cache_destroy(conf->slab_cache)
__kmem_cache_shutdown
free_partial
list_slab_objects // some entries are not released !
Fix it by hiding the origin mddev->thread before takeover, so that
new allocating strip heads can be put into 'conf->inactive_list',
which can be found by drop_one_stripe().
Fixes: 773ca82fa1ee ("raid5: make release_stripe lockless")
Signed-off-by: Zhihao Cheng <chengzhihao1@huawei.com>
---
drivers/md/raid5.c | 37 ++++++++++++++++++++++++++++---------
1 file changed, 28 insertions(+), 9 deletions(-)
Since all pers->run() callers handle the error case, no need to free
conf when raid5_run() fails.
Signed-off-by: Zhihao Cheng <chengzhihao1@huawei.com>
---
drivers/md/raid5.c | 2 --
1 file changed, 2 deletions(-)
@@ -8263,8 +8263,6 @@ static int raid5_run(struct mddev *mddev)abort:md_unregister_thread(mddev,&mddev->thread);print_raid5_conf(conf);-free_conf(conf);-mddev->private=NULL;pr_warn("md/raid:%s: failed to run raid set.\n",mdname(mddev));returnret;}
Since all pers->run() callers handle the error case, no need to free
conf when raid10_run() fails.
Signed-off-by: Zhihao Cheng <chengzhihao1@huawei.com>
---
drivers/md/raid10.c | 22 ++++++++++------------
1 file changed, 10 insertions(+), 12 deletions(-)
@@ -3972,7 +3972,7 @@ static int raid10_run(struct mddev *mddev)if(fc>1||fo>0){pr_err("only near layout is supported by clustered"" raid10\n");-gotoout_free_conf;+gotoout_unregister_thread;}}
@@ -3989,11 +3989,11 @@ static int raid10_run(struct mddev *mddev)if(test_bit(Replacement,&rdev->flags)){if(disk->replacement)-gotoout_free_conf;+gotoout_unregister_thread;disk->replacement=rdev;}else{if(disk->rdev)-gotoout_free_conf;+gotoout_unregister_thread;disk->rdev=rdev;}diff=(rdev->new_data_offset-rdev->data_offset);
@@ -4013,7 +4013,7 @@ static int raid10_run(struct mddev *mddev)if(err){ret=err;-gotoout_free_conf;+gotoout_unregister_thread;}}
@@ -4021,17 +4021,17 @@ static int raid10_run(struct mddev *mddev)if(!enough(conf,-1)){pr_err("md/raid10:%s: not enough operational mirrors.\n",mdname(mddev));-gotoout_free_conf;+gotoout_unregister_thread;}if(conf->reshape_progress!=MaxSector){/* must ensure that shape change is supported */if(conf->geo.far_copies!=1&&conf->geo.far_offset==0)-gotoout_free_conf;+gotoout_unregister_thread;if(conf->prev.far_copies!=1&&conf->prev.far_offset==0)-gotoout_free_conf;+gotoout_unregister_thread;}mddev->degraded=0;
@@ -4081,7 +4081,7 @@ static int raid10_run(struct mddev *mddev)set_bit(MD_FAILFAST_SUPPORTED,&mddev->flags);if(md_integrity_register(mddev))-gotoout_free_conf;+gotoout_unregister_thread;if(conf->reshape_progress!=MaxSector){unsignedlongbefore_length,after_length;
@@ -4094,7 +4094,7 @@ static int raid10_run(struct mddev *mddev)if(max(before_length,after_length)>min_offset_diff){/* This cannot work */pr_warn("md/raid10: offset difference not enough to continue reshape\n");-gotoout_free_conf;+gotoout_unregister_thread;}conf->offset_diff=min_offset_diff;
@@ -4106,10 +4106,8 @@ static int raid10_run(struct mddev *mddev)return0;-out_free_conf:+out_unregister_thread:md_unregister_thread(mddev,&mddev->thread);-raid10_free_conf(conf);-mddev->private=NULL;out:returnret;}
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [High] md: NULL pointer dereference in md_handle_request() when resuming array after pers->run() failure
- [Medium] md: sysfs group md_redundancy_group leaked on pers->run() failure
--
commit 4ab3c278fcfe04083eb0fb2b801da26eb96c6cec
Author: Zhihao Cheng [off-list ref]
md: Handle pers->run failure in level_store
This commit fixes a NULL pointer dereference that occurs when raid_disks
is set after raid5_run() fails during array conversion. It handles the
error by stopping the array if the new personality fails to run, allowing
a subsequent active order to restart it.
@@ -4229,7 +4230,31 @@ level_store(struct mddev *mddev, const char *buf, size_t len) mddev->in_sync = 1; timer_delete_sync(&mddev->safemode_timer); }- pers->run(mddev);+ rv = pers->run(mddev);+ if (rv) {+ /*+ * ->run() has released the private data of the new personality,+ * while the old one has already been released as well. There is+ * nothing to fall back to, so stop the array to avoid leaving+ * 'mddev->pers' pointing to a personality which has no private+ * data, and reminds user to try to active the mddev again.+ */+ pr_warn("md: %s: failed to run %s after takeover, please try to active\n",+ mdname(mddev), pers->head.name);+ if (md_bitmap_enabled(mddev, true))+ mddev->bitmap_ops->flush(mddev);+ clear_bit(MD_SERIALIZE_POLICY, &mddev->flags);+ mddev_destroy_serial_pool(mddev, NULL);+ __md_stop(mddev);
[Severity: High]
Does calling __md_stop() here introduce a NULL pointer dereference for in-flight IOs?
If level_store() is called while IOs are active, mddev_suspend_and_lock() will
force them to wait in md_handle_request(). __md_stop() sets mddev->pers = NULL.
When the error path later reaches out_unlock and calls mddev_unlock_and_resume(),
will the trapped IOs wake up and unconditionally call mddev->pers->make_request()
while mddev->pers is NULL, panicking the kernel?
[Severity: Medium]
When converting from a non-redundancy array to a redundancy array,
sysfs_create_group() is called earlier in level_store() to add md_redundancy_group.
Does this error path leak the sysfs group and its kernfs_node references, since
mddev->to_remove is not set to &md_redundancy_group to trigger cleanup in
mddev_unlock()?
quoted hunk
+ } set_bit(MD_SB_CHANGE_DEVS, &mddev->sb_flags); if (!mddev->thread) md_update_sb(mddev, 1);