mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
* [PATCH net v3 0/2] nfc: st21nfca: fix SE APDU request race condition
@ 2026-11-12 13:39 Joas Antonio dos Santos via B4 Relay
  2026-11-12 13:39 ` [PATCH net v3 1/2] nfc: st21nfca: add spinlock to serialize SE APDU request state Joas Antonio dos Santos via B4 Relay
  2026-11-12 13:39 ` [PATCH net v3 2/2] nfc: st21nfca: prevent double completion of SE APDU request Joas Antonio dos Santos via B4 Relay
  0 siblings, 2 replies; 3+ messages in thread
From: Joas Antonio dos Santos via B4 Relay @ 2026-11-12 13:39 UTC (permalink / raw)
  To: david, krzk, joasantonio108; +Cc: oe-linux-nfc, netdev, linux-kernel, horms

The BWI timeout work and the EVT_TRANSMIT_DATA handler can both
complete the same SE APDU request.  The callback frees its context, so
the second completion is a use-after-free followed by a double free.

  1/2: Add a spinlock around bwi_active, cb and cb_context in the three
       paths that use them.
  2/2: Use the lock to complete a request only once and to reject
       overlapping requests.

This was found with the help of an LLM-based code review tool (Claude).
I checked the code paths by hand and reviewed the fixes myself.  The
series is build-tested only (W=1, arm64); I do not have the hardware.

Not addressed here, all older than this series and pointed out by the
Sashiko review of v2:

- st21nfca_se_deinit() can leave bwi_timer armed when info is freed
   (it runs before nfc_hci_unregister_device() and only deletes the
   timer when bwi_active is set).  The remove ordering needs its own
   patch.
 - EVT_TRANSMIT_DATA carries no request id, so a response that arrives
   after its request timed out can still be taken for the answer to the
   next request.
 - When nfc_hci_send_event() fails in st21nfca_hci_se_io() the request
   stays pending until the BWI timeout completes it with -ETIME.
 - drivers/nfc/st-nci/se.c has the same double completion.  I will send
   a separate patch for it once the approach here is agreed on.

---
Changes in v3:
- 2/2: free cb_context when a request is rejected with -EBUSY; it was
  leaked (Sashiko).
- 2/2: still send EVT_SE_END_OF_APDU_TRANSFER for a response that
  arrives after the timeout, as before the series; only the callback is
  skipped (Sashiko).
- 1/2: comment on what se_lock protects (checkpatch), commit message
  no longer claims every access is serialized.
- Rebased on v7.4-rc3+, target tree added to the subject.
- Link to v2: https://lore.kernel.org/r/20261110-st21nfca-v2-v2-0-cf9102b314cb@gmail.com

Changes in v2:
- Use a spinlock instead of plain bwi_active flag checks (Simon Horman).
- Split into two patches: locking, then the completion fix.
- Set bwi_active before mod_timer().
- Copy cb/cb_context under the lock, call it after unlocking.
- Reject overlapping se_io requests with -EBUSY.
- Drop the Assisted-by trailer.
- Link to v1: https://lore.kernel.org/r/179332800288.52644.8728400883396719965@gmail.com

---
Joas Antonio dos Santos (2):
      nfc: st21nfca: add spinlock to serialize SE APDU request state
      nfc: st21nfca: prevent double completion of SE APDU request

 drivers/nfc/st21nfca/se.c       | 38 ++++++++++++++++++++++++++++++++++----
 drivers/nfc/st21nfca/st21nfca.h |  2 ++
 2 files changed, 36 insertions(+), 4 deletions(-)
---
base-commit: 9d66f26fefacebfeed15de31c1123451a17ff119
change-id: 20261108-st21nfca-v2-f4c5f861e416

Best regards,
-- 
Joas Antonio dos Santos <joasantonio108@gmail.com>



^ permalink raw reply	[flat|nested] 3+ messages in thread

* [PATCH net v3 1/2] nfc: st21nfca: add spinlock to serialize SE APDU request state
  2026-11-12 13:39 [PATCH net v3 0/2] nfc: st21nfca: fix SE APDU request race condition Joas Antonio dos Santos via B4 Relay
@ 2026-11-12 13:39 ` Joas Antonio dos Santos via B4 Relay
  2026-11-12 13:39 ` [PATCH net v3 2/2] nfc: st21nfca: prevent double completion of SE APDU request Joas Antonio dos Santos via B4 Relay
  1 sibling, 0 replies; 3+ messages in thread
From: Joas Antonio dos Santos via B4 Relay @ 2026-11-12 13:39 UTC (permalink / raw)
  To: david, krzk, joasantonio108; +Cc: oe-linux-nfc, netdev, linux-kernel, horms

From: Joas Antonio dos Santos <joasantonio108@gmail.com>

The bwi_active flag, callback pointer (cb), and callback context
(cb_context) in st21nfca_se_info are accessed concurrently from
multiple contexts without synchronization:

 - st21nfca_hci_se_io() sets them from the netlink path
 - st21nfca_se_wt_work() reads/clears them from the timeout workqueue
 - st21nfca_apdu_reader_event_received() reads/clears them from the
   HCI rx path

Add a spinlock (se_lock) and take it around the accesses in these
three paths.  The callback pointer and context are copied under the
lock and invoked after releasing it, since both the callback and
nfc_hci_send_event() may sleep.

Also move the bwi_active = true assignment before mod_timer() so that
the flag is always set before the timeout handler could observe it.

st21nfca_se_deinit() is not changed here.  It still accesses
bwi_active without the lock; the teardown ordering has problems of its
own which need a separate fix.

Found by LLM-assisted manual code review.

Fixes: 2130fb97fecf ("NFC: st21nfca: Adding support for secure element")
Cc: Simon Horman <horms@kernel.org>
Signed-off-by: Joas Antonio dos Santos <joasantonio108@gmail.com>
---
 drivers/nfc/st21nfca/se.c       | 24 ++++++++++++++++++++----
 drivers/nfc/st21nfca/st21nfca.h |  2 ++
 2 files changed, 22 insertions(+), 4 deletions(-)

diff --git a/drivers/nfc/st21nfca/se.c b/drivers/nfc/st21nfca/se.c
index 7154bc1d644a..03a30f797ff8 100644
--- a/drivers/nfc/st21nfca/se.c
+++ b/drivers/nfc/st21nfca/se.c
@@ -227,11 +227,13 @@ int st21nfca_hci_se_io(struct nfc_hci_dev *hdev, u32 se_idx,
 
 	switch (se_idx) {
 	case ST21NFCA_ESE_HOST_ID:
+		spin_lock(&info->se_info.se_lock);
 		info->se_info.cb = cb;
 		info->se_info.cb_context = cb_context;
+		info->se_info.bwi_active = true;
+		spin_unlock(&info->se_info.se_lock);
 		mod_timer(&info->se_info.bwi_timer, jiffies +
 			  msecs_to_jiffies(info->se_info.wt_timeout));
-		info->se_info.bwi_active = true;
 		return nfc_hci_send_event(hdev, ST21NFCA_APDU_READER_GATE,
 					ST21NFCA_EVT_TRANSMIT_DATA,
 					apdu, apdu_length);
@@ -260,11 +262,17 @@ static void st21nfca_se_wt_work(struct work_struct *work)
 	 */
 	/* hardware reset managed through VCC_UICC_OUT power supply */
 	u8 param = 0x01;
+	se_io_cb_t cb;
+	void *cb_context;
 	struct st21nfca_hci_info *info = container_of(work,
 						struct st21nfca_hci_info,
 						se_info.timeout_work);
 
+	spin_lock(&info->se_info.se_lock);
 	info->se_info.bwi_active = false;
+	cb = info->se_info.cb;
+	cb_context = info->se_info.cb_context;
+	spin_unlock(&info->se_info.se_lock);
 
 	if (!info->se_info.xch_error) {
 		info->se_info.xch_error = true;
@@ -275,7 +283,7 @@ static void st21nfca_se_wt_work(struct work_struct *work)
 		nfc_hci_send_event(info->hdev, ST21NFCA_DEVICE_MGNT_GATE,
 				ST21NFCA_EVT_SE_HARD_RESET, &param, 1);
 	}
-	info->se_info.cb(info->se_info.cb_context, NULL, 0, -ETIME);
+	cb(cb_context, NULL, 0, -ETIME);
 }
 
 static void st21nfca_se_wt_timeout(struct timer_list *t)
@@ -375,6 +383,8 @@ int st21nfca_apdu_reader_event_received(struct nfc_hci_dev *hdev,
 					u8 event, struct sk_buff *skb)
 {
 	int r = 0;
+	se_io_cb_t cb;
+	void *cb_context;
 	struct st21nfca_hci_info *info = nfc_hci_get_clientdata(hdev);
 
 	pr_debug("apdu reader gate event: %x\n", event);
@@ -383,14 +393,19 @@ int st21nfca_apdu_reader_event_received(struct nfc_hci_dev *hdev,
 	case ST21NFCA_EVT_TRANSMIT_DATA:
 		timer_delete_sync(&info->se_info.bwi_timer);
 		cancel_work_sync(&info->se_info.timeout_work);
+
+		spin_lock(&info->se_info.se_lock);
 		info->se_info.bwi_active = false;
+		cb = info->se_info.cb;
+		cb_context = info->se_info.cb_context;
+		spin_unlock(&info->se_info.se_lock);
+
 		r = nfc_hci_send_event(hdev, ST21NFCA_DEVICE_MGNT_GATE,
 				ST21NFCA_EVT_SE_END_OF_APDU_TRANSFER, NULL, 0);
 		if (r < 0)
 			goto exit;
 
-		info->se_info.cb(info->se_info.cb_context,
-			skb->data, skb->len, 0);
+		cb(cb_context, skb->data, skb->len, 0);
 		break;
 	case ST21NFCA_EVT_WTX_REQUEST:
 		mod_timer(&info->se_info.bwi_timer, jiffies +
@@ -416,6 +431,7 @@ void st21nfca_se_init(struct nfc_hci_dev *hdev)
 	/* initialize timers */
 	timer_setup(&info->se_info.bwi_timer, st21nfca_se_wt_timeout, 0);
 	info->se_info.bwi_active = false;
+	spin_lock_init(&info->se_info.se_lock);
 
 	timer_setup(&info->se_info.se_active_timer,
 		    st21nfca_se_activation_timeout, 0);
diff --git a/drivers/nfc/st21nfca/st21nfca.h b/drivers/nfc/st21nfca/st21nfca.h
index ae6771cc9894..03e41fadb5ed 100644
--- a/drivers/nfc/st21nfca/st21nfca.h
+++ b/drivers/nfc/st21nfca/st21nfca.h
@@ -6,6 +6,7 @@
 #ifndef __LOCAL_ST21NFCA_H_
 #define __LOCAL_ST21NFCA_H_
 
+#include <linux/spinlock.h>
 #include <net/nfc/hci.h>
 #include <linux/skbuff.h>
 #include <linux/workqueue.h>
@@ -131,6 +132,7 @@ struct st21nfca_se_info {
 	struct timer_list bwi_timer;
 	int wt_timeout; /* in msecs */
 	bool bwi_active;
+	spinlock_t se_lock; /* protects bwi_active, cb and cb_context */
 
 	struct timer_list se_active_timer;
 	bool se_active;

-- 
2.43.0



^ permalink raw reply	[flat|nested] 3+ messages in thread

* [PATCH net v3 2/2] nfc: st21nfca: prevent double completion of SE APDU request
  2026-11-12 13:39 [PATCH net v3 0/2] nfc: st21nfca: fix SE APDU request race condition Joas Antonio dos Santos via B4 Relay
  2026-11-12 13:39 ` [PATCH net v3 1/2] nfc: st21nfca: add spinlock to serialize SE APDU request state Joas Antonio dos Santos via B4 Relay
@ 2026-11-12 13:39 ` Joas Antonio dos Santos via B4 Relay
  1 sibling, 0 replies; 3+ messages in thread
From: Joas Antonio dos Santos via B4 Relay @ 2026-11-12 13:39 UTC (permalink / raw)
  To: david, krzk, joasantonio108; +Cc: oe-linux-nfc, netdev, linux-kernel, horms

From: Joas Antonio dos Santos <joasantonio108@gmail.com>

Use the spinlock added in the previous commit to prevent completing an
SE APDU request more than once.  Without this, the timeout work handler
(st21nfca_se_wt_work) and the EVT_TRANSMIT_DATA event handler can both
invoke the callback for the same request.  The callback frees its
context, so the second call is a use-after-free followed by a double
free.

Check bwi_active under the lock in both completion paths and only
complete a request that is still pending.  A response that arrives
after the timeout still gets its EVT_SE_END_OF_APDU_TRANSFER, as
before, but no callback.

Also reject a new SE APDU request in st21nfca_hci_se_io() with -EBUSY
while one is in progress, instead of overwriting the callback and
context of the pending request.  The callback is not called in that
case, so free the context like the -ENODEV path does; nfc_se_io() does
not free it once the driver has been called.

Found by LLM-assisted manual code review.

Fixes: 2130fb97fecf ("NFC: st21nfca: Adding support for secure element")
Cc: Simon Horman <horms@kernel.org>
Signed-off-by: Joas Antonio dos Santos <joasantonio108@gmail.com>
---
 drivers/nfc/st21nfca/se.c | 16 +++++++++++++++-
 1 file changed, 15 insertions(+), 1 deletion(-)

diff --git a/drivers/nfc/st21nfca/se.c b/drivers/nfc/st21nfca/se.c
index 03a30f797ff8..63a88a61d310 100644
--- a/drivers/nfc/st21nfca/se.c
+++ b/drivers/nfc/st21nfca/se.c
@@ -228,6 +228,12 @@ int st21nfca_hci_se_io(struct nfc_hci_dev *hdev, u32 se_idx,
 	switch (se_idx) {
 	case ST21NFCA_ESE_HOST_ID:
 		spin_lock(&info->se_info.se_lock);
+		if (info->se_info.bwi_active) {
+			spin_unlock(&info->se_info.se_lock);
+			/* The callback is not called, see the comment below */
+			kfree(cb_context);
+			return -EBUSY;
+		}
 		info->se_info.cb = cb;
 		info->se_info.cb_context = cb_context;
 		info->se_info.bwi_active = true;
@@ -269,6 +275,10 @@ static void st21nfca_se_wt_work(struct work_struct *work)
 						se_info.timeout_work);
 
 	spin_lock(&info->se_info.se_lock);
+	if (!info->se_info.bwi_active) {
+		spin_unlock(&info->se_info.se_lock);
+		return;
+	}
 	info->se_info.bwi_active = false;
 	cb = info->se_info.cb;
 	cb_context = info->se_info.cb_context;
@@ -383,6 +393,7 @@ int st21nfca_apdu_reader_event_received(struct nfc_hci_dev *hdev,
 					u8 event, struct sk_buff *skb)
 {
 	int r = 0;
+	bool active;
 	se_io_cb_t cb;
 	void *cb_context;
 	struct st21nfca_hci_info *info = nfc_hci_get_clientdata(hdev);
@@ -395,6 +406,7 @@ int st21nfca_apdu_reader_event_received(struct nfc_hci_dev *hdev,
 		cancel_work_sync(&info->se_info.timeout_work);
 
 		spin_lock(&info->se_info.se_lock);
+		active = info->se_info.bwi_active;
 		info->se_info.bwi_active = false;
 		cb = info->se_info.cb;
 		cb_context = info->se_info.cb_context;
@@ -405,7 +417,9 @@ int st21nfca_apdu_reader_event_received(struct nfc_hci_dev *hdev,
 		if (r < 0)
 			goto exit;
 
-		cb(cb_context, skb->data, skb->len, 0);
+		/* The request may have been completed by the timeout already */
+		if (active)
+			cb(cb_context, skb->data, skb->len, 0);
 		break;
 	case ST21NFCA_EVT_WTX_REQUEST:
 		mod_timer(&info->se_info.bwi_timer, jiffies +

-- 
2.43.0



^ permalink raw reply	[flat|nested] 3+ messages in thread

end of thread, other threads:[~2026-11-12 13:39 UTC | newest]

Thread overview: 3+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-11-12 13:39 [PATCH net v3 0/2] nfc: st21nfca: fix SE APDU request race condition Joas Antonio dos Santos via B4 Relay
2026-11-12 13:39 ` [PATCH net v3 1/2] nfc: st21nfca: add spinlock to serialize SE APDU request state Joas Antonio dos Santos via B4 Relay
2026-11-12 13:39 ` [PATCH net v3 2/2] nfc: st21nfca: prevent double completion of SE APDU request Joas Antonio dos Santos via B4 Relay

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®