[PATCH net v2 2/2] nfc: st-nci: drain SE timers before NCI teardown

From: Fan Wu

Date: Thu Jul 30 2026 - 22:47:44 EST


st_nci_se_deinit() drains the two se_info timers (bwi_timer and
se_active_timer), but it has had no caller since the secure-element
support was added. st_nci_remove() tears the device down with
ndlc_close() / nci_unregister_device() / nci_free_device() and never
reaches the helper. A timer armed during operation therefore survives
st_nci_remove() and the devm release of struct st_nci_info: the bwi
callback st_nci_se_wt_timeout() reaches into the NCI core, and both
callbacks dereference info->se_info.

Register the helper as the NCI pre_unregister callback added by the
previous patch, so the NCI core invokes it in nci_unregister_device()
after the NFC admission barrier (new SE operations are already rejected)
and before nci_close_device() / destroy_workqueue() (the NCI core and its
workqueues still exist).

Shut down both timers with timer_shutdown_sync(). Complete req_completion
after the se_active_timer is shut down so an in-flight SE enable/disable is
woken rather than left waiting: nfc_enable_se()/nfc_disable_se() hold the
NFC device lock across st_nci_control_se(). For an outstanding APDU,
invoke its completion callback with -ENODEV; the existing netlink
completion path then frees se_io_ctx.
bwi_active is an xchg() ownership token shared by the response, the
timeout and teardown paths, so the callback is delivered exactly once.

This issue was found by an in-house static analysis tool.

Fixes: 8ae01f796771 ("NFC: st21nfcb: Add support for secure element")
Cc: stable@xxxxxxxxxxxxxxx # 6.6+
Assisted-by: Codex:gpt-5.6
Signed-off-by: Fan Wu <fanwu01@xxxxxxxxxx>
---

drivers/nfc/st-nci/core.c | 1 +
drivers/nfc/st-nci/se.c | 24 ++++++++++++------------
2 files changed, 13 insertions(+), 12 deletions(-)

diff --git a/drivers/nfc/st-nci/core.c b/drivers/nfc/st-nci/core.c
index a367136d4..6e8f799f5 100644
--- a/drivers/nfc/st-nci/core.c
+++ b/drivers/nfc/st-nci/core.c
@@ -105,6 +105,7 @@ static const struct nci_ops st_nci_ops = {
.hci_load_session = st_nci_hci_load_session,
.hci_event_received = st_nci_hci_event_received,
.hci_cmd_received = st_nci_hci_cmd_received,
+ .pre_unregister = st_nci_se_deinit,
.prop_ops = st_nci_prop_ops,
.n_prop_ops = ARRAY_SIZE(st_nci_prop_ops),
};
diff --git a/drivers/nfc/st-nci/se.c b/drivers/nfc/st-nci/se.c
index 607ec768e..232249d60 100644
--- a/drivers/nfc/st-nci/se.c
+++ b/drivers/nfc/st-nci/se.c
@@ -283,9 +283,9 @@ static int st_nci_hci_apdu_reader_event_received(struct nci_dev *ndev,
switch (event) {
case ST_NCI_EVT_TRANSMIT_DATA:
timer_delete_sync(&info->se_info.bwi_timer);
- info->se_info.bwi_active = false;
- info->se_info.cb(info->se_info.cb_context,
- skb->data, skb->len, 0);
+ if (xchg(&info->se_info.bwi_active, false))
+ info->se_info.cb(info->se_info.cb_context,
+ skb->data, skb->len, 0);
break;
case ST_NCI_EVT_WTX_REQUEST:
mod_timer(&info->se_info.bwi_timer, jiffies +
@@ -665,9 +665,9 @@ int st_nci_se_io(struct nci_dev *ndev, u32 se_idx,
case ST_NCI_ESE_HOST_ID:
info->se_info.cb = cb;
info->se_info.cb_context = cb_context;
+ xchg(&info->se_info.bwi_active, true);
mod_timer(&info->se_info.bwi_timer, jiffies +
msecs_to_jiffies(info->se_info.wt_timeout));
- info->se_info.bwi_active = true;
return nci_hci_send_event(ndev, ST_NCI_APDU_READER_GATE,
ST_NCI_EVT_TRANSMIT_DATA, apdu,
apdu_length);
@@ -699,7 +699,8 @@ static void st_nci_se_wt_timeout(struct timer_list *t)
struct st_nci_info *info = timer_container_of(info, t,
se_info.bwi_timer);

- info->se_info.bwi_active = false;
+ if (!xchg(&info->se_info.bwi_active, false))
+ return;

if (!info->se_info.xch_error) {
info->se_info.xch_error = true;
@@ -751,13 +752,12 @@ void st_nci_se_deinit(struct nci_dev *ndev)
{
struct st_nci_info *info = nci_get_drvdata(ndev);

- if (info->se_info.bwi_active)
- timer_delete_sync(&info->se_info.bwi_timer);
- if (info->se_info.se_active)
- timer_delete_sync(&info->se_info.se_active_timer);
+ timer_shutdown_sync(&info->se_info.bwi_timer);
+ if (xchg(&info->se_info.bwi_active, false))
+ info->se_info.cb(info->se_info.cb_context, NULL, 0, -ENODEV);

- info->se_info.se_active = false;
- info->se_info.bwi_active = false;
+ timer_shutdown_sync(&info->se_info.se_active_timer);
+ xchg(&info->se_info.se_active, false);
+ complete(&info->se_info.req_completion);
}
EXPORT_SYMBOL(st_nci_se_deinit);
-