From: Lee Jones <lee@kernel.org>
To: lee@kernel.org, David Heidelberg <david+nfc@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>,
Szymon Janc <szymon.janc@tieto.com>,
Samuel Ortiz <sameo@linux.intel.com>,
oe-linux-nfc@lists.linux.dev, netdev@vger.kernel.org,
linux-kernel@vger.kernel.org
Subject: [PATCH 1/1] nfc: llcp: Fix race condition in accept_queue lifecycle
Date: Mon, 1 Jun 2026 14:09:30 +0000 [thread overview]
Message-ID: <20260601140934.1040144-1-lee@kernel.org> (raw)
nfc_llcp_socket_release() walks the local sockets list and the listener
socket's accept_queue under bh_lock_sock(). However, bh_lock_sock()
does not provide mutual exclusion against process-context lock_sock()
held by nfc_llcp_accept_dequeue() during accept(). Since socket_release()
runs in process context (workqueue or kref cleanup), it never checks
sock_owned_by_user(), allowing both paths to manipulate the same child
socket concurrently. This leads to a double sock_put() (Use-After-Free)
or a NULL pointer dereference of child->parent in nfc_llcp_accept_unlink().
Fix this by converting nfc_llcp_socket_release() to use lock_sock().
Since it is called with the local sockets write-lock held (which disables
preemption, preventing sleep), repeatedly pop the head socket from the
list under the write-lock using a new helper nfc_llcp_sock_list_pop(),
then safely acquire lock_sock() on it after dropping the list lock.
A sock_hold() is taken under the list lock to keep the socket alive
across the sleep and balanced with a sock_put() after release.
Additionally, add a check for a non-NULL parent in nfc_llcp_accept_unlink()
to ensure idempotency and prevent NULL pointer dereference if the unlink
races with dequeue.
Fixes: 50b78b2a6500 ("NFC: Fix sleeping in atomic when releasing socket")
Signed-off-by: Lee Jones <lee@kernel.org>
---
net/nfc/llcp_core.c | 47 +++++++++++++++++++++++++--------------------
net/nfc/llcp_sock.c | 13 +++++++------
2 files changed, 33 insertions(+), 27 deletions(-)
diff --git a/net/nfc/llcp_core.c b/net/nfc/llcp_core.c
index dc65c719f35f2..e51bd0d081b62 100644
--- a/net/nfc/llcp_core.c
+++ b/net/nfc/llcp_core.c
@@ -63,21 +63,33 @@ static void nfc_llcp_socket_purge(struct nfc_llcp_sock *sock)
}
}
+static struct sock *nfc_llcp_sock_list_pop(struct llcp_sock_list *l)
+{
+ struct sock *sk;
+
+ write_lock(&l->lock);
+ sk = sk_head(&l->head);
+ if (sk) {
+ sock_hold(sk);
+ sk_del_node_init(sk);
+ }
+ write_unlock(&l->lock);
+
+ return sk;
+}
+
static void nfc_llcp_socket_release(struct nfc_llcp_local *local, bool device,
int err)
{
struct sock *sk;
- struct hlist_node *tmp;
struct nfc_llcp_sock *llcp_sock;
skb_queue_purge(&local->tx_queue);
- write_lock(&local->sockets.lock);
-
- sk_for_each_safe(sk, tmp, &local->sockets.head) {
+ while ((sk = nfc_llcp_sock_list_pop(&local->sockets))) {
llcp_sock = nfc_llcp_sock(sk);
- bh_lock_sock(sk);
+ lock_sock(sk);
nfc_llcp_socket_purge(llcp_sock);
@@ -92,7 +104,8 @@ static void nfc_llcp_socket_release(struct nfc_llcp_local *local, bool device,
&llcp_sock->accept_queue,
accept_queue) {
accept_sk = &lsk->sk;
- bh_lock_sock(accept_sk);
+ lock_sock_nested(accept_sk,
+ SINGLE_DEPTH_NESTING);
nfc_llcp_accept_unlink(accept_sk);
@@ -101,7 +114,7 @@ static void nfc_llcp_socket_release(struct nfc_llcp_local *local, bool device,
accept_sk->sk_state = LLCP_CLOSED;
accept_sk->sk_state_change(sk);
- bh_unlock_sock(accept_sk);
+ release_sock(accept_sk);
}
}
@@ -110,23 +123,18 @@ static void nfc_llcp_socket_release(struct nfc_llcp_local *local, bool device,
sk->sk_state = LLCP_CLOSED;
sk->sk_state_change(sk);
- bh_unlock_sock(sk);
-
- sk_del_node_init(sk);
+ release_sock(sk);
+ sock_put(sk);
}
- write_unlock(&local->sockets.lock);
-
/* If we still have a device, we keep the RAW sockets alive */
if (device == true)
return;
- write_lock(&local->raw_sockets.lock);
-
- sk_for_each_safe(sk, tmp, &local->raw_sockets.head) {
+ while ((sk = nfc_llcp_sock_list_pop(&local->raw_sockets))) {
llcp_sock = nfc_llcp_sock(sk);
- bh_lock_sock(sk);
+ lock_sock(sk);
nfc_llcp_socket_purge(llcp_sock);
@@ -135,12 +143,9 @@ static void nfc_llcp_socket_release(struct nfc_llcp_local *local, bool device,
sk->sk_state = LLCP_CLOSED;
sk->sk_state_change(sk);
- bh_unlock_sock(sk);
-
- sk_del_node_init(sk);
+ release_sock(sk);
+ sock_put(sk);
}
-
- write_unlock(&local->raw_sockets.lock);
}
static struct nfc_llcp_local *nfc_llcp_local_get(struct nfc_llcp_local *local)
diff --git a/net/nfc/llcp_sock.c b/net/nfc/llcp_sock.c
index feab29fc62f44..3d30f0233b986 100644
--- a/net/nfc/llcp_sock.c
+++ b/net/nfc/llcp_sock.c
@@ -384,11 +384,12 @@ void nfc_llcp_accept_unlink(struct sock *sk)
pr_debug("state %d\n", sk->sk_state);
- list_del_init(&llcp_sock->accept_queue);
- sk_acceptq_removed(llcp_sock->parent);
- llcp_sock->parent = NULL;
-
- sock_put(sk);
+ if (llcp_sock->parent) {
+ list_del_init(&llcp_sock->accept_queue);
+ sk_acceptq_removed(llcp_sock->parent);
+ llcp_sock->parent = NULL;
+ sock_put(sk);
+ }
}
void nfc_llcp_accept_enqueue(struct sock *parent, struct sock *sk)
@@ -622,7 +623,7 @@ static int llcp_sock_release(struct socket *sock)
list_for_each_entry_safe(lsk, n, &llcp_sock->accept_queue,
accept_queue) {
accept_sk = &lsk->sk;
- lock_sock(accept_sk);
+ lock_sock_nested(accept_sk, SINGLE_DEPTH_NESTING);
nfc_llcp_send_disconnect(lsk);
nfc_llcp_accept_unlink(accept_sk);
--
2.54.0.823.g6e5bcc1fc9-goog
next reply other threads:[~2026-06-01 14:09 UTC|newest]
Thread overview: 3+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-06-01 14:09 Lee Jones [this message]
2026-08-11 13:14 ` [PATCH 1/1] nfc: llcp: Fix race condition in accept_queue lifecycle Lee Jones
2026-08-11 19:02 ` David Heidelberg
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=20260601140934.1040144-1-lee@kernel.org \
--to=lee@kernel.org \
--cc=davem@davemloft.net \
--cc=david+nfc@ixit.cz \
--cc=edumazet@google.com \
--cc=horms@kernel.org \
--cc=kuba@kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=netdev@vger.kernel.org \
--cc=oe-linux-nfc@lists.linux.dev \
--cc=pabeni@redhat.com \
--cc=sameo@linux.intel.com \
--cc=szymon.janc@tieto.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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.