Linux bluetooth development
 help / color / mirror / Atom feed
* [PATCH] Bluetooth: L2CAP: fix race l2cap_sock_cleanup_listen() vs. put_chan
@ 2026-08-02 12:12 Pauli Virtanen
  2026-08-02 13:42 ` bluez.test.bot
                   ` (2 more replies)
  0 siblings, 3 replies; 8+ messages in thread
From: Pauli Virtanen @ 2026-08-02 12:12 UTC (permalink / raw)
  To: linux-bluetooth
  Cc: Pauli Virtanen, marcel, luiz.dentz, oss, linux-kernel,
	syzbot+e6382a2f53f5fc7453ac, syzkaller-bugs

For L2CAP sockets without owning sk->sk_socket, reading
l2cap_pi(sk)->chan may race against concurrent l2cap_sock_kill() ->
l2cap_sock_put_chan().  This excludes simultaneous proto_ops callbacks,
but access in l2cap_sock_cleanup_listen() has unsafe lockless read.

Fix the race by taking lock_sock() in l2cap_sock_kill() to
synchronize with l2cap_sock_cleanup_listen().  hold_unless_zero() is not
needed here, l2cap_pi(sk)->chan owns reference if it is non-NULL.

Fixes: 0e2c0392b9dc ("Bluetooth: L2CAP: Fix use-after-free in l2cap_sock_new_connection_cb()")
Reported-by: syzbot+e6382a2f53f5fc7453ac@syzkaller.appspotmail.com
Closes: https://syzkaller.appspot.com/bug?extid=e6382a2f53f5fc7453ac
Signed-off-by: Pauli Virtanen <pav@iki.fi>
---
 include/net/bluetooth/l2cap.h |  5 +++++
 net/bluetooth/l2cap_sock.c    | 23 +++++++++++++----------
 2 files changed, 18 insertions(+), 10 deletions(-)

diff --git a/include/net/bluetooth/l2cap.h b/include/net/bluetooth/l2cap.h
index ef6ce1c20a4f..3d9a32094347 100644
--- a/include/net/bluetooth/l2cap.h
+++ b/include/net/bluetooth/l2cap.h
@@ -699,7 +699,12 @@ struct l2cap_rx_busy {
 
 struct l2cap_pinfo {
 	struct bt_sock		bt;
+
+	/* With owning sk_socket chan may be read without lock, other access
+	 * should hold lock_sock.
+	 */
 	struct l2cap_chan	*chan;
+
 	struct list_head	rx_busy;
 };
 
diff --git a/net/bluetooth/l2cap_sock.c b/net/bluetooth/l2cap_sock.c
index 735167f73f31..9540617a0e6c 100644
--- a/net/bluetooth/l2cap_sock.c
+++ b/net/bluetooth/l2cap_sock.c
@@ -1312,7 +1312,12 @@ static void l2cap_sock_kill(struct sock *sk)
 
 	BT_DBG("sk %p state %s", sk, state_to_string(sk->sk_state));
 
+	/* Take lock to synchronize against access without owning sk->sk_socket,
+	 * eg. in l2cap_sock_cleanup_listen(). proto_ops etc. don't need lock.
+	 */
+	lock_sock(sk);
 	l2cap_sock_put_chan(sk);
+	release_sock(sk);
 
 	/* Kill poor orphan */
 	sock_set_flag(sk, SOCK_DEAD);
@@ -1516,14 +1521,10 @@ static void l2cap_sock_cleanup_listen(struct sock *parent)
 	 * establish sk_lock -> conn->lock and invert the established
 	 * conn->lock -> chan->lock -> sk_lock order (lockdep deadlock).
 	 *
-	 * Instead, briefly take the child sk lock to fetch and pin its chan.
-	 * l2cap_conn_del() reaches the chan free only via
-	 * l2cap_chan_del() -> l2cap_sock_teardown_cb(), which itself takes
-	 * the child sk lock; holding it across l2cap_chan_hold_unless_zero()
-	 * therefore guarantees the chan cannot be freed while we read and
-	 * pin it (hold_unless_zero() additionally skips a chan already past
-	 * its last reference).  We then drop the sk lock before taking
-	 * chan->lock, so sk and chan locks are never held together.
+	 * Instead, briefly take the child sk lock to synchronize vs.
+	 * l2cap_sock_kill that puts l2cap_pi(sk)->chan. We then drop the sk
+	 * lock before taking chan->lock, so sk and chan locks are never held
+	 * together.
 	 *
 	 * Since we cannot call l2cap_chan_close() without conn->lock,
 	 * schedule l2cap_chan_timeout to close the channel; it already
@@ -1533,10 +1534,12 @@ static void l2cap_sock_cleanup_listen(struct sock *parent)
 		struct l2cap_chan *chan;
 
 		lock_sock_nested(sk, L2CAP_NESTING_NORMAL);
-		chan = l2cap_chan_hold_unless_zero(l2cap_pi(sk)->chan);
+		chan = l2cap_pi(sk)->chan;
+		if (chan)
+			l2cap_chan_hold(chan);
 		release_sock(sk);
 		if (!chan) {
-			/* l2cap_conn_del() already tearing this child down */
+			/* Already torn down */
 			sock_put(sk);
 			continue;
 		}
-- 
2.55.0


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

* RE: Bluetooth: L2CAP: fix race l2cap_sock_cleanup_listen() vs. put_chan
  2026-08-02 12:12 [PATCH] Bluetooth: L2CAP: fix race l2cap_sock_cleanup_listen() vs. put_chan Pauli Virtanen
@ 2026-08-02 13:42 ` bluez.test.bot
  2026-08-03  6:13 ` [PATCH] " Hillf Danton
  2026-08-03 16:53 ` Pauli Virtanen
  2 siblings, 0 replies; 8+ messages in thread
From: bluez.test.bot @ 2026-08-02 13:42 UTC (permalink / raw)
  To: linux-bluetooth, pav

[-- Attachment #1: Type: text/plain, Size: 1235 bytes --]

This is automated email and please do not reply to this email!

Dear submitter,

Thank you for submitting the patches to the linux bluetooth mailing list.
This is a CI test results with your patch series:
PW Link:https://patchwork.kernel.org/project/bluetooth/list/?series=1138893

---Test result---

Test Summary:
CheckPatch                    PASS      9.03 seconds
VerifyFixes                   PASS      3.43 seconds
VerifySignedoff               PASS      1.99 seconds
GitLint                       PASS      0.78 seconds
SubjectPrefix                 PASS      0.12 seconds
BuildKernel                   PASS      24.55 seconds
CheckAllWarning               PASS      27.10 seconds
CheckSparse                   PASS      25.90 seconds
BuildKernel32                 PASS      23.93 seconds
CheckKernelLLVM               SKIP      0.00 seconds
TestRunnerSetup               PASS      451.75 seconds
TestRunner_l2cap-tester       PASS      63.97 seconds
IncrementalBuild              PASS      23.53 seconds

Details
##############################
Test: CheckKernelLLVM - SKIP
Desc: Build kernel with LLVM + context analysis
Output:
Clang not found


https://github.com/bluez/bluetooth-next/pull/528

---
Regards,
Linux Bluetooth


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

* Re: [PATCH] Bluetooth: L2CAP: fix race l2cap_sock_cleanup_listen() vs. put_chan
  2026-08-02 12:12 [PATCH] Bluetooth: L2CAP: fix race l2cap_sock_cleanup_listen() vs. put_chan Pauli Virtanen
  2026-08-02 13:42 ` bluez.test.bot
@ 2026-08-03  6:13 ` Hillf Danton
  2026-08-03 16:53 ` Pauli Virtanen
  2 siblings, 0 replies; 8+ messages in thread
From: Hillf Danton @ 2026-08-03  6:13 UTC (permalink / raw)
  To: Pauli Virtanen
  Cc: linux-bluetooth, marcel, luiz.dentz, linux-kernel,
	syzbot+e6382a2f53f5fc7453ac, syzkaller-bugs

On Sun,  2 Aug 2026 15:12:28 +0300 Pauli Virtanen wrote:
> For L2CAP sockets without owning sk->sk_socket, reading
> l2cap_pi(sk)->chan may race against concurrent l2cap_sock_kill() ->
> l2cap_sock_put_chan().  This excludes simultaneous proto_ops callbacks,
> but access in l2cap_sock_cleanup_listen() has unsafe lockless read.
> 
> Fix the race by taking lock_sock() in l2cap_sock_kill() to
> synchronize with l2cap_sock_cleanup_listen().  hold_unless_zero() is not
> needed here, l2cap_pi(sk)->chan owns reference if it is non-NULL.
> 
> Fixes: 0e2c0392b9dc ("Bluetooth: L2CAP: Fix use-after-free in l2cap_sock_new_connection_cb()")
> Reported-by: syzbot+e6382a2f53f5fc7453ac@syzkaller.appspotmail.com
> Closes: https://syzkaller.appspot.com/bug?extid=e6382a2f53f5fc7453ac
> Signed-off-by: Pauli Virtanen <pav@iki.fi>
> ---
>  include/net/bluetooth/l2cap.h |  5 +++++
>  net/bluetooth/l2cap_sock.c    | 23 +++++++++++++----------
>  2 files changed, 18 insertions(+), 10 deletions(-)
> 
> diff --git a/include/net/bluetooth/l2cap.h b/include/net/bluetooth/l2cap.h
> index ef6ce1c20a4f..3d9a32094347 100644
> --- a/include/net/bluetooth/l2cap.h
> +++ b/include/net/bluetooth/l2cap.h
> @@ -699,7 +699,12 @@ struct l2cap_rx_busy {
>  
>  struct l2cap_pinfo {
>  	struct bt_sock		bt;
> +
> +	/* With owning sk_socket chan may be read without lock, other access
> +	 * should hold lock_sock.
> +	 */
>  	struct l2cap_chan	*chan;
> +
>  	struct list_head	rx_busy;
>  };
>  
> diff --git a/net/bluetooth/l2cap_sock.c b/net/bluetooth/l2cap_sock.c
> index 735167f73f31..9540617a0e6c 100644
> --- a/net/bluetooth/l2cap_sock.c
> +++ b/net/bluetooth/l2cap_sock.c
> @@ -1312,7 +1312,12 @@ static void l2cap_sock_kill(struct sock *sk)
>  
>  	BT_DBG("sk %p state %s", sk, state_to_string(sk->sk_state));
>  
> +	/* Take lock to synchronize against access without owning sk->sk_socket,
> +	 * eg. in l2cap_sock_cleanup_listen(). proto_ops etc. don't need lock.
> +	 */
> +	lock_sock(sk);
>  	l2cap_sock_put_chan(sk);
> +	release_sock(sk);
>  
>  	/* Kill poor orphan */
>  	sock_set_flag(sk, SOCK_DEAD);

In l2cap_sock_teardown_cb(), sock is only zapped after cleanup including unlink,
so why do you see a linked and zapped sock in l2cap_sock_cleanup_listen()?

> @@ -1516,14 +1521,10 @@ static void l2cap_sock_cleanup_listen(struct sock *parent)
>  	 * establish sk_lock -> conn->lock and invert the established
>  	 * conn->lock -> chan->lock -> sk_lock order (lockdep deadlock).
>  	 *
> -	 * Instead, briefly take the child sk lock to fetch and pin its chan.
> -	 * l2cap_conn_del() reaches the chan free only via
> -	 * l2cap_chan_del() -> l2cap_sock_teardown_cb(), which itself takes
> -	 * the child sk lock; holding it across l2cap_chan_hold_unless_zero()
> -	 * therefore guarantees the chan cannot be freed while we read and
> -	 * pin it (hold_unless_zero() additionally skips a chan already past
> -	 * its last reference).  We then drop the sk lock before taking
> -	 * chan->lock, so sk and chan locks are never held together.
> +	 * Instead, briefly take the child sk lock to synchronize vs.
> +	 * l2cap_sock_kill that puts l2cap_pi(sk)->chan. We then drop the sk
> +	 * lock before taking chan->lock, so sk and chan locks are never held
> +	 * together.
>  	 *
>  	 * Since we cannot call l2cap_chan_close() without conn->lock,
>  	 * schedule l2cap_chan_timeout to close the channel; it already
> @@ -1533,10 +1534,12 @@ static void l2cap_sock_cleanup_listen(struct sock *parent)
>  		struct l2cap_chan *chan;
>  
>  		lock_sock_nested(sk, L2CAP_NESTING_NORMAL);
> -		chan = l2cap_chan_hold_unless_zero(l2cap_pi(sk)->chan);
> +		chan = l2cap_pi(sk)->chan;
> +		if (chan)
> +			l2cap_chan_hold(chan);
>  		release_sock(sk);
>  		if (!chan) {
> -			/* l2cap_conn_del() already tearing this child down */
> +			/* Already torn down */
>  			sock_put(sk);
>  			continue;
>  		}
> -- 
> 2.55.0

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

* Re: [PATCH] Bluetooth: L2CAP: fix race l2cap_sock_cleanup_listen() vs. put_chan
  2026-08-02 12:12 [PATCH] Bluetooth: L2CAP: fix race l2cap_sock_cleanup_listen() vs. put_chan Pauli Virtanen
  2026-08-02 13:42 ` bluez.test.bot
  2026-08-03  6:13 ` [PATCH] " Hillf Danton
@ 2026-08-03 16:53 ` Pauli Virtanen
  2026-08-04  0:47   ` Hillf Danton
  2 siblings, 1 reply; 8+ messages in thread
From: Pauli Virtanen @ 2026-08-03 16:53 UTC (permalink / raw)
  To: Hillf Danton
  Cc: linux-bluetooth, marcel, luiz.dentz, linux-kernel,
	syzbot+e6382a2f53f5fc7453ac, syzkaller-bugs

Hi,

ma, 2026-08-03 kello 14:13 +0800, Hillf Danton kirjoitti:
> On Sun,  2 Aug 2026 15:12:28 +0300 Pauli Virtanen wrote:
> > For L2CAP sockets without owning sk->sk_socket, reading
> > l2cap_pi(sk)->chan may race against concurrent l2cap_sock_kill() ->
> > l2cap_sock_put_chan().  This excludes simultaneous proto_ops callbacks,
> > but access in l2cap_sock_cleanup_listen() has unsafe lockless read.
> > 
> > Fix the race by taking lock_sock() in l2cap_sock_kill() to
> > synchronize with l2cap_sock_cleanup_listen().  hold_unless_zero() is not
> > needed here, l2cap_pi(sk)->chan owns reference if it is non-NULL.
> > 
> > Fixes: 0e2c0392b9dc ("Bluetooth: L2CAP: Fix use-after-free in l2cap_sock_new_connection_cb()")
> > Reported-by: syzbot+e6382a2f53f5fc7453ac@syzkaller.appspotmail.com
> > Closes: https://syzkaller.appspot.com/bug?extid=e6382a2f53f5fc7453ac
> > Signed-off-by: Pauli Virtanen <pav@iki.fi>
> > ---
> >  include/net/bluetooth/l2cap.h |  5 +++++
> >  net/bluetooth/l2cap_sock.c    | 23 +++++++++++++----------
> >  2 files changed, 18 insertions(+), 10 deletions(-)
> > 
> > diff --git a/include/net/bluetooth/l2cap.h b/include/net/bluetooth/l2cap.h
> > index ef6ce1c20a4f..3d9a32094347 100644
> > --- a/include/net/bluetooth/l2cap.h
> > +++ b/include/net/bluetooth/l2cap.h
> > @@ -699,7 +699,12 @@ struct l2cap_rx_busy {
> >  
> >  struct l2cap_pinfo {
> >  	struct bt_sock		bt;
> > +
> > +	/* With owning sk_socket chan may be read without lock, other access
> > +	 * should hold lock_sock.
> > +	 */
> >  	struct l2cap_chan	*chan;
> > +
> >  	struct list_head	rx_busy;
> >  };
> >  
> > diff --git a/net/bluetooth/l2cap_sock.c b/net/bluetooth/l2cap_sock.c
> > index 735167f73f31..9540617a0e6c 100644
> > --- a/net/bluetooth/l2cap_sock.c
> > +++ b/net/bluetooth/l2cap_sock.c
> > @@ -1312,7 +1312,12 @@ static void l2cap_sock_kill(struct sock *sk)
> >  
> >  	BT_DBG("sk %p state %s", sk, state_to_string(sk->sk_state));
> >  
> > +	/* Take lock to synchronize against access without owning sk->sk_socket,
> > +	 * eg. in l2cap_sock_cleanup_listen(). proto_ops etc. don't need lock.
> > +	 */
> > +	lock_sock(sk);
> >  	l2cap_sock_put_chan(sk);
> > +	release_sock(sk);
> >  
> >  	/* Kill poor orphan */
> >  	sock_set_flag(sk, SOCK_DEAD);
> 
> In l2cap_sock_teardown_cb(), sock is only zapped after cleanup including unlink,
> so why do you see a linked and zapped sock in l2cap_sock_cleanup_listen()?

l2cap_sock_cleanup_listen() is not a single critical section.

There is the following race:

   [Task 1]                         [Task 2 (hdev->workqueue)]
   l2cap_sock_release(parent)       l2cap_disconn_cfm
     l2cap_sock_cleanup_listen        l2cap_conn_del
       bt_accept_dequeue                l2cap_chan_del
         lock_sock(sk)                    l2cap_sock_teardown_cb
         bt_accept_unlink
           bt_sk(sk)->parent = NULL
         release_sock(sk) ----------------> lock_sock(sk)
                                            parent = bt_sk(sk)->parent /* == NULL */
       lock_sock(sk) <--------------------- release_sock(sk)
                                            sock_set_flag(sk, SOCK_ZAPPED)
                                        l2cap_sock_close_cb
                                          l2cap_sock_kill(sk)
                                            l2cap_sock_put_chan
       chan = READ l2cap_pi(sk)->chan         l2cap_pi(sk)->chan = NULL
       l2cap_chan_hold_unless_zero            l2cap_put_chan(chan)
         kref_get_unless_zero(&chan->ref)

> > @@ -1516,14 +1521,10 @@ static void l2cap_sock_cleanup_listen(struct sock *parent)
> >  	 * establish sk_lock -> conn->lock and invert the established
> >  	 * conn->lock -> chan->lock -> sk_lock order (lockdep deadlock).
> >  	 *
> > -	 * Instead, briefly take the child sk lock to fetch and pin its chan.
> > -	 * l2cap_conn_del() reaches the chan free only via
> > -	 * l2cap_chan_del() -> l2cap_sock_teardown_cb(), which itself takes
> > -	 * the child sk lock; holding it across l2cap_chan_hold_unless_zero()
> > -	 * therefore guarantees the chan cannot be freed while we read and
> > -	 * pin it (hold_unless_zero() additionally skips a chan already past
> > -	 * its last reference).  We then drop the sk lock before taking
> > -	 * chan->lock, so sk and chan locks are never held together.
> > +	 * Instead, briefly take the child sk lock to synchronize vs.
> > +	 * l2cap_sock_kill that puts l2cap_pi(sk)->chan. We then drop the sk
> > +	 * lock before taking chan->lock, so sk and chan locks are never held
> > +	 * together.
> >  	 *
> >  	 * Since we cannot call l2cap_chan_close() without conn->lock,
> >  	 * schedule l2cap_chan_timeout to close the channel; it already
> > @@ -1533,10 +1534,12 @@ static void l2cap_sock_cleanup_listen(struct sock *parent)
> >  		struct l2cap_chan *chan;
> >  
> >  		lock_sock_nested(sk, L2CAP_NESTING_NORMAL);
> > -		chan = l2cap_chan_hold_unless_zero(l2cap_pi(sk)->chan);
> > +		chan = l2cap_pi(sk)->chan;
> > +		if (chan)
> > +			l2cap_chan_hold(chan);
> >  		release_sock(sk);
> >  		if (!chan) {
> > -			/* l2cap_conn_del() already tearing this child down */
> > +			/* Already torn down */
> >  			sock_put(sk);
> >  			continue;
> >  		}
> > -- 
> > 2.55.0

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

* Re: [PATCH] Bluetooth: L2CAP: fix race l2cap_sock_cleanup_listen() vs. put_chan
  2026-08-03 16:53 ` Pauli Virtanen
@ 2026-08-04  0:47   ` Hillf Danton
  2026-08-04  5:40     ` Pauli Virtanen
  0 siblings, 1 reply; 8+ messages in thread
From: Hillf Danton @ 2026-08-04  0:47 UTC (permalink / raw)
  To: Pauli Virtanen
  Cc: linux-bluetooth, marcel, luiz.dentz, linux-kernel,
	syzbot+e6382a2f53f5fc7453ac, syzkaller-bugs

On Mon, 03 Aug 2026 19:53:31 +0300 Pauli Virtanen wrote:
> ma, 2026-08-03 kello 14:13 +0800, Hillf Danton kirjoitti:
> > On Sun,  2 Aug 2026 15:12:28 +0300 Pauli Virtanen wrote:
> > > For L2CAP sockets without owning sk->sk_socket, reading
> > > l2cap_pi(sk)->chan may race against concurrent l2cap_sock_kill() ->
> > > l2cap_sock_put_chan().  This excludes simultaneous proto_ops callbacks,
> > > but access in l2cap_sock_cleanup_listen() has unsafe lockless read.
> > > 
> > > Fix the race by taking lock_sock() in l2cap_sock_kill() to
> > > synchronize with l2cap_sock_cleanup_listen().  hold_unless_zero() is not
> > > needed here, l2cap_pi(sk)->chan owns reference if it is non-NULL.
> > > 
> > > Fixes: 0e2c0392b9dc ("Bluetooth: L2CAP: Fix use-after-free in l2cap_sock_new_connection_cb()")
> > > Reported-by: syzbot+e6382a2f53f5fc7453ac@syzkaller.appspotmail.com
> > > Closes: https://syzkaller.appspot.com/bug?extid=e6382a2f53f5fc7453ac
> > > Signed-off-by: Pauli Virtanen <pav@iki.fi>
> > > ---
> > >  include/net/bluetooth/l2cap.h |  5 +++++
> > >  net/bluetooth/l2cap_sock.c    | 23 +++++++++++++----------
> > >  2 files changed, 18 insertions(+), 10 deletions(-)
> > > 
> > > diff --git a/include/net/bluetooth/l2cap.h b/include/net/bluetooth/l2cap.h
> > > index ef6ce1c20a4f..3d9a32094347 100644
> > > --- a/include/net/bluetooth/l2cap.h
> > > +++ b/include/net/bluetooth/l2cap.h
> > > @@ -699,7 +699,12 @@ struct l2cap_rx_busy {
> > >  
> > >  struct l2cap_pinfo {
> > >  	struct bt_sock		bt;
> > > +
> > > +	/* With owning sk_socket chan may be read without lock, other access
> > > +	 * should hold lock_sock.
> > > +	 */
> > >  	struct l2cap_chan	*chan;
> > > +
> > >  	struct list_head	rx_busy;
> > >  };
> > >  
> > > diff --git a/net/bluetooth/l2cap_sock.c b/net/bluetooth/l2cap_sock.c
> > > index 735167f73f31..9540617a0e6c 100644
> > > --- a/net/bluetooth/l2cap_sock.c
> > > +++ b/net/bluetooth/l2cap_sock.c
> > > @@ -1312,7 +1312,12 @@ static void l2cap_sock_kill(struct sock *sk)
> > >  
> > >  	BT_DBG("sk %p state %s", sk, state_to_string(sk->sk_state));
> > >  
> > > +	/* Take lock to synchronize against access without owning sk->sk_socket,
> > > +	 * eg. in l2cap_sock_cleanup_listen(). proto_ops etc. don't need lock.
> > > +	 */
> > > +	lock_sock(sk);
> > >  	l2cap_sock_put_chan(sk);
> > > +	release_sock(sk);
> > >  
> > >  	/* Kill poor orphan */
> > >  	sock_set_flag(sk, SOCK_DEAD);
> > 
> > In l2cap_sock_teardown_cb(), sock is only zapped after cleanup including unlink,
> > so why do you see a linked and zapped sock in l2cap_sock_cleanup_listen()?
> 
> l2cap_sock_cleanup_listen() is not a single critical section.
> 
> There is the following race:
> 
>    [Task 1]                         [Task 2 (hdev->workqueue)]
>    l2cap_sock_release(parent)       l2cap_disconn_cfm
>      l2cap_sock_cleanup_listen        l2cap_conn_del
>        bt_accept_dequeue                l2cap_chan_del
>          lock_sock(sk)                    l2cap_sock_teardown_cb
>          bt_accept_unlink
>            bt_sk(sk)->parent = NULL
>          release_sock(sk) ----------------> lock_sock(sk)
>                                             parent = bt_sk(sk)->parent /* == NULL */
>        lock_sock(sk) <--------------------- release_sock(sk)
>                                             sock_set_flag(sk, SOCK_ZAPPED)
>                                         l2cap_sock_close_cb
>                                           l2cap_sock_kill(sk)
>                                             l2cap_sock_put_chan
>        chan = READ l2cap_pi(sk)->chan         l2cap_pi(sk)->chan = NULL
>        l2cap_chan_hold_unless_zero            l2cap_put_chan(chan)
>          kref_get_unless_zero(&chan->ref)
>
The race window is still open after this work.

					     release_sock(sk)
                                             sock_set_flag(sk, SOCK_ZAPPED)
                                         l2cap_sock_close_cb
                                           l2cap_sock_kill(sk)
                                             l2cap_sock_put_chan
					       l2cap_pi(sk)->chan = NULL
					       l2cap_put_chan(chan)
					     sock_set_flag(sk, SOCK_DEAD);
					     sock_put(sk); // free sk
        lock_sock(sk) // uaf
        chan = READ l2cap_pi(sk)->chan
        l2cap_chan_hold_unless_zero

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

* Re: [PATCH] Bluetooth: L2CAP: fix race l2cap_sock_cleanup_listen() vs. put_chan
  2026-08-04  0:47   ` Hillf Danton
@ 2026-08-04  5:40     ` Pauli Virtanen
  2026-08-04  8:16       ` Hillf Danton
  0 siblings, 1 reply; 8+ messages in thread
From: Pauli Virtanen @ 2026-08-04  5:40 UTC (permalink / raw)
  To: Hillf Danton
  Cc: linux-bluetooth, marcel, luiz.dentz, linux-kernel,
	syzbot+e6382a2f53f5fc7453ac, syzkaller-bugs

Hi,

ti, 2026-08-04 kello 08:47 +0800, Hillf Danton kirjoitti:
> On Mon, 03 Aug 2026 19:53:31 +0300 Pauli Virtanen wrote:
> > ma, 2026-08-03 kello 14:13 +0800, Hillf Danton kirjoitti:
> > > On Sun,  2 Aug 2026 15:12:28 +0300 Pauli Virtanen wrote:
> > > > For L2CAP sockets without owning sk->sk_socket, reading
> > > > l2cap_pi(sk)->chan may race against concurrent
> > > > l2cap_sock_kill() ->
> > > > l2cap_sock_put_chan().  This excludes simultaneous proto_ops
> > > > callbacks,
> > > > but access in l2cap_sock_cleanup_listen() has unsafe lockless
> > > > read.
> > > > 
> > > > Fix the race by taking lock_sock() in l2cap_sock_kill() to
> > > > synchronize with l2cap_sock_cleanup_listen(). 
> > > > hold_unless_zero() is not
> > > > needed here, l2cap_pi(sk)->chan owns reference if it is non-
> > > > NULL.
> > > > 
> > > > Fixes: 0e2c0392b9dc ("Bluetooth: L2CAP: Fix use-after-free in
> > > > l2cap_sock_new_connection_cb()")
> > > > Reported-by:
> > > > syzbot+e6382a2f53f5fc7453ac@syzkaller.appspotmail.com
> > > > Closes:
> > > > https://syzkaller.appspot.com/bug?extid=e6382a2f53f5fc7453ac
> > > > Signed-off-by: Pauli Virtanen <pav@iki.fi>
> > > > ---
> > > >  include/net/bluetooth/l2cap.h |  5 +++++
> > > >  net/bluetooth/l2cap_sock.c    | 23 +++++++++++++----------
> > > >  2 files changed, 18 insertions(+), 10 deletions(-)
> > > > 
> > > > diff --git a/include/net/bluetooth/l2cap.h
> > > > b/include/net/bluetooth/l2cap.h
> > > > index ef6ce1c20a4f..3d9a32094347 100644
> > > > --- a/include/net/bluetooth/l2cap.h
> > > > +++ b/include/net/bluetooth/l2cap.h
> > > > @@ -699,7 +699,12 @@ struct l2cap_rx_busy {
> > > >  
> > > >  struct l2cap_pinfo {
> > > >  	struct bt_sock		bt;
> > > > +
> > > > +	/* With owning sk_socket chan may be read without
> > > > lock, other access
> > > > +	 * should hold lock_sock.
> > > > +	 */
> > > >  	struct l2cap_chan	*chan;
> > > > +
> > > >  	struct list_head	rx_busy;
> > > >  };
> > > >  
> > > > diff --git a/net/bluetooth/l2cap_sock.c
> > > > b/net/bluetooth/l2cap_sock.c
> > > > index 735167f73f31..9540617a0e6c 100644
> > > > --- a/net/bluetooth/l2cap_sock.c
> > > > +++ b/net/bluetooth/l2cap_sock.c
> > > > @@ -1312,7 +1312,12 @@ static void l2cap_sock_kill(struct sock
> > > > *sk)
> > > >  
> > > >  	BT_DBG("sk %p state %s", sk, state_to_string(sk-
> > > > >sk_state));
> > > >  
> > > > +	/* Take lock to synchronize against access without
> > > > owning sk->sk_socket,
> > > > +	 * eg. in l2cap_sock_cleanup_listen(). proto_ops etc.
> > > > don't need lock.
> > > > +	 */
> > > > +	lock_sock(sk);
> > > >  	l2cap_sock_put_chan(sk);
> > > > +	release_sock(sk);
> > > >  
> > > >  	/* Kill poor orphan */
> > > >  	sock_set_flag(sk, SOCK_DEAD);
> > > 
> > > In l2cap_sock_teardown_cb(), sock is only zapped after cleanup
> > > including unlink,
> > > so why do you see a linked and zapped sock in
> > > l2cap_sock_cleanup_listen()?
> > 
> > l2cap_sock_cleanup_listen() is not a single critical section.
> > 
> > There is the following race:
> > 
> >    [Task 1]                         [Task 2 (hdev->workqueue)]
> >    l2cap_sock_release(parent)       l2cap_disconn_cfm
> >      l2cap_sock_cleanup_listen        l2cap_conn_del
> >        bt_accept_dequeue                l2cap_chan_del
> >          lock_sock(sk)                    l2cap_sock_teardown_cb
> >          bt_accept_unlink
> >            bt_sk(sk)->parent = NULL
> >          release_sock(sk) ----------------> lock_sock(sk)
> >                                             parent = bt_sk(sk)-
> > >parent /* == NULL */
> >        lock_sock(sk) <--------------------- release_sock(sk)
> >                                             sock_set_flag(sk,
> > SOCK_ZAPPED)
> >                                         l2cap_sock_close_cb
> >                                           l2cap_sock_kill(sk)
> >                                             l2cap_sock_put_chan
> >        chan = READ l2cap_pi(sk)->chan         l2cap_pi(sk)->chan =
> > NULL
> >        l2cap_chan_hold_unless_zero            l2cap_put_chan(chan)
> >          kref_get_unless_zero(&chan->ref)
> > 
> The race window is still open after this work.
> 
> 					     release_sock(sk)
>                                              sock_set_flag(sk,
> SOCK_ZAPPED)
>                                          l2cap_sock_close_cb
>                                            l2cap_sock_kill(sk)
>                                              l2cap_sock_put_chan
> 					       l2cap_pi(sk)->chan =
> NULL
> 					       l2cap_put_chan(chan)
> 					     sock_set_flag(sk,
> SOCK_DEAD);
> 					     sock_put(sk); // free
> sk
>         lock_sock(sk) // uaf
>         chan = READ l2cap_pi(sk)->chan
>         l2cap_chan_hold_unless_zero

There is no UAF there, Task 1 holds a reference on sk at this point, if
you look at the code sock_put() follows.

I don't think there is a remaining problem.

-- 
Pauli Virtanen


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

* Re: [PATCH] Bluetooth: L2CAP: fix race l2cap_sock_cleanup_listen() vs. put_chan
  2026-08-04  5:40     ` Pauli Virtanen
@ 2026-08-04  8:16       ` Hillf Danton
  2026-08-04 16:06         ` Pauli Virtanen
  0 siblings, 1 reply; 8+ messages in thread
From: Hillf Danton @ 2026-08-04  8:16 UTC (permalink / raw)
  To: Pauli Virtanen
  Cc: linux-bluetooth, marcel, luiz.dentz, linux-kernel,
	syzbot+e6382a2f53f5fc7453ac, syzkaller-bugs

On Tue, 04 Aug 2026 08:40:16 +0300 Pauli Virtanen wrote:
>
> There is no UAF there, Task 1 holds a reference on sk at this point, if
> you look at the code sock_put() follows.
> 
If the subsequent put works without both race and mm leak, then it is the
very evidence of uaf with race.

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

* Re: [PATCH] Bluetooth: L2CAP: fix race l2cap_sock_cleanup_listen() vs. put_chan
  2026-08-04  8:16       ` Hillf Danton
@ 2026-08-04 16:06         ` Pauli Virtanen
  0 siblings, 0 replies; 8+ messages in thread
From: Pauli Virtanen @ 2026-08-04 16:06 UTC (permalink / raw)
  To: Hillf Danton
  Cc: linux-bluetooth, marcel, luiz.dentz, linux-kernel,
	syzbot+e6382a2f53f5fc7453ac, syzkaller-bugs

ti, 2026-08-04 kello 16:16 +0800, Hillf Danton kirjoitti:
> On Tue, 04 Aug 2026 08:40:16 +0300 Pauli Virtanen wrote:
> > ti, 2026-08-04 kello 08:47 +0800, Hillf Danton kirjoitti:
> > > On Mon, 03 Aug 2026 19:53:31 +0300 Pauli Virtanen wrote:
> > > >
> > > >    [Task 1]                         [Task 2 (hdev->workqueue)]
> > > >    l2cap_sock_release(parent)       l2cap_disconn_cfm
> > > >      l2cap_sock_cleanup_listen        l2cap_conn_del
> > > >        bt_accept_dequeue                l2cap_chan_del
> > > >          lock_sock(sk)                    l2cap_sock_teardown_cb
> > > >          bt_accept_unlink
> > > >            bt_sk(sk)->parent = NULL
> > > >          release_sock(sk) ----------------> lock_sock(sk)
> > > >                                             parent = bt_sk(sk)->parent /* == NULL */
> > > >        lock_sock(sk) <--------------------- release_sock(sk)
> > > >                                             sock_set_flag(sk, SOCK_ZAPPED)
> > > >                                         l2cap_sock_close_cb
> > > >                                           l2cap_sock_kill(sk)
> > > >                                             l2cap_sock_put_chan
> > > >        chan = READ l2cap_pi(sk)->chan         l2cap_pi(sk)->chan = NULL
> > > >        l2cap_chan_hold_unless_zero            l2cap_put_chan(chan)
> > > >          kref_get_unless_zero(&chan->ref)
> > >
> > > The race window is still open after this work.
> > >
> > > 					     release_sock(sk)
> > >                                              sock_set_flag(sk, SOCK_ZAPPED)
> > >                                          l2cap_sock_close_cb
> > >                                            l2cap_sock_kill(sk)
> > >                                              l2cap_sock_put_chan
> > > 					       l2cap_pi(sk)->chan = NULL
> > > 					       l2cap_put_chan(chan)
> > > 					     sock_set_flag(sk, SOCK_DEAD);
> > > 					     sock_put(sk); // free sk
> > >         lock_sock(sk) // uaf
> > >         chan = READ l2cap_pi(sk)->chan
> > >         l2cap_chan_hold_unless_zero
> > 
> > There is no UAF there, Task 1 holds a reference on sk at this point, if
> > you look at the code sock_put() follows.
> > 
> If the subsequent put works without both race and mm leak, then it is the
> very evidence of uaf with race.

This and the "// uaf" and "// free sk" above appear to ignore what is
explained also in the comment in l2cap_sock_cleanup_listen():

	 * bt_accept_dequeue() returns sk with its temporary queue-
walk
	 * reference held, so a concurrent l2cap_conn_del()
	 * -> l2cap_sock_kill() cannot free sk under us.

bt_accept_dequeue() returns a new temporary reference to sk, which is
owned by the caller who needs to put it after use.

The "sock_put(sk); // free sk" cannot free sk, because
bt_accept_dequeue(sk) got a new reference on sk before it, so the
refcount is at least 2 here. Then "lock_sock(sk) // uaf" cannot be UAF.

If chan != NULL in Task 1, it schedules l2cap_chan teardown. That
eventually ends up in l2cap_sock_kill() to put the reference owned by
l2cap_chan & sk_socket.

If chan == NULL in Task 1, l2cap_chan cleanup is completed or
concurrent, and nothing more needs to be done.

-- 
Pauli Virtanen

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

end of thread, other threads:[~2026-08-04 16:06 UTC | newest]

Thread overview: 8+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-08-02 12:12 [PATCH] Bluetooth: L2CAP: fix race l2cap_sock_cleanup_listen() vs. put_chan Pauli Virtanen
2026-08-02 13:42 ` bluez.test.bot
2026-08-03  6:13 ` [PATCH] " Hillf Danton
2026-08-03 16:53 ` Pauli Virtanen
2026-08-04  0:47   ` Hillf Danton
2026-08-04  5:40     ` Pauli Virtanen
2026-08-04  8:16       ` Hillf Danton
2026-08-04 16:06         ` Pauli Virtanen

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox