syzbot is reporting NULL pointer dereference in lo_rw_aio(). An analysis by the Gemini AI collaborator considers that this problem is caused by a timing shift primarily exposed by commit 65565ca5f99b ("block: unify the synchronous bi_end_io callbacks"), along with helper refactorings like commit 92c3737a2473 ("block: add a bio_submit_or_kill helper"). But due to difficulty of reproducing this race, discussion about what is happening and how to fix this problem is stalling. Also, we haven't identified how many filesystems are subjected to this problem. Therefore, introduce a grace period for flushing outstanding I/O (which should be a good thing from the perspective of defensive programming) so that we won't hit NULL pointer dereference problem. Since calling drain_workqueue() from __loop_clr_fd() with disk->open_mutex held causes lockdep warnings, schedule lo_clr_work from lo_release() which calls __loop_clr_fd() and wait for completion from lo_post_release(). Link: https://lkml.kernel.org/r/fbb3edda-f108-4e5b-acf2-266f043f8125@I-love.SAKURA.ne.jp Reported-by: syzbot+cd8a9a308e879a4e2c28@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=cd8a9a308e879a4e2c28 Reported-by: syzbot+bc273027d5643e48e5b3@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=bc273027d5643e48e5b3 Depends-on: "block: Add post_release() operation" Fixes: 65565ca5f99b ("block: unify the synchronous bi_end_io callbacks") Assisted-by: Gemini-Pro Signed-off-by: Tetsuo Handa --- drivers/block/loop.c | 68 +++++++++++++++++++++++++++++++++++--------- 1 file changed, 55 insertions(+), 13 deletions(-) diff --git a/drivers/block/loop.c b/drivers/block/loop.c index 758c20678bf6..9fe0f7ca4c7e 100644 --- a/drivers/block/loop.c +++ b/drivers/block/loop.c @@ -75,6 +75,7 @@ struct loop_device { struct gendisk *lo_disk; struct mutex lo_mutex; bool idr_visible; + struct work_struct lo_clr_work; }; struct loop_cmd { @@ -1136,13 +1137,42 @@ static int loop_configure(struct loop_device *lo, blk_mode_t mode, return error; } -static void __loop_clr_fd(struct loop_device *lo) +static void __loop_clr_fd(struct work_struct *work) { + struct loop_device *lo = container_of(work, struct loop_device, lo_clr_work); + struct gendisk *disk = lo->lo_disk; struct queue_limits lim; struct file *filp; gfp_t gfp = lo->old_gfp_mask; int err; + /* Step 1: Flush all outstanding I/O, without open_mutex held. */ + /* + * Since loop_queue_rq() is called with RCU read lock, this synchronize_rcu() + * makes sure that no more queue_work() calls are made from loop_queue_work() + * from loop_queue_rq(). Subsequent loop_queue_rq() calls which are made after + * this synchronize_rcu() returned shall see lo->lo_state != Lo_bound and + * return with BLK_STS_IOERR. + */ + synchronize_rcu(); + /* + * This drain_workqueue() makes sure that no more loop_handle_cmd() calls are + * made from loop_process_work() from loop_workfn()/loop_rootcg_workfn(). + */ + drain_workqueue(lo->workqueue); + /* + * This blk_mq_freeze_queue() waits for completion of all outstanding I/O + * which has been scheduled via loop_queue_rq(), by waiting for q_usage_counter + * to reach 0. Since the lo->lo_state != Lo_bound check in loop_queue_rq() + * guarantees that no more new I/O requests are made, we can call + * blk_mq_unfreeze_queue() immediately after blk_mq_freeze_queue() returns. + */ + blk_mq_unfreeze_queue(lo->lo_queue, blk_mq_freeze_queue(lo->lo_queue)); + + /* Step 2: Perform remaining cleanup, with open_mutex held. */ + mutex_lock(&disk->open_mutex); + WARN_ON_ONCE(lo->lo_state != Lo_rundown); + spin_lock_irq(&lo->lo_lock); filp = lo->lo_backing_file; lo->lo_backing_file = NULL; @@ -1153,12 +1183,7 @@ static void __loop_clr_fd(struct loop_device *lo) lo->lo_sizelimit = 0; memset(lo->lo_file_name, 0, LO_NAME_SIZE); - /* - * Reset the block size to the default. - * - * No queue freezing needed because this is called from the final - * ->release call only, so there can't be any outstanding I/O. - */ + /* Reset the block size to the default. */ lim = queue_limits_start_update(lo->lo_queue); lim.logical_block_size = SECTOR_SIZE; lim.physical_block_size = SECTOR_SIZE; @@ -1201,11 +1226,9 @@ static void __loop_clr_fd(struct loop_device *lo) WRITE_ONCE(lo->lo_state, Lo_unbound); mutex_unlock(&lo->lo_mutex); - /* - * Need not hold lo_mutex to fput backing file. Calling fput holding - * lo_mutex triggers a circular lock dependency possibility warning as - * fput can take open_mutex which is usually taken before lo_mutex. - */ + /* Step 3: Drop refcounts, without open_mutex held. */ + mutex_unlock(&disk->open_mutex); + fput(filp); } @@ -1771,8 +1794,22 @@ static void lo_release(struct gendisk *disk) need_clear = (lo->lo_state == Lo_rundown); mutex_unlock(&lo->lo_mutex); + /* + * In order to flush outstanding I/O (without open_mutex for deadlock + * avoidance) before clearing the backing device, defer __loop_clr_fd() + * to WQ context and let lo_post_release() wait for completion. + * The Lo_rundown state guarantees that lo_open() will fail with -ENXIO. + */ if (need_clear) - __loop_clr_fd(lo); + queue_work(system_long_wq, &lo->lo_clr_work); +} + +static void lo_post_release(struct gendisk *disk) +{ + struct loop_device *lo = disk->private_data; + + /* Wait for __loop_clr_fd() to complete. */ + flush_work(&lo->lo_clr_work); } static void lo_free_disk(struct gendisk *disk) @@ -1791,6 +1828,7 @@ static const struct block_device_operations lo_fops = { .owner = THIS_MODULE, .open = lo_open, .release = lo_release, + .post_release = lo_post_release, .ioctl = lo_ioctl, #ifdef CONFIG_COMPAT .compat_ioctl = lo_compat_ioctl, @@ -2036,6 +2074,7 @@ static int loop_add(int i) lo = kzalloc_obj(*lo); if (!lo) goto out; + INIT_WORK(&lo->lo_clr_work, __loop_clr_fd); lo->worker_tree = RB_ROOT; INIT_LIST_HEAD(&lo->idle_worker_list); timer_setup(&lo->timer, loop_free_idle_workers_timer, TIMER_DEFERRABLE); @@ -2140,6 +2179,9 @@ static int loop_add(int i) static void loop_remove(struct loop_device *lo) { + /* Wait for __loop_clr_fd() to complete. */ + flush_work(&lo->lo_clr_work); + /* Make this loop device unreachable from pathname. */ del_gendisk(lo->lo_disk); blk_mq_free_tag_set(&lo->tag_set); -- 2.55.0