[PATCH 5/5] Bluetooth: 6lowpan: quiesce peers before channel deletion
From: Cen Zhang
Date: Mon Sep 21 2026 - 12:13:09 EST
6LoWPAN removes a peer and drops its channel reference from the close
callback. A transmitter which already observed the RCU-published peer
can still use the freed channel. Keeping the allocation alive alone is
insufficient: a transmitter which passed the deletion check can enqueue
a packet after L2CAP has purged the transmit queue.
Move peer cleanup to teardown, before FLAG_DEL and the transmit purge.
Mark the channel closed, unlink the peer under devices_lock, then drop
the lock and wait for network RCU readers with synchronize_net(). Free
the peer and release the initial channel reference only after those
transmitters have returned. Queue last-peer network-device deletion
before releasing the channel's ownership reference.
Disabling 6LoWPAN must also close the listener before sweeping existing
peers. Otherwise a request holding the old listener can publish a child
after the sweep. Serialize the transition with set_lock: requests which
complete admission before listener teardown are included in the sweep,
and requests which reach the closed listener are rejected.
The transmit/removal race produced this report:
[ 59.413897] BUG: KASAN: slab-use-after-free in send_pkt+0x3b1/0x3e0
[ 59.415014] Write of size 8 at addr ffff88810cf0e4a0 by task python3/583
[ ... report excerpt omitted ... ]
[ 59.490444] Freed by task 504:
[ ... report excerpt omitted ... ]
[ 59.493046] kfree+0x307/0x580
[ 59.493497] l2cap_chan_put+0x273/0x3a0
[ 59.494020] l2cap_disconnect_req+0x613/0x890
[ ... report excerpt omitted ... ]
Fixes: 6b8d4a6a0314 ("Bluetooth: 6LoWPAN: Use connected oriented channel instead of fixed one")
Assisted-by: LLM
Signed-off-by: Cen Zhang <zzzccc427@xxxxxxxxx>
---
net/bluetooth/6lowpan.c | 64 +++++++++++++++++++++--------------------
1 file changed, 33 insertions(+), 31 deletions(-)
diff --git a/net/bluetooth/6lowpan.c b/net/bluetooth/6lowpan.c
index 5c49dc146086..60d2c6d1be99 100644
--- a/net/bluetooth/6lowpan.c
+++ b/net/bluetooth/6lowpan.c
@@ -61,7 +61,6 @@ enum {
struct lowpan_peer {
struct list_head list;
- struct rcu_head rcu;
struct l2cap_chan *chan;
/* peer addresses in various formats */
@@ -100,7 +99,6 @@ static inline bool peer_del(struct lowpan_btle_dev *dev,
struct lowpan_peer *peer)
{
list_del_rcu(&peer->list);
- kfree_rcu(peer, rcu);
if (atomic_dec_and_test(&dev->peer_count)) {
BT_DBG("last peer");
@@ -776,16 +774,15 @@ static void delete_netdev(struct work_struct *work)
/* The entry pointer is deleted by the netdev destructor. */
}
-static void chan_close_cb(struct l2cap_chan *chan)
+static void chan_teardown_cb(struct l2cap_chan *chan, int err)
{
struct lowpan_btle_dev *entry;
struct lowpan_btle_dev *dev = NULL;
- struct lowpan_peer *peer;
- int err = -ENOENT;
+ struct lowpan_peer *peer = NULL;
bool last = false;
- bool queued;
BT_DBG("chan %p conn %p", chan, chan->conn);
+ chan->state = BT_CLOSED;
spin_lock(&devices_lock);
@@ -794,7 +791,6 @@ static void chan_close_cb(struct l2cap_chan *chan)
peer = __peer_lookup_chan(dev, chan);
if (peer) {
last = peer_del(dev, peer);
- err = 0;
BT_DBG("dev %p removing %speer %p", dev,
last ? "last " : "1 ", peer);
@@ -802,19 +798,28 @@ static void chan_close_cb(struct l2cap_chan *chan)
}
}
- if (!err && last && dev && !atomic_read(&dev->peer_count)) {
- spin_unlock(&devices_lock);
+ spin_unlock(&devices_lock);
- cancel_delayed_work_sync(&dev->notify_peers);
+ if (peer) {
+ /* ndo_start_xmit() holds network RCU while using peer->chan. */
+ synchronize_net();
+ kfree(peer);
- ifdown(dev->netdev);
+ if (last && dev) {
+ bool queued;
- queued = schedule_module_work(&entry->delete_netdev,
- delete_netdev, THIS_MODULE);
- WARN_ON_ONCE(!queued);
- } else {
- spin_unlock(&devices_lock);
+ cancel_delayed_work_sync(&dev->notify_peers);
+
+ ifdown(dev->netdev);
+
+ queued = schedule_module_work(&entry->delete_netdev,
+ delete_netdev, THIS_MODULE);
+ WARN_ON_ONCE(!queued);
+ }
}
+
+ if (test_and_clear_bit(FLAG_RELEASE_CREATOR, &chan->flags))
+ l2cap_chan_put(chan);
}
static void chan_state_change_cb(struct l2cap_chan *chan, int state, int err)
@@ -873,6 +878,9 @@ static long chan_get_sndtimeo_cb(struct l2cap_chan *chan)
static int chan_new_connection_cb(struct l2cap_chan *chan,
struct l2cap_chan *new_chan)
{
+ if (chan->state != BT_LISTEN)
+ return -EINVAL;
+
if (!l2cap_chan_set_ops(new_chan, &bt_6lowpan_chan_ops, THIS_MODULE))
return -ENODEV;
@@ -880,19 +888,11 @@ static int chan_new_connection_cb(struct l2cap_chan *chan,
return 0;
}
-static void chan_teardown_cb(struct l2cap_chan *chan, int err)
-{
- chan->state = BT_CLOSED;
-
- if (test_and_clear_bit(FLAG_RELEASE_CREATOR, &chan->flags))
- l2cap_chan_put(chan);
-}
-
static const struct l2cap_ops bt_6lowpan_chan_ops = {
.name = "L2CAP 6LoWPAN channel",
.new_connection = chan_new_connection_cb,
.recv = chan_recv_cb,
- .close = chan_close_cb,
+ .close = l2cap_chan_no_close,
.state_change = chan_state_change_cb,
.ready = chan_ready_cb,
.resume = chan_resume_cb,
@@ -1103,20 +1103,22 @@ static void disconnect_all_peers(void)
static void do_enable_set(bool flag)
{
- if (!flag || enable_6lowpan != flag)
- /* Disconnect existing connections if 6lowpan is
- * disabled
- */
- disconnect_all_peers();
+ bool disconnect;
+
+ mutex_lock(&set_lock);
+ disconnect = !flag || enable_6lowpan != flag;
enable_6lowpan = flag;
- mutex_lock(&set_lock);
if (listen_chan) {
l2cap_chan_close_unlocked(listen_chan, 0);
l2cap_chan_put(listen_chan);
+ listen_chan = NULL;
}
+ if (disconnect)
+ disconnect_all_peers();
+
listen_chan = bt_6lowpan_listen();
mutex_unlock(&set_lock);
}
--
2.43.0