| From 58bd80650256206f1be211cc153e3e6d0492e21b Mon Sep 17 00:00:00 2001 |
| From: Sasha Levin <sashal@kernel.org> |
| Date: Wed, 29 Nov 2023 13:38:43 -0500 |
| Subject: dm-delay: fix a race between delay_presuspend and delay_bio |
| |
| From: Mikulas Patocka <mpatocka@redhat.com> |
| |
| [ Upstream commit 6fc45b6ed921dc00dfb264dc08c7d67ee63d2656 ] |
| |
| In delay_presuspend, we set the atomic variable may_delay and then stop |
| the timer and flush pending bios. The intention here is to prevent the |
| delay target from re-arming the timer again. |
| |
| However, this test is racy. Suppose that one thread goes to delay_bio, |
| sees that dc->may_delay is one and proceeds; now, another thread executes |
| delay_presuspend, it sets dc->may_delay to zero, deletes the timer and |
| flushes pending bios. Then, the first thread continues and adds the bio to |
| delayed->list despite the fact that dc->may_delay is false. |
| |
| Fix this bug by changing may_delay's type from atomic_t to bool and |
| only access it while holding the delayed_bios_lock mutex. Note that we |
| don't have to grab the mutex in delay_resume because there are no bios |
| in flight at this point. |
| |
| Signed-off-by: Mikulas Patocka <mpatocka@redhat.com> |
| Cc: stable@vger.kernel.org |
| Signed-off-by: Mike Snitzer <snitzer@kernel.org> |
| Signed-off-by: Sasha Levin <sashal@kernel.org> |
| --- |
| drivers/md/dm-delay.c | 17 ++++++++++++----- |
| 1 file changed, 12 insertions(+), 5 deletions(-) |
| |
| diff --git a/drivers/md/dm-delay.c b/drivers/md/dm-delay.c |
| index f496213f8b675..7c0e7c662e07f 100644 |
| --- a/drivers/md/dm-delay.c |
| +++ b/drivers/md/dm-delay.c |
| @@ -30,7 +30,7 @@ struct delay_c { |
| struct workqueue_struct *kdelayd_wq; |
| struct work_struct flush_expired_bios; |
| struct list_head delayed_bios; |
| - atomic_t may_delay; |
| + bool may_delay; |
| |
| struct delay_class read; |
| struct delay_class write; |
| @@ -191,7 +191,7 @@ static int delay_ctr(struct dm_target *ti, unsigned int argc, char **argv) |
| INIT_WORK(&dc->flush_expired_bios, flush_expired_bios); |
| INIT_LIST_HEAD(&dc->delayed_bios); |
| mutex_init(&dc->timer_lock); |
| - atomic_set(&dc->may_delay, 1); |
| + dc->may_delay = true; |
| dc->argc = argc; |
| |
| ret = delay_class_ctr(ti, &dc->read, argv); |
| @@ -245,7 +245,7 @@ static int delay_bio(struct delay_c *dc, struct delay_class *c, struct bio *bio) |
| struct dm_delay_info *delayed; |
| unsigned long expires = 0; |
| |
| - if (!c->delay || !atomic_read(&dc->may_delay)) |
| + if (!c->delay) |
| return DM_MAPIO_REMAPPED; |
| |
| delayed = dm_per_bio_data(bio, sizeof(struct dm_delay_info)); |
| @@ -254,6 +254,10 @@ static int delay_bio(struct delay_c *dc, struct delay_class *c, struct bio *bio) |
| delayed->expires = expires = jiffies + msecs_to_jiffies(c->delay); |
| |
| mutex_lock(&delayed_bios_lock); |
| + if (unlikely(!dc->may_delay)) { |
| + mutex_unlock(&delayed_bios_lock); |
| + return DM_MAPIO_REMAPPED; |
| + } |
| c->ops++; |
| list_add_tail(&delayed->list, &dc->delayed_bios); |
| mutex_unlock(&delayed_bios_lock); |
| @@ -267,7 +271,10 @@ static void delay_presuspend(struct dm_target *ti) |
| { |
| struct delay_c *dc = ti->private; |
| |
| - atomic_set(&dc->may_delay, 0); |
| + mutex_lock(&delayed_bios_lock); |
| + dc->may_delay = false; |
| + mutex_unlock(&delayed_bios_lock); |
| + |
| del_timer_sync(&dc->delay_timer); |
| flush_bios(flush_delayed_bios(dc, 1)); |
| } |
| @@ -276,7 +283,7 @@ static void delay_resume(struct dm_target *ti) |
| { |
| struct delay_c *dc = ti->private; |
| |
| - atomic_set(&dc->may_delay, 1); |
| + dc->may_delay = true; |
| } |
| |
| static int delay_map(struct dm_target *ti, struct bio *bio) |
| -- |
| 2.42.0 |
| |