* [PATCH net] nfc: nci: fix out-of-bounds write in nci_target_auto_activated()
@ 2026-06-22 14:52 Samuel Page
2026-06-23 17:21 ` Simon Horman
0 siblings, 1 reply; 3+ messages in thread
From: Samuel Page @ 2026-06-22 14:52 UTC (permalink / raw)
To: David Heidelberg
Cc: David S . Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Simon Horman, oe-linux-nfc, netdev, linux-kernel, stable,
Samuel Page
nci_target_auto_activated() appends a target to the fixed-size array
ndev->targets[NCI_MAX_DISCOVERED_TARGETS] and increments ndev->n_targets
without first checking the array is full; unlike its sibling
nci_add_new_target(), which bails out when n_targets already equals
NCI_MAX_DISCOVERED_TARGETS.
ndev->n_targets is only cleared by nci_clear_target_list(), so an NFCC
that repeatedly re-runs discovery (RF_DISCOVER_RSP, which re-enters
NCI_DISCOVERY without clearing the target list) and reports an
auto-activated target (RF_INTF_ACTIVATED_NTF) drives n_targets past the
limit. The append then writes a struct nfc_target past the end of the
array (a slab out-of-bounds write), and nfc_targets_found() goes on to
walk the array with the inflated count:
BUG: KASAN: slab-out-of-bounds in nci_add_new_protocol+0x94/0x2ac [nci]
Write of size 2 at addr ffff0000c7299a18 by task kworker/u8:0/12
Workqueue: nfc0_nci_rx_wq nci_rx_work [nci]
Call trace:
nci_add_new_protocol+0x94/0x2ac [nci]
nci_ntf_packet+0xddc/0x11a0 [nci]
nci_rx_work+0x15c/0x1e0 [nci]
process_one_work+0x2dc/0x500
worker_thread+0x240/0x460
kthread+0x1c0/0x1d0
ret_from_fork+0x10/0x20
The buggy address belongs to the cache kmalloc-2k of size 2048
The buggy address is located 1024 bytes to the right of
allocated 1560-byte region [ffff0000c7299000, ffff0000c7299618)
Guard nci_target_auto_activated() with the same check used by
nci_add_new_target().
Fixes: 019c4fbaa790 ("NFC: Add NCI multiple targets support")
Cc: stable@vger.kernel.org
Assisted-by: Bynario AI
Signed-off-by: Samuel Page <sam@bynar.io>
---
net/nfc/nci/ntf.c | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/net/nfc/nci/ntf.c b/net/nfc/nci/ntf.c
index c96512bb8653..566ca839fa48 100644
--- a/net/nfc/nci/ntf.c
+++ b/net/nfc/nci/ntf.c
@@ -603,6 +603,12 @@ static void nci_target_auto_activated(struct nci_dev *ndev,
struct nfc_target *target;
int rc;
+ /* This is a new target, check if we've enough room */
+ if (ndev->n_targets == NCI_MAX_DISCOVERED_TARGETS) {
+ pr_debug("not enough room, ignoring new target...\n");
+ return;
+ }
+
target = &ndev->targets[ndev->n_targets];
rc = nci_add_new_protocol(ndev, target, ntf->rf_protocol,
base-commit: 47186409c092cd7dd70350999186c700233e854d
--
2.54.0
^ permalink raw reply related [flat|nested] 3+ messages in thread* Re: [PATCH net] nfc: nci: fix out-of-bounds write in nci_target_auto_activated() 2026-06-22 14:52 [PATCH net] nfc: nci: fix out-of-bounds write in nci_target_auto_activated() Samuel Page @ 2026-06-23 17:21 ` Simon Horman 2026-06-23 22:33 ` Sam P 0 siblings, 1 reply; 3+ messages in thread From: Simon Horman @ 2026-06-23 17:21 UTC (permalink / raw) To: sam Cc: Simon Horman, david, davem, edumazet, kuba, pabeni, oe-linux-nfc, netdev, linux-kernel, stable This is an AI-generated review of your patch. The human sending this email has considered the AI review valid, or at least plausible. Full review at: https://sashiko.dev --- nfc: nci: fix out-of-bounds write in nci_target_auto_activated() This commit adds a bounds check to nci_target_auto_activated() to prevent an out-of-bounds array write. If ndev->n_targets equals NCI_MAX_DISCOVERED_TARGETS, it prints a debug message and returns early. > diff --git a/net/nfc/nci/ntf.c b/net/nfc/nci/ntf.c > index c96512bb86531..566ca839fa488 100644 > --- a/net/nfc/nci/ntf.c > +++ b/net/nfc/nci/ntf.c > @@ -603,6 +603,12 @@ static void nci_target_auto_activated(struct nci_dev *ndev, > struct nfc_target *target; > int rc; > > + /* This is a new target, check if we've enough room */ > + if (ndev->n_targets == NCI_MAX_DISCOVERED_TARGETS) { > + pr_debug("not enough room, ignoring new target...\n"); > + return; [Severity: High] Does this early return cause the state machine to stall? Looking at nci_rf_intf_activated_ntf_packet(), the state is transitioned to NCI_POLL_ACTIVE right before this function is called: net/nfc/nci/ntf.c:nci_rf_intf_activated_ntf_packet() { ... atomic_set(&ndev->state, NCI_POLL_ACTIVE); if (err == NCI_STATUS_OK) nci_target_auto_activated(ndev, &ntf); ... } Because of the early return here, nfc_targets_found() is skipped. Does this leave the state wedged in NCI_POLL_ACTIVE, permanently blocking future attempts to stop polling since nci_stop_poll() requires the state to be NCI_DISCOVERY? > + } > + > target = &ndev->targets[ndev->n_targets]; [ ... ] [Severity: Critical] This isn't a bug introduced by this patch, but can the ntf union copy uninitialized stack memory into persistent device structures? In net/nfc/nci/ntf.c:nci_rf_discover_ntf_packet(), the local ntf struct is declared on the stack: struct nci_rf_discover_ntf ntf; If the incoming packet has ntf.rf_tech_specific_params_len == 0, the extraction routines are bypassed, leaving the rf_tech_specific_params union uninitialized. The struct is then passed to nci_add_new_target(): if (add_target == true) nci_add_new_target(ndev, &ntf); This eventually calls nci_add_new_protocol(), which blindly reads from this union: nfca_poll = (struct rf_tech_specific_params_nfca_poll *)params; target->sens_res = nfca_poll->sens_res; target->sel_res = nfca_poll->sel_res; target->nfcid1_len = nfca_poll->nfcid1_len; Could this uninitialized stack garbage be copied into ndev->targets, and later broadcasted to userspace via Netlink in nfc_targets_found()? [Severity: Critical] This isn't a bug introduced by this patch, but does the general bytes length calculation underflow? In net/nfc/nci/ntf.c:nci_store_general_bytes_nfc_dep(): ndev->remote_gb_len = min_t(__u8, (ntf->activation_params.listen_nfc_dep.atr_req_len - NFC_ATR_REQ_GT_OFFSET), NFC_ATR_REQ_GB_MAXSIZE); memcpy(ndev->remote_gb, (ntf->activation_params.listen_nfc_dep.atr_req + NFC_ATR_REQ_GT_OFFSET), ndev->remote_gb_len); The length is computed by subtracting NFC_ATR_REQ_GT_OFFSET (14) from atr_req_len. If atr_req_len is less than 14, this subtraction will underflow. Because of the min_t cast, this large underflowed value is bounded to NFC_ATR_REQ_GB_MAXSIZE (48). The memcpy() then copies 48 bytes starting from atr_req + 14 into ndev->remote_gb. Since the source buffer is part of the ntf union allocated on the stack in nci_rf_intf_activated_ntf_packet(), could this leak up to 48 bytes of uninitialized kernel stack memory to userspace? [Severity: High] This isn't a bug introduced by this patch, but does the lack of skb->len validation in NCI response handlers allow out-of-bounds reads? In nci_core_init_rsp_packet_v1() in net/nfc/nci/rsp.c, fields are read and arrays are copied from the payload: ndev->num_supported_rf_interfaces = rsp_1->num_supported_rf_interfaces; memcpy(ndev->supported_rf_interfaces, rsp_1->supported_rf_interfaces, ndev->num_supported_rf_interfaces); Similarly in nci_core_init_rsp_packet_v2(): while (rf_interface_idx < ndev->num_supported_rf_interfaces) { ndev->supported_rf_interfaces[rf_interface_idx++] = *supported_rf_interface++; } Since the NCI control header is stripped via skb_pull(), leaving the payload length in skb->len, what happens if an NFCC sends a short response packet (e.g. plen = 1)? Could this cause the handlers to read adjacent kernel slab memory? [Severity: High] This isn't a bug introduced by this patch, but is there a missing boundary validation when extracting activation parameters? In net/nfc/nci/ntf.c:nci_extract_activation_params_iso_dep(): nfca_poll->rats_res_len = min_t(__u8, *data++, NFC_ATS_MAXSIZE); if (nfca_poll->rats_res_len > 0) { memcpy(nfca_poll->rats_res, data, nfca_poll->rats_res_len); } And in nci_extract_activation_params_nfc_dep(): poll->atr_res_len = min_t(__u8, *data++, NFC_ATR_RES_MAXSIZE - 2); if (poll->atr_res_len > 0) memcpy(poll->atr_res, data, poll->atr_res_len); These inner lengths are bounded by a maximum size like NFC_ATS_MAXSIZE, but they are not validated against the remaining bytes in the SKB (skb->len). If a malformed packet provides a large inner length but a short payload, could this memcpy() read out of bounds into the device structure, potentially leaking adjacent slab memory to userspace later? [Severity: High] This isn't a bug introduced by this patch, but is there a potential priority inversion or timeout deadlock involving the device lock? Functions like nfc_stop_poll() in net/nfc/core.c acquire the device lock: device_lock(&dev->dev); ... if (dev->ops->stop_poll) dev->ops->stop_poll(dev); This eventually calls nci_request(), which synchronously waits for a completion signaled by the nci_rx_work thread. However, if an NTF packet is received first, nci_rx_work processes it and invokes nfc_targets_found(), which also attempts to acquire the device lock: device_lock(&dev->dev); Since the calling thread already holds the device lock, nci_rx_work blocks indefinitely. Because the RX worker is blocked, it cannot process the pending RSP, causing nci_request() to time out and fail. Could this deadlock the RX thread? ^ permalink raw reply [flat|nested] 3+ messages in thread
* Re: [PATCH net] nfc: nci: fix out-of-bounds write in nci_target_auto_activated() 2026-06-23 17:21 ` Simon Horman @ 2026-06-23 22:33 ` Sam P 0 siblings, 0 replies; 3+ messages in thread From: Sam P @ 2026-06-23 22:33 UTC (permalink / raw) To: Simon Horman Cc: david, davem, edumazet, kuba, pabeni, oe-linux-nfc, netdev, linux-kernel, stable On 23/06/2026 19:21, Simon Horman wrote: >> diff --git a/net/nfc/nci/ntf.c b/net/nfc/nci/ntf.c >> index c96512bb86531..566ca839fa488 100644 >> --- a/net/nfc/nci/ntf.c >> +++ b/net/nfc/nci/ntf.c >> @@ -603,6 +603,12 @@ static void nci_target_auto_activated(struct nci_dev *ndev, >> struct nfc_target *target; >> int rc; >> >> + /* This is a new target, check if we've enough room */ >> + if (ndev->n_targets == NCI_MAX_DISCOVERED_TARGETS) { >> + pr_debug("not enough room, ignoring new target...\n"); >> + return; > > [Severity: High] > Does this early return cause the state machine to stall? > > Looking at nci_rf_intf_activated_ntf_packet(), the state is transitioned > to NCI_POLL_ACTIVE right before this function is called: > > net/nfc/nci/ntf.c:nci_rf_intf_activated_ntf_packet() { > ... > atomic_set(&ndev->state, NCI_POLL_ACTIVE); > if (err == NCI_STATUS_OK) > nci_target_auto_activated(ndev, &ntf); > ... > } > > Because of the early return here, nfc_targets_found() is skipped. Does this > leave the state wedged in NCI_POLL_ACTIVE, permanently blocking future > attempts to stop polling since nci_stop_poll() requires the state to be > NCI_DISCOVERY? > >> + } >> + >> target = &ndev->targets[ndev->n_targets]; > > [ ... ] Thanks for sharing the review. I don't believe the early return can stall the state machine, as nci_target_auto_activated() does not touch ndev->state. nci_rf_intf_activated_ntf_packet() sets ndev->state = NCI_POLL_ACTIVE before it calls nci_target_auto_activated(), so the transition has already happened by the time the new check runs. Neither nci_target_auto_activated() nor nfc_targets_found() modifies ndev->state; nfc_targets_found() only clears the nfc_dev dev->polling flag under device_lock, which is independent of the NCI state machine that nci_stop_poll() tests. So ndev->state is NCI_POLL_ACTIVE after an auto-activation whether the target is appended or the guard returns early; the state behaviour is the same with or without this patch. The check is the same n_targets == NCI_MAX_DISCOVERED_TARGETS guard the sibling nci_add_new_target() (the RF_DISCOVER_NTF path) has used since the Fixes: commit; it only drops the out-of-spec 11th+ target, which a conformant controller shouldn't produce. The device still returns to NCI_DISCOVERY/NCI_IDLE through the normal RF_DEACTIVATE path. > [Severity: Critical] > This isn't a bug introduced by this patch, but can the ntf union copy > uninitialized stack memory into persistent device structures? > > In net/nfc/nci/ntf.c:nci_rf_discover_ntf_packet(), the local ntf struct > is declared on the stack: > > struct nci_rf_discover_ntf ntf; > > If the incoming packet has ntf.rf_tech_specific_params_len == 0, the > extraction routines are bypassed, leaving the rf_tech_specific_params union > uninitialized. The struct is then passed to nci_add_new_target(): > > if (add_target == true) > nci_add_new_target(ndev, &ntf); > > This eventually calls nci_add_new_protocol(), which blindly reads from this > union: > > nfca_poll = (struct rf_tech_specific_params_nfca_poll *)params; > target->sens_res = nfca_poll->sens_res; > target->sel_res = nfca_poll->sel_res; > target->nfcid1_len = nfca_poll->nfcid1_len; > > Could this uninitialized stack garbage be copied into ndev->targets, and > later broadcasted to userspace via Netlink in nfc_targets_found()? I managed to repro this issue locally via KMSAN, I can queue up a patch for this that I can submit separately, ntf just needs to be zero-initd. > [Severity: Critical] > This isn't a bug introduced by this patch, but does the general bytes length > calculation underflow? > > In net/nfc/nci/ntf.c:nci_store_general_bytes_nfc_dep(): > > ndev->remote_gb_len = min_t(__u8, > (ntf->activation_params.listen_nfc_dep.atr_req_len - > NFC_ATR_REQ_GT_OFFSET), NFC_ATR_REQ_GB_MAXSIZE); > > memcpy(ndev->remote_gb, > (ntf->activation_params.listen_nfc_dep.atr_req + > NFC_ATR_REQ_GT_OFFSET), ndev->remote_gb_len); > > The length is computed by subtracting NFC_ATR_REQ_GT_OFFSET (14) from > atr_req_len. If atr_req_len is less than 14, this subtraction will > underflow. > > Because of the min_t cast, this large underflowed value is bounded to > NFC_ATR_REQ_GB_MAXSIZE (48). The memcpy() then copies 48 bytes starting > from atr_req + 14 into ndev->remote_gb. > > Since the source buffer is part of the ntf union allocated on the stack > in nci_rf_intf_activated_ntf_packet(), could this leak up to 48 bytes of > uninitialized kernel stack memory to userspace? This one is already being fixed on the list: [PATCH net v4 1/5] nfc: nci: fix u8 underflow in nci_store_general_bytes_nfc_dep (Lekë Hapçiu) https://lore.kernel.org/all/20260424180151.3808557-2-snowwlake@icloud.com/ > [Severity: High] > This isn't a bug introduced by this patch, but does the lack of skb->len > validation in NCI response handlers allow out-of-bounds reads? > > In nci_core_init_rsp_packet_v1() in net/nfc/nci/rsp.c, fields are read > and arrays are copied from the payload: > > ndev->num_supported_rf_interfaces = > rsp_1->num_supported_rf_interfaces; > > memcpy(ndev->supported_rf_interfaces, rsp_1->supported_rf_interfaces, > ndev->num_supported_rf_interfaces); > > Similarly in nci_core_init_rsp_packet_v2(): > > while (rf_interface_idx < ndev->num_supported_rf_interfaces) { > ndev->supported_rf_interfaces[rf_interface_idx++] = > *supported_rf_interface++; > } > > Since the NCI control header is stripped via skb_pull(), leaving the > payload length in skb->len, what happens if an NFCC sends a short response > packet (e.g. plen = 1)? Could this cause the handlers to read adjacent > kernel slab memory? I've since submitted a fix for this issue: [PATCH net] nfc: nci: fix uninit-value in nci_core_init_rsp_packet() https://lore.kernel.org/netdev/20260623222402.175798-1-sam@bynar.io/ > [Severity: High] > This isn't a bug introduced by this patch, but is there a missing boundary > validation when extracting activation parameters? > > In net/nfc/nci/ntf.c:nci_extract_activation_params_iso_dep(): > > nfca_poll->rats_res_len = min_t(__u8, *data++, NFC_ATS_MAXSIZE); > if (nfca_poll->rats_res_len > 0) { > memcpy(nfca_poll->rats_res, data, nfca_poll->rats_res_len); > } > > And in nci_extract_activation_params_nfc_dep(): > > poll->atr_res_len = min_t(__u8, *data++, NFC_ATR_RES_MAXSIZE - 2); > if (poll->atr_res_len > 0) > memcpy(poll->atr_res, data, poll->atr_res_len); > > These inner lengths are bounded by a maximum size like NFC_ATS_MAXSIZE, but > they are not validated against the remaining bytes in the SKB (skb->len). > > If a malformed packet provides a large inner length but a short payload, > could this memcpy() read out of bounds into the device structure, > potentially leaking adjacent slab memory to userspace later? This one is already being fixed on the list too: [PATCH v3] nfc: nci: add data_len bound checks to activation parameter extractors (Bryam Vargas) https://lore.kernel.org/all/20260612-b4-disp-6d52d8b0-v3-1-e26221f8826d@proton.me/ > [Severity: High] > This isn't a bug introduced by this patch, but is there a potential > priority inversion or timeout deadlock involving the device lock? > > Functions like nfc_stop_poll() in net/nfc/core.c acquire the device lock: > > device_lock(&dev->dev); > ... > if (dev->ops->stop_poll) > dev->ops->stop_poll(dev); > > This eventually calls nci_request(), which synchronously waits for a > completion signaled by the nci_rx_work thread. However, if an NTF packet > is received first, nci_rx_work processes it and invokes > nfc_targets_found(), which also attempts to acquire the device lock: > > device_lock(&dev->dev); > > Since the calling thread already holds the device lock, nci_rx_work blocks > indefinitely. Because the RX worker is blocked, it cannot process the > pending RSP, causing nci_request() to time out and fail. Could this > deadlock the RX thread? No patch for this one, although I'm not sure how accurate it is. Thanks, Sam ^ permalink raw reply [flat|nested] 3+ messages in thread
end of thread, other threads:[~2026-06-23 22:33 UTC | newest] Thread overview: 3+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2026-06-22 14:52 [PATCH net] nfc: nci: fix out-of-bounds write in nci_target_auto_activated() Samuel Page 2026-06-23 17:21 ` Simon Horman 2026-06-23 22:33 ` Sam P
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox