From: Yuqi Xu <xuyuqiabc@gmail.com>
To: netdev@vger.kernel.org, oe-linux-nfc@lists.linux.dev
Cc: David Heidelberg <david@ixit.cz>,
"David S. Miller" <davem@davemloft.net>,
Eric Dumazet <edumazet@google.com>,
Jakub Kicinski <kuba@kernel.org>, Paolo Abeni <pabeni@redhat.com>,
Simon Horman <horms@kernel.org>,
stable@vger.kernel.org, Vega <vega@nebusec.ai>,
Ren Wei <weir@nebusec.ai>,
xuyq21@lenovo.com, Mark Greer <mgreer@animalcreek.com>,
"Uwe Kleine-König (The Capable Hub)"
<u.kleine-koenig@baylibre.com>,
Ian Ray <ian.ray@gehealthcare.com>,
Dmitry Torokhov <dmitry.torokhov@gmail.com>,
Thierry Escande <thierry.escande@linux.intel.com>,
Samuel Ortiz <sameo@linux.intel.com>
Subject: [PATCH net 1/1] nfc: digital: fix use-after-free in unregister
Date: Mon, 26 Oct 2026 15:24:07 +0800 [thread overview]
Message-ID: <1792999448.107874-2-xuyuqiabc@gmail.com> (raw)
In-Reply-To: <cover.1791172164.git.xuyuqiabc@gmail.com>
nfc_digital_unregister_device() cancels its own workqueue items and
then frees every queued digital_cmd. A command already submitted to
the driver remains the argument of the async completion.
digital_send_cmd_complete() can still assign cmd->resp after that
object has been freed, which is a use-after-free.
Abort the in-flight driver command before reclaiming the queue, and
drain the completion work the driver may schedule from abort. Once
the nfc device is shutting down, do not queue or submit another
command: finishing the aborted one would otherwise hand the next
queued command to the driver while it is being freed.
Port100 and nfcsim invoke the digital completion from a private work
item, so flush that work before abort returns. trf7970a completes from
a delayed timeout work and from its IRQ thread; handle its MD listening
state in abort and shutdown, and stop the timer before the device is
released. st95hf's IRQ thread can observe that remove already ran and
must not call back into the freed command.
Fixes: 59ee2361c924 ("NFC Digital: Implement driver commands mechanism")
Cc: stable@vger.kernel.org
Reported-by: Vega <vega@nebusec.ai>
Assisted-by: LLM
Signed-off-by: Yuqi Xu <xuyuqiabc@gmail.com>
Reviewed-by: Ren Wei <weir@nebusec.ai>
---
drivers/nfc/nfcsim.c | 8 +++++++-
drivers/nfc/port100.c | 7 +++++++
drivers/nfc/st95hf/core.c | 13 ++++++++++---
drivers/nfc/trf7970a.c | 5 +++++
net/nfc/digital_core.c | 22 +++++++++++++++++++++-
5 files changed, 50 insertions(+), 5 deletions(-)
diff --git a/drivers/nfc/nfcsim.c b/drivers/nfc/nfcsim.c
index f8e5c4da3b13..52d1fbf8e532 100644
--- a/drivers/nfc/nfcsim.c
+++ b/drivers/nfc/nfcsim.c
@@ -239,9 +239,15 @@ static int nfcsim_send(struct nfc_digital_dev *ddev, struct sk_buff *skb,
static void nfcsim_abort_cmd(struct nfc_digital_dev *ddev)
{
- const struct nfcsim *dev = nfc_digital_get_drvdata(ddev);
+ struct nfcsim *dev = nfc_digital_get_drvdata(ddev);
nfcsim_link_recv_cancel(dev->link_in);
+ /*
+ * recv_work blocks in the link wait and then calls the digital
+ * completion. Wake it and wait so the callback cannot run after
+ * abort_cmd() returns.
+ */
+ flush_work(&dev->recv_work);
}
static int nfcsim_switch_rf(struct nfc_digital_dev *ddev, bool on)
diff --git a/drivers/nfc/port100.c b/drivers/nfc/port100.c
index e769a30b8b5c..738acb87f19b 100644
--- a/drivers/nfc/port100.c
+++ b/drivers/nfc/port100.c
@@ -968,6 +968,13 @@ static void port100_abort_cmd(struct nfc_digital_dev *ddev)
/* cancel the urb request */
usb_kill_urb(dev->in_urb);
+
+ /*
+ * The URB completion handler only queues cmd_complete_work, which
+ * is what calls back into the digital stack. Run it before
+ * returning so that callback cannot outlive abort_cmd().
+ */
+ flush_work(&dev->cmd_complete_work);
}
static struct sk_buff *port100_alloc_skb(const struct port100 *dev, unsigned int size)
diff --git a/drivers/nfc/st95hf/core.c b/drivers/nfc/st95hf/core.c
index 4d772a308bff..b39a5215dad7 100644
--- a/drivers/nfc/st95hf/core.c
+++ b/drivers/nfc/st95hf/core.c
@@ -856,9 +856,16 @@ static irqreturn_t st95hf_irq_thread_handler(int irq, void *st95hfcontext)
kfree_skb(skb_resp);
wtx = false;
cb_arg->rats = false;
- skb_resp = ERR_PTR(result);
- /* call of callback with error */
- cb_arg->complete_cb(stcontext->ddev, cb_arg->cb_usrarg, skb_resp);
+ /*
+ * remove() frees the digital command before dropping rm_lock.
+ * The completion argument is then gone; do not write it.
+ */
+ if (!stcontext->nfcdev_free) {
+ skb_resp = ERR_PTR(result);
+ /* call of callback with error */
+ cb_arg->complete_cb(stcontext->ddev, cb_arg->cb_usrarg,
+ skb_resp);
+ }
/* up the semaphore before returning */
up(&stcontext->exchange_lock);
mutex_unlock(&stcontext->rm_lock);
diff --git a/drivers/nfc/trf7970a.c b/drivers/nfc/trf7970a.c
index ddfc58c29964..962a5e08bcd0 100644
--- a/drivers/nfc/trf7970a.c
+++ b/drivers/nfc/trf7970a.c
@@ -1903,6 +1903,7 @@ static void trf7970a_abort_cmd(struct nfc_digital_dev *ddev)
trf->aborting = true;
break;
case TRF7970A_ST_LISTENING:
+ case TRF7970A_ST_LISTENING_MD:
trf->ignore_timeout = !cancel_delayed_work(&trf->timeout_work);
trf7970a_send_err_upstream(trf, -ECANCELED);
dev_dbg(trf->dev, "Abort process complete\n");
@@ -2017,6 +2018,8 @@ static void trf7970a_shutdown(struct trf7970a *trf)
case TRF7970A_ST_WAIT_FOR_RX_DATA_CONT:
case TRF7970A_ST_WAIT_TO_ISSUE_EOF:
case TRF7970A_ST_LISTENING:
+ case TRF7970A_ST_LISTENING_MD:
+ trf->ignore_timeout = !cancel_delayed_work(&trf->timeout_work);
trf7970a_send_err_upstream(trf, -ECANCELED);
fallthrough;
case TRF7970A_ST_IDLE:
@@ -2223,6 +2226,8 @@ static void trf7970a_remove(struct spi_device *spi)
{
struct trf7970a *trf = spi_get_drvdata(spi);
+ cancel_delayed_work_sync(&trf->timeout_work);
+
mutex_lock(&trf->lock);
trf7970a_shutdown(trf);
diff --git a/net/nfc/digital_core.c b/net/nfc/digital_core.c
index 18236221d898..51d826c7325f 100644
--- a/net/nfc/digital_core.c
+++ b/net/nfc/digital_core.c
@@ -162,7 +162,11 @@ static void digital_wq_cmd(struct work_struct *work)
cmd = list_first_entry_or_null(&ddev->cmd_queue, struct digital_cmd,
queue);
- if (!cmd || cmd->pending) {
+ /*
+ * Unregister has already handed an in-flight command to the driver
+ * and will free whatever is still queued. Do not submit another one.
+ */
+ if (ddev->nfc_dev->shutting_down || !cmd || cmd->pending) {
mutex_unlock(&ddev->cmd_lock);
return;
}
@@ -244,6 +248,11 @@ int digital_send_cmd(struct nfc_digital_dev *ddev, u8 cmd_type,
INIT_LIST_HEAD(&cmd->queue);
mutex_lock(&ddev->cmd_lock);
+ if (ddev->nfc_dev->shutting_down) {
+ mutex_unlock(&ddev->cmd_lock);
+ kfree(cmd);
+ return -ENODEV;
+ }
list_add_tail(&cmd->queue, &ddev->cmd_queue);
mutex_unlock(&ddev->cmd_lock);
@@ -846,6 +855,17 @@ void nfc_digital_unregister_device(struct nfc_digital_dev *ddev)
cancel_work_sync(&ddev->cmd_work);
cancel_work_sync(&ddev->cmd_complete_work);
+ /*
+ * A command with pending set has been given to the driver as the
+ * completion argument. Freeing it here lets a late
+ * digital_send_cmd_complete() write cmd->resp into freed memory.
+ * abort_cmd() drops that exchange; the driver may schedule
+ * cmd_complete_work before it returns, so drain both works again.
+ */
+ digital_abort_cmd(ddev);
+ cancel_work_sync(&ddev->cmd_complete_work);
+ cancel_work_sync(&ddev->cmd_work);
+
list_for_each_entry_safe(cmd, n, &ddev->cmd_queue, queue) {
list_del(&cmd->queue);
--
2.56.0
prev parent reply other threads:[~2026-10-26 7:25 UTC|newest]
Thread overview: 2+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-10-26 7:24 [PATCH net 0/1] nfc: digital: fix use-after-free in unregister Yuqi Xu
2026-10-26 7:24 ` Yuqi Xu [this message]
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=1792999448.107874-2-xuyuqiabc@gmail.com \
--to=xuyuqiabc@gmail.com \
--cc=davem@davemloft.net \
--cc=david@ixit.cz \
--cc=dmitry.torokhov@gmail.com \
--cc=edumazet@google.com \
--cc=horms@kernel.org \
--cc=ian.ray@gehealthcare.com \
--cc=kuba@kernel.org \
--cc=mgreer@animalcreek.com \
--cc=netdev@vger.kernel.org \
--cc=oe-linux-nfc@lists.linux.dev \
--cc=pabeni@redhat.com \
--cc=sameo@linux.intel.com \
--cc=stable@vger.kernel.org \
--cc=thierry.escande@linux.intel.com \
--cc=u.kleine-koenig@baylibre.com \
--cc=vega@nebusec.ai \
--cc=weir@nebusec.ai \
--cc=xuyq21@lenovo.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox