diff options
| author | Chengfeng Ye <nicoyip.dev@gmail.com> | 2026-09-27 19:04:50 +0800 |
|---|---|---|
| committer | Luiz Augusto von Dentz <luiz.von.dentz@intel.com> | 2026-09-28 11:00:20 -0400 |
| commit | 024e05f73a4c1263d031614bc36700ec135eecb4 (patch) | |
| tree | ffb91e8d953f6e918d45877f551a327f465e3d1a /net | |
| parent | c9c15d4d8956df8c564f7c210e4dc5b9b820d97a (diff) | |
Bluetooth: hci_conn: Lock parent access during enhanced SCO setup
Bluetooth: hci_conn: Lock parent access during enhanced SCO setup
hci_enhanced_setup_sync() runs on the request workqueue without the
hci_dev_lock held by its caller when setup was queued. Its CVSD
capability check and find_next_esco_param() dereference conn->parent
while the receive workqueue can unlink and release that parent.
The following interleaving can cause a use-after-free:
hci_enhanced_setup_sync() hci_disconn_complete_evt()
load conn->parent
hci_dev_lock()
hci_conn_del(ACL parent)
unlink SCO child
drop link's parent reference
clear child->parent
release ACL parent
bt_link_release()
kfree(parent)
hci_dev_unlock()
read parent->features[0][3]
The reference held for the queued SCO child does not keep its ACL parent
alive after unlinking. Commit 42de40abe25d ("Bluetooth: hci_conn: fix the
SCO setup context lifetime") protects the child stored in the queued
context, but leaves these parent accesses unprotected.
With a 40 ms diagnostic delay after loading conn->parent, an instrumented
kernel based on fd179f8a05be, which already contains 42de40abe25d,
reported:
BUG: KASAN: slab-use-after-free in hci_enhanced_setup_sync+0xda5/0xdf0
Read of size 1 at addr ffff888102204047 by task kworker/u17:0/93
Call Trace:
hci_enhanced_setup_sync+0xda5/0xdf0
hci_cmd_sync_work+0x13c/0x290
process_one_work+0x6b4/0x10e0
Allocated by task 92:
__hci_conn_add+0x304/0x1df0
hci_connect_acl+0x349/0x3e0
hci_connect_sco+0x3b/0x9a0
sco_sock_connect+0x475/0xca0
Freed by task 94:
kfree+0x121/0x3c0
bt_link_release+0x79/0xa0
device_release+0xc8/0x240
kobject_put+0x14d/0x280
hci_conn_del+0x524/0xe30
hci_disconn_complete_evt+0x403/0x8c0
hci_event_packet+0x71b/0xb20
hci_rx_work+0x293/0x730
The accessed address is 71 bytes into the freed ACL parent, at its
features[0][3] byte; the queued SCO child is a different object. The
diagnostic preserves the loaded parent across the delay, matching the
unmodified compiled capability check, and does not change the parent
references or teardown path.
Hold hci_dev_lock() across the codec switch, including every call to
find_next_esco_param(), and release it on all selection errors. Keep
configure_datapath_sync() outside the critical section because it waits
for HCI events. Preserve parameter selection and existing return values.
Fixes: e07a06b4eb41 ("Bluetooth: Convert SCO configure_datapath to hci_sync")
Cc: stable@vger.kernel.org
Assisted-by: GPT-6-Astra
Signed-off-by: Chengfeng Ye <nicoyip.dev@gmail.com>
Signed-off-by: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>
Diffstat (limited to 'net')
| -rw-r--r-- | net/bluetooth/hci_conn.c | 18 |
1 files changed, 13 insertions, 5 deletions
diff --git a/net/bluetooth/hci_conn.c b/net/bluetooth/hci_conn.c index 96195d2fd10f..cf44452e0766 100644 --- a/net/bluetooth/hci_conn.c +++ b/net/bluetooth/hci_conn.c @@ -302,11 +302,13 @@ static int hci_enhanced_setup_sync(struct hci_dev *hdev, void *data) cp.tx_bandwidth = cpu_to_le32(0x00001f40); cp.rx_bandwidth = cpu_to_le32(0x00001f40); + hci_dev_lock(hdev); + switch (conn->codec.id) { case BT_CODEC_MSBC: if (!find_next_esco_param(conn, esco_param_msbc, ARRAY_SIZE(esco_param_msbc))) - return -EINVAL; + goto unlock; param = &esco_param_msbc[conn->attempt - 1]; cp.tx_coding_format.id = 0x05; @@ -332,7 +334,7 @@ static int hci_enhanced_setup_sync(struct hci_dev *hdev, void *data) case BT_CODEC_TRANSPARENT: if (!find_next_esco_param(conn, esco_param_msbc, ARRAY_SIZE(esco_param_msbc))) - return -EINVAL; + goto unlock; param = &esco_param_msbc[conn->attempt - 1]; cp.tx_coding_format.id = 0x03; @@ -359,11 +361,11 @@ static int hci_enhanced_setup_sync(struct hci_dev *hdev, void *data) if (conn->parent && lmp_esco_capable(conn->parent)) { if (!find_next_esco_param(conn, esco_param_cvsd, ARRAY_SIZE(esco_param_cvsd))) - return -EINVAL; + goto unlock; param = &esco_param_cvsd[conn->attempt - 1]; } else { if (conn->attempt > ARRAY_SIZE(sco_param_cvsd)) - return -EINVAL; + goto unlock; param = &sco_param_cvsd[conn->attempt - 1]; } cp.tx_coding_format.id = 2; @@ -386,9 +388,11 @@ static int hci_enhanced_setup_sync(struct hci_dev *hdev, void *data) cp.out_transport_unit_size = 16; break; default: - return -EINVAL; + goto unlock; } + hci_dev_unlock(hdev); + cp.retrans_effort = param->retrans_effort; cp.pkt_type = __cpu_to_le16(param->pkt_type); cp.max_latency = __cpu_to_le16(param->max_latency); @@ -397,6 +401,10 @@ static int hci_enhanced_setup_sync(struct hci_dev *hdev, void *data) return -EIO; return 0; + +unlock: + hci_dev_unlock(hdev); + return -EINVAL; } static bool hci_setup_sync_conn(struct hci_conn *conn, __u16 handle) |
