Pauli Virtanen (3): Bluetooth: ISO: avoid deadlocks in iso_sock_timeout Bluetooth: ISO: ensure no dangling hcon references in iso_conn Bluetooth: ISO: fix refcounting of iso_conn net/bluetooth/iso.c | 107 ++++++++++++++++++++++++++------------------ 1 file changed, 64 insertions(+), 43 deletions(-) -- 2.34.1
From: Pauli Virtanen <pav@iki.fi> stable inclusion from stable-v6.18.44 commit 82e982f54f962f72646868ddbb2c3bd9ea178568 category: bugfix bugzilla: https://atomgit.com/src-openeuler/kernel/issues/18470 Reference: https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/commit/?id=... -------------------------------- [ Upstream commit 200fa1629c57a3ca2b03d3ca63fd3a9bfd910c43 ] iso_sock_timeout() takes lock_sock, so sync disabling the timer while holding that lock may deadlock. iso_sock_timeout() may also run concurrently with iso_conn_del(), which leads to UAF [Task 1] [Task hdev->workqueue] iso_sock_timeout iso_conn_del iso_conn_hold_unless_zero iso_chan_del `------------> iso_conn_put caller frees hcon iso_conn_put iso_conn_free conn->hcon->iso_data = NULL; /* UAF */ Fix the deadlock by removing the disable from the lock_sock sections. Move the timer from iso_conn to iso_pinfo to decouple it from iso_conn which may need to be freed in lock_sock section. Convert some of the clear_timer to disable_timer. Fixes: dc26097bdb86 ("Bluetooth: ISO: Use kref to track lifetime of iso_conn") Signed-off-by: Pauli Virtanen <pav@iki.fi> Signed-off-by: Luiz Augusto von Dentz <luiz.von.dentz@intel.com> Signed-off-by: Sasha Levin <sashal@kernel.org> Conflicts: net/bluetooth/iso.c [Context conflicts] Signed-off-by: Yao Yiqi <yaoyiqi3@huawei.com> --- net/bluetooth/iso.c | 60 ++++++++++++++++++++++----------------------- 1 file changed, 29 insertions(+), 31 deletions(-) diff --git a/net/bluetooth/iso.c b/net/bluetooth/iso.c index 56aab26ced0e..97649e371f28 100644 --- a/net/bluetooth/iso.c +++ b/net/bluetooth/iso.c @@ -36,8 +36,6 @@ struct iso_conn { spinlock_t lock; struct sock *sk; - struct delayed_work timeout_work; - struct sk_buff *rx_skb; __u32 rx_len; __u16 tx_sn; @@ -81,6 +79,7 @@ struct iso_pinfo { __u8 base_len; __u8 base[BASE_MAX_LENGTH]; struct iso_conn *conn; + struct delayed_work timeout_work; }; static struct bt_iso_qos default_qos; @@ -115,9 +114,6 @@ static void iso_conn_free(struct kref *ref) hci_conn_drop(conn->hcon); } - /* Ensure no more work items will run since hci_conn has been dropped */ - disable_delayed_work_sync(&conn->timeout_work); - kfree_skb(conn->rx_skb); kfree(conn); @@ -158,48 +154,45 @@ static struct sock *iso_sock_hold(struct iso_conn *conn) static void iso_sock_timeout(struct work_struct *work) { - struct iso_conn *conn = container_of(work, struct iso_conn, - timeout_work.work); - struct sock *sk; - - conn = iso_conn_hold_unless_zero(conn); - if (!conn) - return; - - iso_conn_lock(conn); - sk = iso_sock_hold(conn); - iso_conn_unlock(conn); - iso_conn_put(conn); - - if (!sk) - return; + struct iso_pinfo *pi = container_of(work, struct iso_pinfo, + timeout_work.work); + struct sock *sk = &pi->bt.sk; BT_DBG("sock %p state %d", sk, sk->sk_state); lock_sock(sk); - sk->sk_err = ETIMEDOUT; - sk->sk_state_change(sk); + if (!sock_flag(sk, SOCK_ZAPPED)) { + sk->sk_err = ETIMEDOUT; + sk->sk_state_change(sk); + } release_sock(sk); - sock_put(sk); } static void iso_sock_set_timer(struct sock *sk, long timeout) { + lockdep_assert(lockdep_sock_is_held(sk)); + + cancel_delayed_work(&iso_pi(sk)->timeout_work); + if (!iso_pi(sk)->conn) return; BT_DBG("sock %p state %d timeout %ld", sk, sk->sk_state, timeout); - cancel_delayed_work(&iso_pi(sk)->conn->timeout_work); - schedule_delayed_work(&iso_pi(sk)->conn->timeout_work, timeout); + schedule_delayed_work(&iso_pi(sk)->timeout_work, timeout); } static void iso_sock_clear_timer(struct sock *sk) { - if (!iso_pi(sk)->conn) - return; + BT_DBG("sock %p state %d", sk, sk->sk_state); + cancel_delayed_work(&iso_pi(sk)->timeout_work); +} + +static void iso_sock_disable_timer(struct sock *sk) +{ + lockdep_assert(!lockdep_sock_is_held(sk)); BT_DBG("sock %p state %d", sk, sk->sk_state); - cancel_delayed_work(&iso_pi(sk)->conn->timeout_work); + disable_delayed_work_sync(&iso_pi(sk)->timeout_work); } /* ---- ISO connections ---- */ @@ -224,7 +217,6 @@ static struct iso_conn *iso_conn_add(struct hci_conn *hcon) kref_init(&conn->ref); spin_lock_init(&conn->lock); - INIT_DELAYED_WORK(&conn->timeout_work, iso_sock_timeout); hcon->iso_data = conn; conn->hcon = hcon; @@ -300,6 +292,8 @@ static void iso_conn_del(struct hci_conn *hcon, int err) return; } + iso_sock_disable_timer(sk); + lock_sock(sk); /* While a PA sync hcon is in the process of closing, @@ -319,7 +313,6 @@ static void iso_conn_del(struct hci_conn *hcon, int err) } } - iso_sock_clear_timer(sk); iso_chan_del(sk, err); release_sock(sk); iso_sock_kill(sk); @@ -800,6 +793,8 @@ static void iso_sock_cleanup_listen(struct sock *parent) */ static void iso_sock_kill(struct sock *sk) { + iso_sock_disable_timer(sk); + lock_sock(sk); if (!sock_flag(sk, SOCK_ZAPPED) || sk->sk_socket || @@ -895,8 +890,9 @@ static void __iso_sock_close(struct sock *sk) /* Must be called on unlocked socket. */ static void iso_sock_close(struct sock *sk) { + iso_sock_disable_timer(sk); + lock_sock(sk); - iso_sock_clear_timer(sk); __iso_sock_close(sk); release_sock(sk); } @@ -964,6 +960,8 @@ static struct sock *iso_sock_alloc(struct net *net, struct socket *sock, iso_pi(sk)->qos = default_qos; + INIT_DELAYED_WORK(&iso_pi(sk)->timeout_work, iso_sock_timeout); + bt_sock_link(&iso_sk_list, sk); return sk; } -- 2.34.1
From: Pauli Virtanen <pav@iki.fi> stable inclusion from stable-v6.18.44 commit e941799c31f68e67ce0976efb38a79101f921b64 category: bugfix bugzilla: https://atomgit.com/src-openeuler/kernel/issues/18470 Reference: https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/commit/?id=... -------------------------------- [ Upstream commit aa9f7cb2bd3a2be998ceb739fc9a2f986eba43eb ] After iso_conn_del(), ISO sockets should not dereference the hcon any more. Currently, clearing iso_conn::hcon relies on iso_conn_del() releasing the last reference to the iso_conn. Simplify this by explicitly clearing conn->hcon in iso_conn_del(), to avoid more complex reasoning on races about who holds the last reference. Signed-off-by: Pauli Virtanen <pav@iki.fi> Signed-off-by: Luiz Augusto von Dentz <luiz.von.dentz@intel.com> Stable-dep-of: fdfde532ab1c ("Bluetooth: ISO: fix refcounting of iso_conn") Signed-off-by: Sasha Levin <sashal@kernel.org> Conflicts: net/bluetooth/iso.c [Context conflicts] Signed-off-by: Yao Yiqi <yaoyiqi3@huawei.com> --- net/bluetooth/iso.c | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/net/bluetooth/iso.c b/net/bluetooth/iso.c index 97649e371f28..cbe670247ddd 100644 --- a/net/bluetooth/iso.c +++ b/net/bluetooth/iso.c @@ -270,6 +270,7 @@ static bool iso_match_conn_sync_handle(struct sock *sk, void *data) } static void iso_conn_del(struct hci_conn *hcon, int err) + __must_hold(&hcon->hdev->lock) { struct iso_conn *conn = hcon->iso_data; struct sock *sk; @@ -285,11 +286,10 @@ static void iso_conn_del(struct hci_conn *hcon, int err) iso_conn_lock(conn); sk = iso_sock_hold(conn); iso_conn_unlock(conn); - iso_conn_put(conn); if (!sk) { iso_conn_put(conn); - return; + goto done; } iso_sock_disable_timer(sk); @@ -318,6 +318,14 @@ static void iso_conn_del(struct hci_conn *hcon, int err) iso_sock_kill(sk); sock_put(sk); +done: + /* No sk access to conn->hcon any more (lock_sock + hdev->lock) */ + iso_conn_lock(conn); + conn->hcon = NULL; + hcon->iso_data = NULL; + iso_conn_unlock(conn); + + iso_conn_put(conn); } static int __iso_chan_add(struct iso_conn *conn, struct sock *sk, @@ -333,6 +341,11 @@ static int __iso_chan_add(struct iso_conn *conn, struct sock *sk, return -EBUSY; } + if (!conn->hcon) { + BT_ERR("conn->hcon missing"); + return -EIO; + } + iso_pi(sk)->conn = conn; conn->sk = sk; clear_bit(ISO_CONN_DROPPED, conn->flags); @@ -2061,6 +2074,7 @@ int iso_connect_ind(struct hci_dev *hdev, bdaddr_t *bdaddr, __u8 *flags) } static void iso_connect_cfm(struct hci_conn *hcon, __u8 status) + __must_hold(&hcon->hdev->lock) { if (hcon->type != CIS_LINK && hcon->type != BIS_LINK) { if (hcon->type != LE_LINK) @@ -2071,8 +2085,10 @@ static void iso_connect_cfm(struct hci_conn *hcon, __u8 status) struct hci_link *link, *t; list_for_each_entry_safe(link, t, &hcon->link_list, - list) + list) { + lockdep_assert_held(&link->conn->hdev->lock); iso_conn_del(link->conn, bt_to_errno(status)); + } return; } @@ -2102,6 +2118,7 @@ static void iso_connect_cfm(struct hci_conn *hcon, __u8 status) } static void iso_disconn_cfm(struct hci_conn *hcon, __u8 reason) + __must_hold(&hcon->hdev->lock) { if (hcon->type != CIS_LINK && hcon->type != BIS_LINK) return; -- 2.34.1
From: Pauli Virtanen <pav@iki.fi> stable inclusion from stable-v6.18.44 commit 3b921533e8aa95b77aadcf31737595578e735f3c category: bugfix bugzilla: https://atomgit.com/src-openeuler/kernel/issues/18470 Reference: https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/commit/?id=... -------------------------------- [ Upstream commit fdfde532ab1caa165fcd8985001157ac8b4db365 ] iso_conn_del() and iso_chan_del() have a race that results to double-put of iso_conn: [Task hdev->workqueue] [Task 2] iso_conn_del iso_chan_del iso_conn_hold_unless_zero iso_conn_lock iso_conn_lock conn->sk = NULL iso_conn_unlock sk = iso_sock_hold(conn) <---------´ if (!sk) iso_conn_put iso_conn_put iso_conn_put /* UAF */ The extra put for !sk in iso_conn_del() is currently required since failing iso_chan_add() may leave iso_conn not associated with any sk. Fix by having iso_pi(sk)->conn own refcount when non-NULL, so iso_conn_del does not need to put it. Adjust the iso_conn_add() refcounting so that conn is put if it does not get associated with an sk. Fixes: dc26097bdb86 ("Bluetooth: ISO: Use kref to track lifetime of iso_conn") Signed-off-by: Pauli Virtanen <pav@iki.fi> Signed-off-by: Luiz Augusto von Dentz <luiz.von.dentz@intel.com> Signed-off-by: Sasha Levin <sashal@kernel.org> Conflicts: net/bluetooth/iso.c [Context conflicts] Signed-off-by: Yao Yiqi <yaoyiqi3@huawei.com> --- net/bluetooth/iso.c | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/net/bluetooth/iso.c b/net/bluetooth/iso.c index cbe670247ddd..5bcf7d2e1625 100644 --- a/net/bluetooth/iso.c +++ b/net/bluetooth/iso.c @@ -105,9 +105,6 @@ static void iso_conn_free(struct kref *ref) BT_DBG("conn %p", conn); - if (conn->sk) - iso_pi(conn->sk)->conn = NULL; - if (conn->hcon) { conn->hcon->iso_data = NULL; if (!test_and_set_bit(ISO_CONN_DROPPED, conn->flags)) @@ -142,6 +139,14 @@ static struct iso_conn *iso_conn_hold_unless_zero(struct iso_conn *conn) return conn; } +static struct iso_conn *iso_conn_hold(struct iso_conn *conn) +{ + BT_DBG("conn %p refcnt %u", conn, kref_read(&conn->ref)); + + kref_get(&conn->ref); + return conn; +} + static struct sock *iso_sock_hold(struct iso_conn *conn) { if (!conn || !bt_sock_linked(&iso_sk_list, conn->sk)) @@ -207,7 +212,6 @@ static struct iso_conn *iso_conn_add(struct hci_conn *hcon) conn->hcon = hcon; iso_conn_unlock(conn); } - iso_conn_put(conn); return conn; } @@ -287,10 +291,8 @@ static void iso_conn_del(struct hci_conn *hcon, int err) sk = iso_sock_hold(conn); iso_conn_unlock(conn); - if (!sk) { - iso_conn_put(conn); + if (!sk) goto done; - } iso_sock_disable_timer(sk); @@ -346,7 +348,7 @@ static int __iso_chan_add(struct iso_conn *conn, struct sock *sk, return -EIO; } - iso_pi(sk)->conn = conn; + iso_pi(sk)->conn = iso_conn_hold(conn); conn->sk = sk; clear_bit(ISO_CONN_DROPPED, conn->flags); @@ -447,6 +449,7 @@ static int iso_connect_bis(struct sock *sk) lock_sock(sk); err = iso_chan_add(conn, sk, NULL); + iso_conn_put(conn); if (err) { release_sock(sk); goto unlock; @@ -544,6 +547,7 @@ static int iso_connect_cis(struct sock *sk) lock_sock(sk); err = iso_chan_add(conn, sk, NULL); + iso_conn_put(conn); if (err) { release_sock(sk); goto unlock; @@ -2110,8 +2114,10 @@ static void iso_connect_cfm(struct hci_conn *hcon, __u8 status) struct iso_conn *conn; conn = iso_conn_add(hcon); - if (conn) + if (conn) { iso_conn_ready(conn); + iso_conn_put(conn); + } } else { iso_conn_del(hcon, bt_to_errno(status)); } -- 2.34.1
participants (1)
-
Yao Yiqi