2023-05-02 15:07:13

by Ruihan Li

[permalink] [raw]
Subject: [PATCH v3 0/6] Bluetooth: Fix potential double free caused by hci_conn_unlink

This patch series contains six fixes related to hci_conn_unlink. The
purpose is to prevent merge conflicts between each other. I'm not
intentially linking them together. So if any patch is not suitable,
please just let me know (I'll be grateful if you can explain the
reason).

The first three patches are the most important, each fixing a
triggerable use-after-free bug (see the report URL for details). And the
fourth through sixth patches are a bit more minor, containing mostly
tweaks and refactorings.

Changes since v2:
* Put all fixes, adjustments, and refactorings about hci_conn_unlink
in one patch series.
Link to v2:
* https://lore.kernel.org/linux-bluetooth/[email protected]/
See also:
* https://lore.kernel.org/linux-bluetooth/[email protected]/

Changes since v1:
* Resolve merge conflicts.
Link to v1:
* https://lore.kernel.org/linux-bluetooth/[email protected]/

Ruihan Li (6):
Bluetooth: Fix potential double free caused by hci_conn_unlink
Bluetooth: Refcnt drop must be placed last in hci_conn_unlink
Bluetooth: Fix UAF in hci_conn_hash_flush again
Bluetooth: Perform hci_conn_drop in hci_conn_unlink
Bluetooth: Unlink CISes when LE disconnects in hci_conn_del
Bluetooth: Avoid recursion in hci_conn_unlink

include/net/bluetooth/hci_core.h | 2 +-
net/bluetooth/hci_conn.c | 96 ++++++++++++++++++--------------
2 files changed, 54 insertions(+), 44 deletions(-)

--
2.40.0


2023-05-02 15:07:20

by Ruihan Li

[permalink] [raw]
Subject: [PATCH v3 4/6] Bluetooth: Perform hci_conn_drop in hci_conn_unlink

Since hci_conn_link invokes both hci_conn_get and hci_conn_hold,
hci_conn_unlink should perform both hci_conn_put and hci_conn_drop as
well. However, currently it performs only hci_conn_put.

This patch makes hci_conn_unlink call hci_conn_drop as well, which
simplifies the logic in hci_conn_del a bit and may benefit future users
of hci_conn_unlink. But it is noted that this change additionally
implies that hci_conn_unlink can queue disc_work on conn itself, with
the following call stack:

hci_conn_unlink(conn) [conn->parent == NULL]
-> hci_conn_unlink(child) [child->parent == conn]
-> hci_conn_drop(child->parent)
-> queue_delayed_work(&conn->disc_work)

Queued disc_work after hci_conn_del can be spurious, so during the
process of hci_conn_del, it is necessary to make the call to
cancel_delayed_work(&conn->disc_work) after invoking hci_conn_unlink.

Signed-off-by: Ruihan Li <[email protected]>
---
net/bluetooth/hci_conn.c | 14 +++++++-------
1 file changed, 7 insertions(+), 7 deletions(-)

diff --git a/net/bluetooth/hci_conn.c b/net/bluetooth/hci_conn.c
index eef148291..e76ebb50d 100644
--- a/net/bluetooth/hci_conn.c
+++ b/net/bluetooth/hci_conn.c
@@ -1105,6 +1105,7 @@ static void hci_conn_unlink(struct hci_conn *conn)
list_del_rcu(&conn->link->list);
synchronize_rcu();

+ hci_conn_drop(conn->parent);
hci_conn_put(conn->parent);
conn->parent = NULL;

@@ -1118,7 +1119,6 @@ void hci_conn_del(struct hci_conn *conn)

BT_DBG("%s hcon %p handle %d", hdev->name, conn, conn->handle);

- cancel_delayed_work_sync(&conn->disc_work);
cancel_delayed_work_sync(&conn->auto_accept_work);
cancel_delayed_work_sync(&conn->idle_work);

@@ -1134,12 +1134,7 @@ void hci_conn_del(struct hci_conn *conn)
else
hdev->acl_cnt += conn->sent;
} else {
- struct hci_conn *acl = conn->parent;
-
- if (acl) {
- hci_conn_unlink(conn);
- hci_conn_drop(acl);
- }
+ hci_conn_unlink(conn);

/* Unacked ISO frames */
if (conn->type == ISO_LINK) {
@@ -1152,6 +1147,11 @@ void hci_conn_del(struct hci_conn *conn)
}
}

+ /* hci_conn_unlink may trigger additional disc_work, so
+ * ensure to perform cancelling after that.
+ */
+ cancel_delayed_work_sync(&conn->disc_work);
+
if (conn->amp_mgr)
amp_mgr_put(conn->amp_mgr);

--
2.40.0

2023-05-02 15:08:36

by Ruihan Li

[permalink] [raw]
Subject: [PATCH v3 3/6] Bluetooth: Fix UAF in hci_conn_hash_flush again

Commit 06149746e720 ("Bluetooth: hci_conn: Add support for linking
multiple hcon") reintroduced a previously fixed bug [1] ("KASAN:
slab-use-after-free Read in hci_conn_hash_flush"). This bug was
originally fixed by commit 5dc7d23e167e ("Bluetooth: hci_conn: Fix
possible UAF").

The hci_conn_unlink function was added to avoid invalidating the link
traversal caused by successive hci_conn_del operations releasing extra
connections. However, currently hci_conn_unlink itself also releases
extra connections, resulted in the reintroduced bug.

This patch follows a more robust solution for cleaning up all
connections, by repeatedly removing the first connection until there are
none left. This approach does not rely on the inner workings of
hci_conn_del and ensures proper cleanup of all connections.

Meanwhile, we need to make sure that hci_conn_del never fails. Indeed it
doesn't, as it now always returns zero. To make this a bit clearer, this
patch also changes its return type to void.

Reported-by: [email protected]
Closes: https://lore.kernel.org/linux-bluetooth/[email protected]/
Fixes: 06149746e720 ("Bluetooth: hci_conn: Add support for linking multiple hcon")
Signed-off-by: Ruihan Li <[email protected]>
---
include/net/bluetooth/hci_core.h | 2 +-
net/bluetooth/hci_conn.c | 29 +++++++++++++----------------
2 files changed, 14 insertions(+), 17 deletions(-)

diff --git a/include/net/bluetooth/hci_core.h b/include/net/bluetooth/hci_core.h
index a6c8aee2f..8baf34639 100644
--- a/include/net/bluetooth/hci_core.h
+++ b/include/net/bluetooth/hci_core.h
@@ -1327,7 +1327,7 @@ int hci_le_create_cis(struct hci_conn *conn);

struct hci_conn *hci_conn_add(struct hci_dev *hdev, int type, bdaddr_t *dst,
u8 role);
-int hci_conn_del(struct hci_conn *conn);
+void hci_conn_del(struct hci_conn *conn);
void hci_conn_hash_flush(struct hci_dev *hdev);
void hci_conn_check_pending(struct hci_dev *hdev);

diff --git a/net/bluetooth/hci_conn.c b/net/bluetooth/hci_conn.c
index 44d0643fc..eef148291 100644
--- a/net/bluetooth/hci_conn.c
+++ b/net/bluetooth/hci_conn.c
@@ -1112,7 +1112,7 @@ static void hci_conn_unlink(struct hci_conn *conn)
conn->link = NULL;
}

-int hci_conn_del(struct hci_conn *conn)
+void hci_conn_del(struct hci_conn *conn)
{
struct hci_dev *hdev = conn->hdev;

@@ -1163,8 +1163,6 @@ int hci_conn_del(struct hci_conn *conn)
* rest of hci_conn_del.
*/
hci_conn_cleanup(conn);
-
- return 0;
}

struct hci_dev *hci_get_route(bdaddr_t *dst, bdaddr_t *src, uint8_t src_type)
@@ -2465,22 +2463,21 @@ void hci_conn_enter_active_mode(struct hci_conn *conn, __u8 force_active)
/* Drop all connection on the device */
void hci_conn_hash_flush(struct hci_dev *hdev)
{
- struct hci_conn_hash *h = &hdev->conn_hash;
- struct hci_conn *c, *n;
+ struct list_head *head = &hdev->conn_hash.list;
+ struct hci_conn *conn;

BT_DBG("hdev %s", hdev->name);

- list_for_each_entry_safe(c, n, &h->list, list) {
- c->state = BT_CLOSED;
-
- hci_disconn_cfm(c, HCI_ERROR_LOCAL_HOST_TERM);
-
- /* Unlink before deleting otherwise it is possible that
- * hci_conn_del removes the link which may cause the list to
- * contain items already freed.
- */
- hci_conn_unlink(c);
- hci_conn_del(c);
+ /* We should not traverse the list here, because hci_conn_del
+ * can remove extra links, which may cause the list traversal
+ * to hit items that have already been released.
+ */
+ while ((conn = list_first_entry_or_null(head,
+ struct hci_conn,
+ list)) != NULL) {
+ conn->state = BT_CLOSED;
+ hci_disconn_cfm(conn, HCI_ERROR_LOCAL_HOST_TERM);
+ hci_conn_del(conn);
}
}

--
2.40.0

2023-05-02 16:36:28

by Luiz Augusto von Dentz

[permalink] [raw]
Subject: Re: [PATCH v3 4/6] Bluetooth: Perform hci_conn_drop in hci_conn_unlink

Hi Ruihan,

On Tue, May 2, 2023 at 7:57 AM Ruihan Li <[email protected]> wrote:
>
> Since hci_conn_link invokes both hci_conn_get and hci_conn_hold,
> hci_conn_unlink should perform both hci_conn_put and hci_conn_drop as
> well. However, currently it performs only hci_conn_put.
>
> This patch makes hci_conn_unlink call hci_conn_drop as well, which
> simplifies the logic in hci_conn_del a bit and may benefit future users
> of hci_conn_unlink. But it is noted that this change additionally
> implies that hci_conn_unlink can queue disc_work on conn itself, with
> the following call stack:
>
> hci_conn_unlink(conn) [conn->parent == NULL]
> -> hci_conn_unlink(child) [child->parent == conn]
> -> hci_conn_drop(child->parent)
> -> queue_delayed_work(&conn->disc_work)
>
> Queued disc_work after hci_conn_del can be spurious, so during the
> process of hci_conn_del, it is necessary to make the call to
> cancel_delayed_work(&conn->disc_work) after invoking hci_conn_unlink.
>
> Signed-off-by: Ruihan Li <[email protected]>
> ---
> net/bluetooth/hci_conn.c | 14 +++++++-------
> 1 file changed, 7 insertions(+), 7 deletions(-)
>
> diff --git a/net/bluetooth/hci_conn.c b/net/bluetooth/hci_conn.c
> index eef148291..e76ebb50d 100644
> --- a/net/bluetooth/hci_conn.c
> +++ b/net/bluetooth/hci_conn.c
> @@ -1105,6 +1105,7 @@ static void hci_conn_unlink(struct hci_conn *conn)
> list_del_rcu(&conn->link->list);
> synchronize_rcu();
>
> + hci_conn_drop(conn->parent);
> hci_conn_put(conn->parent);
> conn->parent = NULL;
>
> @@ -1118,7 +1119,6 @@ void hci_conn_del(struct hci_conn *conn)
>
> BT_DBG("%s hcon %p handle %d", hdev->name, conn, conn->handle);
>
> - cancel_delayed_work_sync(&conn->disc_work);
> cancel_delayed_work_sync(&conn->auto_accept_work);
> cancel_delayed_work_sync(&conn->idle_work);
>
> @@ -1134,12 +1134,7 @@ void hci_conn_del(struct hci_conn *conn)
> else
> hdev->acl_cnt += conn->sent;
> } else {
> - struct hci_conn *acl = conn->parent;
> -
> - if (acl) {
> - hci_conn_unlink(conn);
> - hci_conn_drop(acl);
> - }
> + hci_conn_unlink(conn);
>
> /* Unacked ISO frames */
> if (conn->type == ISO_LINK) {
> @@ -1152,6 +1147,11 @@ void hci_conn_del(struct hci_conn *conn)
> }
> }
>
> + /* hci_conn_unlink may trigger additional disc_work, so
> + * ensure to perform cancelling after that.
> + */
> + cancel_delayed_work_sync(&conn->disc_work);

Just merge the change where hci_conn_del calls hci_conn_unlink
unconditionally so we don't have to do this change independently just
to revert later.

> if (conn->amp_mgr)
> amp_mgr_put(conn->amp_mgr);
>
> --
> 2.40.0
>


--
Luiz Augusto von Dentz