* [PATCH net RESEND] nfc: llcp: Fix list corruption / refcount desync in nfc_llcp_recv_dm()
@ 2026-09-23 13:33 Aldo Ariel Panzardo
2026-09-24 3:10 ` patchwork-bot+netdevbpf
` (2 more replies)
0 siblings, 3 replies; 6+ messages in thread
From: Aldo Ariel Panzardo @ 2026-09-23 13:33 UTC (permalink / raw)
To: David Heidelberg, David S . Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni
Cc: Simon Horman, oe-linux-nfc, netdev, linux-kernel, Aldo Ariel Panzardo
nfc_llcp_recv_dm() handles DM(NOBOUND)/DM(REJ) for a socket that is still
linked on local->connecting_sockets: it looks the socket up with
nfc_llcp_connecting_sock_get(), sets sk->sk_state = LLCP_CLOSED and
returns, without taking the socket lock and without unlinking the socket
from the connecting_sockets list.
llcp_sock_release() selects the list to unlink from by sk_state: a socket
in LLCP_CONNECTING is unlinked from connecting_sockets, otherwise from the
sockets list. Because recv_dm left the socket physically on
connecting_sockets but in the LLCP_CLOSED state, release() takes the else
branch and calls nfc_llcp_sock_unlink(&local->sockets, sk). That runs
sk_del_node_init() while holding sockets.lock, i.e. it removes the socket
from the connecting_sockets hlist under the wrong lock. A concurrent
connect() linking another socket onto connecting_sockets under
connecting_sockets.lock then mutates the same hlist unserialized, which
corrupts the list and desyncs the sk_add_node()/sk_del_node_init()
sock_hold()/__sock_put() pairing. An unprivileged local process holding
LLCP sockets, with the DM supplied by the remote peer over an established
LLCP link, can drive this to leak kernel sockets without bound (the
mis-decrement goes through the non-freeing __sock_put() path, so the
object is never released), leading to memory exhaustion / DoS.
This is the same class of bug that was fixed in the sibling handler
nfc_llcp_recv_cc() by commit b493ea2765cc ("nfc: llcp: Fix use-after-free
race in nfc_llcp_recv_cc()"); recv_dm did not receive the equivalent fix.
Fix it the same way: take lock_sock(), re-check that the socket is still
hashed (release() may have won the race), and for the NOBOUND/REJ case
unlink it from connecting_sockets before moving it to LLCP_CLOSED. The
unlink drops the connecting_sockets membership reference via
sk_del_node_init(), leaving the socket unhashed, so the later
nfc_llcp_sock_unlink() in llcp_sock_release() becomes a no-op and no
double put occurs.
Fixes: a69f32af86e3 ("NFC: Socket linked list")
Signed-off-by: Aldo Ariel Panzardo <qwe.aldo@gmail.com>
---
net/nfc/llcp_core.c | 25 +++++++++++++++++++++++++
1 file changed, 25 insertions(+)
diff --git a/net/nfc/llcp_core.c b/net/nfc/llcp_core.c
index cac1b5487..bd6361e2e 100644
--- a/net/nfc/llcp_core.c
+++ b/net/nfc/llcp_core.c
@@ -1251,6 +1251,7 @@ static void nfc_llcp_recv_dm(struct nfc_llcp_local *local,
struct nfc_llcp_sock *llcp_sock;
struct sock *sk;
u8 dsap, ssap, reason;
+ bool connecting = false;
dsap = nfc_llcp_dsap(skb);
ssap = nfc_llcp_ssap(skb);
@@ -1262,6 +1263,7 @@ static void nfc_llcp_recv_dm(struct nfc_llcp_local *local,
case LLCP_DM_NOBOUND:
case LLCP_DM_REJ:
llcp_sock = nfc_llcp_connecting_sock_get(local, dsap);
+ connecting = true;
break;
default:
@@ -1276,10 +1278,33 @@ static void nfc_llcp_recv_dm(struct nfc_llcp_local *local,
sk = &llcp_sock->sk;
+ lock_sock(sk);
+
+ /* Check if socket was destroyed whilst waiting for the lock */
+ if (!sk_hashed(sk)) {
+ release_sock(sk);
+ nfc_llcp_sock_put(llcp_sock);
+ return;
+ }
+
+ /*
+ * For DM(NOBOUND)/DM(REJ) the socket is still linked on the
+ * connecting_sockets list. Unlink it here, under the socket lock,
+ * before moving it to LLCP_CLOSED: llcp_sock_release() selects the
+ * list to unlink from by sk_state, so leaving a connecting socket
+ * in the CLOSED state would make it unlink from the wrong list and
+ * corrupt the connecting_sockets list / desync the socket refcount.
+ * This mirrors nfc_llcp_recv_cc().
+ */
+ if (connecting)
+ nfc_llcp_sock_unlink(&local->connecting_sockets, sk);
+
sk->sk_err = ENXIO;
sk->sk_state = LLCP_CLOSED;
sk->sk_state_change(sk);
+ release_sock(sk);
+
nfc_llcp_sock_put(llcp_sock);
}
--
2.43.0
^ permalink raw reply [flat|nested] 6+ messages in thread* Re: [PATCH net RESEND] nfc: llcp: Fix list corruption / refcount desync in nfc_llcp_recv_dm() 2026-09-23 13:33 [PATCH net RESEND] nfc: llcp: Fix list corruption / refcount desync in nfc_llcp_recv_dm() Aldo Ariel Panzardo @ 2026-09-24 3:10 ` patchwork-bot+netdevbpf 2026-09-25 1:35 ` netdev-bot+sashiko 2026-09-25 2:35 ` [PATCH net] nfc: llcp: check socket state after locking " Aldo Ariel Panzardo 2 siblings, 0 replies; 6+ messages in thread From: patchwork-bot+netdevbpf @ 2026-09-24 3:10 UTC (permalink / raw) To: Aldo Ariel Panzardo Cc: david, davem, edumazet, kuba, pabeni, horms, oe-linux-nfc, netdev, linux-kernel Hello: This patch was applied to netdev/net.git (main) by David Heidelberg <david@ixit.cz>: On Wed, 23 Sep 2026 10:33:39 -0300 you wrote: > nfc_llcp_recv_dm() handles DM(NOBOUND)/DM(REJ) for a socket that is still > linked on local->connecting_sockets: it looks the socket up with > nfc_llcp_connecting_sock_get(), sets sk->sk_state = LLCP_CLOSED and > returns, without taking the socket lock and without unlinking the socket > from the connecting_sockets list. > > llcp_sock_release() selects the list to unlink from by sk_state: a socket > in LLCP_CONNECTING is unlinked from connecting_sockets, otherwise from the > sockets list. Because recv_dm left the socket physically on > connecting_sockets but in the LLCP_CLOSED state, release() takes the else > branch and calls nfc_llcp_sock_unlink(&local->sockets, sk). That runs > sk_del_node_init() while holding sockets.lock, i.e. it removes the socket > from the connecting_sockets hlist under the wrong lock. A concurrent > connect() linking another socket onto connecting_sockets under > connecting_sockets.lock then mutates the same hlist unserialized, which > corrupts the list and desyncs the sk_add_node()/sk_del_node_init() > sock_hold()/__sock_put() pairing. An unprivileged local process holding > LLCP sockets, with the DM supplied by the remote peer over an established > LLCP link, can drive this to leak kernel sockets without bound (the > mis-decrement goes through the non-freeing __sock_put() path, so the > object is never released), leading to memory exhaustion / DoS. > > [...] Here is the summary with links: - [net,RESEND] nfc: llcp: Fix list corruption / refcount desync in nfc_llcp_recv_dm() https://git.kernel.org/netdev/net/c/bf1460acdf8c You are awesome, thank you! -- Deet-doot-dot, I am a bot. https://korg.docs.kernel.org/patchwork/pwbot.html ^ permalink raw reply [flat|nested] 6+ messages in thread
* Re: [PATCH net RESEND] nfc: llcp: Fix list corruption / refcount desync in nfc_llcp_recv_dm() 2026-09-23 13:33 [PATCH net RESEND] nfc: llcp: Fix list corruption / refcount desync in nfc_llcp_recv_dm() Aldo Ariel Panzardo 2026-09-24 3:10 ` patchwork-bot+netdevbpf @ 2026-09-25 1:35 ` netdev-bot+sashiko 2026-09-25 2:35 ` [PATCH net] nfc: llcp: check socket state after locking " Aldo Ariel Panzardo 2 siblings, 0 replies; 6+ messages in thread From: netdev-bot+sashiko @ 2026-09-25 1:35 UTC (permalink / raw) To: qwe.aldo Cc: david, davem, edumazet, kuba, pabeni, horms, oe-linux-nfc, netdev, linux-kernel Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider. Critical: 0 · High: 1 (1) · Medium: 0 (1) · Low: 0 If you believe none of the issues are legitimate, please reply to each issue of Medium severity or higher and explain why the AI is wrong. If any of them are legitimate and you plan to respin, please let patchwork know by including "pw-bot: cr" as a separate line at the end of your reply (one such reply per series is enough). - [High] After lock_sock(), the patched nfc_llcp_recv_dm() only checks sk_hashed(sk). Pre-existing issues: - [High] After nfc_llcp_recv_dm() moves a CONNECTING socket to LLCP_CLOSED, the socket still holds everything connect() gave it:… - [Medium] nfc_llcp_recv_dm() reads the DM reason from skb->data[2] (llcp_core.c:1258) without checking skb->len >= 3. -- nfc: llcp: Fix list corruption / refcount desync in nfc_llcp_recv_dm() With this patch, nfc_llcp_recv_dm() takes the socket lock and re-checks that the socket is still hashed. For DM(NOBOUND) and DM(REJ) it also unlinks the socket from local->connecting_sockets before moving it to LLCP_CLOSED. This follows the earlier nfc_llcp_recv_cc() fix, so llcp_sock_release() no longer unlinks a connecting socket from the wrong list. > diff --git a/net/nfc/llcp_core.c b/net/nfc/llcp_core.c > index cac1b5487064d..bd6361e2efa4f 100644 > --- a/net/nfc/llcp_core.c > +++ b/net/nfc/llcp_core.c [ ... ] > @@ -1262,6 +1263,7 @@ static void nfc_llcp_recv_dm(struct nfc_llcp_local *local, > case LLCP_DM_NOBOUND: > case LLCP_DM_REJ: > llcp_sock = nfc_llcp_connecting_sock_get(local, dsap); > + connecting = true; > break; [Severity: Medium] This is a pre-existing issue, but the reason byte that picks this branch is read without a length check: net/nfc/llcp_core.c:nfc_llcp_recv_dm() { ... reason = skb->data[2]; ... } The only length check on the way in is in __nfc_llcp_recv(): if (!pskb_may_pull(skb, LLCP_HEADER_SIZE)) { LLCP_HEADER_SIZE is 2. nfc_llcp_recv_agf() also accepts 2-byte inner PDUs: if (pdu_len < LLCP_HEADER_SIZE || pdu_len > skb->len) { It then copies them into an nfc_alloc_recv_skb(pdu_len) buffer. For a 2-byte inner DM, data[2] is uninitialized tailroom. On the direct path it is one byte past skb->len. After this patch, that byte also decides whether the new connecting_sockets unlink runs. Should there be a check such as "if (skb->len < LLCP_HEADER_SIZE + 1) return;" before the reason is read? [ ... ] > @@ -1276,10 +1278,33 @@ static void nfc_llcp_recv_dm(struct nfc_llcp_local *local, > > sk = &llcp_sock->sk; > > + lock_sock(sk); > + > + /* Check if socket was destroyed whilst waiting for the lock */ > + if (!sk_hashed(sk)) { > + release_sock(sk); > + nfc_llcp_sock_put(llcp_sock); > + return; > + } > + > + /* > + * For DM(NOBOUND)/DM(REJ) the socket is still linked on the > + * connecting_sockets list. Unlink it here, under the socket lock, > + * before moving it to LLCP_CLOSED: llcp_sock_release() selects the > + * list to unlink from by sk_state, so leaving a connecting socket > + * in the CLOSED state would make it unlink from the wrong list and > + * corrupt the connecting_sockets list / desync the socket refcount. > + * This mirrors nfc_llcp_recv_cc(). > + */ > + if (connecting) > + nfc_llcp_sock_unlink(&local->connecting_sockets, sk); [Severity: High] Is sk_hashed() enough to show the socket is still on local->connecting_sockets at this point? The connecting flag is set before lock_sock(). While this handler waits for the lock, the socket could move to local->sockets. Consider this sequence: llcp_sock_connect() nfc_llcp_sock_link(&local->connecting_sockets, sk); /* sk_state is still LLCP_CLOSED */ rx_work: nfc_llcp_recv_dm() nfc_llcp_connecting_sock_get() /* matches on ssap only */ sock_hold(&llcp_sock->sk); lock_sock(sk); /* waits */ nfc_llcp_send_connect() fails sock_unlink: nfc_llcp_sock_unlink(&local->connecting_sockets, sk); release_sock(sk); llcp_sock_bind() in another thread sharing the fd wins the lock sk->sk_state == LLCP_CLOSED, check passes nfc_llcp_sock_link(&local->sockets, sk); sk->sk_state = LLCP_BOUND; /* now gets the lock */ sk_hashed(sk) is true nfc_llcp_sock_unlink( &local->connecting_sockets, sk); A user can force the nfc_llcp_send_connect() failure. If sk_err is still pending from an earlier DM, this call chain returns that error: llcp_allocate_pdu()->nfc_alloc_send_skb()->sock_alloc_send_pskb() At the end of this sequence, the socket is removed from the local->sockets hlist while connecting_sockets.lock is held, not sockets.lock. If bind used a different dev_idx, that lock may belong to a different local. The newly bound socket is also closed with ENXIO. Can this race with other writers of local->sockets, such as bind or close of another socket, and corrupt that list? That is the same kind of wrong-lock corruption the commit message describes. Before this patch, nfc_llcp_recv_dm() never unlinked, so this path is new. nfc_llcp_recv_cc() has the same hashed-only check. For the connecting case, would it work to also require sk->sk_state == LLCP_CONNECTING and llcp_sock->local == local after lock_sock(), and bail out otherwise? > + > sk->sk_err = ENXIO; > sk->sk_state = LLCP_CLOSED; > sk->sk_state_change(sk); [Severity: High] This isn't a bug introduced by this patch, but after the socket is moved to LLCP_CLOSED here, it still holds everything llcp_sock_connect() gave it: - llcp_sock->local, with its kref and nfc_dev ref - the nfc_get_device() ref taken by connect - the SSAP bit from nfc_llcp_get_local_ssap() - the kmemdup'd service_name llcp_sock_connect() rejects only two states: if (sk->sk_state == LLCP_CONNECTED) { ret = -EISCONN; goto error; } if (sk->sk_state == LLCP_CONNECTING) { So an unprivileged user can call connect() again on the rejected socket. The old values are then overwritten without being released: llcp_sock->dev = dev; llcp_sock->local = local; llcp_sock->ssap = nfc_llcp_get_local_ssap(local); ... llcp_sock->service_name = kmemdup(addr->service_name, Does each retry leak the old nfc_llcp_local kref, both nfc_dev refs, the local SAP bit and the old service_name? llcp_sock_destruct() drops the connect-time nfc_dev ref only for connected sockets: if (sk->sk_state == LLCP_CONNECTED) nfc_put_device(llcp_sock->dev); Each device has only LLCP_LOCAL_NUM_SAP (32) local SAPs. The leaked bits are never cleared: nfc_llcp_mac_is_down() does not reset local->local_sap, and the leaked local kref keeps local_release() from running. The trigger is a repeated non-blocking connect() to a peer that answers DM(NOBOUND) or DM(REJ). That is the normal reply for an unknown service, so no malicious peer is needed. After enough retries, every LLCP client connect() on that device would fail with -ENOMEM until the device is re-registered. Before this patch, the same reconnect also added a node that was already hashed. The patch removes that part, but the leaks remain. There are two possible fixes. nfc_llcp_recv_dm() could do the same teardown as the connect() error path (sock_unlink, sock_llcp_release, sock_llcp_nullify). Or connect() could reject sockets whose llcp_sock->local is already set. -- Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260923133339.2518641-1-qwe.aldo%40gmail.com ^ permalink raw reply [flat|nested] 6+ messages in thread
* [PATCH net] nfc: llcp: check socket state after locking in nfc_llcp_recv_dm() 2026-09-23 13:33 [PATCH net RESEND] nfc: llcp: Fix list corruption / refcount desync in nfc_llcp_recv_dm() Aldo Ariel Panzardo 2026-09-24 3:10 ` patchwork-bot+netdevbpf 2026-09-25 1:35 ` netdev-bot+sashiko @ 2026-09-25 2:35 ` Aldo Ariel Panzardo 2026-09-25 15:16 ` David Heidelberg 2026-09-28 17:38 ` netdev-bot+sashiko 2 siblings, 2 replies; 6+ messages in thread From: Aldo Ariel Panzardo @ 2026-09-25 2:35 UTC (permalink / raw) To: david, davem, edumazet, kuba, pabeni Cc: horms, netdev, linux-kernel, stable, Aldo Ariel Panzardo, Sashiko Between nfc_llcp_connecting_sock_get() and lock_sock(), another thread can move the socket from connecting_sockets to sockets via a failed connect + re-bind. After lock_sock(), the socket may no longer be in LLCP_CONNECTING state, so unconditionally unlinking from connecting_sockets corrupts the wrong list. Add a state check after acquiring the socket lock: only unlink from connecting_sockets if the socket is still LLCP_CONNECTING. Fixes: bf1460acdf8c ("nfc: llcp: Fix list corruption / refcount desync in nfc_llcp_recv_dm()") Cc: stable@vger.kernel.org Reported-by: Sashiko <sashiko-bot@kernel.org> Signed-off-by: Aldo Ariel Panzardo <qwe.aldo@gmail.com> --- net/nfc/llcp_core.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/net/nfc/llcp_core.c b/net/nfc/llcp_core.c index 74bf81700..acf113311 100644 --- a/net/nfc/llcp_core.c +++ b/net/nfc/llcp_core.c @@ -1326,7 +1326,7 @@ static void nfc_llcp_recv_dm(struct nfc_llcp_local *local, * corrupt the connecting_sockets list / desync the socket refcount. * This mirrors nfc_llcp_recv_cc(). */ - if (connecting) + if (connecting && sk->sk_state == LLCP_CONNECTING) nfc_llcp_sock_unlink(&local->connecting_sockets, sk); sk->sk_err = ENXIO; -- 2.43.0 ^ permalink raw reply [flat|nested] 6+ messages in thread
* Re: [PATCH net] nfc: llcp: check socket state after locking in nfc_llcp_recv_dm() 2026-09-25 2:35 ` [PATCH net] nfc: llcp: check socket state after locking " Aldo Ariel Panzardo @ 2026-09-25 15:16 ` David Heidelberg 2026-09-28 17:38 ` netdev-bot+sashiko 1 sibling, 0 replies; 6+ messages in thread From: David Heidelberg @ 2026-09-25 15:16 UTC (permalink / raw) To: Aldo Ariel Panzardo, davem, edumazet, kuba, pabeni Cc: horms, netdev, linux-kernel, stable, Sashiko On 25/09/2026 04:35, Aldo Ariel Panzardo wrote: > Between nfc_llcp_connecting_sock_get() and lock_sock(), another > thread can move the socket from connecting_sockets to sockets via > a failed connect + re-bind. After lock_sock(), the socket may no > longer be in LLCP_CONNECTING state, so unconditionally unlinking > from connecting_sockets corrupts the wrong list. > > Add a state check after acquiring the socket lock: only unlink > from connecting_sockets if the socket is still LLCP_CONNECTING. > > Fixes: bf1460acdf8c ("nfc: llcp: Fix list corruption / refcount desync in nfc_llcp_recv_dm()") > Cc: stable@vger.kernel.org > Reported-by: Sashiko <sashiko-bot@kernel.org> > Signed-off-by: Aldo Ariel Panzardo <qwe.aldo@gmail.com> > --- Thank you for your patch, but please never send new version OR another patches as follow up to existing thread. Please read: https://docs.kernel.org/process/submitting-patches.html Thank you David ^ permalink raw reply [flat|nested] 6+ messages in thread
* Re: [PATCH net] nfc: llcp: check socket state after locking in nfc_llcp_recv_dm() 2026-09-25 2:35 ` [PATCH net] nfc: llcp: check socket state after locking " Aldo Ariel Panzardo 2026-09-25 15:16 ` David Heidelberg @ 2026-09-28 17:38 ` netdev-bot+sashiko 1 sibling, 0 replies; 6+ messages in thread From: netdev-bot+sashiko @ 2026-09-28 17:38 UTC (permalink / raw) To: qwe.aldo Cc: david, davem, edumazet, kuba, pabeni, horms, netdev, linux-kernel, stable, sashiko-bot Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider. Critical: 0 · High: 3 (1) · Medium: 0 · Low: 0 If you believe none of the issues are legitimate, please reply to each issue of Medium severity or higher and explain why the AI is wrong. If any of them are legitimate and you plan to respin, please let patchwork know by including "pw-bot: cr" as a separate line at the end of your reply (one such reply per series is enough). - [High] Incomplete fix: the new check only skips the unlink, so a stale DM still resets the re-bound socket and leaves it hashed but… - [High] nfc_llcp_recv_cc(), which the comment says recv_dm mirrors, still has the same race and unconditionally unlinks the socket from… - [High] In nfc_llcp_recv_dm() (net/nfc/llcp_core.c:1329-1330), a socket in LLCP_CONNECTING may be on a different local's connecting_sockets… Pre-existing issues: - [High] This problem wasn't introduced by this patch, but llcp_sock_connect() (net/nfc/llcp_sock.c:709-716) rejects only… -- nfc: llcp: check socket state after locking in nfc_llcp_recv_dm() For DM(NOBOUND)/DM(REJ), nfc_llcp_recv_dm() now unlinks the socket from local->connecting_sockets only if the socket is still LLCP_CONNECTING after lock_sock(). The goal is to close a race where a failed connect followed by a re-bind moves the socket onto local->sockets between the lookup and the lock. > diff --git a/net/nfc/llcp_core.c b/net/nfc/llcp_core.c > index 74bf817007cf2..acf1133114821 100644 > --- a/net/nfc/llcp_core.c > +++ b/net/nfc/llcp_core.c > @@ -1326,7 +1326,7 @@ static void nfc_llcp_recv_dm(struct nfc_llcp_local *local, > * corrupt the connecting_sockets list / desync the socket refcount. > * This mirrors nfc_llcp_recv_cc(). > */ > - if (connecting) > + if (connecting && sk->sk_state == LLCP_CONNECTING) > nfc_llcp_sock_unlink(&local->connecting_sockets, sk); > > sk->sk_err = ENXIO; [Severity: High] When the socket is no longer LLCP_CONNECTING, the new check skips only the unlink. Execution still falls through to: sk->sk_err = ENXIO; sk->sk_state = LLCP_CLOSED; sk->sk_state_change(sk); In the race the commit message describes, can a stale DM still reset a socket that has just been re-bound? llcp_sock_connect() lock_sock(sk) nfc_llcp_sock_link(&local->connecting_sockets, sk) rx_work: nfc_llcp_recv_dm() nfc_llcp_connecting_sock_get() finds sk lock_sock(sk) blocks nfc_llcp_send_connect() fails goto sock_unlink /* sk_state is still LLCP_CLOSED */ release_sock(sk) llcp_sock_bind() sk_state != LLCP_CLOSED check passes nfc_llcp_sock_link(&local->sockets, sk) sk->sk_state = LLCP_BOUND lock_sock(sk) acquired sk_hashed(sk) is true sk_state != LLCP_CONNECTING, skip unlink sk_state = LLCP_CLOSED, sk_err = ENXIO nfc_llcp_send_connect() can fail with -ENOMEM from the TLV or PDU allocations, or through sock_alloc_send_skb() with MSG_DONTWAIT. The DM comes from the remote peer. The new ssap is predictable because nfc_llcp_get_local_ssap() uses find_first_zero_bit(). After this, sk is still hashed on local->sockets but in LLCP_CLOSED. A later listen() then fails with -EBADFD in llcp_sock_listen() even though bind() succeeded. A second bind() also passes the LLCP_CLOSED check in llcp_sock_bind() and calls nfc_llcp_sock_link(&local->sockets, sk) again. sk_add_node() is sock_hold() plus hlist_add_head() with no sk_hashed() check. Would that re-insert an already linked node and create a cycle in local->sockets? If so, sk_for_each() in nfc_llcp_sock_get() or nfc_llcp_sock_from_sn() would never terminate. The second bind() would also leak a sock reference, the first local kref, the first service_name buffer and any well-known service bit. Before this patch, this particular race left the socket unhashed. Other paths can also leave a socket hashed but LLCP_CLOSED, such as nfc_llcp_recv_disc() on a connected socket. When connecting is set but sk_state is not LLCP_CONNECTING, should the stale DM be ignored completely? The handler could call release_sock() and nfc_llcp_sock_put() and return, as the !sk_hashed() branch does, leaving sk_err and sk_state alone. [Severity: High] Should the check after lock_sock() also confirm that llcp_sock->local is still the local the DM was looked up on? Suppose nfc_llcp_send_connect() fails on local A while A's rx_work is waiting in lock_sock(). Userspace can then retry connect() on a second NFC device, local B, and get the lock first: llcp_sock_connect() /* retry */ llcp_sock->local = local; /* B */ nfc_llcp_sock_link(&local->connecting_sockets, sk); nfc_llcp_send_connect() succeeds sk->sk_state = LLCP_CONNECTING; sock_wait_state() drops the socket lock A's DM worker then gets the lock. It sees sk_hashed() true and sk_state == LLCP_CONNECTING, and calls: nfc_llcp_sock_unlink(&local->connecting_sockets, sk); /* local == A */ That takes A->connecting_sockets.lock, but sk_del_node_init() removes the node from B's list. Can this race with nfc_llcp_connecting_sock_get() in B's rx_work, or with connect() and release() on B's list, and corrupt B->connecting_sockets? The stale DM from A would also force the connect attempt on B to LLCP_CLOSED with ENXIO. [Severity: High] The comment above this check still says: * For DM(NOBOUND)/DM(REJ) the socket is still linked on the * connecting_sockets list. ... * This mirrors nfc_llcp_recv_cc(). After this change neither statement is accurate. The code no longer assumes the socket is on connecting_sockets, and nfc_llcp_recv_cc() has no matching check. Does nfc_llcp_recv_cc() have the same race? It also looks up the socket with nfc_llcp_connecting_sock_get() and calls lock_sock(). It checks only sk_hashed() and then does: /* Unlink from connecting and link to the client array */ nfc_llcp_sock_unlink(&local->connecting_sockets, sk); nfc_llcp_sock_link(&local->sockets, sk); llcp_sock->dsap = ssap; ... sk->sk_state = LLCP_CONNECTED; Take the same failed connect plus re-bind sequence, with the peer sending a CC instead of a DM. sk is then on local->sockets in LLCP_BOUND. sk_del_node_init() would remove it from local->sockets while holding connecting_sockets.lock, not sockets.lock. Nothing synchronizes that removal with the other users of local->sockets. The socket would then be re-linked with a peer-chosen dsap and marked LLCP_CONNECTED, although it never sent a connect PDU. Should nfc_llcp_recv_cc() get the same revalidation, and the comment be updated? [Severity: High] This isn't a bug introduced by this patch, but the new check assumes sk_state tells which list the socket is on, and llcp_sock_connect() can break that. It rejects only two states: if (sk->sk_state == LLCP_CONNECTED) { ret = -EISCONN; goto error; } if (sk->sk_state == LLCP_CONNECTING) { ret = -EINPROGRESS; goto error; } A socket in LLCP_BOUND or LLCP_LISTEN is already on local->sockets because llcp_sock_bind() put it there. If connect() is then called on that socket, it overwrites llcp_sock->local, ssap and service_name without releasing the bind-time ones. That leaks a local kref, the service_name buffer and any well-known service bit. It then calls: nfc_llcp_sock_link(&local->connecting_sockets, sk); sk_add_node() does sock_hold() and hlist_add_head() without an sk_hashed() check. This rewrites sk_node while its predecessor on local->sockets still points at it. Doesn't that cross-link local->sockets and connecting_sockets? Any later unlink in nfc_llcp_recv_cc(), nfc_llcp_recv_dm() or llcp_sock_release() would then work on a node that is on both lists. An unprivileged user can reach this by calling bind() and then connect() on the same socket while the DEP link is up. -- Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260925023509.3060096-1-qwe.aldo%40gmail.com ^ permalink raw reply [flat|nested] 6+ messages in thread
end of thread, other threads:[~2026-09-28 17:38 UTC | newest] Thread overview: 6+ messages (download: mbox.gz / follow: Atom feed) -- links below jump to the message on this page -- 2026-09-23 13:33 [PATCH net RESEND] nfc: llcp: Fix list corruption / refcount desync in nfc_llcp_recv_dm() Aldo Ariel Panzardo 2026-09-24 3:10 ` patchwork-bot+netdevbpf 2026-09-25 1:35 ` netdev-bot+sashiko 2026-09-25 2:35 ` [PATCH net] nfc: llcp: check socket state after locking " Aldo Ariel Panzardo 2026-09-25 15:16 ` David Heidelberg 2026-09-28 17:38 ` netdev-bot+sashiko
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®