mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2026-08-31 04:46:30 -04:00
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:
@@ -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;
|
||||
|
||||
@@ -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) &&
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user