reset_fdc() arms do_floppy = reset_interrupt; the IRQ handler then queues that function via schedule_bh(). If unlock_fdc() or do_wakeup() clears cont before the work runs, reset_interrupt() dereferences a NULL cont and oopses. Example crash excerpt: [ 1070.468148] status=80 [ 1070.468152] fdc_busy=1 [ 1070.468158] cont= (null) [ 1070.468161] current_req= (null) [ 1070.468162] command_status=-1 [ 1070.468163] [ 1070.557051] floppy0: floppy timeout called [ 1070.557053] no cont in shutdown! [ 1070.557056] floppy0: floppy_shutdown: timeout handler died. [ 1074.419509] floppy0: FDC access conflict! [ 1074.419782] BUG: unable to handle kernel NULL pointer dereference at 0000000000000008 [ 1074.421969] PGD 0 P4D 0 [ 1074.422320] Oops: 0000 [#1] SMP NOPTI [ 1074.422648] CPU: 10 PID: 3269 Comm: kworker/u256:4 Kdump: loaded [ 1074.423830] Hardware name: inspur Standard PC (i440FX + PIIX, 1996), BIOS 0.0.0 02/06/2015 [ 1074.424284] Workqueue: floppy floppy_work_workfn [floppy] [ 1074.424757] RIP: 0010:reset_interrupt+0x3a/0xa0 [floppy] The same NULL deref has also been reported by syzbot on upstream. While a stale reset_interrupt() sits in result(), unlock_fdc() can also clear cont and let a new request install another continuation, so a plain !cont check after result() is not enough. Bump cont_seq when clearing cont, sample it in schedule_bh() as bh_seq, and use cont in reset_interrupt() only when the sequences still match. Reported-by: syzbot+619e27617b2abe6b9b72@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=619e27617b2abe6b9b72 Link: https://lore.kernel.org/linux-block/00000000000093c4d105f9aa34d6@google.com/ Link: https://lists.openwall.net/linux-kernel/2021/10/27/56 Assisted-by: Cursor:Composer Signed-off-by: Yang Xiuwei --- Changes in v2: - Use cont_seq/bh_seq instead of a plain !cont check v1: https://lore.kernel.org/linux-block/20260723073512.11120-1-yangxiuwei@kylinos.cn/ drivers/block/floppy.c | 35 ++++++++++++++++++++++++++++++----- 1 file changed, 30 insertions(+), 5 deletions(-) diff --git a/drivers/block/floppy.c b/drivers/block/floppy.c index f04397b8e381..ec17625b8c08 100644 --- a/drivers/block/floppy.c +++ b/drivers/block/floppy.c @@ -536,6 +536,25 @@ static const struct cont_t { * succeeded/failed */ } *cont; +static atomic_t cont_seq = ATOMIC_INIT(0); +static unsigned int bh_seq; + +static void cont_invalidate(void) +{ + WRITE_ONCE(cont, NULL); + atomic_inc_return_release(&cont_seq); +} + +static const struct cont_t *cont_for_bh(void) +{ + unsigned int seq = atomic_read_acquire(&cont_seq); + const struct cont_t *c = READ_ONCE(cont); + + if (seq != bh_seq || !c) + return NULL; + return c; +} + static void floppy_ready(void); static void floppy_start(void); static void process_fd_request(void); @@ -907,7 +926,7 @@ static void unlock_fdc(void) command_status = FD_COMMAND_NONE; cancel_delayed_work(&fd_timeout); do_floppy = NULL; - cont = NULL; + cont_invalidate(); clear_bit(0, &fdc_busy); wake_up(&fdc_wait); } @@ -997,6 +1016,7 @@ static void schedule_bh(void (*handler)(void)) { WARN_ON(work_pending(&floppy_work)); + bh_seq = atomic_read(&cont_seq); floppy_work_fn = handler; queue_work(floppy_wq, &floppy_work); } @@ -1782,13 +1802,18 @@ static void recalibrate_floppy(void) */ static void reset_interrupt(void) { + const struct cont_t *c; + debugt(__func__, ""); result(current_fdc); /* get the status ready for set_fdc */ + c = cont_for_bh(); + if (!c) + return; if (fdc_state[current_fdc].reset) { - pr_info("reset set in interrupt, calling %ps\n", cont->error); - cont->error(); /* a reset just after a reset. BAD! */ + pr_info("reset set in interrupt, calling %ps\n", c->error); + c->error(); /* a reset just after a reset. BAD! */ } - cont->redo(); + c->redo(); } /* @@ -1988,7 +2013,7 @@ static void floppy_start(void) static void do_wakeup(void) { reschedule_timeout(MAXTIMEOUT, "do wakeup"); - cont = NULL; + cont_invalidate(); command_status += 2; wake_up(&command_done); } -- 2.25.1