Linux bluetooth development
 help / color / mirror / Atom feed
From: Cen Zhang <zzzccc427@gmail.com>
To: marcel@holtmann.org, luiz.dentz@gmail.com,
	johan.hedberg@intel.com, vinicius.gomes@openbossa.org
Cc: linux-bluetooth@vger.kernel.org, linux-kernel@vger.kernel.org,
	baijiaju1990@gmail.com, jjzuming@gmail.com, zzzccc427@gmail.com
Subject: [PATCH] Bluetooth: MGMT: Reject LTK loads while connections exist
Date: Wed, 14 Oct 2026 00:26:26 +0800	[thread overview]
Message-ID: <pm-bluetooth-objects-candidate-0136-v7-7f507d267b3bec2ac955@gmail.com> (raw)

SMP channels retain LTK pointers from the controller's long_term_keys
list until key notification or pairing teardown. load_long_term_keys()
clears that list under hdev->lock without invalidating the channels'
saved pointers. The SMP channel lock does not serialize the management
clear; kfree_rcu() does not keep a key alive for the rest of pairing.

An ongoing pairing and management LTK loads permit this ordering:

1. smp_cmd_initiator_ident() or sc_add_ltk() saves the hci_add_ltk()
   result A in smp->ltk.
2. load_long_term_keys() takes hdev->lock, and hci_smp_ltks_clear()
   unlinks A and queues its RCU free. The pairing retains its alias.
3. After the RCU grace period, A is freed. A later management load
   allocates LTK B at the same address, possibly on another controller.
4. The old pairing reaches smp_chan_destroy() and reads smp->ltk->type
   through the saved address.

If B is still live, the old pairing can read its type and remove it
through the stale alias; smp_notify_keys() can also overwrite and report
its fields. If B has already been freed, the access is a use-after-free.
The KASAN report below shows the latter ordering: the stale teardown
read follows allocation and RCU release of the replacement LTK.

Reject MGMT_OP_LOAD_LONG_TERM_KEYS with MGMT_STATUS_BUSY while LE or
BR/EDR ACL connections exist. Both transports can have SMP channels
retaining LTKs, including keys derived over BR/EDR. Check the connection
counts under hdev->lock and keep that lock through clearing and loading.
Connections are published under the same lock, and disconnect tears
down SMP before removing the connection from the hash. A successful
load therefore runs without pairing aliases, and a new connection
cannot acquire one until the replacement finishes.

KASAN report as below:

    ==================================================================
    BUG: KASAN: slab-use-after-free in smp_chan_destroy.isra.0+0xc8b/0x1060
    Read of size 1 at addr ffff88810a45eb28 by task kworker/u17:3/506

    Workqueue: hci1 hci_rx_work
    Call Trace:
     <TASK>
     dump_stack_lvl+0x93/0xd0
     print_report+0xce/0x630
     ? smp_chan_destroy.isra.0+0xc8b/0x1060
     ? srso_alias_return_thunk+0x5/0xfbef5
     ? __virt_addr_valid+0x20d/0x410
     ? smp_chan_destroy.isra.0+0xc8b/0x1060
     kasan_report+0xe0/0x110
     ? smp_chan_destroy.isra.0+0xc8b/0x1060
     smp_chan_destroy.isra.0+0xc8b/0x1060
     smp_teardown_cb+0xc7/0x230
     l2cap_chan_del+0x123/0x8f0
     l2cap_conn_del+0x31b/0x740
     ? reacquire_held_locks+0x1e7/0x1f0
     ? __pfx_l2cap_disconn_cfm+0x10/0x10
     l2cap_disconn_cfm+0x87/0xd0
     hci_disconn_complete_evt+0x30f/0x990
     ? srso_alias_return_thunk+0x5/0xfbef5
     hci_event_packet+0x894/0xc70
     ? __pfx_hci_disconn_complete_evt+0x10/0x10
     ? srso_alias_return_thunk+0x5/0xfbef5
     ? __pfx_hci_event_packet+0x10/0x10
     ? srso_alias_return_thunk+0x5/0xfbef5
     ? free_zapped_rcu+0xd0/0x1e0
     ? srso_alias_return_thunk+0x5/0xfbef5
     ? lockdep_hardirqs_on_prepare+0xea/0x1a0
     ? __pfx_hci_cmd_sync_complete+0x10/0x10
     ? trace_hardirqs_on+0x18/0x160
     ? srso_alias_return_thunk+0x5/0xfbef5
     hci_rx_work+0x367/0xf00
     ? srso_alias_return_thunk+0x5/0xfbef5
     process_one_work+0x908/0x19c0
     ? __pfx_process_one_work+0x10/0x10
     ? srso_alias_return_thunk+0x5/0xfbef5
     ? lock_is_held_type+0x8f/0x100
     ? srso_alias_return_thunk+0x5/0xfbef5
     worker_thread+0x65c/0xe40
     ? __pfx_worker_thread+0x10/0x10
     kthread+0x34f/0x460
     ? srso_alias_return_thunk+0x5/0xfbef5
     ? __pfx_kthread+0x10/0x10
     ret_from_fork+0x659/0x940
     ? __pfx_ret_from_fork+0x10/0x10
     ? srso_alias_return_thunk+0x5/0xfbef5
     ? __switch_to+0x74f/0xf80
     ? __pfx_kthread+0x10/0x10
     ret_from_fork_asm+0x1a/0x30
     </TASK>

    Allocated by task 523:
     kasan_save_stack+0x33/0x60
     kasan_save_track+0x14/0x30
     __kasan_kmalloc+0xaa/0xb0
     __kmalloc_cache_noprof+0x251/0x610
     hci_add_ltk+0x285/0x4c0
     load_long_term_keys+0x74a/0x8d0
     hci_sock_sendmsg+0x128b/0x22e0
     __sys_sendto+0x425/0x470
     __x64_sys_sendto+0xe5/0x1c0
     do_syscall_64+0x115/0x6a0
     entry_SYSCALL_64_after_hwframe+0x77/0x7f

    Freed by task 523:
     kasan_save_stack+0x33/0x60
     kasan_save_track+0x14/0x30
     kasan_save_free_info+0x3b/0x60
     __kasan_slab_free+0x5f/0x80
     __rcu_free_sheaf_prepare+0x65/0x2a0
     rcu_free_sheaf+0x1f/0xe0
     rcu_core+0x661/0x1d10
     handle_softirqs+0x201/0x930
     __irq_exit_rcu+0x110/0x1e0
     irq_exit_rcu+0xe/0x20
     sysvec_apic_timer_interrupt+0x37/0x80
     asm_sysvec_apic_timer_interrupt+0x1a/0x20

    The buggy address belongs to the object at ffff88810a45eb00
     which belongs to the cache kmalloc-96 of size 96
    The buggy address is located 40 bytes inside of
     freed 96-byte region [ffff88810a45eb00, ffff88810a45eb60)

    The buggy address belongs to the physical page:
        page: refcount:0 mapcount:0 mapping:0000000000000000 index:0x0
    pfn:0x10a45e
    flags: 0x200000000000000(node=0|zone=2)
    page_type: f5(slab)
        raw: 0200000000000000 ffff888100042280 dead000000000100
    dead000000000122
        raw: 0000000000000000 0000000000200020 00000000f5000000
    0000000000000000
    page dumped because: kasan: bad access detected

    Memory state around the buggy address:
     ffff88810a45ea00: fa fb fb fb fb fb fb fb fb fb fb fb fc fc fc fc
     ffff88810a45ea80: fa fb fb fb fb fb fb fb fb fb fb fb fc fc fc fc
    >ffff88810a45eb00: fa fb fb fb fb fb fb fb fb fb fb fb fc fc fc fc
                                      ^
     ffff88810a45eb80: 00 00 00 00 00 00 00 00 00 00 fc fc fc fc fc fc
     ffff88810a45ec00: 00 00 00 00 00 00 00 00 00 00 00 fc fc fc fc fc
    ==================================================================

Fixes: 346af67b8d11 ("Bluetooth: Add MGMT handlers for dealing with SMP LTK's")
Assisted-by: LLM
Signed-off-by: Cen Zhang <zzzccc427@gmail.com>
---
diff --git a/net/bluetooth/mgmt.c b/net/bluetooth/mgmt.c
index 9f65863e7457d..c3e8ca48a0337 100644
--- a/net/bluetooth/mgmt.c
+++ b/net/bluetooth/mgmt.c
@@ -7460,6 +7460,16 @@ static int load_long_term_keys(struct sock *sk, struct hci_dev *hdev,
 
 	hci_dev_lock(hdev);
 
+	/*
+	 * SMP over LE or BR/EDR may retain pointers to the current LTKs.
+	 */
+	if (hci_conn_num(hdev, LE_LINK) || hci_conn_num(hdev, ACL_LINK)) {
+		err = mgmt_cmd_status(sk, hdev->id,
+				      MGMT_OP_LOAD_LONG_TERM_KEYS,
+				      MGMT_STATUS_BUSY);
+		goto unlock;
+	}
+
 	hci_smp_ltks_clear(hdev);
 
 	for (i = 0; i < key_count; i++) {
@@ -7513,6 +7523,7 @@ static int load_long_term_keys(struct sock *sk, struct hci_dev *hdev,
 	err = mgmt_cmd_complete(sk, hdev->id, MGMT_OP_LOAD_LONG_TERM_KEYS, 0,
 			   NULL, 0);
 
+unlock:
 	hci_dev_unlock(hdev);
 
 	return err;

             reply	other threads:[~2026-10-13 16:26 UTC|newest]

Thread overview: 2+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-10-13 16:26 Cen Zhang [this message]
2026-10-13 20:08 ` Bluetooth: MGMT: Reject LTK loads while connections exist bluez.test.bot

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=pm-bluetooth-objects-candidate-0136-v7-7f507d267b3bec2ac955@gmail.com \
    --to=zzzccc427@gmail.com \
    --cc=baijiaju1990@gmail.com \
    --cc=jjzuming@gmail.com \
    --cc=johan.hedberg@intel.com \
    --cc=linux-bluetooth@vger.kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=luiz.dentz@gmail.com \
    --cc=marcel@holtmann.org \
    --cc=vinicius.gomes@openbossa.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox