* [PATCH 3/5] Bluetooth: L2CAP: drain channel timers on connection teardown
2026-09-21 15:38 [PATCH 0/5] Bluetooth: 6LoWPAN lifecycle fixes Cen Zhang
2026-09-21 15:38 ` [PATCH 1/5] Bluetooth: L2CAP: ignore close requests for deleted channels Cen Zhang
2026-09-21 15:38 ` [PATCH 2/5] workqueue: add support for module-owned work Cen Zhang
@ 2026-09-21 15:39 ` Cen Zhang
2026-09-21 15:54 ` Luiz Augusto von Dentz
2026-09-21 15:39 ` [PATCH 4/5] Bluetooth: 6lowpan: handle channel setup failure and callback lifetime Cen Zhang
` (2 subsequent siblings)
5 siblings, 1 reply; 12+ messages in thread
From: Cen Zhang @ 2026-09-21 15:39 UTC (permalink / raw)
To: Tejun Heo, Lai Jiangshan, Marcel Holtmann,
Luiz Augusto von Dentz, Marco Elver, Jukka Rissanen
Cc: linux-kernel, linux-bluetooth, baijiaju1990, jjzuming, zzzccc427
L2CAP channel timers run on system_percpu_wq. Connection deletion cancels
them without waiting because callbacks may need conn->lock, but HCI
unregister does not drain that workqueue after releasing the lock. A
running callback can therefore outlive HCI-driver unregister. If it drops
the last channel reference and releases a protocol module, it still has
to return through Bluetooth code after those dependencies are gone.
Individual channel deletion has another cancellation race. It cancels
timers before calling the socket teardown callback, which can wait for
sk lock. A concurrent recvmsg holding that lock can clear local busy and
rearm the monitor timer. Deletion then unlinks the channel, so connection
teardown can no longer find that delayed work through the channel list.
Give each connection an ordered timer workqueue. Serialize timer queueing
with channel and connection stop flags, and stop each channel before
canceling its timers or calling teardown. At connection deletion, stop
queueing for the connection, cancel all four timer types, then drop
conn->lock and drain running callbacks before releasing the connection.
This lets callbacks acquire their locks, observe FLAG_DEL and return
before HCI unregister completes.
The channel stop flag also ensures that no unlinked channel can leave
delayed work behind on the new queue. The queue remains allocated until
the drained connection is freed. This adds one workqueue per connection
and serializes that connection's channel timers.
Assisted-by: LLM
Signed-off-by: Cen Zhang <zzzccc427@gmail.com>
---
include/net/bluetooth/l2cap.h | 23 ++++++++++++++++++++++-
net/bluetooth/l2cap_core.c | 34 ++++++++++++++++++++++++++++++----
2 files changed, 52 insertions(+), 5 deletions(-)
diff --git a/include/net/bluetooth/l2cap.h b/include/net/bluetooth/l2cap.h
index efb9b7f422d1..b4af087a0a81 100644
--- a/include/net/bluetooth/l2cap.h
+++ b/include/net/bluetooth/l2cap.h
@@ -611,6 +611,7 @@ struct l2cap_chan {
void *data;
const struct l2cap_ops *ops;
+ bool timers_stopped; /* protected by conn->timer_lock */
struct mutex lock;
};
@@ -636,6 +637,10 @@ struct l2cap_conn {
struct sk_buff_head pending_rx;
struct work_struct pending_rx_work;
+ struct workqueue_struct *timer_workqueue;
+ spinlock_t timer_lock; /* protects timer scheduling */
+
+ bool timers_stopped __guarded_by(&timer_lock);
struct delayed_work id_addr_timer;
@@ -856,13 +861,29 @@ static inline void l2cap_chan_unlock(struct l2cap_chan *chan)
static inline void l2cap_set_timer(struct l2cap_chan *chan,
struct delayed_work *work, long timeout)
{
+ struct l2cap_conn *conn = chan->conn;
+ unsigned long flags;
+ bool pending;
+
BT_DBG("chan %p state %s timeout %ld", chan,
state_to_string(chan->state), timeout);
+ if (WARN_ON_ONCE(!conn))
+ return;
+
+ spin_lock_irqsave(&conn->timer_lock, flags);
+ if (conn->timers_stopped || chan->timers_stopped) {
+ spin_unlock_irqrestore(&conn->timer_lock, flags);
+ return;
+ }
+
l2cap_chan_hold(chan);
/* put(chan) if timer was already queued so it already has a ref */
- if (mod_delayed_work(system_percpu_wq, work, timeout))
+ pending = mod_delayed_work(conn->timer_workqueue, work, timeout);
+ spin_unlock_irqrestore(&conn->timer_lock, flags);
+
+ if (pending)
l2cap_chan_put(chan);
}
diff --git a/net/bluetooth/l2cap_core.c b/net/bluetooth/l2cap_core.c
index 65e957fdc7ae..0df7bda54473 100644
--- a/net/bluetooth/l2cap_core.c
+++ b/net/bluetooth/l2cap_core.c
@@ -686,9 +686,21 @@ void l2cap_chan_add(struct l2cap_conn *conn, struct l2cap_chan *chan)
void l2cap_chan_del(struct l2cap_chan *chan, int err)
{
+ struct l2cap_conn *conn = chan->conn;
+ unsigned long flags;
+
lockdep_assert(!chan->conn || lockdep_is_held(&chan->conn->lock));
+ if (conn) {
+ spin_lock_irqsave(&conn->timer_lock, flags);
+ chan->timers_stopped = true;
+ spin_unlock_irqrestore(&conn->timer_lock, flags);
+ }
+
__clear_chan_timer(chan);
+ __clear_retrans_timer(chan);
+ __clear_monitor_timer(chan);
+ __clear_ack_timer(chan);
BT_DBG("chan %p, err %d, state %s", chan, err,
state_to_string(chan->state));
@@ -723,10 +735,6 @@ void l2cap_chan_del(struct l2cap_chan *chan, int err)
break;
case L2CAP_MODE_ERTM:
- __clear_retrans_timer(chan);
- __clear_monitor_timer(chan);
- __clear_ack_timer(chan);
-
skb_queue_purge(&chan->srej_q);
l2cap_seq_list_free(&chan->srej_list);
@@ -1879,6 +1887,7 @@ static void l2cap_conn_del(struct hci_conn *hcon, int err)
{
struct l2cap_conn *conn = hcon->l2cap_data;
struct l2cap_chan *chan, *l;
+ unsigned long flags;
if (!conn)
return;
@@ -1891,6 +1900,9 @@ static void l2cap_conn_del(struct hci_conn *hcon, int err)
cancel_work_sync(&conn->pending_rx_work);
mutex_lock(&conn->lock);
+ spin_lock_irqsave(&conn->timer_lock, flags);
+ conn->timers_stopped = true;
+ spin_unlock_irqrestore(&conn->timer_lock, flags);
kfree_skb(conn->rx_skb);
@@ -1925,6 +1937,11 @@ static void l2cap_conn_del(struct hci_conn *hcon, int err)
spin_unlock(&hcon->proto_lock);
mutex_unlock(&conn->lock);
+
+ /* Channel deletion canceled pending timers. Drop conn->lock before
+ * waiting for running callbacks so they can acquire it and return.
+ */
+ drain_workqueue(conn->timer_workqueue);
l2cap_conn_put(conn);
}
@@ -1932,6 +1949,7 @@ static void l2cap_conn_free(struct kref *ref)
{
struct l2cap_conn *conn = container_of(ref, struct l2cap_conn, ref);
+ destroy_workqueue(conn->timer_workqueue);
hci_conn_put(conn->hcon);
kfree(conn);
}
@@ -7416,6 +7434,14 @@ static struct l2cap_conn *l2cap_conn_add(struct hci_conn *hcon)
return NULL;
}
+ conn->timer_workqueue = alloc_ordered_workqueue("l2cap", WQ_MEM_RECLAIM);
+ if (!conn->timer_workqueue) {
+ kfree(conn);
+ hci_chan_del(hchan);
+ return NULL;
+ }
+ spin_lock_init(&conn->timer_lock);
+
kref_init(&conn->ref);
conn->hchan = hchan;
--
2.43.0
^ permalink raw reply [flat|nested] 12+ messages in thread* [PATCH 4/5] Bluetooth: 6lowpan: handle channel setup failure and callback lifetime
2026-09-21 15:38 [PATCH 0/5] Bluetooth: 6LoWPAN lifecycle fixes Cen Zhang
` (2 preceding siblings ...)
2026-09-21 15:39 ` [PATCH 3/5] Bluetooth: L2CAP: drain channel timers on connection teardown Cen Zhang
@ 2026-09-21 15:39 ` Cen Zhang
2026-09-21 15:39 ` [PATCH 5/5] Bluetooth: 6lowpan: quiesce peers before channel deletion Cen Zhang
2026-09-21 16:07 ` [PATCH 0/5] Bluetooth: 6LoWPAN lifecycle fixes Luiz Augusto von Dentz
5 siblings, 0 replies; 12+ messages in thread
From: Cen Zhang @ 2026-09-21 15:39 UTC (permalink / raw)
To: Tejun Heo, Lai Jiangshan, Marcel Holtmann,
Luiz Augusto von Dentz, Marco Elver, Jukka Rissanen
Cc: linux-kernel, linux-bluetooth, baijiaju1990, jjzuming, zzzccc427
L2CAP marks a channel connected before invoking its ready callback.
6LoWPAN can then fail to allocate or publish a peer, leaving an unusable
connected channel or a network device without a peer. Deletion before
peer publication also leaks the channel's initial reference: close only
releases it after finding a peer.
The peer's module reference ends too early as well. Removing the last
peer releases it while the close callback is still executing, allowing
bluetooth_6lowpan to be unloaded before that callback returns. Deferred
network-device deletion also needs protection through callback return.
Let ready return an error and handle it in the LE CoC and ECRED request
and response paths. Allocate the peer before setting up a network device,
and publish it before bringing the device up. Track ownership of the
initial reference explicitly and release it once from teardown, even
when no peer was published. Hold separate request-handler references
through rollback, unlock and response construction.
Hold the operations owner's module reference for accepted and outgoing
channels until channel destruction. Keep the listener itself unpinned so
module exit can close it; check BT_LISTEN under the parent lock and take
a temporary owner reference before invoking new_connection. Use
module-owned work for deferred network-device deletion.
A close/unload race produced this instruction-fetch fault:
[ 101.222759] BUG: unable to handle page fault for address: ffffffffc040199f
[ 101.224102] #PF: supervisor instruction fetch in kernel mode
[ 101.225119] #PF: error_code(0x0010) - not-present page
[ 101.226980] Oops: Oops: 0010 [#1] SMP KASAN NOPTI
[... omitted ...]
[ 101.232306] Workqueue: hci1 hci_rx_work
[ 101.233015] RIP: 0010:0xffffffffc040199f
[ 101.233780] Code: Unable to access opcode bytes at 0xffffffffc0401975.
[... omitted ...]
[ 101.275265] Modules linked in: [last unloaded: bluetooth_6lowpan(O)]
[ 101.276117] CR2: ffffffffc040199f
[... omitted ...]
[ 101.287138] Kernel panic - not syncing: Fatal exception
Fixes: 6b8d4a6a0314 ("Bluetooth: 6LoWPAN: Use connected oriented channel instead of fixed one")
Assisted-by: LLM
Signed-off-by: Cen Zhang <zzzccc427@gmail.com>
---
include/net/bluetooth/l2cap.h | 10 +++-
net/bluetooth/6lowpan.c | 86 ++++++++++++++++++++------------
net/bluetooth/l2cap_core.c | 94 +++++++++++++++++++++++++++++------
net/bluetooth/l2cap_sock.c | 6 ++-
net/bluetooth/smp.c | 4 +-
5 files changed, 148 insertions(+), 52 deletions(-)
diff --git a/include/net/bluetooth/l2cap.h b/include/net/bluetooth/l2cap.h
index b4af087a0a81..7194dc570ee8 100644
--- a/include/net/bluetooth/l2cap.h
+++ b/include/net/bluetooth/l2cap.h
@@ -611,6 +611,8 @@ struct l2cap_chan {
void *data;
const struct l2cap_ops *ops;
+ struct module *ops_owner;
+ bool ops_owner_pinned;
bool timers_stopped; /* protected by conn->timer_lock */
struct mutex lock;
};
@@ -669,7 +671,7 @@ struct l2cap_ops {
__must_hold(&chan->lock);
void (*state_change) (struct l2cap_chan *chan,
int state, int err);
- void (*ready) (struct l2cap_chan *chan)
+ int (*ready)(struct l2cap_chan *chan)
__must_hold(&chan->lock)
__must_hold(&chan->conn->lock);
void (*defer) (struct l2cap_chan *chan);
@@ -764,6 +766,7 @@ enum {
FLAG_ECRED_CONN_REQ_SENT,
FLAG_PENDING_SECURITY,
FLAG_HOLD_HCI_CONN,
+ FLAG_RELEASE_CREATOR,
FLAG_DEL,
};
@@ -948,8 +951,9 @@ static inline void l2cap_chan_no_close(struct l2cap_chan *chan)
{
}
-static inline void l2cap_chan_no_ready(struct l2cap_chan *chan)
+static inline int l2cap_chan_no_ready(struct l2cap_chan *chan)
{
+ return 0;
}
static inline void l2cap_chan_no_state_change(struct l2cap_chan *chan,
@@ -994,6 +998,8 @@ int l2cap_add_psm(struct l2cap_chan *chan, bdaddr_t *src, __le16 psm);
int l2cap_add_scid(struct l2cap_chan *chan, __u16 scid);
struct l2cap_chan *l2cap_chan_create(void);
+bool l2cap_chan_set_ops(struct l2cap_chan *chan,
+ const struct l2cap_ops *ops, struct module *owner);
void l2cap_chan_close_unlocked(struct l2cap_chan *chan, int reason)
__must_not_hold(&chan->lock);
int l2cap_chan_connect(struct l2cap_chan *chan, __le16 psm, u16 cid,
diff --git a/net/bluetooth/6lowpan.c b/net/bluetooth/6lowpan.c
index 836add41f5d1..5c49dc146086 100644
--- a/net/bluetooth/6lowpan.c
+++ b/net/bluetooth/6lowpan.c
@@ -52,6 +52,7 @@ static bool enable_6lowpan;
*/
static struct l2cap_chan *listen_chan;
static DEFINE_MUTEX(set_lock);
+static const struct l2cap_ops bt_6lowpan_chan_ops;
enum {
LOWPAN_PEER_CLOSING,
@@ -78,7 +79,7 @@ struct lowpan_btle_dev {
struct list_head peers;
atomic_t peer_count; /* number of items in peers list */
- struct work_struct delete_netdev;
+ struct module_work delete_netdev;
struct delayed_work notify_peers;
};
@@ -101,8 +102,6 @@ static inline bool peer_del(struct lowpan_btle_dev *dev,
list_del_rcu(&peer->list);
kfree_rcu(peer, rcu);
- module_put(THIS_MODULE);
-
if (atomic_dec_and_test(&dev->peer_count)) {
BT_DBG("last peer");
return true;
@@ -641,16 +640,10 @@ static struct l2cap_chan *chan_create(void)
return chan;
}
-static struct l2cap_chan *add_peer_chan(struct l2cap_chan *chan,
- struct lowpan_btle_dev *dev,
- bool new_netdev)
+static void add_peer_chan(struct l2cap_chan *chan,
+ struct lowpan_btle_dev *dev,
+ struct lowpan_peer *peer, bool new_netdev)
{
- struct lowpan_peer *peer;
-
- peer = kzalloc_obj(*peer, GFP_ATOMIC);
- if (!peer)
- return NULL;
-
peer->chan = chan;
baswap((void *)peer->lladdr, &chan->dst);
@@ -666,8 +659,6 @@ static struct l2cap_chan *add_peer_chan(struct l2cap_chan *chan,
if (new_netdev)
INIT_DELAYED_WORK(&dev->notify_peers, do_notify_peers);
schedule_delayed_work(&dev->notify_peers, msecs_to_jiffies(100));
-
- return peer->chan;
}
static int setup_netdev(struct l2cap_chan *chan, struct lowpan_btle_dev **dev)
@@ -721,30 +712,37 @@ static int setup_netdev(struct l2cap_chan *chan, struct lowpan_btle_dev **dev)
return err;
}
-static inline void chan_ready_cb(struct l2cap_chan *chan)
+static inline int chan_ready_cb(struct l2cap_chan *chan)
__must_hold(&chan->lock)
__must_hold(&chan->conn->lock)
{
struct lowpan_btle_dev *dev;
+ struct lowpan_peer *peer;
bool new_netdev = false;
+ int err;
+
+ peer = kzalloc_obj(*peer, GFP_ATOMIC);
+ if (!peer)
+ return -ENOMEM;
dev = lookup_dev(chan->conn);
BT_DBG("chan %p conn %p dev %p", chan, chan->conn, dev);
if (!dev) {
- if (setup_netdev(chan, &dev) < 0) {
- l2cap_chan_del(chan, -ENOENT);
- return;
- }
+ err = setup_netdev(chan, &dev);
+ if (err < 0)
+ goto free_peer;
new_netdev = true;
}
- if (!try_module_get(THIS_MODULE))
- return;
-
- add_peer_chan(chan, dev, new_netdev);
+ add_peer_chan(chan, dev, peer, new_netdev);
ifup(dev->netdev);
+ return 0;
+
+free_peer:
+ kfree(peer);
+ return err;
}
static void unregister_dev(struct lowpan_btle_dev *dev)
@@ -771,7 +769,7 @@ static void delete_netdev(struct work_struct *work)
{
struct lowpan_btle_dev *entry = container_of(work,
struct lowpan_btle_dev,
- delete_netdev);
+ delete_netdev.work);
unregister_dev(entry);
@@ -785,6 +783,7 @@ static void chan_close_cb(struct l2cap_chan *chan)
struct lowpan_peer *peer;
int err = -ENOENT;
bool last = false;
+ bool queued;
BT_DBG("chan %p conn %p", chan, chan->conn);
@@ -799,10 +798,6 @@ static void chan_close_cb(struct l2cap_chan *chan)
BT_DBG("dev %p removing %speer %p", dev,
last ? "last " : "1 ", peer);
- BT_DBG("chan %p orig refcnt %u", chan,
- kref_read(&chan->kref));
-
- l2cap_chan_put(chan);
break;
}
}
@@ -814,8 +809,9 @@ static void chan_close_cb(struct l2cap_chan *chan)
ifdown(dev->netdev);
- INIT_WORK(&entry->delete_netdev, delete_netdev);
- schedule_work(&entry->delete_netdev);
+ queued = schedule_module_work(&entry->delete_netdev,
+ delete_netdev, THIS_MODULE);
+ WARN_ON_ONCE(!queued);
} else {
spin_unlock(&devices_lock);
}
@@ -874,8 +870,27 @@ static long chan_get_sndtimeo_cb(struct l2cap_chan *chan)
return L2CAP_CONN_TIMEOUT;
}
+static int chan_new_connection_cb(struct l2cap_chan *chan,
+ struct l2cap_chan *new_chan)
+{
+ if (!l2cap_chan_set_ops(new_chan, &bt_6lowpan_chan_ops, THIS_MODULE))
+ return -ENODEV;
+
+ set_bit(FLAG_RELEASE_CREATOR, &new_chan->flags);
+ 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,
.state_change = chan_state_change_cb,
@@ -885,7 +900,7 @@ static const struct l2cap_ops bt_6lowpan_chan_ops = {
.get_sndtimeo = chan_get_sndtimeo_cb,
.alloc_skb = chan_alloc_skb_cb,
- .teardown = l2cap_chan_no_teardown,
+ .teardown = chan_teardown_cb,
.defer = l2cap_chan_no_defer,
.set_shutdown = l2cap_chan_no_set_shutdown,
};
@@ -899,7 +914,12 @@ static int bt_6lowpan_connect(bdaddr_t *addr, u8 dst_type)
if (!chan)
return -EINVAL;
- chan->ops = &bt_6lowpan_chan_ops;
+ if (!l2cap_chan_set_ops(chan, &bt_6lowpan_chan_ops, THIS_MODULE)) {
+ l2cap_chan_put(chan);
+ return -ENODEV;
+ }
+
+ set_bit(FLAG_RELEASE_CREATOR, &chan->flags);
err = l2cap_chan_connect(chan, cpu_to_le16(L2CAP_PSM_IPSP), 0,
addr, dst_type, L2CAP_CONN_TIMEOUT);
@@ -952,7 +972,9 @@ static struct l2cap_chan *bt_6lowpan_listen(void)
if (!chan)
return NULL;
+ /* The listener is closed by module_exit(), so it must not self-pin. */
chan->ops = &bt_6lowpan_chan_ops;
+ chan->ops_owner = THIS_MODULE;
chan->state = BT_LISTEN;
chan->src_type = BDADDR_LE_PUBLIC;
diff --git a/net/bluetooth/l2cap_core.c b/net/bluetooth/l2cap_core.c
index 0df7bda54473..f6a87a44d8a6 100644
--- a/net/bluetooth/l2cap_core.c
+++ b/net/bluetooth/l2cap_core.c
@@ -481,9 +481,27 @@ struct l2cap_chan *l2cap_chan_create(void)
}
EXPORT_SYMBOL_GPL(l2cap_chan_create);
+bool l2cap_chan_set_ops(struct l2cap_chan *chan,
+ const struct l2cap_ops *ops, struct module *owner)
+{
+ if (WARN_ON_ONCE(chan->ops_owner))
+ return false;
+
+ if (!try_module_get(owner))
+ return false;
+
+ chan->ops = ops;
+ chan->ops_owner = owner;
+ chan->ops_owner_pinned = true;
+ return true;
+}
+EXPORT_SYMBOL_GPL(l2cap_chan_set_ops);
+
static void l2cap_chan_destroy(struct kref *kref)
{
struct l2cap_chan *chan = container_of(kref, struct l2cap_chan, kref);
+ struct module *ops_owner = chan->ops_owner;
+ bool ops_owner_pinned = chan->ops_owner_pinned;
BT_DBG("chan %p", chan);
@@ -495,6 +513,8 @@ static void l2cap_chan_destroy(struct kref *kref)
l2cap_conn_put(chan->conn);
kfree(chan);
+ if (ops_owner_pinned)
+ module_put(ops_owner);
}
void l2cap_chan_hold(struct l2cap_chan *c)
@@ -1352,7 +1372,7 @@ void l2cap_send_conn_req(struct l2cap_chan *chan)
l2cap_send_cmd(conn, chan->ident, L2CAP_CONN_REQ, sizeof(req), &req);
}
-static void l2cap_chan_ready(struct l2cap_chan *chan)
+static int l2cap_chan_ready(struct l2cap_chan *chan)
__must_hold(&chan->lock)
__must_hold(&chan->conn->lock)
{
@@ -1361,7 +1381,7 @@ static void l2cap_chan_ready(struct l2cap_chan *chan)
* procedure is complete.
*/
if (chan->state == BT_CONNECTED)
- return;
+ return 0;
/* This clears all conf flags, including CONF_NOT_COMPLETE */
chan->conf_state = 0;
@@ -1377,7 +1397,7 @@ static void l2cap_chan_ready(struct l2cap_chan *chan)
chan->state = BT_CONNECTED;
- chan->ops->ready(chan);
+ return chan->ops->ready(chan);
}
static void l2cap_le_connect(struct l2cap_chan *chan)
@@ -4241,10 +4261,20 @@ static struct l2cap_chan *l2cap_new_connection(struct l2cap_conn *conn,
__must_hold(&pchan->lock)
{
struct l2cap_chan *chan;
+ struct module *owner;
+
+ if (pchan->state != BT_LISTEN)
+ return NULL;
+
+ owner = pchan->ops_owner;
+ if (!try_module_get(owner))
+ return NULL;
chan = l2cap_chan_create();
- if (!chan)
+ if (!chan) {
+ module_put(owner);
return NULL;
+ }
l2cap_chan_lock(chan);
@@ -4260,10 +4290,12 @@ static struct l2cap_chan *l2cap_new_connection(struct l2cap_conn *conn,
l2cap_chan_del(chan, 0);
l2cap_chan_unlock(chan);
l2cap_chan_put(chan);
+ module_put(owner);
return NULL;
}
l2cap_chan_unlock(chan);
+ module_put(owner);
return chan;
}
@@ -5011,7 +5043,7 @@ static int l2cap_le_connect_rsp(struct l2cap_conn *conn,
struct hci_conn *hcon = conn->hcon;
u16 dcid, mtu, mps, credits, result;
struct l2cap_chan *chan;
- int err, sec_level;
+ int err, ready_err, sec_level;
if (cmd_len < sizeof(*rsp))
return -EPROTO;
@@ -5056,7 +5088,11 @@ static int l2cap_le_connect_rsp(struct l2cap_conn *conn,
chan->omtu = mtu;
chan->remote_mps = mps;
chan->tx_credits = credits;
- l2cap_chan_ready(chan);
+ ready_err = l2cap_chan_ready(chan);
+ if (ready_err < 0) {
+ l2cap_send_disconn_req(chan, -ready_err);
+ l2cap_chan_del(chan, -ready_err);
+ }
break;
case L2CAP_CR_LE_AUTHENTICATION:
@@ -5180,9 +5216,10 @@ static int l2cap_le_connect_req(struct l2cap_conn *conn,
{
struct l2cap_le_conn_req *req = (struct l2cap_le_conn_req *) data;
struct l2cap_le_conn_rsp rsp;
- struct l2cap_chan *chan, *pchan;
+ struct l2cap_chan *chan, *chan_ref = NULL, *pchan;
u16 dcid, scid, credits, mtu, mps;
__le16 psm;
+ int err;
u8 result;
if (cmd_len != sizeof(*req))
@@ -5260,6 +5297,9 @@ static int l2cap_le_connect_req(struct l2cap_conn *conn,
goto response_unlock;
}
+ /* ->ready() may delete the channel. */
+ l2cap_chan_hold(chan);
+ chan_ref = chan;
l2cap_chan_lock(chan);
lockdep_assert_held(&chan->conn->lock);
@@ -5293,18 +5333,26 @@ static int l2cap_le_connect_req(struct l2cap_conn *conn,
result = L2CAP_CR_PEND;
chan->ops->defer(chan);
} else {
- l2cap_chan_ready(chan);
- result = L2CAP_CR_LE_SUCCESS;
+ err = l2cap_chan_ready(chan);
+ if (err < 0) {
+ l2cap_chan_del(chan, -err);
+ chan = NULL;
+ dcid = 0;
+ credits = 0;
+ result = L2CAP_CR_LE_NO_MEM;
+ } else {
+ result = L2CAP_CR_LE_SUCCESS;
+ }
}
- l2cap_chan_unlock(chan);
+ l2cap_chan_unlock(chan_ref);
response_unlock:
l2cap_chan_unlock(pchan);
l2cap_chan_put(pchan);
if (result == L2CAP_CR_PEND)
- return 0;
+ goto done;
response:
if (chan) {
@@ -5321,6 +5369,10 @@ static int l2cap_le_connect_req(struct l2cap_conn *conn,
l2cap_send_cmd(conn, cmd->ident, L2CAP_LE_CONN_RSP, sizeof(rsp), &rsp);
+done:
+ if (chan_ref)
+ l2cap_chan_put(chan_ref);
+
return 0;
}
@@ -5385,7 +5437,7 @@ static inline int l2cap_ecred_conn_req(struct l2cap_conn *conn,
u16 mtu, mps;
__le16 psm;
u8 result, rsp_len = 0;
- int i, num_scid = 0;
+ int err, i, num_scid = 0;
bool defer = false;
if (!enable_ecred)
@@ -5493,6 +5545,8 @@ static inline int l2cap_ecred_conn_req(struct l2cap_conn *conn,
continue;
}
+ /* ->ready() may delete the channel. */
+ l2cap_chan_hold(chan);
l2cap_chan_lock(chan);
lockdep_assert_held(&chan->conn->lock);
@@ -5527,10 +5581,16 @@ static inline int l2cap_ecred_conn_req(struct l2cap_conn *conn,
defer = true;
chan->ops->defer(chan);
} else {
- l2cap_chan_ready(chan);
+ err = l2cap_chan_ready(chan);
+ if (err < 0) {
+ l2cap_chan_del(chan, -err);
+ pdu->dcid[i] = 0;
+ result = L2CAP_CR_LE_NO_MEM;
+ }
}
l2cap_chan_unlock(chan);
+ l2cap_chan_put(chan);
}
unlock:
@@ -5558,7 +5618,7 @@ static inline int l2cap_ecred_conn_rsp(struct l2cap_conn *conn,
struct hci_conn *hcon = conn->hcon;
u16 mtu, mps, credits, result;
struct l2cap_chan *chan, *tmp;
- int err = 0, sec_level;
+ int err = 0, ready_err, sec_level;
int i = 0;
if (cmd_len < sizeof(*rsp))
@@ -5673,7 +5733,11 @@ static inline int l2cap_ecred_conn_rsp(struct l2cap_conn *conn,
chan->omtu = mtu;
chan->remote_mps = mps;
chan->tx_credits = credits;
- l2cap_chan_ready(chan);
+ ready_err = l2cap_chan_ready(chan);
+ if (ready_err < 0) {
+ l2cap_send_disconn_req(chan, -ready_err);
+ l2cap_chan_del(chan, -ready_err);
+ }
break;
}
diff --git a/net/bluetooth/l2cap_sock.c b/net/bluetooth/l2cap_sock.c
index 278adb05c4c9..7a631581950f 100644
--- a/net/bluetooth/l2cap_sock.c
+++ b/net/bluetooth/l2cap_sock.c
@@ -1825,13 +1825,13 @@ static struct sk_buff *l2cap_sock_alloc_skb_cb(struct l2cap_chan *chan,
return skb;
}
-static void l2cap_sock_ready_cb(struct l2cap_chan *chan)
+static int l2cap_sock_ready_cb(struct l2cap_chan *chan)
{
struct sock *sk = chan->data;
struct sock *parent;
if (!sk)
- return;
+ return 0;
lock_sock(sk);
@@ -1846,6 +1846,8 @@ static void l2cap_sock_ready_cb(struct l2cap_chan *chan)
parent->sk_data_ready(parent);
release_sock(sk);
+
+ return 0;
}
static void l2cap_sock_defer_cb(struct l2cap_chan *chan)
diff --git a/net/bluetooth/smp.c b/net/bluetooth/smp.c
index 0badb93a5725..28e32e8cf3b6 100644
--- a/net/bluetooth/smp.c
+++ b/net/bluetooth/smp.c
@@ -3155,7 +3155,7 @@ static void smp_resume_cb(struct l2cap_chan *chan)
smp_distribute_keys(smp);
}
-static void smp_ready_cb(struct l2cap_chan *chan)
+static int smp_ready_cb(struct l2cap_chan *chan)
{
struct l2cap_conn *conn = chan->conn;
struct hci_conn *hcon = conn->hcon;
@@ -3172,6 +3172,8 @@ static void smp_ready_cb(struct l2cap_chan *chan)
if (hcon->type == ACL_LINK && test_bit(HCI_CONN_ENCRYPT, &hcon->flags))
bredr_pairing(chan);
+
+ return 0;
}
static int smp_recv_cb(struct l2cap_chan *chan, struct sk_buff *skb)
--
2.43.0
^ permalink raw reply [flat|nested] 12+ messages in thread* [PATCH 5/5] Bluetooth: 6lowpan: quiesce peers before channel deletion
2026-09-21 15:38 [PATCH 0/5] Bluetooth: 6LoWPAN lifecycle fixes Cen Zhang
` (3 preceding siblings ...)
2026-09-21 15:39 ` [PATCH 4/5] Bluetooth: 6lowpan: handle channel setup failure and callback lifetime Cen Zhang
@ 2026-09-21 15:39 ` Cen Zhang
2026-09-21 16:07 ` [PATCH 0/5] Bluetooth: 6LoWPAN lifecycle fixes Luiz Augusto von Dentz
5 siblings, 0 replies; 12+ messages in thread
From: Cen Zhang @ 2026-09-21 15:39 UTC (permalink / raw)
To: Tejun Heo, Lai Jiangshan, Marcel Holtmann,
Luiz Augusto von Dentz, Marco Elver, Jukka Rissanen
Cc: linux-kernel, linux-bluetooth, baijiaju1990, jjzuming, zzzccc427
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@gmail.com>
---
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
^ permalink raw reply [flat|nested] 12+ messages in thread