diff mbox series

[1/3] Bluetooth: hci_sync: iterate over hci_conn_params safely

Message ID b723c232c6fd1f90e49b147180420d1856fb907d.1686252492.git.pav@iki.fi
State New
Headers show
Series Bluetooth: ISO-related concurrency fixes | expand

Commit Message

Pauli Virtanen June 8, 2023, 9:06 p.m. UTC
hci_update_accept_list_sync iterates over hdev->pend_le_conns and
hdev->pend_le_reports, and waits for controller events in the loop body,
without holding hdev lock.

Meanwhile, these lists and the items may be modified e.g. by
le_scan_cleanup.  This can invalidate the list cursor in the ongoing
iterations, resulting to invalid behavior (eg use-after-free).

hdev lock should be held when operating on these lists, but it cannot be
held in the loop bodies here as they block. The loops are in hci_sync,
so at most one loop runs at a time (per hdev).

Fix this by doing the iteration in a safe way vs. list mutation, and
copy data to avoid locking. Add hci_conn_params.add_pending flag to
track which items are left.

Lock also around hci_pend_le_action_lookup there.

This fixes the following, which can be triggered at least by changing
hci_le_set_cig_params to always return false, and running BlueZ
iso-tester, which causes connections to be created and dropped fast:

==================================================================
BUG: KASAN: slab-use-after-free in hci_update_passive_scan_sync (net/bluetooth/hci_sync.c:2536 net/bluetooth/hci_sync.c:2723 net/bluetooth/hci_sync.c:2841)
Read of size 8 at addr ffff888001265018 by task kworker/u3:0/32

Hardware name: QEMU Standard PC (Q35 + ICH9, 2009), BIOS 1.16.2-1.fc38 04/01/2014
Workqueue: hci0 hci_cmd_sync_work
Call Trace:
<TASK>
dump_stack_lvl (./arch/x86/include/asm/irqflags.h:134 lib/dump_stack.c:107)
print_report (mm/kasan/report.c:320 mm/kasan/report.c:430)
? __virt_addr_valid (./include/linux/mmzone.h:1915 ./include/linux/mmzone.h:2011 arch/x86/mm/physaddr.c:65)
? hci_update_passive_scan_sync (net/bluetooth/hci_sync.c:2536 net/bluetooth/hci_sync.c:2723 net/bluetooth/hci_sync.c:2841)
kasan_report (mm/kasan/report.c:538)
? hci_update_passive_scan_sync (net/bluetooth/hci_sync.c:2536 net/bluetooth/hci_sync.c:2723 net/bluetooth/hci_sync.c:2841)
hci_update_passive_scan_sync (net/bluetooth/hci_sync.c:2536 net/bluetooth/hci_sync.c:2723 net/bluetooth/hci_sync.c:2841)
? __pfx_hci_update_passive_scan_sync (net/bluetooth/hci_sync.c:2780)
? mutex_lock (kernel/locking/mutex.c:282)
? __pfx_mutex_lock (kernel/locking/mutex.c:282)
? __pfx_mutex_unlock (kernel/locking/mutex.c:538)
? __pfx_update_passive_scan_sync (net/bluetooth/hci_sync.c:2861)
hci_cmd_sync_work (net/bluetooth/hci_sync.c:306)
process_one_work (./arch/x86/include/asm/preempt.h:27 kernel/workqueue.c:2399)
worker_thread (./include/linux/list.h:292 kernel/workqueue.c:2538)
? __pfx_worker_thread (kernel/workqueue.c:2480)
kthread (kernel/kthread.c:376)
? __pfx_kthread (kernel/kthread.c:331)
ret_from_fork (arch/x86/entry/entry_64.S:314)
</TASK>

Allocated by task 31:
kasan_save_stack (mm/kasan/common.c:46)
kasan_set_track (mm/kasan/common.c:52)
__kasan_kmalloc (mm/kasan/common.c:374 mm/kasan/common.c:383)
hci_conn_params_add (./include/linux/slab.h:580 ./include/linux/slab.h:720 net/bluetooth/hci_core.c:2277)
hci_connect_le_scan (net/bluetooth/hci_conn.c:1419 net/bluetooth/hci_conn.c:1589)
hci_connect_cis (net/bluetooth/hci_conn.c:2266)
iso_connect_cis (net/bluetooth/iso.c:390)
iso_sock_connect (net/bluetooth/iso.c:899)
__sys_connect (net/socket.c:2003 net/socket.c:2020)
__x64_sys_connect (net/socket.c:2027)
do_syscall_64 (arch/x86/entry/common.c:50 arch/x86/entry/common.c:80)
entry_SYSCALL_64_after_hwframe (arch/x86/entry/entry_64.S:120)

Freed by task 15:
kasan_save_stack (mm/kasan/common.c:46)
kasan_set_track (mm/kasan/common.c:52)
kasan_save_free_info (mm/kasan/generic.c:523)
__kasan_slab_free (mm/kasan/common.c:238 mm/kasan/common.c:200 mm/kasan/common.c:244)
__kmem_cache_free (mm/slub.c:1807 mm/slub.c:3787 mm/slub.c:3800)
hci_conn_params_del (net/bluetooth/hci_core.c:2323)
le_scan_cleanup (net/bluetooth/hci_conn.c:202)
process_one_work (./arch/x86/include/asm/preempt.h:27 kernel/workqueue.c:2399)
worker_thread (./include/linux/list.h:292 kernel/workqueue.c:2538)
kthread (kernel/kthread.c:376)
ret_from_fork (arch/x86/entry/entry_64.S:314)
==================================================================

Fixes: e8907f76544f ("Bluetooth: hci_sync: Make use of hci_cmd_sync_queue set 3")
Signed-off-by: Pauli Virtanen <pav@iki.fi>
---

Notes:
    It might be that even more of hci_passive_scan_sync and the routines it
    calls should hold hdev->lock, but don't know that right now.

 include/net/bluetooth/hci_core.h |  1 +
 net/bluetooth/hci_sync.c         | 81 ++++++++++++++++++++++++++++----
 2 files changed, 74 insertions(+), 8 deletions(-)

Comments

bluez.test.bot@gmail.com June 8, 2023, 9:33 p.m. UTC | #1
This is automated email and please do not reply to this email!

Dear submitter,

Thank you for submitting the patches to the linux bluetooth mailing list.
This is a CI test results with your patch series:
PW Link:https://patchwork.kernel.org/project/bluetooth/list/?series=755455

---Test result---

Test Summary:
CheckPatch                    FAIL      2.84 seconds
GitLint                       FAIL      1.32 seconds
SubjectPrefix                 PASS      0.41 seconds
BuildKernel                   PASS      32.59 seconds
CheckAllWarning               PASS      35.36 seconds
CheckSparse                   WARNING   40.70 seconds
CheckSmatch                   WARNING   110.07 seconds
BuildKernel32                 PASS      32.20 seconds
TestRunnerSetup               PASS      448.29 seconds
TestRunner_l2cap-tester       PASS      16.94 seconds
TestRunner_iso-tester         PASS      23.46 seconds
TestRunner_bnep-tester        PASS      5.73 seconds
TestRunner_mgmt-tester        PASS      117.30 seconds
TestRunner_rfcomm-tester      PASS      9.10 seconds
TestRunner_sco-tester         PASS      8.48 seconds
TestRunner_ioctl-tester       PASS      9.91 seconds
TestRunner_mesh-tester        PASS      7.27 seconds
TestRunner_smp-tester         PASS      8.35 seconds
TestRunner_userchan-tester    PASS      6.00 seconds
IncrementalBuild              PASS      41.08 seconds

Details
##############################
Test: CheckPatch - FAIL
Desc: Run checkpatch.pl script
Output:
[1/3] Bluetooth: hci_sync: iterate over hci_conn_params safely
WARNING: Possible unwrapped commit description (prefer a maximum 75 chars per line)
#104: 
Hardware name: QEMU Standard PC (Q35 + ICH9, 2009), BIOS 1.16.2-1.fc38 04/01/2014

total: 0 errors, 1 warnings, 0 checks, 165 lines checked

NOTE: For some of the reported defects, checkpatch may be able to
      mechanically convert to the typical style using --fix or --fix-inplace.

/github/workspace/src/src/13272928.patch has style problems, please review.

NOTE: Ignored message types: UNKNOWN_COMMIT_ID

NOTE: If any of the errors are false positives, please report
      them to the maintainer, see CHECKPATCH in MAINTAINERS.


[3/3] Bluetooth: ISO: fix iso_conn related locking and validity issues
WARNING: Possible unwrapped commit description (prefer a maximum 75 chars per line)
#120: 
iso_connect_cfm:1700: hcon 000000007b65d182 bdaddr 28:3d:c2:4a:7e:da status 12

total: 0 errors, 1 warnings, 0 checks, 123 lines checked

NOTE: For some of the reported defects, checkpatch may be able to
      mechanically convert to the typical style using --fix or --fix-inplace.

/github/workspace/src/src/13272927.patch has style problems, please review.

NOTE: Ignored message types: UNKNOWN_COMMIT_ID

NOTE: If any of the errors are false positives, please report
      them to the maintainer, see CHECKPATCH in MAINTAINERS.


##############################
Test: GitLint - FAIL
Desc: Run gitlint
Output:
[1/3] Bluetooth: hci_sync: iterate over hci_conn_params safely

WARNING: I3 - ignore-body-lines: gitlint will be switching from using Python regex 'match' (match beginning) to 'search' (match anywhere) semantics. Please review your ignore-body-lines.regex option accordingly. To remove this warning, set general.regex-style-search=True. More details: https://jorisroovers.github.io/gitlint/configuration/#regex-style-search
26: B1 Line exceeds max length (155>80): "BUG: KASAN: slab-use-after-free in hci_update_passive_scan_sync (net/bluetooth/hci_sync.c:2536 net/bluetooth/hci_sync.c:2723 net/bluetooth/hci_sync.c:2841)"
29: B1 Line exceeds max length (81>80): "Hardware name: QEMU Standard PC (Q35 + ICH9, 2009), BIOS 1.16.2-1.fc38 04/01/2014"
35: B1 Line exceeds max length (107>80): "? __virt_addr_valid (./include/linux/mmzone.h:1915 ./include/linux/mmzone.h:2011 arch/x86/mm/physaddr.c:65)"
36: B1 Line exceeds max length (122>80): "? hci_update_passive_scan_sync (net/bluetooth/hci_sync.c:2536 net/bluetooth/hci_sync.c:2723 net/bluetooth/hci_sync.c:2841)"
38: B1 Line exceeds max length (122>80): "? hci_update_passive_scan_sync (net/bluetooth/hci_sync.c:2536 net/bluetooth/hci_sync.c:2723 net/bluetooth/hci_sync.c:2841)"
39: B1 Line exceeds max length (120>80): "hci_update_passive_scan_sync (net/bluetooth/hci_sync.c:2536 net/bluetooth/hci_sync.c:2723 net/bluetooth/hci_sync.c:2841)"
58: B1 Line exceeds max length (105>80): "hci_conn_params_add (./include/linux/slab.h:580 ./include/linux/slab.h:720 net/bluetooth/hci_core.c:2277)"
59: B1 Line exceeds max length (81>80): "hci_connect_le_scan (net/bluetooth/hci_conn.c:1419 net/bluetooth/hci_conn.c:1589)"
72: B1 Line exceeds max length (85>80): "__kasan_slab_free (mm/kasan/common.c:238 mm/kasan/common.c:200 mm/kasan/common.c:244)"
[2/3] Bluetooth: hci_event: call ISO disconnect callback before deleting conn

WARNING: I3 - ignore-body-lines: gitlint will be switching from using Python regex 'match' (match beginning) to 'search' (match anywhere) semantics. Please review your ignore-body-lines.regex option accordingly. To remove this warning, set general.regex-style-search=True. More details: https://jorisroovers.github.io/gitlint/configuration/#regex-style-search
40: B1 Line exceeds max length (81>80): "Hardware name: QEMU Standard PC (Q35 + ICH9, 2009), BIOS 1.16.2-1.fc38 04/01/2014"
[3/3] Bluetooth: ISO: fix iso_conn related locking and validity issues

WARNING: I3 - ignore-body-lines: gitlint will be switching from using Python regex 'match' (match beginning) to 'search' (match anywhere) semantics. Please review your ignore-body-lines.regex option accordingly. To remove this warning, set general.regex-style-search=True. More details: https://jorisroovers.github.io/gitlint/configuration/#regex-style-search
68: B1 Line exceeds max length (81>80): "Hardware name: QEMU Standard PC (Q35 + ICH9, 2009), BIOS 1.16.2-1.fc38 04/01/2014"
90: B1 Line exceeds max length (106>80): "general protection fault, probably for non-canonical address 0x30b29c630930aec8: 0000 [#1] PREEMPT SMP PTI"
92: B1 Line exceeds max length (81>80): "Hardware name: QEMU Standard PC (Q35 + ICH9, 2009), BIOS 1.16.2-1.fc38 04/01/2014"
94: B1 Line exceeds max length (134>80): "Code: 90 90 0f 1f 44 00 00 48 8b 47 08 48 85 c0 0f 84 ad 00 00 00 55 89 d5 53 48 83 3f 00 48 89 fb 74 7d 66 90 48 8b 03 48 8b 53 08 <>"
134: B1 Line exceeds max length (81>80): "Hardware name: QEMU Standard PC (Q35 + ICH9, 2009), BIOS 1.16.2-1.fc38 04/01/2014"
146: B2 Line has trailing whitespace: "    "
##############################
Test: CheckSparse - WARNING
Desc: Run sparse tool with linux kernel
Output:
net/bluetooth/hci_event.c: note: in included file (through include/net/bluetooth/hci_core.h):
##############################
Test: CheckSmatch - WARNING
Desc: Run smatch tool with source
Output:
net/bluetooth/hci_event.c: note: in included file (through include/net/bluetooth/hci_core.h):


---
Regards,
Linux Bluetooth
Luiz Augusto von Dentz June 9, 2023, 5:19 p.m. UTC | #2
Hi Pauli.

On Thu, Jun 8, 2023 at 2:10 PM Pauli Virtanen <pav@iki.fi> wrote:
>
> hci_update_accept_list_sync iterates over hdev->pend_le_conns and
> hdev->pend_le_reports, and waits for controller events in the loop body,
> without holding hdev lock.
>
> Meanwhile, these lists and the items may be modified e.g. by
> le_scan_cleanup.  This can invalidate the list cursor in the ongoing
> iterations, resulting to invalid behavior (eg use-after-free).
>
> hdev lock should be held when operating on these lists, but it cannot be
> held in the loop bodies here as they block. The loops are in hci_sync,
> so at most one loop runs at a time (per hdev).
>
> Fix this by doing the iteration in a safe way vs. list mutation, and
> copy data to avoid locking. Add hci_conn_params.add_pending flag to
> track which items are left.
>
> Lock also around hci_pend_le_action_lookup there.
>
> This fixes the following, which can be triggered at least by changing
> hci_le_set_cig_params to always return false, and running BlueZ
> iso-tester, which causes connections to be created and dropped fast:
>
> ==================================================================
> BUG: KASAN: slab-use-after-free in hci_update_passive_scan_sync (net/bluetooth/hci_sync.c:2536 net/bluetooth/hci_sync.c:2723 net/bluetooth/hci_sync.c:2841)
> Read of size 8 at addr ffff888001265018 by task kworker/u3:0/32
>
> Hardware name: QEMU Standard PC (Q35 + ICH9, 2009), BIOS 1.16.2-1.fc38 04/01/2014
> Workqueue: hci0 hci_cmd_sync_work
> Call Trace:
> <TASK>
> dump_stack_lvl (./arch/x86/include/asm/irqflags.h:134 lib/dump_stack.c:107)
> print_report (mm/kasan/report.c:320 mm/kasan/report.c:430)
> ? __virt_addr_valid (./include/linux/mmzone.h:1915 ./include/linux/mmzone.h:2011 arch/x86/mm/physaddr.c:65)
> ? hci_update_passive_scan_sync (net/bluetooth/hci_sync.c:2536 net/bluetooth/hci_sync.c:2723 net/bluetooth/hci_sync.c:2841)
> kasan_report (mm/kasan/report.c:538)
> ? hci_update_passive_scan_sync (net/bluetooth/hci_sync.c:2536 net/bluetooth/hci_sync.c:2723 net/bluetooth/hci_sync.c:2841)
> hci_update_passive_scan_sync (net/bluetooth/hci_sync.c:2536 net/bluetooth/hci_sync.c:2723 net/bluetooth/hci_sync.c:2841)
> ? __pfx_hci_update_passive_scan_sync (net/bluetooth/hci_sync.c:2780)
> ? mutex_lock (kernel/locking/mutex.c:282)
> ? __pfx_mutex_lock (kernel/locking/mutex.c:282)
> ? __pfx_mutex_unlock (kernel/locking/mutex.c:538)
> ? __pfx_update_passive_scan_sync (net/bluetooth/hci_sync.c:2861)
> hci_cmd_sync_work (net/bluetooth/hci_sync.c:306)
> process_one_work (./arch/x86/include/asm/preempt.h:27 kernel/workqueue.c:2399)
> worker_thread (./include/linux/list.h:292 kernel/workqueue.c:2538)
> ? __pfx_worker_thread (kernel/workqueue.c:2480)
> kthread (kernel/kthread.c:376)
> ? __pfx_kthread (kernel/kthread.c:331)
> ret_from_fork (arch/x86/entry/entry_64.S:314)
> </TASK>
>
> Allocated by task 31:
> kasan_save_stack (mm/kasan/common.c:46)
> kasan_set_track (mm/kasan/common.c:52)
> __kasan_kmalloc (mm/kasan/common.c:374 mm/kasan/common.c:383)
> hci_conn_params_add (./include/linux/slab.h:580 ./include/linux/slab.h:720 net/bluetooth/hci_core.c:2277)
> hci_connect_le_scan (net/bluetooth/hci_conn.c:1419 net/bluetooth/hci_conn.c:1589)
> hci_connect_cis (net/bluetooth/hci_conn.c:2266)
> iso_connect_cis (net/bluetooth/iso.c:390)
> iso_sock_connect (net/bluetooth/iso.c:899)
> __sys_connect (net/socket.c:2003 net/socket.c:2020)
> __x64_sys_connect (net/socket.c:2027)
> do_syscall_64 (arch/x86/entry/common.c:50 arch/x86/entry/common.c:80)
> entry_SYSCALL_64_after_hwframe (arch/x86/entry/entry_64.S:120)
>
> Freed by task 15:
> kasan_save_stack (mm/kasan/common.c:46)
> kasan_set_track (mm/kasan/common.c:52)
> kasan_save_free_info (mm/kasan/generic.c:523)
> __kasan_slab_free (mm/kasan/common.c:238 mm/kasan/common.c:200 mm/kasan/common.c:244)
> __kmem_cache_free (mm/slub.c:1807 mm/slub.c:3787 mm/slub.c:3800)
> hci_conn_params_del (net/bluetooth/hci_core.c:2323)
> le_scan_cleanup (net/bluetooth/hci_conn.c:202)
> process_one_work (./arch/x86/include/asm/preempt.h:27 kernel/workqueue.c:2399)
> worker_thread (./include/linux/list.h:292 kernel/workqueue.c:2538)
> kthread (kernel/kthread.c:376)
> ret_from_fork (arch/x86/entry/entry_64.S:314)
> ==================================================================
>
> Fixes: e8907f76544f ("Bluetooth: hci_sync: Make use of hci_cmd_sync_queue set 3")
> Signed-off-by: Pauli Virtanen <pav@iki.fi>
> ---
>
> Notes:
>     It might be that even more of hci_passive_scan_sync and the routines it
>     calls should hold hdev->lock, but don't know that right now.
>
>  include/net/bluetooth/hci_core.h |  1 +
>  net/bluetooth/hci_sync.c         | 81 ++++++++++++++++++++++++++++----
>  2 files changed, 74 insertions(+), 8 deletions(-)
>
> diff --git a/include/net/bluetooth/hci_core.h b/include/net/bluetooth/hci_core.h
> index 683666ea210c..e79b3831fcf3 100644
> --- a/include/net/bluetooth/hci_core.h
> +++ b/include/net/bluetooth/hci_core.h
> @@ -822,6 +822,7 @@ struct hci_conn_params {
>
>         struct hci_conn *conn;
>         bool explicit_connect;
> +       bool add_pending;
>         hci_conn_flags_t flags;
>         u8  privacy_mode;
>  };
> diff --git a/net/bluetooth/hci_sync.c b/net/bluetooth/hci_sync.c
> index 97da5bcaa904..e6fde15dc9ca 100644
> --- a/net/bluetooth/hci_sync.c
> +++ b/net/bluetooth/hci_sync.c
> @@ -2160,15 +2160,23 @@ static int hci_le_del_accept_list_sync(struct hci_dev *hdev,
>         return 0;
>  }
>
> +struct conn_params {
> +       bdaddr_t addr;
> +       u8 addr_type;
> +       hci_conn_flags_t flags;
> +       u8 privacy_mode;
> +};
> +
>  /* Adds connection to resolve list if needed.
>   * Setting params to NULL programs local hdev->irk
>   */
>  static int hci_le_add_resolve_list_sync(struct hci_dev *hdev,
> -                                       struct hci_conn_params *params)
> +                                       struct conn_params *params)
>  {
>         struct hci_cp_le_add_to_resolv_list cp;
>         struct smp_irk *irk;
>         struct bdaddr_list_with_irk *entry;
> +       struct hci_conn_params *hparams;
>
>         if (!use_ll_privacy(hdev))
>                 return 0;
> @@ -2203,6 +2211,13 @@ static int hci_le_add_resolve_list_sync(struct hci_dev *hdev,
>         /* Default privacy mode is always Network */
>         params->privacy_mode = HCI_NETWORK_PRIVACY;
>
> +       hci_dev_lock(hdev);
> +       hparams = hci_conn_params_lookup(hdev, &params->addr,
> +                                        params->addr_type);
> +       if (hparams)
> +               hparams->privacy_mode = HCI_NETWORK_PRIVACY;
> +       hci_dev_unlock(hdev);
> +
>  done:
>         if (hci_dev_test_flag(hdev, HCI_PRIVACY))
>                 memcpy(cp.local_irk, hdev->irk, 16);
> @@ -2215,7 +2230,7 @@ static int hci_le_add_resolve_list_sync(struct hci_dev *hdev,
>
>  /* Set Device Privacy Mode. */
>  static int hci_le_set_privacy_mode_sync(struct hci_dev *hdev,
> -                                       struct hci_conn_params *params)
> +                                       struct conn_params *params)
>  {
>         struct hci_cp_le_set_privacy_mode cp;
>         struct smp_irk *irk;
> @@ -2249,7 +2264,7 @@ static int hci_le_set_privacy_mode_sync(struct hci_dev *hdev,
>   * properly set the privacy mode.
>   */
>  static int hci_le_add_accept_list_sync(struct hci_dev *hdev,
> -                                      struct hci_conn_params *params,
> +                                      struct conn_params *params,
>                                        u8 *num_entries)
>  {
>         struct hci_cp_le_add_to_accept_list cp;
> @@ -2447,6 +2462,37 @@ struct sk_buff *hci_read_local_oob_data_sync(struct hci_dev *hdev,
>         return __hci_cmd_sync_sk(hdev, opcode, 0, NULL, 0, HCI_CMD_TIMEOUT, sk);
>  }
>
> +static void conn_params_iter_init(struct list_head *list)
> +{
> +       struct hci_conn_params *params;
> +
> +       list_for_each_entry(params, list, action)
> +               params->add_pending = true;
> +}
> +
> +static bool conn_params_iter_next(struct list_head *list,
> +                                 struct conn_params *item)
> +{
> +       struct hci_conn_params *params;
> +
> +       /* Must hold hdev lock. Not reentrant. Mutating list is allowed. */
> +
> +       list_for_each_entry(params, list, action) {
> +               if (!params->add_pending)
> +                       continue;
> +
> +               memcpy(&item->addr, &params->addr, sizeof(params->addr));
> +               item->addr_type = params->addr_type;
> +               item->privacy_mode = params->privacy_mode;
> +               item->flags = params->flags;
> +
> +               params->add_pending = false;
> +               return true;
> +       }
> +
> +       return false;
> +}

I wonder if we shouldn't take the approach of hci_lookup_connect_le
though, it uses rcu_read_lock + list_for_each_entry_rcu which seems to
be more efficient than having a dedicated lock to protect the list.

>  /* Device must not be scanning when updating the accept list.
>   *
>   * Update is done using the following sequence:
> @@ -2466,7 +2512,7 @@ struct sk_buff *hci_read_local_oob_data_sync(struct hci_dev *hdev,
>   */
>  static u8 hci_update_accept_list_sync(struct hci_dev *hdev)
>  {
> -       struct hci_conn_params *params;
> +       struct conn_params params;
>         struct bdaddr_list *b, *t;
>         u8 num_entries = 0;
>         bool pend_conn, pend_report;
> @@ -2494,6 +2540,8 @@ static u8 hci_update_accept_list_sync(struct hci_dev *hdev)
>                 goto done;
>         }
>
> +       hci_dev_lock(hdev);
> +
>         /* Go through the current accept list programmed into the
>          * controller one by one and check if that address is connected or is
>          * still in the list of pending connections or list of devices to
> @@ -2515,8 +2563,10 @@ static u8 hci_update_accept_list_sync(struct hci_dev *hdev)
>                  * remove it from the acceptlist.
>                  */
>                 if (!pend_conn && !pend_report) {
> +                       hci_dev_unlock(hdev);
>                         hci_le_del_accept_list_sync(hdev, &b->bdaddr,
>                                                     b->bdaddr_type);
> +                       hci_dev_lock(hdev);
>                         continue;
>                 }
>
> @@ -2532,23 +2582,38 @@ static u8 hci_update_accept_list_sync(struct hci_dev *hdev)
>          * available accept list entries in the controller, then
>          * just abort and return filer policy value to not use the
>          * accept list.
> +        *
> +        * The list may be mutated at any time outside hdev lock,
> +        * so use special iteration with copies.
>          */
> -       list_for_each_entry(params, &hdev->pend_le_conns, action) {
> -               err = hci_le_add_accept_list_sync(hdev, params, &num_entries);
> +
> +       conn_params_iter_init(&hdev->pend_le_conns);
> +
> +       while (conn_params_iter_next(&hdev->pend_le_conns, &params)) {
> +               hci_dev_unlock(hdev);
> +               err = hci_le_add_accept_list_sync(hdev, &params, &num_entries);
>                 if (err)
>                         goto done;
> +               hci_dev_lock(hdev);
>         }
>
>         /* After adding all new pending connections, walk through
>          * the list of pending reports and also add these to the
>          * accept list if there is still space. Abort if space runs out.
>          */
> -       list_for_each_entry(params, &hdev->pend_le_reports, action) {
> -               err = hci_le_add_accept_list_sync(hdev, params, &num_entries);
> +
> +       conn_params_iter_init(&hdev->pend_le_reports);
> +
> +       while (conn_params_iter_next(&hdev->pend_le_reports, &params)) {
> +               hci_dev_unlock(hdev);
> +               err = hci_le_add_accept_list_sync(hdev, &params, &num_entries);
>                 if (err)
>                         goto done;
> +               hci_dev_lock(hdev);
>         }
>
> +       hci_dev_unlock(hdev);
> +
>         /* Use the allowlist unless the following conditions are all true:
>          * - We are not currently suspending
>          * - There are 1 or more ADV monitors registered and it's not offloaded
> --
> 2.40.1
>
Pauli Virtanen June 9, 2023, 6:20 p.m. UTC | #3
Hi Luiz,

pe, 2023-06-09 kello 10:19 -0700, Luiz Augusto von Dentz kirjoitti:
> On Thu, Jun 8, 2023 at 2:10 PM Pauli Virtanen <pav@iki.fi> wrote:
> > 
> > hci_update_accept_list_sync iterates over hdev->pend_le_conns and
> > hdev->pend_le_reports, and waits for controller events in the loop body,
> > without holding hdev lock.
> > 
> > Meanwhile, these lists and the items may be modified e.g. by
> > le_scan_cleanup.  This can invalidate the list cursor in the ongoing
> > iterations, resulting to invalid behavior (eg use-after-free).
> > 
> > hdev lock should be held when operating on these lists, but it cannot be
> > held in the loop bodies here as they block. The loops are in hci_sync,
> > so at most one loop runs at a time (per hdev).
> > 
> > Fix this by doing the iteration in a safe way vs. list mutation, and
> > copy data to avoid locking. Add hci_conn_params.add_pending flag to
> > track which items are left.
> > 
> > Lock also around hci_pend_le_action_lookup there.
> > 
> > This fixes the following, which can be triggered at least by changing
> > hci_le_set_cig_params to always return false, and running BlueZ
> > iso-tester, which causes connections to be created and dropped fast:
> > 
> > ==================================================================
> > BUG: KASAN: slab-use-after-free in hci_update_passive_scan_sync (net/bluetooth/hci_sync.c:2536 net/bluetooth/hci_sync.c:2723 net/bluetooth/hci_sync.c:2841)
> > Read of size 8 at addr ffff888001265018 by task kworker/u3:0/32
> > 
> > Hardware name: QEMU Standard PC (Q35 + ICH9, 2009), BIOS 1.16.2-1.fc38 04/01/2014
> > Workqueue: hci0 hci_cmd_sync_work
> > Call Trace:
> > <TASK>
> > dump_stack_lvl (./arch/x86/include/asm/irqflags.h:134 lib/dump_stack.c:107)
> > print_report (mm/kasan/report.c:320 mm/kasan/report.c:430)
> > ? __virt_addr_valid (./include/linux/mmzone.h:1915 ./include/linux/mmzone.h:2011 arch/x86/mm/physaddr.c:65)
> > ? hci_update_passive_scan_sync (net/bluetooth/hci_sync.c:2536 net/bluetooth/hci_sync.c:2723 net/bluetooth/hci_sync.c:2841)
> > kasan_report (mm/kasan/report.c:538)
> > ? hci_update_passive_scan_sync (net/bluetooth/hci_sync.c:2536 net/bluetooth/hci_sync.c:2723 net/bluetooth/hci_sync.c:2841)
> > hci_update_passive_scan_sync (net/bluetooth/hci_sync.c:2536 net/bluetooth/hci_sync.c:2723 net/bluetooth/hci_sync.c:2841)
> > ? __pfx_hci_update_passive_scan_sync (net/bluetooth/hci_sync.c:2780)
> > ? mutex_lock (kernel/locking/mutex.c:282)
> > ? __pfx_mutex_lock (kernel/locking/mutex.c:282)
> > ? __pfx_mutex_unlock (kernel/locking/mutex.c:538)
> > ? __pfx_update_passive_scan_sync (net/bluetooth/hci_sync.c:2861)
> > hci_cmd_sync_work (net/bluetooth/hci_sync.c:306)
> > process_one_work (./arch/x86/include/asm/preempt.h:27 kernel/workqueue.c:2399)
> > worker_thread (./include/linux/list.h:292 kernel/workqueue.c:2538)
> > ? __pfx_worker_thread (kernel/workqueue.c:2480)
> > kthread (kernel/kthread.c:376)
> > ? __pfx_kthread (kernel/kthread.c:331)
> > ret_from_fork (arch/x86/entry/entry_64.S:314)
> > </TASK>
> > 
> > Allocated by task 31:
> > kasan_save_stack (mm/kasan/common.c:46)
> > kasan_set_track (mm/kasan/common.c:52)
> > __kasan_kmalloc (mm/kasan/common.c:374 mm/kasan/common.c:383)
> > hci_conn_params_add (./include/linux/slab.h:580 ./include/linux/slab.h:720 net/bluetooth/hci_core.c:2277)
> > hci_connect_le_scan (net/bluetooth/hci_conn.c:1419 net/bluetooth/hci_conn.c:1589)
> > hci_connect_cis (net/bluetooth/hci_conn.c:2266)
> > iso_connect_cis (net/bluetooth/iso.c:390)
> > iso_sock_connect (net/bluetooth/iso.c:899)
> > __sys_connect (net/socket.c:2003 net/socket.c:2020)
> > __x64_sys_connect (net/socket.c:2027)
> > do_syscall_64 (arch/x86/entry/common.c:50 arch/x86/entry/common.c:80)
> > entry_SYSCALL_64_after_hwframe (arch/x86/entry/entry_64.S:120)
> > 
> > Freed by task 15:
> > kasan_save_stack (mm/kasan/common.c:46)
> > kasan_set_track (mm/kasan/common.c:52)
> > kasan_save_free_info (mm/kasan/generic.c:523)
> > __kasan_slab_free (mm/kasan/common.c:238 mm/kasan/common.c:200 mm/kasan/common.c:244)
> > __kmem_cache_free (mm/slub.c:1807 mm/slub.c:3787 mm/slub.c:3800)
> > hci_conn_params_del (net/bluetooth/hci_core.c:2323)
> > le_scan_cleanup (net/bluetooth/hci_conn.c:202)
> > process_one_work (./arch/x86/include/asm/preempt.h:27 kernel/workqueue.c:2399)
> > worker_thread (./include/linux/list.h:292 kernel/workqueue.c:2538)
> > kthread (kernel/kthread.c:376)
> > ret_from_fork (arch/x86/entry/entry_64.S:314)
> > ==================================================================
> > 
> > Fixes: e8907f76544f ("Bluetooth: hci_sync: Make use of hci_cmd_sync_queue set 3")
> > Signed-off-by: Pauli Virtanen <pav@iki.fi>
> > ---
> > 
> > Notes:
> >     It might be that even more of hci_passive_scan_sync and the routines it
> >     calls should hold hdev->lock, but don't know that right now.
> > 
> >  include/net/bluetooth/hci_core.h |  1 +
> >  net/bluetooth/hci_sync.c         | 81 ++++++++++++++++++++++++++++----
> >  2 files changed, 74 insertions(+), 8 deletions(-)
> > 
> > diff --git a/include/net/bluetooth/hci_core.h b/include/net/bluetooth/hci_core.h
> > index 683666ea210c..e79b3831fcf3 100644
> > --- a/include/net/bluetooth/hci_core.h
> > +++ b/include/net/bluetooth/hci_core.h
> > @@ -822,6 +822,7 @@ struct hci_conn_params {
> > 
> >         struct hci_conn *conn;
> >         bool explicit_connect;
> > +       bool add_pending;
> >         hci_conn_flags_t flags;
> >         u8  privacy_mode;
> >  };
> > diff --git a/net/bluetooth/hci_sync.c b/net/bluetooth/hci_sync.c
> > index 97da5bcaa904..e6fde15dc9ca 100644
> > --- a/net/bluetooth/hci_sync.c
> > +++ b/net/bluetooth/hci_sync.c
> > @@ -2160,15 +2160,23 @@ static int hci_le_del_accept_list_sync(struct hci_dev *hdev,
> >         return 0;
> >  }
> > 
> > +struct conn_params {
> > +       bdaddr_t addr;
> > +       u8 addr_type;
> > +       hci_conn_flags_t flags;
> > +       u8 privacy_mode;
> > +};
> > +
> >  /* Adds connection to resolve list if needed.
> >   * Setting params to NULL programs local hdev->irk
> >   */
> >  static int hci_le_add_resolve_list_sync(struct hci_dev *hdev,
> > -                                       struct hci_conn_params *params)
> > +                                       struct conn_params *params)
> >  {
> >         struct hci_cp_le_add_to_resolv_list cp;
> >         struct smp_irk *irk;
> >         struct bdaddr_list_with_irk *entry;
> > +       struct hci_conn_params *hparams;
> > 
> >         if (!use_ll_privacy(hdev))
> >                 return 0;
> > @@ -2203,6 +2211,13 @@ static int hci_le_add_resolve_list_sync(struct hci_dev *hdev,
> >         /* Default privacy mode is always Network */
> >         params->privacy_mode = HCI_NETWORK_PRIVACY;
> > 
> > +       hci_dev_lock(hdev);
> > +       hparams = hci_conn_params_lookup(hdev, &params->addr,
> > +                                        params->addr_type);
> > +       if (hparams)
> > +               hparams->privacy_mode = HCI_NETWORK_PRIVACY;
> > +       hci_dev_unlock(hdev);
> > +
> >  done:
> >         if (hci_dev_test_flag(hdev, HCI_PRIVACY))
> >                 memcpy(cp.local_irk, hdev->irk, 16);
> > @@ -2215,7 +2230,7 @@ static int hci_le_add_resolve_list_sync(struct hci_dev *hdev,
> > 
> >  /* Set Device Privacy Mode. */
> >  static int hci_le_set_privacy_mode_sync(struct hci_dev *hdev,
> > -                                       struct hci_conn_params *params)
> > +                                       struct conn_params *params)
> >  {
> >         struct hci_cp_le_set_privacy_mode cp;
> >         struct smp_irk *irk;
> > @@ -2249,7 +2264,7 @@ static int hci_le_set_privacy_mode_sync(struct hci_dev *hdev,
> >   * properly set the privacy mode.
> >   */
> >  static int hci_le_add_accept_list_sync(struct hci_dev *hdev,
> > -                                      struct hci_conn_params *params,
> > +                                      struct conn_params *params,
> >                                        u8 *num_entries)
> >  {
> >         struct hci_cp_le_add_to_accept_list cp;
> > @@ -2447,6 +2462,37 @@ struct sk_buff *hci_read_local_oob_data_sync(struct hci_dev *hdev,
> >         return __hci_cmd_sync_sk(hdev, opcode, 0, NULL, 0, HCI_CMD_TIMEOUT, sk);
> >  }
> > 
> > +static void conn_params_iter_init(struct list_head *list)
> > +{
> > +       struct hci_conn_params *params;
> > +
> > +       list_for_each_entry(params, list, action)
> > +               params->add_pending = true;
> > +}
> > +
> > +static bool conn_params_iter_next(struct list_head *list,
> > +                                 struct conn_params *item)
> > +{
> > +       struct hci_conn_params *params;
> > +
> > +       /* Must hold hdev lock. Not reentrant. Mutating list is allowed. */
> > +
> > +       list_for_each_entry(params, list, action) {
> > +               if (!params->add_pending)
> > +                       continue;
> > +
> > +               memcpy(&item->addr, &params->addr, sizeof(params->addr));
> > +               item->addr_type = params->addr_type;
> > +               item->privacy_mode = params->privacy_mode;
> > +               item->flags = params->flags;
> > +
> > +               params->add_pending = false;
> > +               return true;
> > +       }
> > +
> > +       return false;
> > +}
> 
> I wonder if we shouldn't take the approach of hci_lookup_connect_le
> though, it uses rcu_read_lock + list_for_each_entry_rcu which seems to
> be more efficient than having a dedicated lock to protect the list.

It could use RCU in this function instead of hdev->lock, we'd need to
change the several places where the list is modified though. I can
change that if it's better that way (I don't have a good idea which is
more efficient, or if this list is "mostly-read" one).


I'm not sure RCU would help getting rid of the add_pending flag or
copying the params data though: IIUC, RCU does not guarantee items or
the list cursor stay alive once you leave the read side critical
section (Documentation/RCU/checklist.rst point 2). If it's like this,
it wouldn't be allowed to rcu_read_unlock in the loop body, and then
rcu_read_lock again and continue the iteration, and we'd need a lock or
data copy to prevent params from being freed.


> >  /* Device must not be scanning when updating the accept list.
> >   *
> >   * Update is done using the following sequence:
> > @@ -2466,7 +2512,7 @@ struct sk_buff *hci_read_local_oob_data_sync(struct hci_dev *hdev,
> >   */
> >  static u8 hci_update_accept_list_sync(struct hci_dev *hdev)
> >  {
> > -       struct hci_conn_params *params;
> > +       struct conn_params params;
> >         struct bdaddr_list *b, *t;
> >         u8 num_entries = 0;
> >         bool pend_conn, pend_report;
> > @@ -2494,6 +2540,8 @@ static u8 hci_update_accept_list_sync(struct hci_dev *hdev)
> >                 goto done;
> >         }
> > 
> > +       hci_dev_lock(hdev);
> > +
> >         /* Go through the current accept list programmed into the
> >          * controller one by one and check if that address is connected or is
> >          * still in the list of pending connections or list of devices to
> > @@ -2515,8 +2563,10 @@ static u8 hci_update_accept_list_sync(struct hci_dev *hdev)
> >                  * remove it from the acceptlist.
> >                  */
> >                 if (!pend_conn && !pend_report) {
> > +                       hci_dev_unlock(hdev);
> >                         hci_le_del_accept_list_sync(hdev, &b->bdaddr,
> >                                                     b->bdaddr_type);
> > +                       hci_dev_lock(hdev);
> >                         continue;
> >                 }
> > 
> > @@ -2532,23 +2582,38 @@ static u8 hci_update_accept_list_sync(struct hci_dev *hdev)
> >          * available accept list entries in the controller, then
> >          * just abort and return filer policy value to not use the
> >          * accept list.
> > +        *
> > +        * The list may be mutated at any time outside hdev lock,
> > +        * so use special iteration with copies.
> >          */
> > -       list_for_each_entry(params, &hdev->pend_le_conns, action) {
> > -               err = hci_le_add_accept_list_sync(hdev, params, &num_entries);
> > +
> > +       conn_params_iter_init(&hdev->pend_le_conns);
> > +
> > +       while (conn_params_iter_next(&hdev->pend_le_conns, &params)) {
> > +               hci_dev_unlock(hdev);
> > +               err = hci_le_add_accept_list_sync(hdev, &params, &num_entries);
> >                 if (err)
> >                         goto done;
> > +               hci_dev_lock(hdev);
> >         }
> > 
> >         /* After adding all new pending connections, walk through
> >          * the list of pending reports and also add these to the
> >          * accept list if there is still space. Abort if space runs out.
> >          */
> > -       list_for_each_entry(params, &hdev->pend_le_reports, action) {
> > -               err = hci_le_add_accept_list_sync(hdev, params, &num_entries);
> > +
> > +       conn_params_iter_init(&hdev->pend_le_reports);
> > +
> > +       while (conn_params_iter_next(&hdev->pend_le_reports, &params)) {
> > +               hci_dev_unlock(hdev);
> > +               err = hci_le_add_accept_list_sync(hdev, &params, &num_entries);
> >                 if (err)
> >                         goto done;
> > +               hci_dev_lock(hdev);
> >         }
> > 
> > +       hci_dev_unlock(hdev);
> > +
> >         /* Use the allowlist unless the following conditions are all true:
> >          * - We are not currently suspending
> >          * - There are 1 or more ADV monitors registered and it's not offloaded
> > --
> > 2.40.1
> > 
> 
>
Luiz Augusto von Dentz June 9, 2023, 6:34 p.m. UTC | #4
Hi Pauli,

On Fri, Jun 9, 2023 at 11:22 AM Pauli Virtanen <pav@iki.fi> wrote:
>
> Hi Luiz,
>
> pe, 2023-06-09 kello 10:19 -0700, Luiz Augusto von Dentz kirjoitti:
> > On Thu, Jun 8, 2023 at 2:10 PM Pauli Virtanen <pav@iki.fi> wrote:
> > >
> > > hci_update_accept_list_sync iterates over hdev->pend_le_conns and
> > > hdev->pend_le_reports, and waits for controller events in the loop body,
> > > without holding hdev lock.
> > >
> > > Meanwhile, these lists and the items may be modified e.g. by
> > > le_scan_cleanup.  This can invalidate the list cursor in the ongoing
> > > iterations, resulting to invalid behavior (eg use-after-free).
> > >
> > > hdev lock should be held when operating on these lists, but it cannot be
> > > held in the loop bodies here as they block. The loops are in hci_sync,
> > > so at most one loop runs at a time (per hdev).
> > >
> > > Fix this by doing the iteration in a safe way vs. list mutation, and
> > > copy data to avoid locking. Add hci_conn_params.add_pending flag to
> > > track which items are left.
> > >
> > > Lock also around hci_pend_le_action_lookup there.
> > >
> > > This fixes the following, which can be triggered at least by changing
> > > hci_le_set_cig_params to always return false, and running BlueZ
> > > iso-tester, which causes connections to be created and dropped fast:
> > >
> > > ==================================================================
> > > BUG: KASAN: slab-use-after-free in hci_update_passive_scan_sync (net/bluetooth/hci_sync.c:2536 net/bluetooth/hci_sync.c:2723 net/bluetooth/hci_sync.c:2841)
> > > Read of size 8 at addr ffff888001265018 by task kworker/u3:0/32
> > >
> > > Hardware name: QEMU Standard PC (Q35 + ICH9, 2009), BIOS 1.16.2-1.fc38 04/01/2014
> > > Workqueue: hci0 hci_cmd_sync_work
> > > Call Trace:
> > > <TASK>
> > > dump_stack_lvl (./arch/x86/include/asm/irqflags.h:134 lib/dump_stack.c:107)
> > > print_report (mm/kasan/report.c:320 mm/kasan/report.c:430)
> > > ? __virt_addr_valid (./include/linux/mmzone.h:1915 ./include/linux/mmzone.h:2011 arch/x86/mm/physaddr.c:65)
> > > ? hci_update_passive_scan_sync (net/bluetooth/hci_sync.c:2536 net/bluetooth/hci_sync.c:2723 net/bluetooth/hci_sync.c:2841)
> > > kasan_report (mm/kasan/report.c:538)
> > > ? hci_update_passive_scan_sync (net/bluetooth/hci_sync.c:2536 net/bluetooth/hci_sync.c:2723 net/bluetooth/hci_sync.c:2841)
> > > hci_update_passive_scan_sync (net/bluetooth/hci_sync.c:2536 net/bluetooth/hci_sync.c:2723 net/bluetooth/hci_sync.c:2841)
> > > ? __pfx_hci_update_passive_scan_sync (net/bluetooth/hci_sync.c:2780)
> > > ? mutex_lock (kernel/locking/mutex.c:282)
> > > ? __pfx_mutex_lock (kernel/locking/mutex.c:282)
> > > ? __pfx_mutex_unlock (kernel/locking/mutex.c:538)
> > > ? __pfx_update_passive_scan_sync (net/bluetooth/hci_sync.c:2861)
> > > hci_cmd_sync_work (net/bluetooth/hci_sync.c:306)
> > > process_one_work (./arch/x86/include/asm/preempt.h:27 kernel/workqueue.c:2399)
> > > worker_thread (./include/linux/list.h:292 kernel/workqueue.c:2538)
> > > ? __pfx_worker_thread (kernel/workqueue.c:2480)
> > > kthread (kernel/kthread.c:376)
> > > ? __pfx_kthread (kernel/kthread.c:331)
> > > ret_from_fork (arch/x86/entry/entry_64.S:314)
> > > </TASK>
> > >
> > > Allocated by task 31:
> > > kasan_save_stack (mm/kasan/common.c:46)
> > > kasan_set_track (mm/kasan/common.c:52)
> > > __kasan_kmalloc (mm/kasan/common.c:374 mm/kasan/common.c:383)
> > > hci_conn_params_add (./include/linux/slab.h:580 ./include/linux/slab.h:720 net/bluetooth/hci_core.c:2277)
> > > hci_connect_le_scan (net/bluetooth/hci_conn.c:1419 net/bluetooth/hci_conn.c:1589)
> > > hci_connect_cis (net/bluetooth/hci_conn.c:2266)
> > > iso_connect_cis (net/bluetooth/iso.c:390)
> > > iso_sock_connect (net/bluetooth/iso.c:899)
> > > __sys_connect (net/socket.c:2003 net/socket.c:2020)
> > > __x64_sys_connect (net/socket.c:2027)
> > > do_syscall_64 (arch/x86/entry/common.c:50 arch/x86/entry/common.c:80)
> > > entry_SYSCALL_64_after_hwframe (arch/x86/entry/entry_64.S:120)
> > >
> > > Freed by task 15:
> > > kasan_save_stack (mm/kasan/common.c:46)
> > > kasan_set_track (mm/kasan/common.c:52)
> > > kasan_save_free_info (mm/kasan/generic.c:523)
> > > __kasan_slab_free (mm/kasan/common.c:238 mm/kasan/common.c:200 mm/kasan/common.c:244)
> > > __kmem_cache_free (mm/slub.c:1807 mm/slub.c:3787 mm/slub.c:3800)
> > > hci_conn_params_del (net/bluetooth/hci_core.c:2323)
> > > le_scan_cleanup (net/bluetooth/hci_conn.c:202)
> > > process_one_work (./arch/x86/include/asm/preempt.h:27 kernel/workqueue.c:2399)
> > > worker_thread (./include/linux/list.h:292 kernel/workqueue.c:2538)
> > > kthread (kernel/kthread.c:376)
> > > ret_from_fork (arch/x86/entry/entry_64.S:314)
> > > ==================================================================
> > >
> > > Fixes: e8907f76544f ("Bluetooth: hci_sync: Make use of hci_cmd_sync_queue set 3")
> > > Signed-off-by: Pauli Virtanen <pav@iki.fi>
> > > ---
> > >
> > > Notes:
> > >     It might be that even more of hci_passive_scan_sync and the routines it
> > >     calls should hold hdev->lock, but don't know that right now.
> > >
> > >  include/net/bluetooth/hci_core.h |  1 +
> > >  net/bluetooth/hci_sync.c         | 81 ++++++++++++++++++++++++++++----
> > >  2 files changed, 74 insertions(+), 8 deletions(-)
> > >
> > > diff --git a/include/net/bluetooth/hci_core.h b/include/net/bluetooth/hci_core.h
> > > index 683666ea210c..e79b3831fcf3 100644
> > > --- a/include/net/bluetooth/hci_core.h
> > > +++ b/include/net/bluetooth/hci_core.h
> > > @@ -822,6 +822,7 @@ struct hci_conn_params {
> > >
> > >         struct hci_conn *conn;
> > >         bool explicit_connect;
> > > +       bool add_pending;
> > >         hci_conn_flags_t flags;
> > >         u8  privacy_mode;
> > >  };
> > > diff --git a/net/bluetooth/hci_sync.c b/net/bluetooth/hci_sync.c
> > > index 97da5bcaa904..e6fde15dc9ca 100644
> > > --- a/net/bluetooth/hci_sync.c
> > > +++ b/net/bluetooth/hci_sync.c
> > > @@ -2160,15 +2160,23 @@ static int hci_le_del_accept_list_sync(struct hci_dev *hdev,
> > >         return 0;
> > >  }
> > >
> > > +struct conn_params {
> > > +       bdaddr_t addr;
> > > +       u8 addr_type;
> > > +       hci_conn_flags_t flags;
> > > +       u8 privacy_mode;
> > > +};
> > > +
> > >  /* Adds connection to resolve list if needed.
> > >   * Setting params to NULL programs local hdev->irk
> > >   */
> > >  static int hci_le_add_resolve_list_sync(struct hci_dev *hdev,
> > > -                                       struct hci_conn_params *params)
> > > +                                       struct conn_params *params)
> > >  {
> > >         struct hci_cp_le_add_to_resolv_list cp;
> > >         struct smp_irk *irk;
> > >         struct bdaddr_list_with_irk *entry;
> > > +       struct hci_conn_params *hparams;
> > >
> > >         if (!use_ll_privacy(hdev))
> > >                 return 0;
> > > @@ -2203,6 +2211,13 @@ static int hci_le_add_resolve_list_sync(struct hci_dev *hdev,
> > >         /* Default privacy mode is always Network */
> > >         params->privacy_mode = HCI_NETWORK_PRIVACY;
> > >
> > > +       hci_dev_lock(hdev);
> > > +       hparams = hci_conn_params_lookup(hdev, &params->addr,
> > > +                                        params->addr_type);
> > > +       if (hparams)
> > > +               hparams->privacy_mode = HCI_NETWORK_PRIVACY;
> > > +       hci_dev_unlock(hdev);
> > > +
> > >  done:
> > >         if (hci_dev_test_flag(hdev, HCI_PRIVACY))
> > >                 memcpy(cp.local_irk, hdev->irk, 16);
> > > @@ -2215,7 +2230,7 @@ static int hci_le_add_resolve_list_sync(struct hci_dev *hdev,
> > >
> > >  /* Set Device Privacy Mode. */
> > >  static int hci_le_set_privacy_mode_sync(struct hci_dev *hdev,
> > > -                                       struct hci_conn_params *params)
> > > +                                       struct conn_params *params)
> > >  {
> > >         struct hci_cp_le_set_privacy_mode cp;
> > >         struct smp_irk *irk;
> > > @@ -2249,7 +2264,7 @@ static int hci_le_set_privacy_mode_sync(struct hci_dev *hdev,
> > >   * properly set the privacy mode.
> > >   */
> > >  static int hci_le_add_accept_list_sync(struct hci_dev *hdev,
> > > -                                      struct hci_conn_params *params,
> > > +                                      struct conn_params *params,
> > >                                        u8 *num_entries)
> > >  {
> > >         struct hci_cp_le_add_to_accept_list cp;
> > > @@ -2447,6 +2462,37 @@ struct sk_buff *hci_read_local_oob_data_sync(struct hci_dev *hdev,
> > >         return __hci_cmd_sync_sk(hdev, opcode, 0, NULL, 0, HCI_CMD_TIMEOUT, sk);
> > >  }
> > >
> > > +static void conn_params_iter_init(struct list_head *list)
> > > +{
> > > +       struct hci_conn_params *params;
> > > +
> > > +       list_for_each_entry(params, list, action)
> > > +               params->add_pending = true;
> > > +}
> > > +
> > > +static bool conn_params_iter_next(struct list_head *list,
> > > +                                 struct conn_params *item)
> > > +{
> > > +       struct hci_conn_params *params;
> > > +
> > > +       /* Must hold hdev lock. Not reentrant. Mutating list is allowed. */
> > > +
> > > +       list_for_each_entry(params, list, action) {
> > > +               if (!params->add_pending)
> > > +                       continue;
> > > +
> > > +               memcpy(&item->addr, &params->addr, sizeof(params->addr));
> > > +               item->addr_type = params->addr_type;
> > > +               item->privacy_mode = params->privacy_mode;
> > > +               item->flags = params->flags;
> > > +
> > > +               params->add_pending = false;
> > > +               return true;
> > > +       }
> > > +
> > > +       return false;
> > > +}
> >
> > I wonder if we shouldn't take the approach of hci_lookup_connect_le
> > though, it uses rcu_read_lock + list_for_each_entry_rcu which seems to
> > be more efficient than having a dedicated lock to protect the list.
>
> It could use RCU in this function instead of hdev->lock, we'd need to
> change the several places where the list is modified though. I can
> change that if it's better that way (I don't have a good idea which is
> more efficient, or if this list is "mostly-read" one).

We have been extensively using it for hci_conn lists though so I'd
give it a try.

>
> I'm not sure RCU would help getting rid of the add_pending flag or
> copying the params data though: IIUC, RCU does not guarantee items or
> the list cursor stay alive once you leave the read side critical
> section (Documentation/RCU/checklist.rst point 2). If it's like this,
> it wouldn't be allowed to rcu_read_unlock in the loop body, and then
> rcu_read_lock again and continue the iteration, and we'd need a lock or
> data copy to prevent params from being freed.

Yeah, I got that doing RCU unlock+lock pattern on a loop is not going
to be safe, but it is not that different than using a dedicated lock
either since once you unlock other threats can modify the data as
well, so RCU only really help us not introducing more and more locks
to protect lists for example.

>
> > >  /* Device must not be scanning when updating the accept list.
> > >   *
> > >   * Update is done using the following sequence:
> > > @@ -2466,7 +2512,7 @@ struct sk_buff *hci_read_local_oob_data_sync(struct hci_dev *hdev,
> > >   */
> > >  static u8 hci_update_accept_list_sync(struct hci_dev *hdev)
> > >  {
> > > -       struct hci_conn_params *params;
> > > +       struct conn_params params;
> > >         struct bdaddr_list *b, *t;
> > >         u8 num_entries = 0;
> > >         bool pend_conn, pend_report;
> > > @@ -2494,6 +2540,8 @@ static u8 hci_update_accept_list_sync(struct hci_dev *hdev)
> > >                 goto done;
> > >         }
> > >
> > > +       hci_dev_lock(hdev);
> > > +
> > >         /* Go through the current accept list programmed into the
> > >          * controller one by one and check if that address is connected or is
> > >          * still in the list of pending connections or list of devices to
> > > @@ -2515,8 +2563,10 @@ static u8 hci_update_accept_list_sync(struct hci_dev *hdev)
> > >                  * remove it from the acceptlist.
> > >                  */
> > >                 if (!pend_conn && !pend_report) {
> > > +                       hci_dev_unlock(hdev);
> > >                         hci_le_del_accept_list_sync(hdev, &b->bdaddr,
> > >                                                     b->bdaddr_type);
> > > +                       hci_dev_lock(hdev);
> > >                         continue;
> > >                 }
> > >
> > > @@ -2532,23 +2582,38 @@ static u8 hci_update_accept_list_sync(struct hci_dev *hdev)
> > >          * available accept list entries in the controller, then
> > >          * just abort and return filer policy value to not use the
> > >          * accept list.
> > > +        *
> > > +        * The list may be mutated at any time outside hdev lock,
> > > +        * so use special iteration with copies.
> > >          */
> > > -       list_for_each_entry(params, &hdev->pend_le_conns, action) {
> > > -               err = hci_le_add_accept_list_sync(hdev, params, &num_entries);
> > > +
> > > +       conn_params_iter_init(&hdev->pend_le_conns);
> > > +
> > > +       while (conn_params_iter_next(&hdev->pend_le_conns, &params)) {
> > > +               hci_dev_unlock(hdev);
> > > +               err = hci_le_add_accept_list_sync(hdev, &params, &num_entries);
> > >                 if (err)
> > >                         goto done;
> > > +               hci_dev_lock(hdev);
> > >         }
> > >
> > >         /* After adding all new pending connections, walk through
> > >          * the list of pending reports and also add these to the
> > >          * accept list if there is still space. Abort if space runs out.
> > >          */
> > > -       list_for_each_entry(params, &hdev->pend_le_reports, action) {
> > > -               err = hci_le_add_accept_list_sync(hdev, params, &num_entries);
> > > +
> > > +       conn_params_iter_init(&hdev->pend_le_reports);
> > > +
> > > +       while (conn_params_iter_next(&hdev->pend_le_reports, &params)) {
> > > +               hci_dev_unlock(hdev);
> > > +               err = hci_le_add_accept_list_sync(hdev, &params, &num_entries);
> > >                 if (err)
> > >                         goto done;
> > > +               hci_dev_lock(hdev);
> > >         }
> > >
> > > +       hci_dev_unlock(hdev);
> > > +
> > >         /* Use the allowlist unless the following conditions are all true:
> > >          * - We are not currently suspending
> > >          * - There are 1 or more ADV monitors registered and it's not offloaded
> > > --
> > > 2.40.1
> > >
> >
> >
>
diff mbox series

Patch

diff --git a/include/net/bluetooth/hci_core.h b/include/net/bluetooth/hci_core.h
index 683666ea210c..e79b3831fcf3 100644
--- a/include/net/bluetooth/hci_core.h
+++ b/include/net/bluetooth/hci_core.h
@@ -822,6 +822,7 @@  struct hci_conn_params {
 
 	struct hci_conn *conn;
 	bool explicit_connect;
+	bool add_pending;
 	hci_conn_flags_t flags;
 	u8  privacy_mode;
 };
diff --git a/net/bluetooth/hci_sync.c b/net/bluetooth/hci_sync.c
index 97da5bcaa904..e6fde15dc9ca 100644
--- a/net/bluetooth/hci_sync.c
+++ b/net/bluetooth/hci_sync.c
@@ -2160,15 +2160,23 @@  static int hci_le_del_accept_list_sync(struct hci_dev *hdev,
 	return 0;
 }
 
+struct conn_params {
+	bdaddr_t addr;
+	u8 addr_type;
+	hci_conn_flags_t flags;
+	u8 privacy_mode;
+};
+
 /* Adds connection to resolve list if needed.
  * Setting params to NULL programs local hdev->irk
  */
 static int hci_le_add_resolve_list_sync(struct hci_dev *hdev,
-					struct hci_conn_params *params)
+					struct conn_params *params)
 {
 	struct hci_cp_le_add_to_resolv_list cp;
 	struct smp_irk *irk;
 	struct bdaddr_list_with_irk *entry;
+	struct hci_conn_params *hparams;
 
 	if (!use_ll_privacy(hdev))
 		return 0;
@@ -2203,6 +2211,13 @@  static int hci_le_add_resolve_list_sync(struct hci_dev *hdev,
 	/* Default privacy mode is always Network */
 	params->privacy_mode = HCI_NETWORK_PRIVACY;
 
+	hci_dev_lock(hdev);
+	hparams = hci_conn_params_lookup(hdev, &params->addr,
+					 params->addr_type);
+	if (hparams)
+		hparams->privacy_mode = HCI_NETWORK_PRIVACY;
+	hci_dev_unlock(hdev);
+
 done:
 	if (hci_dev_test_flag(hdev, HCI_PRIVACY))
 		memcpy(cp.local_irk, hdev->irk, 16);
@@ -2215,7 +2230,7 @@  static int hci_le_add_resolve_list_sync(struct hci_dev *hdev,
 
 /* Set Device Privacy Mode. */
 static int hci_le_set_privacy_mode_sync(struct hci_dev *hdev,
-					struct hci_conn_params *params)
+					struct conn_params *params)
 {
 	struct hci_cp_le_set_privacy_mode cp;
 	struct smp_irk *irk;
@@ -2249,7 +2264,7 @@  static int hci_le_set_privacy_mode_sync(struct hci_dev *hdev,
  * properly set the privacy mode.
  */
 static int hci_le_add_accept_list_sync(struct hci_dev *hdev,
-				       struct hci_conn_params *params,
+				       struct conn_params *params,
 				       u8 *num_entries)
 {
 	struct hci_cp_le_add_to_accept_list cp;
@@ -2447,6 +2462,37 @@  struct sk_buff *hci_read_local_oob_data_sync(struct hci_dev *hdev,
 	return __hci_cmd_sync_sk(hdev, opcode, 0, NULL, 0, HCI_CMD_TIMEOUT, sk);
 }
 
+static void conn_params_iter_init(struct list_head *list)
+{
+	struct hci_conn_params *params;
+
+	list_for_each_entry(params, list, action)
+		params->add_pending = true;
+}
+
+static bool conn_params_iter_next(struct list_head *list,
+				  struct conn_params *item)
+{
+	struct hci_conn_params *params;
+
+	/* Must hold hdev lock. Not reentrant. Mutating list is allowed. */
+
+	list_for_each_entry(params, list, action) {
+		if (!params->add_pending)
+			continue;
+
+		memcpy(&item->addr, &params->addr, sizeof(params->addr));
+		item->addr_type = params->addr_type;
+		item->privacy_mode = params->privacy_mode;
+		item->flags = params->flags;
+
+		params->add_pending = false;
+		return true;
+	}
+
+	return false;
+}
+
 /* Device must not be scanning when updating the accept list.
  *
  * Update is done using the following sequence:
@@ -2466,7 +2512,7 @@  struct sk_buff *hci_read_local_oob_data_sync(struct hci_dev *hdev,
  */
 static u8 hci_update_accept_list_sync(struct hci_dev *hdev)
 {
-	struct hci_conn_params *params;
+	struct conn_params params;
 	struct bdaddr_list *b, *t;
 	u8 num_entries = 0;
 	bool pend_conn, pend_report;
@@ -2494,6 +2540,8 @@  static u8 hci_update_accept_list_sync(struct hci_dev *hdev)
 		goto done;
 	}
 
+	hci_dev_lock(hdev);
+
 	/* Go through the current accept list programmed into the
 	 * controller one by one and check if that address is connected or is
 	 * still in the list of pending connections or list of devices to
@@ -2515,8 +2563,10 @@  static u8 hci_update_accept_list_sync(struct hci_dev *hdev)
 		 * remove it from the acceptlist.
 		 */
 		if (!pend_conn && !pend_report) {
+			hci_dev_unlock(hdev);
 			hci_le_del_accept_list_sync(hdev, &b->bdaddr,
 						    b->bdaddr_type);
+			hci_dev_lock(hdev);
 			continue;
 		}
 
@@ -2532,23 +2582,38 @@  static u8 hci_update_accept_list_sync(struct hci_dev *hdev)
 	 * available accept list entries in the controller, then
 	 * just abort and return filer policy value to not use the
 	 * accept list.
+	 *
+	 * The list may be mutated at any time outside hdev lock,
+	 * so use special iteration with copies.
 	 */
-	list_for_each_entry(params, &hdev->pend_le_conns, action) {
-		err = hci_le_add_accept_list_sync(hdev, params, &num_entries);
+
+	conn_params_iter_init(&hdev->pend_le_conns);
+
+	while (conn_params_iter_next(&hdev->pend_le_conns, &params)) {
+		hci_dev_unlock(hdev);
+		err = hci_le_add_accept_list_sync(hdev, &params, &num_entries);
 		if (err)
 			goto done;
+		hci_dev_lock(hdev);
 	}
 
 	/* After adding all new pending connections, walk through
 	 * the list of pending reports and also add these to the
 	 * accept list if there is still space. Abort if space runs out.
 	 */
-	list_for_each_entry(params, &hdev->pend_le_reports, action) {
-		err = hci_le_add_accept_list_sync(hdev, params, &num_entries);
+
+	conn_params_iter_init(&hdev->pend_le_reports);
+
+	while (conn_params_iter_next(&hdev->pend_le_reports, &params)) {
+		hci_dev_unlock(hdev);
+		err = hci_le_add_accept_list_sync(hdev, &params, &num_entries);
 		if (err)
 			goto done;
+		hci_dev_lock(hdev);
 	}
 
+	hci_dev_unlock(hdev);
+
 	/* Use the allowlist unless the following conditions are all true:
 	 * - We are not currently suspending
 	 * - There are 1 or more ADV monitors registered and it's not offloaded