diff mbox series

[RFC,4/5] Bluetooth: ISO: fix use-after-free in __iso_sock_close

Message ID 9fc59119ddbed06455cc8056ddf3c9968fd3ad11.1687525956.git.pav@iki.fi (mailing list archive)
State New, archived
Headers show
Series hci_conn and ISO concurrency fixes | expand

Checks

Context Check Description
tedd_an/pre-ci_am success Success
tedd_an/CheckPatch warning WARNING: Possible unwrapped commit description (prefer a maximum 75 chars per line) #106: Hardware name: QEMU Standard PC (Q35 + ICH9, 2009), BIOS 1.16.2-1.fc38 04/01/2014 total: 0 errors, 1 warnings, 0 checks, 90 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/13290842.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.
tedd_an/GitLint fail 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 20: B1 Line exceeds max length (83>80): "BUG: KASAN: slab-use-after-free in iso_conn_del (net/bluetooth/iso.c:211) bluetooth" 23: B1 Line exceeds max length (81>80): "Hardware name: QEMU Standard PC (Q35 + ICH9, 2009), BIOS 1.16.2-1.fc38 04/01/2014" 29: B1 Line exceeds max length (107>80): "? __virt_addr_valid (./include/linux/mmzone.h:1901 ./include/linux/mmzone.h:1997 arch/x86/mm/physaddr.c:65)" 34: B1 Line exceeds max length (101>80): "hci_conn_hash_flush (./include/net/bluetooth/hci_core.h:1872 net/bluetooth/hci_conn.c:2576) bluetooth" 37: B1 Line exceeds max length (92>80): "hci_set_powered_sync (net/bluetooth/hci_sync.c:5433 net/bluetooth/hci_sync.c:5441) bluetooth" 57: B1 Line exceeds max length (108>80): "hci_conn_add (./include/linux/slab.h:559 ./include/linux/slab.h:680 net/bluetooth/hci_conn.c:1002) bluetooth" 70: 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)" 73: B1 Line exceeds max length (93>80): "kobject_put (lib/kobject.c:683 lib/kobject.c:714 ./include/linux/kref.h:65 lib/kobject.c:731)" 80: B1 Line exceeds max length (117>80): "exit_to_user_mode_prepare (./include/linux/resume_user_mode.h:49 kernel/entry/common.c:171 kernel/entry/common.c:204)"
tedd_an/SubjectPrefix success Gitlint PASS
tedd_an/IncrementalBuild success Incremental Build PASS

Commit Message

Pauli Virtanen June 23, 2023, 5:18 p.m. UTC
__iso_sock_close calls hci_conn_del without hci_dev_lock, which is
invalid and results to use-after-free under suitable conditions.  But
the lock cannot be taken here because of lock ordering hci_dev_lock >
lock_sock > iso_conn_lock.

Instead, take hci_conn_get temporarily, and delete the connection
after lock_sock is released later on, if it is still alive.

Also do iso_conn_del, so that we don't leak the iso_conn.

Log:
==================================================================
hci_chan_list_flush:2769: hcon ffff8880356b9000
hci_le_remove_cig:952: hci0: handle 0x00
hci_dev_put:1487: hci0 orig refcnt 9
iso_chan_del:156: sk ffff888010918800, conn ffff888004daee00, err 104
iso_chan_del:156: sk ffff888010918800, conn 0000000000000000, err 103
BUG: KASAN: slab-use-after-free in iso_conn_del (net/bluetooth/iso.c:211) bluetooth
Write of size 8 at addr ffff8880356b99f0 by task kworker/u5:1/1073

Hardware name: QEMU Standard PC (Q35 + ICH9, 2009), BIOS 1.16.2-1.fc38 04/01/2014
Workqueue: hci0 hci_cmd_sync_work [bluetooth]
Call Trace:
<TASK>
dump_stack_lvl (lib/dump_stack.c:108)
print_report (mm/kasan/report.c:352 mm/kasan/report.c:462)
? __virt_addr_valid (./include/linux/mmzone.h:1901 ./include/linux/mmzone.h:1997 arch/x86/mm/physaddr.c:65)
? iso_conn_del (net/bluetooth/iso.c:211) bluetooth
kasan_report (mm/kasan/report.c:574)
? iso_conn_del (net/bluetooth/iso.c:211) bluetooth
iso_conn_del (net/bluetooth/iso.c:211) bluetooth
hci_conn_hash_flush (./include/net/bluetooth/hci_core.h:1872 net/bluetooth/hci_conn.c:2576) bluetooth
hci_dev_close_sync (net/bluetooth/hci_sync.c:5043) bluetooth
? __pfx_hci_dev_close_sync (net/bluetooth/hci_sync.c:4974) bluetooth
hci_set_powered_sync (net/bluetooth/hci_sync.c:5433 net/bluetooth/hci_sync.c:5441) bluetooth
? __pfx_hci_set_powered_sync (net/bluetooth/hci_sync.c:5437) bluetooth
? set_powered_sync (net/bluetooth/mgmt.c:1369) bluetooth
? __pfx_set_powered_sync (net/bluetooth/mgmt.c:1367) bluetooth
hci_cmd_sync_work (net/bluetooth/hci_sync.c:306) bluetooth
process_one_work (kernel/workqueue.c:2410)
? __pfx_process_one_work (kernel/workqueue.c:2300)
? __pfx_do_raw_spin_lock (kernel/locking/spinlock_debug.c:113)
? mark_held_locks (kernel/locking/lockdep.c:4240)
worker_thread (./include/linux/list.h:292 kernel/workqueue.c:2553)
? __pfx_worker_thread (kernel/workqueue.c:2495)
kthread (kernel/kthread.c:379)
? __pfx_kthread (kernel/kthread.c:332)
ret_from_fork (arch/x86/entry/entry_64.S:314)
</TASK>

Allocated by task 2372:
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_add (./include/linux/slab.h:559 ./include/linux/slab.h:680 net/bluetooth/hci_conn.c:1002) bluetooth
hci_bind_cis (net/bluetooth/hci_conn.c:1920) bluetooth
iso_connect_cis (net/bluetooth/iso.c:383) bluetooth
iso_sock_connect (net/bluetooth/iso.c:890) bluetooth
__sys_connect (./include/linux/file.h:44 net/socket.c:2021)
__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 2372:
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:3786 mm/slub.c:3799)
device_release (drivers/base/core.c:2489)
kobject_put (lib/kobject.c:683 lib/kobject.c:714 ./include/linux/kref.h:65 lib/kobject.c:731)
__iso_sock_close (net/bluetooth/iso.c:665) bluetooth
iso_sock_release (net/bluetooth/iso.c:686 net/bluetooth/iso.c:1473) bluetooth
__sock_release (net/socket.c:654)
sock_close (net/socket.c:1399)
__fput (fs/file_table.c:322)
task_work_run (kernel/task_work.c:180)
exit_to_user_mode_prepare (./include/linux/resume_user_mode.h:49 kernel/entry/common.c:171 kernel/entry/common.c:204)
syscall_exit_to_user_mode (kernel/entry/common.c:130 kernel/entry/common.c:299)
do_syscall_64 (arch/x86/entry/common.c:87)
entry_SYSCALL_64_after_hwframe (arch/x86/entry/entry_64.S:120)

The buggy address belongs to the object at ffff8880356b9000
which belongs to the cache kmalloc-4k of size 4096
The buggy address is located 2544 bytes inside of
freed 4096-byte region [ffff8880356b9000, ffff8880356ba000)
==================================================================

Fixes: ccf74f2390d6 ("Bluetooth: Add BTPROTO_ISO socket type")
Signed-off-by: Pauli Virtanen <pav@iki.fi>
---
 net/bluetooth/iso.c | 50 +++++++++++++++++++++++++++++++++++++--------
 1 file changed, 42 insertions(+), 8 deletions(-)
diff mbox series

Patch

diff --git a/net/bluetooth/iso.c b/net/bluetooth/iso.c
index 84d238d0639a..ea0209fb9872 100644
--- a/net/bluetooth/iso.c
+++ b/net/bluetooth/iso.c
@@ -626,7 +626,38 @@  static void iso_conn_defer_reject(struct hci_conn *conn)
 	hci_send_cmd(conn->hdev, HCI_OP_LE_REJECT_CIS, sizeof(cp), &cp);
 }
 
-static void __iso_sock_close(struct sock *sk)
+static void iso_conn_del_hci_conn(struct iso_conn *conn, struct hci_conn **del)
+{
+	/* Lock ordering forbids taking hdev->lock, postpone hci_conn_del */
+	iso_conn_lock(conn);
+	if (conn->hcon) {
+		hci_conn_get(conn->hcon);
+		hci_dev_hold(conn->hcon->hdev);
+		*del = conn->hcon;
+		conn->hcon = NULL;
+	}
+	iso_conn_unlock(conn);
+}
+
+static void iso_conn_del_hci_conn_finish(struct hci_conn *hcon)
+{
+	struct hci_dev *hdev;
+
+	if (!hcon)
+		return;
+
+	hdev = hcon->hdev;
+	hci_dev_lock(hdev);
+	if (hci_conn_is_alive(hdev, hcon)) {
+		iso_conn_del(hcon, ECONNRESET);
+		hci_conn_del(hcon);
+	}
+	hci_dev_unlock(hdev);
+	hci_dev_put(hdev);
+	hci_conn_put(hcon);
+}
+
+static void __iso_sock_close(struct sock *sk, struct hci_conn **del_conn)
 {
 	BT_DBG("sk %p state %d socket %p", sk, sk->sk_state, sk->sk_socket);
 
@@ -659,11 +690,8 @@  static void __iso_sock_close(struct sock *sk)
 		 * needs to be removed so just call hci_conn_del so the cleanup
 		 * callback do what is needed.
 		 */
-		if (test_bit(BT_SK_DEFER_SETUP, &bt_sk(sk)->flags) &&
-		    iso_pi(sk)->conn->hcon) {
-			hci_conn_del(iso_pi(sk)->conn->hcon);
-			iso_pi(sk)->conn->hcon = NULL;
-		}
+		if (test_bit(BT_SK_DEFER_SETUP, &bt_sk(sk)->flags))
+			iso_conn_del_hci_conn(iso_pi(sk)->conn, del_conn);
 
 		iso_chan_del(sk, ECONNRESET);
 		break;
@@ -680,11 +708,14 @@  static void __iso_sock_close(struct sock *sk)
 /* Must be called on unlocked socket. */
 static void iso_sock_close(struct sock *sk)
 {
+	struct hci_conn *del_conn = NULL;
+
 	iso_sock_clear_timer(sk);
 	lock_sock(sk);
-	__iso_sock_close(sk);
+	__iso_sock_close(sk, &del_conn);
 	release_sock(sk);
 	iso_sock_kill(sk);
+	iso_conn_del_hci_conn_finish(del_conn);
 }
 
 static void iso_sock_init(struct sock *sk, struct sock *parent)
@@ -1418,6 +1449,7 @@  static int iso_sock_getsockopt(struct socket *sock, int level, int optname,
 static int iso_sock_shutdown(struct socket *sock, int how)
 {
 	struct sock *sk = sock->sk;
+	struct hci_conn *del_conn = NULL;
 	int err = 0;
 
 	BT_DBG("sock %p, sk %p, how %d", sock, sk, how);
@@ -1447,7 +1479,7 @@  static int iso_sock_shutdown(struct socket *sock, int how)
 	}
 
 	iso_sock_clear_timer(sk);
-	__iso_sock_close(sk);
+	__iso_sock_close(sk, &del_conn);
 
 	if (sock_flag(sk, SOCK_LINGER) && sk->sk_lingertime &&
 	    !(current->flags & PF_EXITING))
@@ -1457,6 +1489,8 @@  static int iso_sock_shutdown(struct socket *sock, int how)
 	release_sock(sk);
 	sock_put(sk);
 
+	iso_conn_del_hci_conn_finish(del_conn);
+
 	return err;
 }