Merge: CVE-2026-64557: Bluetooth: L2CAP: Fix use-after-free in l2cap_sock_new_connection_cb()

MR: https://gitlab.com/redhat/centos-stream/src/kernel/centos-stream-10/-/merge_requests/3224

JIRA: https://redhat.atlassian.net/browse/RHEL-231358
CVE: CVE-2026-64557

 * 9707a015fe8f3ba8ec7c270f3b2b8efb38823d6b Bluetooth: 6lowpan: fix cyclic locking warning on netdev unregister [linux]
 * 6fef032af0092ed5ccb767239a9ac1bc38c08a40 Bluetooth: L2CAP: Fix use-after-free in l2cap_sock_new_connection_cb() [linux]

Signed-off-by: CKI Backport Bot <cki-ci-bot+cki-gitlab-backport-bot@redhat.com>
[^footer]: Created 2026-08-10 19:08 UTC by backporter - [KWF FAQ](https://red.ht/kernel_workflow_doc) - [Slack #team-kernel-workflow](https://redhat-internal.slack.com/archives/C04LRUPMJQ5) - [Source](https://gitlab.com/cki-project/kernel-workflow/-/blob/main/webhook/utils/backporter.py) - [Documentation](https://gitlab.com/cki-project/kernel-workflow/-/blob/main/docs/README.backporter.md) - [Report an issue](https://redhat.atlassian.net/secure/CreateIssueDetails!init.jspa?pid=11779&issuetype=10016&priority=10001&summary=backporter+webhook+issue&components=66291) [^footer]

Approved-by: David Marlin <dmarlin@redhat.com>
Approved-by: Charles Mirabile <cmirabil@redhat.com>
Approved-by: Tony Camuso <tcamuso@redhat.com>
Approved-by: CKI KWF Bot <cki-ci-bot+kwf-gitlab-com@redhat.com>

Merged-by: CKI GitLab Kmaint Pipeline Bot <26919896-cki-kmaint-pipeline-bot@users.noreply.gitlab.com>
This commit is contained in:
CKI KWF Bot
2026-08-18 10:04:30 +00:00
5 changed files with 146 additions and 107 deletions
+6 -4
View File
@@ -620,7 +620,8 @@ struct l2cap_chan {
struct l2cap_ops {
char *name;
struct l2cap_chan *(*new_connection) (struct l2cap_chan *chan);
int (*new_connection)(struct l2cap_chan *chan,
struct l2cap_chan *new_chan);
int (*recv) (struct l2cap_chan * chan,
struct sk_buff *skb);
void (*teardown) (struct l2cap_chan *chan, int err);
@@ -885,9 +886,10 @@ static inline __u16 __next_seq(struct l2cap_chan *chan, __u16 seq)
return (seq + 1) % (chan->tx_win_max + 1);
}
static inline struct l2cap_chan *l2cap_chan_no_new_connection(struct l2cap_chan *chan)
static inline int l2cap_chan_no_new_connection(struct l2cap_chan *chan,
struct l2cap_chan *new_chan)
{
return NULL;
return -EOPNOTSUPP;
}
static inline int l2cap_chan_no_recv(struct l2cap_chan *chan, struct sk_buff *skb)
@@ -964,7 +966,7 @@ int l2cap_chan_send(struct l2cap_chan *chan, struct msghdr *msg, size_t len,
void l2cap_chan_busy(struct l2cap_chan *chan, int busy);
void l2cap_chan_rx_avail(struct l2cap_chan *chan, ssize_t rx_avail);
int l2cap_chan_check_security(struct l2cap_chan *chan, bool initiator);
void l2cap_chan_set_defaults(struct l2cap_chan *chan);
void l2cap_chan_set_defaults(struct l2cap_chan *chan, struct l2cap_chan *pchan);
int l2cap_ertm_init(struct l2cap_chan *chan);
void l2cap_chan_add(struct l2cap_conn *conn, struct l2cap_chan *chan);
void __l2cap_chan_add(struct l2cap_conn *conn, struct l2cap_chan *chan);
+20 -15
View File
@@ -632,7 +632,7 @@ static struct l2cap_chan *chan_create(void)
if (!chan)
return NULL;
l2cap_chan_set_defaults(chan);
l2cap_chan_set_defaults(chan, NULL);
chan->chan_type = L2CAP_CHAN_CONN_ORIENTED;
chan->mode = L2CAP_MODE_LE_FLOWCTL;
@@ -745,19 +745,24 @@ static inline void chan_ready_cb(struct l2cap_chan *chan)
ifup(dev->netdev);
}
static inline struct l2cap_chan *chan_new_conn_cb(struct l2cap_chan *pchan)
static void unregister_dev(struct lowpan_btle_dev *dev)
{
struct l2cap_chan *chan;
struct hci_dev *hdev = READ_ONCE(dev->hdev);
chan = chan_create();
if (!chan)
return NULL;
chan->ops = pchan->ops;
BT_DBG("chan %p pchan %p", chan, pchan);
return chan;
/* If netdev holds last reference to hci_dev (its parent device), this
* leads to theoretical cyclic locking on lowpan_unregister_netdev:
*
* rtnl_lock -> put_device(parent) -> hci_release_dev ->
* destroy_workqueue -> hci_rx_work -> l2cap_recv_acldata ->
* chan_ready_cb -> ifup -> rtnl_lock
*
* However, hci_rx_work is disabled in hci_unregister_dev, so this
* should not occur. Make lockdep happy by postponing hdev release after
* netdev put.
*/
hci_dev_hold(hdev);
lowpan_unregister_netdev(dev->netdev);
hci_dev_put(hdev);
}
static void delete_netdev(struct work_struct *work)
@@ -766,7 +771,7 @@ static void delete_netdev(struct work_struct *work)
struct lowpan_btle_dev,
delete_netdev);
lowpan_unregister_netdev(entry->netdev);
unregister_dev(entry);
/* The entry pointer is deleted by the netdev destructor. */
}
@@ -881,7 +886,6 @@ static long chan_get_sndtimeo_cb(struct l2cap_chan *chan)
static const struct l2cap_ops bt_6lowpan_chan_ops = {
.name = "L2CAP 6LoWPAN channel",
.new_connection = chan_new_conn_cb,
.recv = chan_recv_cb,
.close = chan_close_cb,
.state_change = chan_state_change_cb,
@@ -1252,6 +1256,7 @@ static void disconnect_devices(void)
break;
new_dev->netdev = entry->netdev;
new_dev->hdev = entry->hdev;
INIT_LIST_HEAD(&new_dev->list);
list_add_rcu(&new_dev->list, &devices);
@@ -1263,7 +1268,7 @@ static void disconnect_devices(void)
ifdown(entry->netdev);
BT_DBG("Unregistering netdev %s %p",
entry->netdev->name, entry->netdev);
lowpan_unregister_netdev(entry->netdev);
unregister_dev(entry);
kfree(entry);
}
}
+65 -13
View File
@@ -525,7 +525,10 @@ void l2cap_chan_put(struct l2cap_chan *c)
}
EXPORT_SYMBOL_GPL(l2cap_chan_put);
void l2cap_chan_set_defaults(struct l2cap_chan *chan)
/* Initialise @chan with default values, inheriting from the parent channel
* @pchan when it is given.
*/
void l2cap_chan_set_defaults(struct l2cap_chan *chan, struct l2cap_chan *pchan)
{
chan->fcs = L2CAP_FCS_CRC16;
chan->max_tx = L2CAP_DEFAULT_MAX_TX;
@@ -539,6 +542,31 @@ void l2cap_chan_set_defaults(struct l2cap_chan *chan)
chan->retrans_timeout = L2CAP_DEFAULT_RETRANS_TO;
chan->monitor_timeout = L2CAP_DEFAULT_MONITOR_TO;
if (pchan) {
BT_DBG("chan %p pchan %p", chan, pchan);
chan->chan_type = pchan->chan_type;
chan->imtu = pchan->imtu;
chan->omtu = pchan->omtu;
chan->mode = pchan->mode;
chan->fcs = pchan->fcs;
chan->max_tx = pchan->max_tx;
chan->tx_win = pchan->tx_win;
chan->tx_win_max = pchan->tx_win_max;
chan->sec_level = pchan->sec_level;
chan->conf_state = pchan->conf_state;
chan->flags = pchan->flags;
chan->tx_credits = pchan->tx_credits;
chan->rx_credits = pchan->rx_credits;
if (chan->chan_type == L2CAP_CHAN_FIXED) {
chan->scid = pchan->scid;
chan->dcid = pchan->scid;
}
return;
}
chan->conf_state = 0;
set_bit(CONF_NOT_COMPLETE, &chan->conf_state);
@@ -4013,6 +4041,38 @@ static inline int l2cap_command_rej(struct l2cap_conn *conn,
return 0;
}
/* Allocate and initialise a channel for an incoming connection.
*
* The channel inherits its configuration from @pchan and is linked into @conn
* before ->new_connection() runs, so the conn list reference keeps it alive if
* the callback exposes it (e.g. via the socket accept queue) before this
* returns. The l2cap_chan_create() reference is taken over by the subsystem on
* success and dropped here on failure.
*/
static struct l2cap_chan *l2cap_new_connection(struct l2cap_conn *conn,
struct l2cap_chan *pchan)
{
struct l2cap_chan *chan;
chan = l2cap_chan_create();
if (!chan)
return NULL;
l2cap_chan_set_defaults(chan, pchan);
chan->ops = pchan->ops;
__l2cap_chan_add(conn, chan);
if (pchan->ops->new_connection &&
pchan->ops->new_connection(pchan, chan) < 0) {
l2cap_chan_del(chan, 0);
l2cap_chan_put(chan);
return NULL;
}
return chan;
}
static void l2cap_connect(struct l2cap_conn *conn, struct l2cap_cmd_hdr *cmd,
u8 *data, u8 rsp_code)
{
@@ -4059,7 +4119,7 @@ static void l2cap_connect(struct l2cap_conn *conn, struct l2cap_cmd_hdr *cmd,
goto response;
}
chan = pchan->ops->new_connection(pchan);
chan = l2cap_new_connection(conn, pchan);
if (!chan)
goto response;
@@ -4077,8 +4137,6 @@ static void l2cap_connect(struct l2cap_conn *conn, struct l2cap_cmd_hdr *cmd,
chan->psm = psm;
chan->dcid = scid;
__l2cap_chan_add(conn, chan);
dcid = chan->scid;
__set_chan_timer(chan, chan->ops->get_sndtimeo(chan));
@@ -4961,7 +5019,7 @@ static int l2cap_le_connect_req(struct l2cap_conn *conn,
goto response_unlock;
}
chan = pchan->ops->new_connection(pchan);
chan = l2cap_new_connection(conn, pchan);
if (!chan) {
result = L2CAP_CR_LE_NO_MEM;
goto response_unlock;
@@ -4976,8 +5034,6 @@ static int l2cap_le_connect_req(struct l2cap_conn *conn,
chan->omtu = mtu;
chan->remote_mps = mps;
__l2cap_chan_add(conn, chan);
l2cap_le_flowctl_init(chan, __le16_to_cpu(req->credits));
dcid = chan->scid;
@@ -5185,7 +5241,7 @@ static inline int l2cap_ecred_conn_req(struct l2cap_conn *conn,
continue;
}
chan = pchan->ops->new_connection(pchan);
chan = l2cap_new_connection(conn, pchan);
if (!chan) {
result = L2CAP_CR_LE_NO_MEM;
continue;
@@ -5200,8 +5256,6 @@ static inline int l2cap_ecred_conn_req(struct l2cap_conn *conn,
chan->omtu = mtu;
chan->remote_mps = mps;
__l2cap_chan_add(conn, chan);
l2cap_ecred_init(chan, __le16_to_cpu(req->credits));
/* Init response */
@@ -7476,14 +7530,12 @@ static void l2cap_connect_cfm(struct hci_conn *hcon, u8 status)
goto next;
l2cap_chan_lock(pchan);
chan = pchan->ops->new_connection(pchan);
chan = l2cap_new_connection(conn, pchan);
if (chan) {
bacpy(&chan->src, &hcon->src);
bacpy(&chan->dst, &hcon->dst);
chan->src_type = bdaddr_src_type(hcon);
chan->dst_type = dst_type;
__l2cap_chan_add(conn, chan);
}
l2cap_chan_unlock(pchan);
+49 -54
View File
@@ -45,7 +45,8 @@ static struct bt_sock_list l2cap_sk_list = {
static const struct proto_ops l2cap_sock_ops;
static void l2cap_sock_init(struct sock *sk, struct sock *parent);
static struct sock *l2cap_sock_alloc(struct net *net, struct socket *sock,
int proto, gfp_t prio, int kern);
int proto, gfp_t prio, int kern,
struct l2cap_chan *chan);
static void l2cap_sock_cleanup_listen(struct sock *parent);
bool l2cap_is_socket(struct socket *sock)
@@ -1274,6 +1275,23 @@ done:
return err;
}
/* Release the sock's ref on chan and clear the pointer so that the ref is
* dropped exactly once even if both l2cap_sock_kill() and
* l2cap_sock_destruct() run. Setting chan->data to NULL first stops any other
* task from dereferencing the now-dead sock pointer.
*/
static void l2cap_sock_put_chan(struct sock *sk)
{
struct l2cap_chan *chan = l2cap_pi(sk)->chan;
if (!chan)
return;
chan->data = NULL;
l2cap_pi(sk)->chan = NULL;
l2cap_chan_put(chan);
}
/* Kill socket (only if zapped and orphan)
* Must be called on unlocked socket, with l2cap channel lock.
*/
@@ -1284,13 +1302,9 @@ static void l2cap_sock_kill(struct sock *sk)
BT_DBG("sk %p state %s", sk, state_to_string(sk->sk_state));
/* Sock is dead, so set chan data to NULL, avoid other task use invalid
* sock pointer.
*/
l2cap_pi(sk)->chan->data = NULL;
/* Kill poor orphan */
l2cap_sock_put_chan(sk);
l2cap_chan_put(l2cap_pi(sk)->chan);
/* Kill poor orphan */
sock_set_flag(sk, SOCK_DEAD);
sock_put(sk);
}
@@ -1533,12 +1547,13 @@ static void l2cap_sock_cleanup_listen(struct sock *parent)
}
}
static struct l2cap_chan *l2cap_sock_new_connection_cb(struct l2cap_chan *chan)
static int l2cap_sock_new_connection_cb(struct l2cap_chan *chan,
struct l2cap_chan *new_chan)
{
struct sock *sk, *parent = chan->data;
if (!parent)
return NULL;
return -EINVAL;
lock_sock(parent);
@@ -1546,25 +1561,28 @@ static struct l2cap_chan *l2cap_sock_new_connection_cb(struct l2cap_chan *chan)
if (sk_acceptq_is_full(parent)) {
BT_DBG("backlog full %d", parent->sk_ack_backlog);
release_sock(parent);
return NULL;
return -ENOBUFS;
}
sk = l2cap_sock_alloc(sock_net(parent), NULL, BTPROTO_L2CAP,
GFP_ATOMIC, 0);
GFP_ATOMIC, 0, new_chan);
if (!sk) {
release_sock(parent);
return NULL;
}
return -ENOMEM;
}
bt_sock_reclassify_lock(sk, BTPROTO_L2CAP);
l2cap_sock_init(sk, parent);
/* The conn list reference taken by l2cap_new_connection() keeps new_chan
* alive once release_sock() lets another task free this socket.
*/
bt_accept_enqueue(parent, sk, false);
release_sock(parent);
return l2cap_pi(sk)->chan;
return 0;
}
static int l2cap_sock_recv_cb(struct l2cap_chan *chan, struct sk_buff *skb)
@@ -1862,10 +1880,7 @@ static void l2cap_sock_destruct(struct sock *sk)
BT_DBG("sk %p", sk);
if (l2cap_pi(sk)->chan) {
l2cap_pi(sk)->chan->data = NULL;
l2cap_chan_put(l2cap_pi(sk)->chan);
}
l2cap_sock_put_chan(sk);
list_for_each_entry_safe(rx_busy, next, &l2cap_pi(sk)->rx_busy, list) {
kfree_skb(rx_busy->skb);
@@ -1898,30 +1913,12 @@ static void l2cap_sock_init(struct sock *sk, struct sock *parent)
BT_DBG("sk %p", sk);
if (parent) {
struct l2cap_chan *pchan = l2cap_pi(parent)->chan;
sk->sk_type = parent->sk_type;
bt_sk(sk)->flags = bt_sk(parent)->flags;
chan->chan_type = pchan->chan_type;
chan->imtu = pchan->imtu;
chan->omtu = pchan->omtu;
chan->conf_state = pchan->conf_state;
chan->mode = pchan->mode;
chan->fcs = pchan->fcs;
chan->max_tx = pchan->max_tx;
chan->tx_win = pchan->tx_win;
chan->tx_win_max = pchan->tx_win_max;
chan->sec_level = pchan->sec_level;
chan->flags = pchan->flags;
chan->tx_credits = pchan->tx_credits;
chan->rx_credits = pchan->rx_credits;
if (chan->chan_type == L2CAP_CHAN_FIXED) {
chan->scid = pchan->scid;
chan->dcid = pchan->scid;
}
/* Channel configuration is inherited from the parent by
* l2cap_new_connection().
*/
security_sk_clone(parent, sk);
} else {
switch (sk->sk_type) {
@@ -1947,7 +1944,7 @@ static void l2cap_sock_init(struct sock *sk, struct sock *parent)
chan->mode = L2CAP_MODE_BASIC;
}
l2cap_chan_set_defaults(chan);
l2cap_chan_set_defaults(chan, NULL);
}
/* Default config options */
@@ -1966,10 +1963,10 @@ static struct proto l2cap_proto = {
};
static struct sock *l2cap_sock_alloc(struct net *net, struct socket *sock,
int proto, gfp_t prio, int kern)
int proto, gfp_t prio, int kern,
struct l2cap_chan *chan)
{
struct sock *sk;
struct l2cap_chan *chan;
sk = bt_sock_alloc(net, sock, &l2cap_proto, proto, prio, kern);
if (!sk)
@@ -1980,16 +1977,7 @@ static struct sock *l2cap_sock_alloc(struct net *net, struct socket *sock,
INIT_LIST_HEAD(&l2cap_pi(sk)->rx_busy);
chan = l2cap_chan_create();
if (!chan) {
sk_free(sk);
if (sock)
sock->sk = NULL;
return NULL;
}
l2cap_chan_hold(chan);
/* The sock takes ownership of the caller's reference on chan. */
l2cap_pi(sk)->chan = chan;
return sk;
@@ -1999,6 +1987,7 @@ static int l2cap_sock_create(struct net *net, struct socket *sock, int protocol,
int kern)
{
struct sock *sk;
struct l2cap_chan *chan;
BT_DBG("sock %p", sock);
@@ -2013,10 +2002,16 @@ static int l2cap_sock_create(struct net *net, struct socket *sock, int protocol,
sock->ops = &l2cap_sock_ops;
sk = l2cap_sock_alloc(net, sock, protocol, GFP_ATOMIC, kern);
if (!sk)
chan = l2cap_chan_create();
if (!chan)
return -ENOMEM;
sk = l2cap_sock_alloc(net, sock, protocol, GFP_ATOMIC, kern, chan);
if (!sk) {
l2cap_chan_put(chan);
return -ENOMEM;
}
l2cap_sock_init(sk, NULL);
bt_sock_link(&l2cap_sk_list, sk);
return 0;
+6 -21
View File
@@ -3234,34 +3234,19 @@ static const struct l2cap_ops smp_chan_ops = {
.get_sndtimeo = l2cap_chan_no_get_sndtimeo,
};
static inline struct l2cap_chan *smp_new_conn_cb(struct l2cap_chan *pchan)
static inline int smp_new_conn_cb(struct l2cap_chan *chan,
struct l2cap_chan *new_chan)
{
struct l2cap_chan *chan;
BT_DBG("pchan %p", pchan);
chan = l2cap_chan_create();
if (!chan)
return NULL;
chan->chan_type = pchan->chan_type;
chan->ops = &smp_chan_ops;
chan->scid = pchan->scid;
chan->dcid = chan->scid;
chan->imtu = pchan->imtu;
chan->omtu = pchan->omtu;
chan->mode = pchan->mode;
new_chan->ops = &smp_chan_ops;
/* Other L2CAP channels may request SMP routines in order to
* change the security level. This means that the SMP channel
* lock must be considered in its own category to avoid lockdep
* warnings.
*/
atomic_set(&chan->nesting, L2CAP_NESTING_SMP);
atomic_set(&new_chan->nesting, L2CAP_NESTING_SMP);
BT_DBG("created chan %p", chan);
return chan;
return 0;
}
static const struct l2cap_ops smp_root_chan_ops = {
@@ -3332,7 +3317,7 @@ create_chan:
l2cap_add_scid(chan, cid);
l2cap_chan_set_defaults(chan);
l2cap_chan_set_defaults(chan, NULL);
if (cid == L2CAP_CID_SMP) {
u8 bdaddr_type;