The loop driver relies on lo_release() to automatically clear the loop device via __loop_clr_fd() when the last file descriptor is closed (LO_FLAGS_AUTOCLEAR). Although the backing file structure itself remains allocated in memory thanks to proper file reference counting (f_count is not zero), a severe race condition exists regarding the visibility of the lo->lo_backing_file pointer. This race window was exposed by commit 65565ca5f99b ("block: unify the synchronous bi_end_io callbacks"). By unifying and optimizing the synchronous I/O completion path, the timing and scheduling behavior of the block layer altered significantly. As a result, a highly-concurrent execution pipeline emerged where lo_release() can progress to __loop_clr_fd() and nullify lo->lo_backing_file while an already-scheduled asynchronous I/O work (lo_rw_aio) is just about to be executed by a kworker thread. Since the kworker enters lo_rw_aio() after lo->lo_backing_file has been cleared, it attempts to dereference the now-NULL pointer when initializing the kiocb, leading to the reported NULL pointer dereference bug. To close this race safely without introducing heavy fast-path checks, we must ensure that any running or scheduled dispatch threads have completed before we nullify the pointer. Since loop_queue_rq() operates within the block layer's RCU read-side critical section, invoke synchronize_rcu() and drain_workqueue() in __loop_clr_fd() prior to clearing lo->lo_backing_file. 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 Analyzed-by: AI Mode in Google Search (no mail address) Signed-off-by: Tetsuo Handa --- drivers/block/loop.c | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/drivers/block/loop.c b/drivers/block/loop.c index 0000913f7efc..ff117f340b2f 100644 --- a/drivers/block/loop.c +++ b/drivers/block/loop.c @@ -1118,6 +1118,17 @@ static void __loop_clr_fd(struct loop_device *lo) struct file *filp; gfp_t gfp = lo->old_gfp_mask; + /* + * Now that loop_queue_rq() sees lo->lo_state != Lo_bound, + * wait for already started loop_queue_rq() to complete. + */ + synchronize_rcu(); + /* + * Now that no more works are scheduled by loop_queue_rq(), + * wait for already scheduled works to complete. + */ + drain_workqueue(lo->workqueue); + spin_lock_irq(&lo->lo_lock); filp = lo->lo_backing_file; lo->lo_backing_file = NULL; -- 2.54.0