md: scope memalloc_noio to allocation critical sections

Storing a memalloc_noio_save() token in mddev->noio_flags lets one task
save the token and another task restore it. With concurrent suspend sysfs
writes, task A can enter PF_MEMALLOC_NOIO, return to userspace still in
that scope, and later task B can restore A's saved token.

Avoid tying the token lifetime to mddev. Keep mddev_suspend() and
mddev_resume() only responsible for array suspension, and enter
PF_MEMALLOC_NOIO only in the MD paths that allocate memory after the array
has been suspended. Restore the token before resuming the array.

A reproducer repeatedly writes suspend_lo and suspend_hi from concurrent
workers and checks each worker's /proc/self/stat flags before and after the
sysfs write.

Link: https://github.com/chencheng-fnnas/reproducer/blob/main/repro-md-noio-token-leak.sh
Fixes: 78f57ef9d5 ("md: use memalloc scope APIs in mddev_suspend()/mddev_resume()")
Signed-off-by: Chen Cheng <chencheng@fnnas.com>
Reviewed-by: Yu Kuai <yukuai@fygo.io>
Link: https://patch.msgid.link/20260718084218.417895-1-chencheng@fnnas.com
Signed-off-by: Yu Kuai <yukuai@fygo.io>
This commit is contained in:
Chen Cheng
2026-07-18 16:42:18 +08:00
committed by Yu Kuai
parent 2911cd0a0f
commit bace2010dd
4 changed files with 48 additions and 23 deletions

View File

@@ -2624,10 +2624,12 @@ static ssize_t
location_store(struct mddev *mddev, const char *buf, size_t len)
{
int rv;
unsigned int noio_flags;
rv = mddev_suspend_and_lock(mddev);
if (rv)
return rv;
noio_flags = memalloc_noio_save();
if (mddev->pers) {
if (mddev->recovery || mddev->sync_thread) {
@@ -2714,6 +2716,7 @@ location_store(struct mddev *mddev, const char *buf, size_t len)
}
rv = 0;
out:
memalloc_noio_restore(noio_flags);
mddev_unlock_and_resume(mddev);
if (rv)
return rv;

View File

@@ -233,23 +233,21 @@ static int rdev_need_serial(struct md_rdev *rdev)
void mddev_create_serial_pool(struct mddev *mddev, struct md_rdev *rdev)
{
int ret = 0;
unsigned int noio_flags;
if (rdev && !rdev_need_serial(rdev) &&
!test_bit(CollisionCheck, &rdev->flags))
return;
noio_flags = memalloc_noio_save();
if (!rdev)
ret = rdevs_init_serial(mddev);
else
ret = rdev_init_serial(rdev);
if (ret)
return;
goto out;
if (mddev->serial_info_pool == NULL) {
/*
* already in memalloc noio context by
* mddev_suspend()
*/
mddev->serial_info_pool =
mempool_create_kmalloc_pool(NR_SERIAL_INFOS,
sizeof(struct serial_info));
@@ -258,6 +256,8 @@ void mddev_create_serial_pool(struct mddev *mddev, struct md_rdev *rdev)
pr_err("can't alloc memory pool for serialization\n");
}
}
out:
memalloc_noio_restore(noio_flags);
}
/*
@@ -516,9 +516,6 @@ int mddev_suspend(struct mddev *mddev, bool interruptible)
*/
WRITE_ONCE(mddev->suspended, mddev->suspended + 1);
/* restrict memory reclaim I/O during raid array is suspend */
mddev->noio_flag = memalloc_noio_save();
mutex_unlock(&mddev->suspend_mutex);
return 0;
}
@@ -535,9 +532,6 @@ static void __mddev_resume(struct mddev *mddev, bool recovery_needed)
return;
}
/* entred the memalloc scope from mddev_suspend() */
memalloc_noio_restore(mddev->noio_flag);
percpu_ref_resurrect(&mddev->active_io);
wake_up(&mddev->sb_wait);
@@ -4047,6 +4041,7 @@ level_store(struct mddev *mddev, const char *buf, size_t len)
char clevel[16];
ssize_t rv;
size_t slen = len;
unsigned int noio_flags;
struct md_personality *pers, *oldpers;
long level;
void *priv, *oldpriv;
@@ -4058,6 +4053,7 @@ level_store(struct mddev *mddev, const char *buf, size_t len)
rv = mddev_suspend_and_lock(mddev);
if (rv)
return rv;
noio_flags = memalloc_noio_save();
if (mddev->pers == NULL) {
memcpy(mddev->clevel, buf, slen);
@@ -4233,6 +4229,7 @@ level_store(struct mddev *mddev, const char *buf, size_t len)
md_new_event();
rv = len;
out_unlock:
memalloc_noio_restore(noio_flags);
mddev_unlock_and_resume(mddev);
return rv;
}
@@ -4412,6 +4409,7 @@ static ssize_t
raid_disks_store(struct mddev *mddev, const char *buf, size_t len)
{
unsigned int n;
unsigned int noio_flags;
int err;
err = kstrtouint(buf, 10, &n);
@@ -4421,6 +4419,7 @@ raid_disks_store(struct mddev *mddev, const char *buf, size_t len)
err = mddev_suspend_and_lock(mddev);
if (err)
return err;
noio_flags = memalloc_noio_save();
if (mddev->pers) {
if (n != mddev->raid_disks)
err = update_raid_disks(mddev, n);
@@ -4444,6 +4443,7 @@ raid_disks_store(struct mddev *mddev, const char *buf, size_t len)
} else
mddev->raid_disks = n;
out_unlock:
memalloc_noio_restore(noio_flags);
mddev_unlock_and_resume(mddev);
return err ? err : len;
}
@@ -4824,6 +4824,7 @@ new_dev_store(struct mddev *mddev, const char *buf, size_t len)
int minor;
dev_t dev;
struct md_rdev *rdev;
unsigned int noio_flags;
int err;
if (!*buf || *e != ':' || !e[1] || e[1] == '\n')
@@ -4839,6 +4840,7 @@ new_dev_store(struct mddev *mddev, const char *buf, size_t len)
err = mddev_suspend_and_lock(mddev);
if (err)
return err;
noio_flags = memalloc_noio_save();
if (mddev->persistent) {
rdev = md_import_device(dev, mddev->major_version,
mddev->minor_version);
@@ -4857,6 +4859,7 @@ new_dev_store(struct mddev *mddev, const char *buf, size_t len)
rdev = md_import_device(dev, -1, -1);
if (IS_ERR(rdev)) {
memalloc_noio_restore(noio_flags);
mddev_unlock_and_resume(mddev);
return PTR_ERR(rdev);
}
@@ -4864,6 +4867,7 @@ new_dev_store(struct mddev *mddev, const char *buf, size_t len)
out:
if (err)
export_rdev(rdev);
memalloc_noio_restore(noio_flags);
mddev_unlock_and_resume(mddev);
if (!err)
md_new_event();
@@ -8331,8 +8335,10 @@ static int md_ioctl(struct block_device *bdev, blk_mode_t mode,
unsigned int cmd, unsigned long arg)
{
int err = 0;
unsigned int noio_flags = 0;
void __user *argp = (void __user *)arg;
struct mddev *mddev = NULL;
bool suspend;
err = md_ioctl_valid(cmd);
if (err)
@@ -8382,13 +8388,15 @@ static int md_ioctl(struct block_device *bdev, blk_mode_t mode,
if (!md_is_rdwr(mddev))
flush_work(&mddev->sync_work);
err = md_ioctl_need_suspend(cmd) ? mddev_suspend_and_lock(mddev) :
mddev_lock(mddev);
suspend = md_ioctl_need_suspend(cmd);
err = suspend ? mddev_suspend_and_lock(mddev) : mddev_lock(mddev);
if (err) {
pr_debug("md: ioctl lock interrupted, reason %d, cmd %d\n",
err, cmd);
goto out;
}
if (suspend)
noio_flags = memalloc_noio_save();
if (cmd == SET_ARRAY_INFO) {
err = __md_set_array_info(mddev, argp);
@@ -8513,8 +8521,12 @@ static int md_ioctl(struct block_device *bdev, blk_mode_t mode,
err != -EINVAL)
mddev->hold_active = 0;
md_ioctl_need_suspend(cmd) ? mddev_unlock_and_resume(mddev) :
mddev_unlock(mddev);
if (suspend) {
memalloc_noio_restore(noio_flags);
mddev_unlock_and_resume(mddev);
} else {
mddev_unlock(mddev);
}
out:
if (cmd == STOP_ARRAY_RO || (err && cmd == STOP_ARRAY))
@@ -10182,6 +10194,7 @@ static void md_start_sync(struct work_struct *ws)
struct mddev *mddev = container_of(ws, struct mddev, sync_work);
int spares = 0;
bool suspend = false;
unsigned int noio_flags = 0;
char *name;
/*
@@ -10192,6 +10205,7 @@ static void md_start_sync(struct work_struct *ws)
md_spares_need_change(mddev)) {
suspend = true;
mddev_suspend(mddev, false);
noio_flags = memalloc_noio_save();
}
mddev_lock_nointr(mddev);
@@ -10205,6 +10219,7 @@ static void md_start_sync(struct work_struct *ws)
mddev_unlock(mddev);
mddev_suspend_and_lock_nointr(mddev);
suspend = true;
noio_flags = memalloc_noio_save();
}
if (!md_is_rdwr(mddev)) {
@@ -10250,8 +10265,10 @@ static void md_start_sync(struct work_struct *ws)
* https://bugzilla.kernel.org/show_bug.cgi?id=218200
* Therefore, use __mddev_resume(mddev, false).
*/
if (suspend)
if (suspend) {
memalloc_noio_restore(noio_flags);
__mddev_resume(mddev, false);
}
md_wakeup_thread(mddev->sync_thread);
sysfs_notify_dirent_safe(mddev->sysfs_action);
md_new_event();
@@ -10270,8 +10287,10 @@ static void md_start_sync(struct work_struct *ws)
* https://bugzilla.kernel.org/show_bug.cgi?id=218200
* Therefore, use __mddev_resume(mddev, false).
*/
if (suspend)
if (suspend) {
memalloc_noio_restore(noio_flags);
__mddev_resume(mddev, false);
}
wake_up(&resync_wait);
if (test_and_clear_bit(MD_RECOVERY_RECOVER, &mddev->recovery) &&

View File

@@ -621,7 +621,6 @@ struct mddev {
struct md_cluster_info *cluster_info;
struct md_cluster_operations *cluster_ops;
unsigned int good_device_nr; /* good device num within cluster raid */
unsigned int noio_flag; /* for memalloc scope API */
/*
* Temporarily store rdev that will be finally removed when

View File

@@ -2471,11 +2471,6 @@ static int scribble_alloc(struct raid5_percpu *percpu,
sizeof(unsigned int) * (num + 2);
void *scribble;
/*
* If here is in raid array suspend context, it is in memalloc noio
* context as well, there is no potential recursive memory reclaim
* I/Os with the GFP_KERNEL flag.
*/
scribble = kvmalloc_array(cnt, obj_size, GFP_KERNEL);
if (!scribble)
return -ENOMEM;
@@ -2490,6 +2485,7 @@ static int scribble_alloc(struct raid5_percpu *percpu,
static int resize_chunks(struct r5conf *conf, int new_disks, int new_sectors)
{
unsigned long cpu;
unsigned int noio_flags;
int err = 0;
/* Never shrink. */
@@ -2498,6 +2494,7 @@ static int resize_chunks(struct r5conf *conf, int new_disks, int new_sectors)
return 0;
raid5_quiesce(conf->mddev, true);
noio_flags = memalloc_noio_save();
cpus_read_lock();
for_each_present_cpu(cpu) {
@@ -2511,6 +2508,7 @@ static int resize_chunks(struct r5conf *conf, int new_disks, int new_sectors)
}
cpus_read_unlock();
memalloc_noio_restore(noio_flags);
raid5_quiesce(conf->mddev, false);
if (!err) {
@@ -7107,6 +7105,7 @@ raid5_store_stripe_size(struct mddev *mddev, const char *page, size_t len)
{
struct r5conf *conf;
unsigned long new;
unsigned int noio_flags = 0;
int err;
int size;
@@ -7147,6 +7146,7 @@ raid5_store_stripe_size(struct mddev *mddev, const char *page, size_t len)
goto out_unlock;
}
noio_flags = memalloc_noio_save();
mutex_lock(&conf->cache_size_mutex);
size = conf->max_nr_stripes;
@@ -7163,6 +7163,7 @@ raid5_store_stripe_size(struct mddev *mddev, const char *page, size_t len)
mutex_unlock(&conf->cache_size_mutex);
out_unlock:
memalloc_noio_restore(noio_flags);
mddev_unlock_and_resume(mddev);
return err ?: len;
}
@@ -9043,6 +9044,7 @@ static void *raid6_takeover(struct mddev *mddev)
static int raid5_change_consistency_policy(struct mddev *mddev, const char *buf)
{
struct r5conf *conf;
unsigned int noio_flags;
int err;
err = mddev_suspend_and_lock(mddev);
@@ -9054,6 +9056,7 @@ static int raid5_change_consistency_policy(struct mddev *mddev, const char *buf)
return -ENODEV;
}
noio_flags = memalloc_noio_save();
if (strncmp(buf, "ppl", 3) == 0) {
/* ppl only works with RAID 5 */
if (!raid5_has_ppl(conf) && conf->level == 5) {
@@ -9093,6 +9096,7 @@ static int raid5_change_consistency_policy(struct mddev *mddev, const char *buf)
if (!err)
md_update_sb(mddev, 1);
memalloc_noio_restore(noio_flags);
mddev_unlock_and_resume(mddev);
return err;