From 74c2e2eeb7e51cdeb5e3eceaf324cfcf8dcd0d32 Mon Sep 17 00:00:00 2001 From: Karl Mehltretter Date: Wed, 22 Jul 2026 05:44:35 +0200 Subject: [PATCH 1/6] floppy: flush pending work before releasing resources on init failure do_floppy_init() probes each FDC with user_reset_fdc(), which queues a redo_fd_request() work item on floppy_wq that may still be pending when the probe finishes. When no controller is found, the error path calls floppy_release_irq_and_dma() to free the IRQ and DMA channel, and only later destroys the workqueue. destroy_workqueue() drains the queued work last, so it can run after the IRQ and DMA are already gone: floppy0: no floppy controllers found work still pending The flush used to be here. Commit 070ad7e793dc ("floppy: convert to delayed work and single-thread wq") renamed the label out_flush_work to out_release_dma and dropped it. Restore it so the work drains before the IRQ and DMA are released, as floppy_module_exit() already does. Fixes: 070ad7e793dc ("floppy: convert to delayed work and single-thread wq") Assisted-by: Claude:claude-opus-4-8 Signed-off-by: Karl Mehltretter Link: https://patch.msgid.link/20260722034435.13432-1-kmehltretter@gmail.com Signed-off-by: Denis Efremov (Oracle) --- drivers/block/floppy.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/block/floppy.c b/drivers/block/floppy.c index f04397b8e381..6cfd114d27df 100644 --- a/drivers/block/floppy.c +++ b/drivers/block/floppy.c @@ -4744,6 +4744,8 @@ static int __init do_floppy_init(void) } } out_release_dma: + /* Drain the queued redo_fd_request() before releasing IRQ and DMA. */ + flush_workqueue(floppy_wq); if (atomic_read(&usage_count)) floppy_release_irq_and_dma(); out_unreg_driver: From 7c82781039c03a3e47b70d3259f336adbb9d2692 Mon Sep 17 00:00:00 2001 From: Yang Xiuwei Date: Thu, 23 Jul 2026 15:35:12 +0800 Subject: [PATCH 2/6] floppy: avoid NULL deref in reset_interrupt when cont is cleared 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. Return early if cont is already NULL after result(). The hardware reset has completed; result() still drains the FDC. A later request can lock the FDC and reset again. This is a defensive fix for stale work, not a rewrite of the floppy continuation state machine. Reported-by: syzbot+619e27617b2abe6b9b72@syzkaller.appspotmail.com Link: https://lore.kernel.org/linux-block/00000000000093c4d105f9aa34d6@google.com/ Link: https://lists.openwall.net/linux-kernel/2021/10/27/56 Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Signed-off-by: Yang Xiuwei Link: https://patch.msgid.link/20260723073512.11120-1-yangxiuwei@kylinos.cn Signed-off-by: Denis Efremov (Oracle) --- drivers/block/floppy.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/block/floppy.c b/drivers/block/floppy.c index 6cfd114d27df..7c35366f601c 100644 --- a/drivers/block/floppy.c +++ b/drivers/block/floppy.c @@ -1784,6 +1784,9 @@ static void reset_interrupt(void) { debugt(__func__, ""); result(current_fdc); /* get the status ready for set_fdc */ + /* Stale work: unlock_fdc()/do_wakeup() may have cleared cont. */ + if (!cont) + 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! */ From f1e72d80ad4ac5ff3573796d07bd1a1e74909f94 Mon Sep 17 00:00:00 2001 From: Guangshuo Li Date: Wed, 8 Jul 2026 18:57:03 +0800 Subject: [PATCH 3/6] floppy: unregister platform device on add_disk failure The change referenced by the Fixes tag reverted a previous fix because that fix added incorrect cleanup for the platform_device_register() failure path. However, reverting the whole change also removed the cleanup for a later failure path. If platform_device_register() succeeds but device_add_disk() fails, the current floppy platform device has already been registered and registered[drive] has been set. The common out_remove_drives path uses while (drive--), so it starts from the previous drive and does not unregister the current one. As a result, the platform device registered for the failing drive is leaked. Unregister the current platform device directly on the device_add_disk() failure path, where platform_device_register() is known to have succeeded. Leave the platform_device_register() failure path unchanged. Fixes: 895a9b37917d ("Revert "floppy: fix reference leak on platform_device_register() failure"") Fixes: 47d34aa2d211 ("floppy: add error handling support for add_disk()") Signed-off-by: Guangshuo Li Link: https://patch.msgid.link/20260708105703.747262-1-lgs201920130244@gmail.com Signed-off-by: Denis Efremov (Oracle) --- drivers/block/floppy.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/block/floppy.c b/drivers/block/floppy.c index 7c35366f601c..b6d5d60f7693 100644 --- a/drivers/block/floppy.c +++ b/drivers/block/floppy.c @@ -4732,8 +4732,11 @@ static int __init do_floppy_init(void) err = device_add_disk(&floppy_device[drive].dev, disks[drive][0], NULL); - if (err) + if (err) { + platform_device_unregister(&floppy_device[drive]); + registered[drive] = false; goto out_remove_drives; + } } return 0; From 9dfafe7ed037eb3fdd102e7a4241a7cfe4f688e5 Mon Sep 17 00:00:00 2001 From: Cen Zhang Date: Wed, 18 Mar 2026 11:48:10 +0800 Subject: [PATCH 4/6] floppy: annotate data-races around command_status and floppy_work_fn command_status is accessed by multiple contexts: - generic_done() and do_wakeup() write it from the floppy workqueue context. - wait_til_done() reads it in wait_event conditions without holding fdc_busy. - is_alive() reads it locklessly to check driver liveness. - floppy_queue_rq(), lock_fdc(), and unlock_fdc() write it during FDC ownership transitions. There is currently no LKMM annotation for these concurrent accesses since command_status relies on the deprecated volatile qualifier. Remove volatile and use READ_ONCE()/WRITE_ONCE() on every access to command_status to provide proper LKMM data-race annotations. Also annotate floppy_work_fn with WRITE_ONCE() in schedule_bh() and READ_ONCE() in floppy_work_workfn(), and current_type[drive] with READ_ONCE() in drive_no_geom() where it can be read without holding the FDC lock. The races were found by our tools: 1) command_status: generic_done (write) vs wait_til_done (read) ============ DATARACE ============ Function: generic_done+0x54/0xa0 drivers/block/floppy.c:985 Function: success_and_wakeup+0xeb/0x1e0 drivers/block/floppy.c:2046 Function: setup_rw_floppy+0x15dc/0x1d80 drivers/block/floppy.c:1046 Function: floppy_ready+0x2630/0x4000 Function: floppy_work_workfn+0x56/0x70 drivers/block/floppy.c:1012 Function: process_one_work kernel/workqueue.c:3236 [inline] Function: process_scheduled_works+0x7a8/0x1030 kernel/workqueue.c:3319 ============OTHER_INFO============ Function: wait_til_done+0x134/0x740 drivers/block/floppy.c:2026 Function: poll_drive+0x1fa/0x2a0 Function: fd_ioctl+0x13c8/0x1da0 drivers/block/floppy.c:3873 Function: blkdev_ioctl+0x421/0x510 block/ioctl.c:705 Function: vfs_ioctl fs/ioctl.c:51 [inline] Function: __do_sys_ioctl fs/ioctl.c:598 [inline] Function: __se_sys_ioctl+0xfc/0x170 fs/ioctl.c:584 =================END============== 2) current_type[drive]: drive_no_geom (read) vs set_geometry (write) ============ DATARACE ============ Function: drive_no_geom drivers/block/floppy.c:606 [inline] Function: floppy_check_events+0x9c9/0xcf0 drivers/block/floppy.c:4187 Function: disk_check_events+0xca/0x4d0 block/disk-events.c:193 Function: process_one_work kernel/workqueue.c:3236 [inline] Function: process_scheduled_works+0x7a8/0x1030 kernel/workqueue.c:3319 Function: worker_thread+0xb97/0x11d0 kernel/workqueue.c:3400 Function: kthread+0x3d4/0x800 kernel/kthread.c:463 ============OTHER_INFO============ Function: set_geometry+0xe1d/0x1980 drivers/block/floppy.c:2237 Function: fd_ioctl+0xff4/0x1da0 drivers/block/floppy.c:3912 Function: blkdev_ioctl+0x421/0x510 block/ioctl.c:705 Function: vfs_ioctl fs/ioctl.c:51 [inline] Function: __do_sys_ioctl fs/ioctl.c:598 [inline] Function: __se_sys_ioctl+0xfc/0x170 fs/ioctl.c:584 =================END============== 3) floppy_work_fn: schedule_bh (write) vs floppy_interrupt->schedule_bh (write) ============ DATARACE ============ Function: schedule_bh drivers/block/floppy.c:1005 [inline] Function: do_wakeup+0x19c/0x220 drivers/block/floppy.c:3219 Function: success_and_wakeup+0x192/0x1e0 drivers/block/floppy.c:1994 Function: setup_rw_floppy+0x15dc/0x1d80 drivers/block/floppy.c:1046 Function: floppy_ready+0x2630/0x4000 Function: floppy_work_workfn+0x56/0x70 drivers/block/floppy.c:1012 Function: process_one_work kernel/workqueue.c:3236 [inline] Function: process_scheduled_works+0x7a8/0x1030 kernel/workqueue.c:3319 ============OTHER_INFO============ Function: floppy_interrupt+0xbf4/0x1220 Function: floppy_hardint+0x432/0x630 Function: __handle_irq_event_percpu+0x10a/0x5d0 kernel/irq/handle.c:158 Function: handle_irq_event+0x8b/0x1e0 kernel/irq/handle.c:210 Function: handle_edge_irq+0x223/0x9f0 kernel/irq/chip.c:855 =================END============== Signed-off-by: Cen Zhang Link: https://patch.msgid.link/20260318034810.3089217-1-zzzccc427@gmail.com Signed-off-by: Denis Efremov (Oracle) --- drivers/block/floppy.c | 38 +++++++++++++++++++++----------------- 1 file changed, 21 insertions(+), 17 deletions(-) diff --git a/drivers/block/floppy.c b/drivers/block/floppy.c index b6d5d60f7693..63304d3ef051 100644 --- a/drivers/block/floppy.c +++ b/drivers/block/floppy.c @@ -500,7 +500,7 @@ static int probing; #define FD_COMMAND_ERROR 2 #define FD_COMMAND_OKAY 3 -static volatile int command_status = FD_COMMAND_NONE; +static int command_status = FD_COMMAND_NONE; static unsigned long fdc_busy; static DECLARE_WAIT_QUEUE_HEAD(fdc_wait); static DECLARE_WAIT_QUEUE_HEAD(command_done); @@ -599,7 +599,8 @@ static inline void fdc_outb(unsigned char value, int fdc, int reg) static inline bool drive_no_geom(int drive) { - return !current_type[drive] && !ITYPE(drive_state[drive].fd_device); + return !READ_ONCE(current_type[drive]) && + !ITYPE(drive_state[drive].fd_device); } #ifndef fd_eject @@ -638,7 +639,7 @@ static const char *timeout_message; static void is_alive(const char *func, const char *message) { /* this routine checks whether the floppy driver is "alive" */ - if (test_bit(0, &fdc_busy) && command_status < 2 && + if (test_bit(0, &fdc_busy) && READ_ONCE(command_status) < 2 && !delayed_work_pending(&fd_timeout)) { DPRINT("%s: timeout handler died. %s\n", func, message); } @@ -890,7 +891,7 @@ static int lock_fdc(int drive) if (wait_event_interruptible(fdc_wait, !test_and_set_bit(0, &fdc_busy))) return -EINTR; - command_status = FD_COMMAND_NONE; + WRITE_ONCE(command_status, FD_COMMAND_NONE); reschedule_timeout(drive, "lock fdc"); set_fdc(drive); @@ -904,7 +905,7 @@ static void unlock_fdc(void) DPRINT("FDC access conflict!\n"); raw_cmd = NULL; - command_status = FD_COMMAND_NONE; + WRITE_ONCE(command_status, FD_COMMAND_NONE); cancel_delayed_work(&fd_timeout); do_floppy = NULL; cont = NULL; @@ -988,7 +989,9 @@ static void (*floppy_work_fn)(void); static void floppy_work_workfn(struct work_struct *work) { - floppy_work_fn(); + void (*fn)(void) = READ_ONCE(floppy_work_fn); + + fn(); } static DECLARE_WORK(floppy_work, floppy_work_workfn); @@ -997,7 +1000,7 @@ static void schedule_bh(void (*handler)(void)) { WARN_ON(work_pending(&floppy_work)); - floppy_work_fn = handler; + WRITE_ONCE(floppy_work_fn, handler); queue_work(floppy_wq, &floppy_work); } @@ -1865,7 +1868,7 @@ static void show_floppy(int fdc) pr_info("cont=%p\n", cont); pr_info("current_req=%p\n", current_req); - pr_info("command_status=%d\n", command_status); + pr_info("command_status=%d\n", READ_ONCE(command_status)); pr_info("\n"); } @@ -1992,7 +1995,7 @@ static void do_wakeup(void) { reschedule_timeout(MAXTIMEOUT, "do wakeup"); cont = NULL; - command_status += 2; + WRITE_ONCE(command_status, READ_ONCE(command_status) + 2); wake_up(&command_done); } @@ -2020,11 +2023,12 @@ static int wait_til_done(void (*handler)(void), bool interruptible) schedule_bh(handler); if (interruptible) - wait_event_interruptible(command_done, command_status >= 2); + wait_event_interruptible(command_done, + READ_ONCE(command_status) >= 2); else - wait_event(command_done, command_status >= 2); + wait_event(command_done, READ_ONCE(command_status) >= 2); - if (command_status < 2) { + if (READ_ONCE(command_status) < 2) { cancel_activity(); cont = &intr_cont; reset_fdc(); @@ -2032,18 +2036,18 @@ static int wait_til_done(void (*handler)(void), bool interruptible) } if (fdc_state[current_fdc].reset) - command_status = FD_COMMAND_ERROR; - if (command_status == FD_COMMAND_OKAY) + WRITE_ONCE(command_status, FD_COMMAND_ERROR); + if (READ_ONCE(command_status) == FD_COMMAND_OKAY) ret = 0; else ret = -EIO; - command_status = FD_COMMAND_NONE; + WRITE_ONCE(command_status, FD_COMMAND_NONE); return ret; } static void generic_done(int result) { - command_status = result; + WRITE_ONCE(command_status, result); cont = &wakeup_cont; } @@ -2874,7 +2878,7 @@ static blk_status_t floppy_queue_rq(struct blk_mq_hw_ctx *hctx, list_add_tail(&bd->rq->queuelist, &floppy_reqs); spin_unlock_irq(&floppy_lock); - command_status = FD_COMMAND_NONE; + WRITE_ONCE(command_status, FD_COMMAND_NONE); __reschedule_timeout(MAXTIMEOUT, "fd_request"); set_fdc(0); process_fd_request(); From 66be62ee5d64b6ad1285978644798d8611add931 Mon Sep 17 00:00:00 2001 From: "Denis Efremov (Oracle)" Date: Thu, 24 Sep 2026 13:48:39 +0400 Subject: [PATCH 5/6] floppy: don't store a stack buffer in timeout_message request_done() formats "request done %d" into a buffer on its stack and passes it to reschedule_timeout(), which keeps the pointer in timeout_message. show_floppy() prints timeout_message long after request_done() has returned, e.g. from result() in the interrupt handler, and reads a dead stack frame: BUG: KASAN: stack-out-of-bounds in string+0x39c/0x3d0 lib/vsprintf.c:720 Read of size 1 at addr ffffc900003cfc70 by task swapper/3/0 Call Trace: ... show_floppy+0xb1/0x45f drivers/block/floppy.c:1838 result.cold+0x2b/0x30 drivers/block/floppy.c:1183 floppy_interrupt+0x191/0x340 drivers/block/floppy.c:1755 floppy_hardint+0x1a7/0x200 arch/x86/include/asm/floppy.h:66 uptodate is either 0 or 1, so pass one of two string literals instead. Cc: stable@vger.kernel.org Fixes: 73507e6cd8dc ("drivers/block/floppy.c: remove unnecessary argument from [__]reschedule_timeout") Reported-by: syzbot+874c2707f97fac24b090@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=874c2707f97fac24b090 Link: https://patch.msgid.link/20260924094839.3239569-1-efremov@linux.com Signed-off-by: Denis Efremov (Oracle) --- drivers/block/floppy.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/drivers/block/floppy.c b/drivers/block/floppy.c index 63304d3ef051..c0deff043dab 100644 --- a/drivers/block/floppy.c +++ b/drivers/block/floppy.c @@ -2283,11 +2283,10 @@ static void request_done(int uptodate) { struct request *req = current_req; int block; - char msg[sizeof("request done ") + sizeof(int) * 3]; probing = 0; - snprintf(msg, sizeof(msg), "request done %d", uptodate); - reschedule_timeout(MAXTIMEOUT, msg); + reschedule_timeout(MAXTIMEOUT, + uptodate ? "request done 1" : "request done 0"); if (!req) { pr_info("floppy.c: no request in request_done\n"); From bb38473f1bcb287567f2fa7980e483a778092504 Mon Sep 17 00:00:00 2001 From: "Denis Efremov (Oracle)" Date: Thu, 24 Sep 2026 15:28:25 +0400 Subject: [PATCH 6/6] floppy: return the drive state flags from the 32-bit FDGETDRVSTAT compat_getdrvstat() copies every field of the drive state to a 32-bit caller except ->flags, which stays zero. 32-bit FDGETDRVSTAT and FDPOLLDRVSTAT therefore never report FD_DISK_WRITABLE, FD_DISK_CHANGED or FD_VERIFY: i386 fdutils (floppycontrol -P, fdmount), and other 32-bit users see every disk as write-protected and never see a disk change. The compat code in block/compat_ioctl.c copied the field; it was lost when that code moved into floppy.c Also declare the field as compat_ulong_t: the 32-bit struct floppy_drive_struct starts with an unsigned long, and a signed char at offset 0 only lines up with its low byte on little-endian machines. The size and offsets of the compat structure, and so FDGETDRVSTAT32, do not change. Cc: stable@vger.kernel.org Fixes: 229b53c9bf4e ("take floppy compat ioctls to sodding floppy.c") Link: https://patch.msgid.link/20260924112825.3525931-1-efremov@linux.com Signed-off-by: Denis Efremov (Oracle) --- drivers/block/floppy.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/block/floppy.c b/drivers/block/floppy.c index c0deff043dab..c677d783811f 100644 --- a/drivers/block/floppy.c +++ b/drivers/block/floppy.c @@ -3607,7 +3607,7 @@ struct compat_floppy_drive_params { }; struct compat_floppy_drive_struct { - signed char flags; + compat_ulong_t flags; compat_ulong_t spinup_date; compat_ulong_t select_date; compat_ulong_t first_read_date; @@ -3791,6 +3791,7 @@ static int compat_getdrvstat(int drive, bool poll, goto Eintr; process_fd_request(); } + v.flags = drive_state[drive].flags; v.spinup_date = drive_state[drive].spinup_date; v.select_date = drive_state[drive].select_date; v.first_read_date = drive_state[drive].first_read_date;