Re: [PATCH v2] Bluetooth: hci_sync: wait for directed advertising completion
Luiz Augusto von Dentz <[email protected]> Tue, 4 Aug 2026 13:12:33 -0400
| Newsgroups | org.kernel.vger.stable,org.kernel.vger.linux-bluetooth,org.kernel.vger.linux-kernel |
|---|---|
| Message-ID | <CABBYNZLH_cZ_4Q+9zEOzJ78tXuVAgRk9QAP8hFyPHTqdOeUYzw@mail.gmail.com> |
Hi Chengfeng, On Sat, Aug 1, 2026 at 10:54 AM Chengfeng Ye <[email protected]> wrote: > > le_conn_timeout is embedded in struct hci_conn, but queuing the work does > not hold a reference to the connection. hci_conn_del() uses > cancel_delayed_work() because synchronous cancellation would deadlock when > le_conn_timeout() itself calls hci_conn_del() while holding hdev->lock. > > This leaves the following interleaving possible: > > CPU 0 CPU 1 > le_conn_timeout() > hci_conn_del() > cancel_delayed_work() = false > hci_conn_cleanup() > put_device() > kfree(conn) > hci_conn_failed(conn, ...) > > The callback then dereferences the released connection. KASAN reported: > > BUG: KASAN: slab-use-after-free in hci_conn_failed+0x232/0x250 > Read of size 8 at addr ffff8881180e8e20 by task kworker/u33:1/111 > Workqueue: hci0 le_conn_timeout > Call Trace: > hci_conn_failed+0x232/0x250 > le_conn_timeout+0x23e/0x2c0 > process_one_work+0x61b/0xf50 > worker_thread+0x45b/0xd10 > > Remove le_conn_timeout instead of adding another connection reference. > Have the legacy and extended directed-advertising enable commands wait for > the appropriate LE Connection Complete event in hci_le_create_conn_sync(). > The command-sync entry already holds a connection reference until its > completion callback returns. > > Mark directed advertising as an in-flight connection attempt so teardown > can cancel the wait. Disable advertising synchronously when that wait > fails, and preserve HCI_ERROR_ADVERTISING_TIMEOUT for a software timeout. > There is then no delayed callback that can race with connection deletion. > > Fixes: 980ffc0a2cec ("Bluetooth: Fix LE connection timeout deadlock") > Cc: [email protected] > Link: https://lore.kernel.org/linux-bluetooth/[email protected]/ > Suggested-by: Luiz Augusto von Dentz <[email protected]> > Signed-off-by: Chengfeng Ye <[email protected]> > --- > Changes in v2: > - Remove le_conn_timeout instead of adding references around delayed work. > - Wait for LE Connection Complete from both directed-advertising enable paths. > - Make the wait cancellable and disable advertising after a failed wait. > > Link: https://lore.kernel.org/linux-bluetooth/[email protected]/ [v1] > > include/net/bluetooth/hci_core.h | 1 - > net/bluetooth/hci_conn.c | 45 -------------------------------- > net/bluetooth/hci_event.c | 25 ++---------------- > net/bluetooth/hci_sync.c | 42 ++++++++++++++++++++--------- > 4 files changed, 32 insertions(+), 81 deletions(-) > > diff --git a/include/net/bluetooth/hci_core.h b/include/net/bluetooth/hci_core.h > index 3df59849dcbe..937c9ba1e6b7 100644 > --- a/include/net/bluetooth/hci_core.h > +++ b/include/net/bluetooth/hci_core.h > @@ -761,7 +761,6 @@ struct hci_conn { > struct delayed_work disc_work; > struct delayed_work auto_accept_work; > struct delayed_work idle_work; > - struct delayed_work le_conn_timeout; > > struct device dev; > struct dentry *debugfs; > diff --git a/net/bluetooth/hci_conn.c b/net/bluetooth/hci_conn.c > index b1f911fd4ad6..74bf0686f428 100644 > --- a/net/bluetooth/hci_conn.c > +++ b/net/bluetooth/hci_conn.c > @@ -687,48 +687,6 @@ static void hci_conn_auto_accept(struct work_struct *work) > &conn->dst); > } > > -static void le_disable_advertising(struct hci_dev *hdev) > -{ > - if (ext_adv_capable(hdev)) { > - struct hci_cp_le_set_ext_adv_enable cp; > - > - cp.enable = 0x00; > - cp.num_of_sets = 0x00; > - > - hci_send_cmd(hdev, HCI_OP_LE_SET_EXT_ADV_ENABLE, sizeof(cp), > - &cp); > - } else { > - u8 enable = 0x00; > - hci_send_cmd(hdev, HCI_OP_LE_SET_ADV_ENABLE, sizeof(enable), > - &enable); > - } > -} > - > -static void le_conn_timeout(struct work_struct *work) > -{ > - struct hci_conn *conn = container_of(work, struct hci_conn, > - le_conn_timeout.work); > - struct hci_dev *hdev = conn->hdev; > - > - BT_DBG(""); > - > - /* We could end up here due to having done directed advertising, > - * so clean up the state if necessary. This should however only > - * happen with broken hardware or if low duty cycle was used > - * (which doesn't have a timeout of its own). > - */ > - if (conn->role == HCI_ROLE_SLAVE) { > - /* Disable LE Advertising */ > - le_disable_advertising(hdev); > - hci_dev_lock(hdev); > - hci_conn_failed(conn, HCI_ERROR_ADVERTISING_TIMEOUT); > - hci_dev_unlock(hdev); > - return; > - } > - > - hci_abort_conn(conn, HCI_ERROR_REMOTE_USER_TERM); > -} > - > struct iso_list_data { > union { > u8 cig; > @@ -1121,7 +1079,6 @@ static struct hci_conn *__hci_conn_add(struct hci_dev *hdev, int type, > INIT_DELAYED_WORK(&conn->disc_work, hci_conn_timeout); > INIT_DELAYED_WORK(&conn->auto_accept_work, hci_conn_auto_accept); > INIT_DELAYED_WORK(&conn->idle_work, hci_conn_idle); > - INIT_DELAYED_WORK(&conn->le_conn_timeout, le_conn_timeout); > > spin_lock_init(&conn->proto_lock); > > @@ -1269,8 +1226,6 @@ void hci_conn_del(struct hci_conn *conn) > hdev->acl_cnt += conn->sent; > break; > case LE_LINK: > - cancel_delayed_work(&conn->le_conn_timeout); > - > if (hdev->le_pkts) { > if (!hci_conn_num(hdev, LE_LINK) || > hdev->le_cnt + conn->sent > hdev->le_pkts) > diff --git a/net/bluetooth/hci_event.c b/net/bluetooth/hci_event.c > index 741d658e9630..b6b279b5a103 100644 > --- a/net/bluetooth/hci_event.c > +++ b/net/bluetooth/hci_event.c > @@ -1557,22 +1557,10 @@ static u8 hci_cc_le_set_adv_enable(struct hci_dev *hdev, void *data, > > hci_dev_lock(hdev); > > - /* If we're doing connection initiation as peripheral. Set a > - * timeout in case something goes wrong. > - */ > - if (*sent) { > - struct hci_conn *conn; > - > + if (*sent) > hci_dev_set_flag(hdev, HCI_LE_ADV); > - > - conn = hci_lookup_le_connect(hdev); > - if (conn) > - queue_delayed_work(hdev->workqueue, > - &conn->le_conn_timeout, > - conn->conn_timeout); > - } else { > + else > hci_dev_clear_flag(hdev, HCI_LE_ADV); > - } > > hci_dev_unlock(hdev); > > @@ -1604,8 +1592,6 @@ static u8 hci_cc_le_set_ext_adv_enable(struct hci_dev *hdev, void *data, > adv = hci_find_adv_instance(hdev, set->handle); > > if (cp->enable) { > - struct hci_conn *conn; > - > hci_dev_set_flag(hdev, HCI_LE_ADV); > > if (adv) > @@ -1613,11 +1599,6 @@ static u8 hci_cc_le_set_ext_adv_enable(struct hci_dev *hdev, void *data, > else if (!set->handle) > hci_dev_set_flag(hdev, HCI_LE_ADV_0); > > - conn = hci_lookup_le_connect(hdev); > - if (conn) > - queue_delayed_work(hdev->workqueue, > - &conn->le_conn_timeout, > - conn->conn_timeout); > } else { > if (cp->num_of_sets) { > if (adv) > @@ -5770,8 +5751,6 @@ static void le_conn_complete_evt(struct hci_dev *hdev, u8 status, > &conn->init_addr_type); > } > } > - } else { > - cancel_delayed_work(&conn->le_conn_timeout); > } > > /* The HCI_LE_Connection_Complete event is only sent once per connection. > diff --git a/net/bluetooth/hci_sync.c b/net/bluetooth/hci_sync.c > index c8d14128c363..714bb1d55926 100644 > --- a/net/bluetooth/hci_sync.c > +++ b/net/bluetooth/hci_sync.c > @@ -1616,7 +1616,9 @@ int hci_update_scan_rsp_data_sync(struct hci_dev *hdev, u8 instance) > return __hci_set_scan_rsp_data_sync(hdev, instance); > } > > -int hci_enable_ext_advertising_sync(struct hci_dev *hdev, u8 instance) > +static int hci_enable_ext_advertising_sync_ev(struct hci_dev *hdev, > + u8 instance, u8 event, > + u32 timeout) > { > struct hci_cp_le_set_ext_adv_enable *cp; > struct hci_cp_ext_adv_set *set; > @@ -1656,10 +1658,16 @@ int hci_enable_ext_advertising_sync(struct hci_dev *hdev, u8 instance) > set->duration = cpu_to_le16(duration / 10); > } > > - return __hci_cmd_sync_status(hdev, HCI_OP_LE_SET_EXT_ADV_ENABLE, > - sizeof(*cp) + > - sizeof(*set) * cp->num_of_sets, > - data, HCI_CMD_TIMEOUT); > + return __hci_cmd_sync_status_sk(hdev, HCI_OP_LE_SET_EXT_ADV_ENABLE, > + sizeof(*cp) + > + sizeof(*set) * cp->num_of_sets, > + data, event, timeout, NULL); > +} > + > +int hci_enable_ext_advertising_sync(struct hci_dev *hdev, u8 instance) > +{ > + return hci_enable_ext_advertising_sync_ev(hdev, instance, 0, > + HCI_CMD_TIMEOUT); > } > > int hci_start_ext_adv_sync(struct hci_dev *hdev, u8 instance) > @@ -6605,7 +6613,11 @@ static int hci_le_ext_directed_advertising_sync(struct hci_dev *hdev, > return err; > } > > - return hci_enable_ext_advertising_sync(hdev, 0x00); > + return hci_enable_ext_advertising_sync_ev(hdev, 0x00, > + use_enhanced_conn_complete(hdev) ? > + HCI_EV_LE_ENHANCED_CONN_COMPLETE : > + HCI_EV_LE_CONN_COMPLETE, > + conn->conn_timeout); > } > > static int hci_le_directed_advertising_sync(struct hci_dev *hdev, > @@ -6656,8 +6668,12 @@ static int hci_le_directed_advertising_sync(struct hci_dev *hdev, > > enable = 0x01; > > - return __hci_cmd_sync_status(hdev, HCI_OP_LE_SET_ADV_ENABLE, > - sizeof(enable), &enable, HCI_CMD_TIMEOUT); > + return __hci_cmd_sync_status_sk(hdev, HCI_OP_LE_SET_ADV_ENABLE, > + sizeof(enable), &enable, > + use_enhanced_conn_complete(hdev) ? > + HCI_EV_LE_ENHANCED_CONN_COMPLETE : > + HCI_EV_LE_CONN_COMPLETE, > + conn->conn_timeout, NULL); > } > > static void set_ext_conn_params(struct hci_conn *conn, > @@ -6761,6 +6777,7 @@ static int hci_le_create_conn_sync(struct hci_dev *hdev, void *data) > /* Pause advertising while doing directed advertising. */ > hci_pause_advertising_sync(hdev); > > + set_bit(HCI_CONN_CREATE, &conn->flags); > err = hci_le_directed_advertising_sync(hdev, conn); > goto done; > } > @@ -6847,7 +6864,9 @@ static int hci_le_create_conn_sync(struct hci_dev *hdev, void *data) > done: > clear_bit(HCI_CONN_CREATE, &conn->flags); > > - if (err == -ETIMEDOUT) > + if (err && conn->role == HCI_ROLE_SLAVE) > + hci_disable_advertising_sync(hdev); > + else if (err == -ETIMEDOUT) > hci_le_connect_cancel_sync(hdev, conn, 0x00); > > /* Re-enable advertising after the connection attempt is finished. */ > @@ -7184,9 +7203,8 @@ static void create_le_conn_complete(struct hci_dev *hdev, void *data, int err) > if (conn != hci_lookup_le_connect(hdev)) > goto unlock; > > - /* Flush to make sure we send create conn cancel command if needed */ > - flush_delayed_work(&conn->le_conn_timeout); > - hci_conn_failed(conn, bt_status(err)); > + hci_conn_failed(conn, conn->role == HCI_ROLE_SLAVE && err == -ETIMEDOUT ? > + HCI_ERROR_ADVERTISING_TIMEOUT : bt_status(err)); > > unlock: > hci_dev_unlock(hdev); > -- > 2.43.0 Looks like there is a problem when the command generates a command complete it bypass the custom event matching, so we will need to fix that first: https://sashiko.dev/#/patchset/20260801145430.3560911-1-nicoyip.dev%40gmail.com -- Luiz Augusto von Dentz