mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
* [PATCH] nfc: port100: drain cmd_complete_work before freeing the device on disconnect
@ 2026-11-06  9:52 hyunjungg
  0 siblings, 0 replies; only message in thread
From: hyunjungg @ 2026-11-06  9:52 UTC (permalink / raw)
  To: David Heidelberg; +Cc: oe-linux-nfc, linux-kernel, Hyunjung Ko

From: Hyunjung Ko <hj351016@gmail.com>

port100_disconnect() tears the device down in an order that lets
cmd_complete_work run against freed memory:

	nfc_digital_unregister_device(dev->nfc_digital_dev);
	nfc_digital_free_device(dev->nfc_digital_dev);   /* ddev freed */

	usb_kill_urb(dev->in_urb);                       /* -> schedule_work() */
	usb_kill_urb(dev->out_urb);

	usb_free_urb(dev->in_urb);
	usb_free_urb(dev->out_urb);

	kfree(dev->cmd);                                 /* cmd freed */

nfc_digital_unregister_device() does not abort a transport command that
is already in flight, so an IN URB can still be pending here. The URB
completion handlers port100_recv_response() / port100_recv_ack() schedule
cmd_complete_work whenever the IN URB completes with an error status,
including the -ECONNRESET that usb_kill_urb(dev->in_urb) produces
synchronously for the in-flight command. The work handler
port100_wq_cmd_complete() then runs port100_send_async_complete():

	static void port100_send_async_complete(struct port100 *dev)
	{
		struct port100_cmd *cmd = dev->cmd;
		int status = cmd->status;		/* UAF read   */
		...
		cmd->complete_cb(dev->nfc_digital_dev, ...);	/* freed ddev */
		...
		kfree(cmd);				/* double free */
	}

Nothing drains the work, so a command in flight at disconnect leaves
cmd_complete_work queued (or running) and it dereferences dev->cmd (freed
by kfree(dev->cmd)), dev->nfc_digital_dev (freed by
nfc_digital_free_device()) and ultimately dev itself (devres-freed after
disconnect returns) -> a use-after-free plus double-free.

Kill the URBs and cancel_work_sync() the work *before*
nfc_digital_free_device(), so the completion that usb_kill_urb() forces
cannot re-queue the work and the handler cannot run (or is waited out)
while dev->nfc_digital_dev and dev are still valid. Placing the drain
after nfc_digital_free_device() would be insufficient: on SMP/PREEMPT a
worker that already started executing would be waited on by
cancel_work_sync() while dereferencing the already-freed ddev.

Found by code review and reproduced on 7.3-rc3 with KASAN and
DEBUG_OBJECTS, using dummy_hcd + raw_gadget to emulate the device and
race an in-flight command against USB disconnect: debugobjects reports a
free-active work_struct for port100_wq_cmd_complete, freed from
devres_release_all() via kfree() while still queued.

Signed-off-by: Hyunjung Ko <hj351016@gmail.com>
---
 drivers/nfc/port100.c | 6 ++++--
 1 file changed, 4 insertions(+), 2 deletions(-)

diff --git a/drivers/nfc/port100.c b/drivers/nfc/port100.c
--- a/drivers/nfc/port100.c
+++ b/drivers/nfc/port100.c
@@ -1619,13 +1619,15 @@ static void port100_disconnect(struct usb_interface *interface)
 	dev = usb_get_intfdata(interface);
 	usb_set_intfdata(interface, NULL);

 	nfc_digital_unregister_device(dev->nfc_digital_dev);
-	nfc_digital_free_device(dev->nfc_digital_dev);

 	usb_kill_urb(dev->in_urb);
 	usb_kill_urb(dev->out_urb);
+	cancel_work_sync(&dev->cmd_complete_work);
+
+	nfc_digital_free_device(dev->nfc_digital_dev);

 	usb_free_urb(dev->in_urb);
 	usb_free_urb(dev->out_urb);

 	kfree(dev->cmd);

^ permalink raw reply	[flat|nested] only message in thread

only message in thread, other threads:[~2026-11-06  9:53 UTC | newest]

Thread overview: (only message) (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-11-06  9:52 [PATCH] nfc: port100: drain cmd_complete_work before freeing the device on disconnect hyunjungg

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

all inboxes | Powered by JetHome®