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: 1da177e4c3 ("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 <chengzhihao1@huawei.com>
Link: https://patch.msgid.link/20260922030538.1634904-1-chengzhihao1@huawei.com
Signed-off-by: Yu Kuai <yukuai@fygo.io>
This commit is contained in:
Zhihao Cheng 2026-09-22 11:05:38 +08:00 committed by Yu Kuai
parent 73b0df9294
commit 4c90b76e01

View File

@ -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-><member>'. 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;