Return-Path: <[email protected]>
Received: by smtp.gmail.com with ESMTPSA id g19-20020a17090613d300b00931db712768sm13742376ejc.4.2023.03.27.02.37.35
(version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256);
Mon, 27 Mar 2023 02:37:36 -0700 (PDT)
From: Arkadiusz Kozdra <[email protected]>
To: [email protected]
Cc: Tomasz Gorochowik <[email protected]>, "Karol Gugała" <[email protected]>, Arkadiusz Kozdra <[email protected]>
Subject: [PATCH] bluetooth: host: add checks for connection types
Date: Mon, 27 Mar 2023 11:21:47 +0200
Message-Id: <[email protected]>
X-Mailer: git-send-email 2.30.2
MIME-Version: 1.0
Content-Transfer-Encoding: 8bit
If an HCI event of one type arrives for a handle of a different
connection type, a union variant confusion is avoided by checking for
the type of the returned connection.
The worst example of this is HCI_Link_Key_Request/HCI_Link_Key_Notify
interleaved with HCI_LE_Connection_Update_Complete, which on 32-bit
builds results in an arbitrary read/write vulnerability that can be
triggered by any rogue controller.
Signed-off-by: Arkadiusz Kozdra <[email protected]>
---
I hope this is the right channel for such reports, since I also found
some past vulnerability listings on both Jira[1] and GitHub Security
Advisories[2], but only this mailing list is recommended in the
documentation. I will proceed with GHSA in case of the list looking
unreachable.
[1]: https://zephyrprojectsec.atlassian.net/browse/ZEPSEC
[2]: https://github.com/zephyrproject-rtos/zephyr/security/advisories
I made a sample exploit for the posix_native target, which should work
for nearly any app with CONFIG_BT_BREDR and CONFIG_BT_CONN enabled.
Since running the exploit is non-trivial I did not include its full code
in this e-mail. Please let me know if you are interested, I can share
it using a channel of your preference. The diffstat and patch follows.
subsys/bluetooth/host/adv.c | 2 +-
subsys/bluetooth/host/br.c | 4 +--
subsys/bluetooth/host/conn.c | 42 +++++++++++++++++++++++++++
subsys/bluetooth/host/conn_internal.h | 11 ++++++-
subsys/bluetooth/host/hci_core.c | 12 ++++----
subsys/bluetooth/host/iso.c | 4 +--
6 files changed, 63 insertions(+), 12 deletions(-)
diff --git a/subsys/bluetooth/host/adv.c b/subsys/bluetooth/host/adv.c
index 5f97831c3c..0a9e7cdff9 100644
--- a/subsys/bluetooth/host/adv.c
+++ b/subsys/bluetooth/host/adv.c
@@ -2024,7 +2024,7 @@ void bt_hci_le_adv_set_terminated(struct net_buf *buf)
}
if (IS_ENABLED(CONFIG_BT_CONN) && !evt->status) {
- struct bt_conn *conn = bt_conn_lookup_handle(conn_handle);
+ struct bt_conn *conn = bt_conn_lookup_handle_le(conn_handle);
if (conn) {
if (IS_ENABLED(CONFIG_BT_PRIVACY) &&
diff --git a/subsys/bluetooth/host/br.c b/subsys/bluetooth/host/br.c
index 390fef2049..b715c52b8d 100644
--- a/subsys/bluetooth/host/br.c
+++ b/subsys/bluetooth/host/br.c
@@ -623,7 +623,7 @@ void bt_hci_read_remote_features_complete(struct net_buf *buf)
LOG_DBG("status 0x%02x handle %u", evt->status, handle);
- conn = bt_conn_lookup_handle(handle);
+ conn = bt_conn_lookup_handle_br(handle);
if (!conn) {
LOG_ERR("Can't find conn for handle %u", handle);
return;
@@ -664,7 +664,7 @@ void bt_hci_read_remote_ext_features_complete(struct net_buf *buf)
LOG_DBG("status 0x%02x handle %u", evt->status, handle);
- conn = bt_conn_lookup_handle(handle);
+ conn = bt_conn_lookup_handle_br(handle);
if (!conn) {
LOG_ERR("Can't find conn for handle %u", handle);
return;
diff --git a/subsys/bluetooth/host/conn.c b/subsys/bluetooth/host/conn.c
index a502424f07..350aff6141 100644
--- a/subsys/bluetooth/host/conn.c
+++ b/subsys/bluetooth/host/conn.c
@@ -1149,6 +1149,48 @@ void bt_conn_set_state(struct bt_conn *conn, bt_conn_state_t state)
}
}
+#if defined(CONFIG_BT_CONN)
+struct bt_conn *bt_conn_lookup_handle_le(uint16_t handle)
+{
+ struct bt_conn *conn;
+
+ conn = conn_lookup_handle(acl_conns, ARRAY_SIZE(acl_conns), handle);
+ if (conn && conn->type == BT_CONN_TYPE_LE) {
+ return conn;
+ }
+
+ return NULL;
+}
+
+#if defined(CONFIG_BT_BREDR)
+struct bt_conn *bt_conn_lookup_handle_br(uint16_t handle)
+{
+ struct bt_conn *conn;
+
+ conn = conn_lookup_handle(acl_conns, ARRAY_SIZE(acl_conns), handle);
+ if (conn && conn->type == BT_CONN_TYPE_BR) {
+ return conn;
+ }
+
+ return NULL;
+}
+#endif /* CONFIG_BT_BREDR */
+#endif /* CONFIG_BT_CONN */
+
+#if defined(CONFIG_BT_ISO)
+struct bt_conn *bt_conn_lookup_handle_iso(uint16_t handle)
+{
+ struct bt_conn *conn;
+
+ conn = conn_lookup_handle(iso_conns, ARRAY_SIZE(iso_conns), handle);
+ if (conn) {
+ return conn;
+ }
+
+ return NULL;
+}
+#endif
+
struct bt_conn *bt_conn_lookup_handle(uint16_t handle)
{
struct bt_conn *conn;
diff --git a/subsys/bluetooth/host/conn_internal.h b/subsys/bluetooth/host/conn_internal.h
index 58ce4776c6..5c330dd01b 100644
--- a/subsys/bluetooth/host/conn_internal.h
+++ b/subsys/bluetooth/host/conn_internal.h
@@ -298,7 +298,16 @@ void bt_conn_disconnect_all(uint8_t id);
/* Allocate new connection object */
struct bt_conn *bt_conn_new(struct bt_conn *conns, size_t size);
-/* Look up an existing connection */
+/* Look up an existing LE connection */
+struct bt_conn *bt_conn_lookup_handle_le(uint16_t handle);
+
+/* Look up an existing BR connection */
+struct bt_conn *bt_conn_lookup_handle_br(uint16_t handle);
+
+/* Look up an existing ISO connection */
+struct bt_conn *bt_conn_lookup_handle_iso(uint16_t handle);
+
+/* Look up an existing connection of any type */
struct bt_conn *bt_conn_lookup_handle(uint16_t handle);
static inline bool bt_conn_is_handle_valid(struct bt_conn *conn)
diff --git a/subsys/bluetooth/host/hci_core.c b/subsys/bluetooth/host/hci_core.c
index 4adcb852fd..95209c9584 100644
--- a/subsys/bluetooth/host/hci_core.c
+++ b/subsys/bluetooth/host/hci_core.c
@@ -1421,7 +1421,7 @@ static void le_remote_feat_complete(struct net_buf *buf)
uint16_t handle = sys_le16_to_cpu(evt->handle);
struct bt_conn *conn;
- conn = bt_conn_lookup_handle(handle);
+ conn = bt_conn_lookup_handle_le(handle);
if (!conn) {
LOG_ERR("Unable to lookup conn for handle %u", handle);
return;
@@ -1449,7 +1449,7 @@ static void le_data_len_change(struct net_buf *buf)
uint16_t handle = sys_le16_to_cpu(evt->handle);
struct bt_conn *conn;
- conn = bt_conn_lookup_handle(handle);
+ conn = bt_conn_lookup_handle_le(handle);
if (!conn) {
LOG_ERR("Unable to lookup conn for handle %u", handle);
return;
@@ -1482,7 +1482,7 @@ static void le_phy_update_complete(struct net_buf *buf)
uint16_t handle = sys_le16_to_cpu(evt->handle);
struct bt_conn *conn;
- conn = bt_conn_lookup_handle(handle);
+ conn = bt_conn_lookup_handle_le(handle);
if (!conn) {
LOG_ERR("Unable to lookup conn for handle %u", handle);
return;
@@ -1578,7 +1578,7 @@ static void le_conn_param_req(struct net_buf *buf)
param.latency = sys_le16_to_cpu(evt->latency);
param.timeout = sys_le16_to_cpu(evt->timeout);
- conn = bt_conn_lookup_handle(handle);
+ conn = bt_conn_lookup_handle_le(handle);
if (!conn) {
LOG_ERR("Unable to lookup conn for handle %u", handle);
le_conn_param_neg_reply(handle, BT_HCI_ERR_UNKNOWN_CONN_ID);
@@ -1604,7 +1604,7 @@ static void le_conn_update_complete(struct net_buf *buf)
LOG_DBG("status 0x%02x, handle %u", evt->status, handle);
- conn = bt_conn_lookup_handle(handle);
+ conn = bt_conn_lookup_handle_le(handle);
if (!conn) {
LOG_ERR("Unable to lookup conn for handle %u", handle);
return;
@@ -2028,7 +2028,7 @@ static void le_ltk_request(struct net_buf *buf)
LOG_DBG("handle %u", handle);
- conn = bt_conn_lookup_handle(handle);
+ conn = bt_conn_lookup_handle_le(handle);
if (!conn) {
LOG_ERR("Unable to lookup conn for handle %u", handle);
return;
diff --git a/subsys/bluetooth/host/iso.c b/subsys/bluetooth/host/iso.c
index 183f7b3a20..b987048163 100644
--- a/subsys/bluetooth/host/iso.c
+++ b/subsys/bluetooth/host/iso.c
@@ -122,7 +122,7 @@ void hci_iso(struct net_buf *buf)
return;
}
- iso = bt_conn_lookup_handle(iso(buf)->handle);
+ iso = bt_conn_lookup_handle_iso(iso(buf)->handle);
if (iso == NULL) {
LOG_ERR("Unable to find conn for handle %u", iso(buf)->handle);
net_buf_unref(buf);
@@ -951,7 +951,7 @@ void hci_le_cis_established(struct net_buf *buf)
LOG_DBG("status %u handle %u", evt->status, handle);
/* ISO connection handles are already assigned at this point */
- iso = bt_conn_lookup_handle(handle);
+ iso = bt_conn_lookup_handle_iso(handle);
if (!iso) {
LOG_ERR("No connection found for handle %u", handle);
return;
--
2.30.2
Summary
Union variant confusion allows any malicious BT controller to execute arbitrary code on the Zephyr host.
Details
If an HCI event of one type arrives for a handle of a different connection type, a union variant confusion happens because of no checks for the type of the indicated connection.
The worst example of this is HCI_Link_Key_Request/HCI_Link_Key_Notify interleaved with HCI_LE_Connection_Update_Complete, which on 32-bit builds results in an arbitrary read/write vulnerability that can be triggered by any rogue controller.
PoC
CONFIG_BT_CONN=1
CONFIG_BT_BREDR=1
Full exploit (32-bit posix_native assumed)
Impact
The only affected are Zephyr BT hosts allowing plugging in untrusted BT controllers.
History
I emailed a ready patch to [email protected] first, but I have received no ACK whatsoever in two weeks' time (maybe it got marked as spam... ugh, hopelessly broken SMTP). Hope the issue gets better recognition here. A copy of the e-mail is provided below for reference:
For more information
If you have any questions or comments about this advisory:
embargo: 2023-07-20