fix(ble/bluedroid): Fix double-free, exec write, bounds and HCI param checks

- gap_ble: add length/attribute checks in gap_proc_write_req
- gatt_cl: set p_cmd->p_cmd=NULL before memset to avoid double-free;
  pending_cl_req %= GATT_CL_MAX_LCB
- gatt_sr: fix exec write zeroed_attrs and offset/len bounds, OOM cleanup
- gatt_sr_hash: null checks for p_attr->p_next, p_data+=2, len==0 in
  gatts_calculate_datebase_hash, gatts_show_local_database
- gatt_utils: explicit return NULL, indent, idx<GATT_MAX_APPS checks,
  len>GATT_MAX_ATTR_LEN, gatt_cleanup_upon_disc dealloc branch
- hciblecmds: length/handle validation in BLE ext adv/BIG sync HCI commands


(cherry picked from commit 1d31286f1a)

Co-authored-by: zhiweijian <zhiweijian@espressif.com>
This commit is contained in:
Zhi Wei Jian
2026-03-25 13:58:27 +08:00
committed by zhiweijian
parent 890cbb1d54
commit 5399361622
6 changed files with 157 additions and 49 deletions
@@ -315,6 +315,9 @@ UINT8 gap_proc_write_req( tGATTS_REQ_TYPE type, tGATT_WRITE_REQ *p_data)
switch (p_db_attr->uuid) {
#if (GATTS_DEVICE_NAME_WRITABLE == TRUE)
case GATT_UUID_GAP_DEVICE_NAME: {
if (p_data->len > BD_NAME_LEN) {
return GATT_INVALID_ATTR_LEN;
}
UINT8 *p_val = p_data->value;
p_val[p_data->len] = '\0';
BTM_SetLocalDeviceName((char *)p_val, BT_DEVICE_TYPE_BLE);
@@ -1146,9 +1146,11 @@ BOOLEAN gatt_cl_send_next_cmd_inq(tGATT_TCB *p_tcb)
}
} else {
GATT_TRACE_ERROR("gatt_cl_send_next_cmd_inq: L2CAP sent error");
/* attp_send_msg_to_l2cap() already freed p_cmd->p_cmd on failure */
p_cmd->p_cmd = NULL;
memset(p_cmd, 0, sizeof(tGATT_CMD_Q));
p_tcb->pending_cl_req ++;
p_tcb->pending_cl_req %= GATT_CL_MAX_LCB;
p_cmd = &p_tcb->cl_cmd_q[p_tcb->pending_cl_req];
}
@@ -486,9 +486,11 @@ void gatt_process_exec_write_req (tGATT_TCB *p_tcb, UINT8 op_code, UINT16 len, U
tGATT_IF gatt_if;
UINT16 conn_id;
UINT16 queue_num = 0;
BOOLEAN is_first = TRUE;
UINT16 zeroed_cap = 0, zeroed_count = 0;
void **zeroed_attrs = NULL;
BOOLEAN is_prepare_write_valid = FALSE;
BOOLEAN is_need_dequeue_sr_cmd = FALSE;
BOOLEAN sr_cmd_already_dequeued = FALSE;
tGATT_PREPARE_WRITE_RECORD *prepare_record = NULL;
tGATT_PREPARE_WRITE_QUEUE_DATA * queue_data = NULL;
@@ -529,6 +531,7 @@ void gatt_process_exec_write_req (tGATT_TCB *p_tcb, UINT8 op_code, UINT16 len, U
gatt_exec_write_rsp.op_code = GATT_RSP_EXEC_WRITE;
gatt_send_packet(p_tcb, (UINT8 *)(&gatt_exec_write_rsp), sizeof(gatt_exec_write_rsp));
gatt_dequeue_sr_cmd(p_tcb);
sr_cmd_already_dequeued = TRUE;
if (flag != GATT_PREP_WRITE_CANCEL){
is_prepare_write_valid = TRUE;
}
@@ -549,23 +552,99 @@ void gatt_process_exec_write_req (tGATT_TCB *p_tcb, UINT8 op_code, UINT16 len, U
gatt_send_error_rsp(p_tcb, prepare_record->error_code_app, GATT_REQ_EXEC_WRITE, 0, is_need_dequeue_sr_cmd);
}
//dequeue prepare write data
/* Track which attributes we've zeroed so we only zero on first write per attribute.
* Zeroing all up-front would leave attr_len=0 for attributes whose write is later
* skipped (e.g. overflow), causing data loss. */
if (is_prepare_write_valid && queue_num > 0) {
zeroed_cap = queue_num; /* max unique attributes in this exec is at most queue_num */
zeroed_attrs = (void **)osi_calloc(zeroed_cap * sizeof(void *));
if (zeroed_attrs == NULL) {
GATT_TRACE_ERROR("%s: no memory for zeroed_attrs, abort exec write", __func__);
while (fixed_queue_try_peek_first(prepare_record->queue)) {
queue_data = fixed_queue_dequeue(prepare_record->queue, FIXED_QUEUE_MAX_TIMEOUT);
osi_free(queue_data);
}
fixed_queue_free(prepare_record->queue, NULL);
prepare_record->queue = NULL;
{
UINT16 total_num_saved = prepare_record->total_num;
prepare_record->total_num = 0;
prepare_record->error_code_app = GATT_SUCCESS;
/* Only send error (and dequeue via gatt_send_error_rsp) if we did not already send success and dequeue (first branch) */
if (!sr_cmd_already_dequeued) {
gatt_send_error_rsp(p_tcb, GATT_NO_RESOURCES, GATT_REQ_EXEC_WRITE, 0, TRUE);
} else {
/* Success already sent to peer; notify apps and clear prep_cnt so state stays consistent */
if (!gatt_sr_is_prep_cnt_zero(p_tcb)) {
if (total_num_saved > queue_num) {
trans_id = gatt_sr_enqueue_cmd(p_tcb, op_code, 0);
gatt_sr_copy_prep_cnt_to_cback_cnt(p_tcb);
}
for (i = 0; i < GATT_MAX_APPS; i++) {
if (p_tcb->prep_cnt[i]) {
gatt_if = (tGATT_IF) (i + 1);
conn_id = GATT_CREATE_CONN_ID(p_tcb->tcb_idx, gatt_if);
gatt_sr_send_req_callback(conn_id,
trans_id,
GATTS_REQ_TYPE_WRITE_EXEC,
(tGATTS_DATA *)&flag);
p_tcb->prep_cnt[i] = 0;
}
}
/* OOM path: we enqueued sr_cmd but will not get app response flow; clear sr_cmd so later requests are not dropped */
if (total_num_saved > queue_num) {
gatt_dequeue_sr_cmd(p_tcb);
}
}
}
}
return;
}
}
while(fixed_queue_try_peek_first(prepare_record->queue)) {
queue_data = fixed_queue_dequeue(prepare_record->queue, FIXED_QUEUE_MAX_TIMEOUT);
if (is_prepare_write_valid){
if((queue_data->p_attr->p_value != NULL) && (queue_data->p_attr->p_value->attr_val.attr_val != NULL)){
if(is_first) {
//clear attr_val.attr_len before handle prepare write data
queue_data->p_attr->p_value->attr_val.attr_len = 0;
is_first = FALSE;
UINT16 attr_max_len = queue_data->p_attr->p_value->attr_val.attr_max_len;
if (queue_data->offset > attr_max_len ||
queue_data->len > attr_max_len - queue_data->offset) {
GATT_TRACE_ERROR("%s: exec write overflow prevented, offset=%u len=%u max=%u",
__func__, queue_data->offset, queue_data->len, attr_max_len);
} else {
UINT16 k;
BOOLEAN need_zero = TRUE;
/* Zero attr_len only on first successful write to this attribute in this exec */
if (zeroed_attrs != NULL) {
for (k = 0; k < zeroed_count; k++) {
if (zeroed_attrs[k] == (void *)queue_data->p_attr) {
need_zero = FALSE;
break;
}
}
}
if (need_zero) {
queue_data->p_attr->p_value->attr_val.attr_len = 0;
if (zeroed_attrs != NULL && zeroed_count < zeroed_cap) {
zeroed_attrs[zeroed_count++] = (void *)queue_data->p_attr;
}
}
memcpy(queue_data->p_attr->p_value->attr_val.attr_val + queue_data->offset,
queue_data->value, queue_data->len);
{
UINT16 write_end = queue_data->offset + queue_data->len;
if (write_end > queue_data->p_attr->p_value->attr_val.attr_len) {
queue_data->p_attr->p_value->attr_val.attr_len = write_end;
}
}
}
memcpy(queue_data->p_attr->p_value->attr_val.attr_val+queue_data->offset, queue_data->value, queue_data->len);
//don't forget to increase the attribute value length in the gatts database.
queue_data->p_attr->p_value->attr_val.attr_len += queue_data->len;
}
}
osi_free(queue_data);
}
if (zeroed_attrs != NULL) {
osi_free(zeroed_attrs);
}
fixed_queue_free(prepare_record->queue, NULL);
prepare_record->queue = NULL;
@@ -86,10 +86,13 @@ static size_t calculate_database_info_size(void)
len += 8 + get_uuid_stream_len(p_attr->p_value->incl_handle.service_type);
} else if (p_attr->uuid == GATT_UUID_CHAR_DECLARE) {
tBT_UUID char_uuid = {0};
// Characteristic declaration
if (p_attr->p_next == NULL) {
GATT_TRACE_ERROR("%s: malformed DB, char decl at handle %u has no value attr",
__func__, p_attr->handle);
break;
}
p_attr = (tGATT_ATTR16 *)p_attr->p_next;
attr_uuid_to_bt_uuid((void *)p_attr, &char_uuid);
// Increment 1 to fetch characteristic uuid from value declaration attribute
len += 7 + get_uuid_stream_len(char_uuid);
} else if (p_attr->uuid == GATT_UUID_CHAR_DESCRIPTION ||
p_attr->uuid == GATT_UUID_CHAR_CLIENT_CONFIG ||
@@ -136,14 +139,17 @@ static void fill_database_info(UINT8 *p_data)
gatt_build_uuid_to_stream(&p_data, p_attr->p_value->incl_handle.service_type);
} else if (p_attr->uuid == GATT_UUID_CHAR_DECLARE) {
tBT_UUID char_uuid = {0};
// Characteristic declaration
if (p_attr->p_next == NULL) {
GATT_TRACE_ERROR("%s: malformed DB, char decl at handle %u has no value attr",
__func__, p_attr->handle);
break;
}
UINT16_TO_STREAM(p_data, p_attr->handle);
UINT16_TO_STREAM(p_data, GATT_UUID_CHAR_DECLARE);
UINT8_TO_STREAM(p_data, p_attr->p_value->char_decl.property);
UINT16_TO_STREAM(p_data, p_attr->p_value->char_decl.char_val_handle);
p_attr = (tGATT_ATTR16 *)p_attr->p_next;
attr_uuid_to_bt_uuid((void *)p_attr, &char_uuid);
// Increment 1 to fetch characteristic uuid from value declaration attribute
gatt_build_uuid_to_stream(&p_data, char_uuid);
} else if (p_attr->uuid == GATT_UUID_CHAR_DESCRIPTION ||
p_attr->uuid == GATT_UUID_CHAR_CLIENT_CONFIG ||
@@ -160,6 +166,7 @@ static void fill_database_info(UINT8 *p_data)
// TODO: process extended properties descriptor
if (p_attr->p_value->attr_val.attr_len == 2) {
memcpy(p_data, p_attr->p_value->attr_val.attr_val, 2);
p_data += 2;
} else {
UINT16_TO_STREAM(p_data, 0x0000);
}
@@ -180,6 +187,11 @@ tGATT_STATUS gatts_calculate_datebase_hash(BT_OCTET16 hash)
len = calculate_database_info_size();
if (len == 0) {
memset(hash, 0, BT_OCTET16_LEN);
return GATT_SUCCESS;
}
data_buf = (UINT8 *)osi_malloc(len);
if (data_buf == NULL) {
GATT_TRACE_ERROR ("%s failed to allocate buffer (%u)\n", __func__, len);
@@ -237,6 +249,10 @@ void gatts_show_local_database(void)
tBT_UUID char_uuid = {0};
tGATT_ATTR16 *p_char_val;
p_char_val = (tGATT_ATTR16 *)p_attr->p_next;
if (p_char_val == NULL) {
printf("characteristic (malformed - no value attr)\n");
break;
}
attr_uuid_to_bt_uuid((void *)p_char_val, &char_uuid);
printf("%s\n", gatt_get_attr_name(p_attr->uuid));
@@ -797,11 +797,11 @@ tGATTS_SRV_CHG *gatt_is_bda_in_the_srv_chg_clt_list (BD_ADDR bda)
p_buf = (tGATTS_SRV_CHG *)list_node(node);
if (!memcmp( bda, p_buf->bda, BD_ADDR_LEN)) {
GATT_TRACE_DEBUG("bda is in the srv chg clt list");
break;
return p_buf;
}
}
return p_buf;
return NULL;
}
#endif // (GATTS_INCLUDED == TRUE)
@@ -1006,11 +1006,11 @@ tGATT_TCB *gatt_allocate_tcb_by_bdaddr(BD_ADDR bda, tBT_TRANSPORT transport)
allocated = TRUE;
}
if (i != GATT_INDEX_INVALID) {
p_tcb = gatt_tcb_alloc(i);
if (!p_tcb) {
return NULL;
}
if (allocated) {
p_tcb = gatt_tcb_alloc(i);
if (!p_tcb) {
return NULL;
}
memset(p_tcb, 0, sizeof(tGATT_TCB));
#if (SMP_INCLUDED == TRUE)
p_tcb->pending_enc_clcb = fixed_queue_new(QUEUE_SIZE_MAX);
@@ -1018,6 +1018,11 @@ tGATT_TCB *gatt_allocate_tcb_by_bdaddr(BD_ADDR bda, tBT_TRANSPORT transport)
p_tcb->in_use = TRUE;
p_tcb->tcb_idx = i;
p_tcb->transport = transport;
} else {
p_tcb = gatt_get_tcb_by_idx(i);
if (!p_tcb) {
return NULL;
}
}
memcpy(p_tcb->peer_bda, bda, BD_ADDR_LEN);
#if GATTS_ROBUST_CACHING_ENABLED
@@ -1778,10 +1783,10 @@ tGATT_TCB *gatt_find_tcb_by_cid (UINT16 lcid)
for(p_node = list_begin(gatt_cb.p_tcb_list); p_node; p_node = list_next(p_node)) {
p_tcb = list_node(p_node);
if (p_tcb->in_use && p_tcb->att_lcid == lcid) {
break;
return p_tcb;
}
}
return p_tcb;
return NULL;
}
/*******************************************************************************
@@ -1969,7 +1974,7 @@ void gatt_sr_update_cback_cnt(tGATT_TCB *p_tcb, tGATT_IF gatt_if, BOOLEAN is_inc
#if (GATTS_INCLUDED == TRUE)
UINT8 idx = ((UINT8) gatt_if) - 1 ;
if (p_tcb) {
if (p_tcb && idx < GATT_MAX_APPS) {
if (is_reset_first) {
gatt_sr_reset_cback_cnt(p_tcb);
}
@@ -1999,9 +2004,9 @@ void gatt_sr_update_prep_cnt(tGATT_TCB *p_tcb, tGATT_IF gatt_if, BOOLEAN is_inc,
UINT8 idx = ((UINT8) gatt_if) - 1 ;
GATT_TRACE_DEBUG("gatt_sr_update_prep_cnt tcb idx=%d gatt_if=%d is_inc=%d is_reset_first=%d",
p_tcb->tcb_idx, gatt_if, is_inc, is_reset_first);
p_tcb ? p_tcb->tcb_idx : 0, gatt_if, is_inc, is_reset_first);
if (p_tcb) {
if (p_tcb && idx < GATT_MAX_APPS) {
if (is_reset_first) {
gatt_sr_reset_prep_cnt(p_tcb);
}
@@ -2164,6 +2169,11 @@ UINT8 gatt_send_write_msg (tGATT_TCB *p_tcb, UINT16 clcb_idx, UINT8 op_code,
{
tGATT_CL_MSG msg;
if (len > GATT_MAX_ATTR_LEN) {
GATT_TRACE_ERROR("%s: len %u exceeds max %u", __func__, len, GATT_MAX_ATTR_LEN);
return GATT_ILLEGAL_PARAMETER;
}
msg.attr_value.handle = handle;
msg.attr_value.len = len;
msg.attr_value.offset = offset;
@@ -2312,9 +2322,9 @@ void gatt_cleanup_upon_disc(BD_ADDR bda, UINT16 reason, tBT_TRANSPORT transport)
GATT_TRACE_DEBUG ("found p_clcb conn_id=%d clcb_idx=%d", p_clcb->conn_id, p_clcb->clcb_idx);
if (p_clcb->operation != GATTC_OPTYPE_NONE) {
gatt_end_operation(p_clcb, GATT_ERROR, NULL);
p_clcb = NULL;
} else {
gatt_clcb_dealloc(p_clcb);
}
gatt_clcb_dealloc(p_clcb);
}
}
#if (GATTC_INCLUDED == TRUE)
@@ -1329,6 +1329,10 @@ UINT8 btsnd_hcic_ble_set_ext_adv_data(UINT8 adv_handle,
HCI_TRACE_EVENT("%s, adv_handle = %d, operation = %d, fragment_prefrence = %d,\
data_len = %d", __func__, adv_handle, operation, fragment_prefrence, data_len);
if (data_len > HCIC_PARAM_SIZE_EXT_ADV_WRITE_DATA) {
data_len = HCIC_PARAM_SIZE_EXT_ADV_WRITE_DATA;
}
HCIC_BLE_CMD_CREATED(p, pp, data_len + 4);
UINT16_TO_STREAM(pp, HCI_BLE_SET_EXT_ADV_DATA);
UINT8_TO_STREAM(pp, data_len + 4);
@@ -1336,10 +1340,6 @@ UINT8 btsnd_hcic_ble_set_ext_adv_data(UINT8 adv_handle,
UINT8_TO_STREAM(pp, operation);
UINT8_TO_STREAM(pp, fragment_prefrence);
if (data_len > HCIC_PARAM_SIZE_EXT_ADV_WRITE_DATA) {
data_len = HCIC_PARAM_SIZE_EXT_ADV_WRITE_DATA;
}
UINT8_TO_STREAM (pp, data_len);
if (p_data != NULL && data_len > 0){
@@ -1361,6 +1361,10 @@ UINT8 btsnd_hcic_ble_set_ext_adv_scan_rsp_data(UINT8 adv_handle,
HCI_TRACE_EVENT("%s, adv_handle = %d, operation = %d, fragment_prefrence = %d,\n\
data_len = %d", __func__, adv_handle, operation, fragment_prefrence, data_len);
if (data_len > HCIC_PARAM_SIZE_EXT_ADV_WRITE_DATA) {
data_len = HCIC_PARAM_SIZE_EXT_ADV_WRITE_DATA;
}
HCIC_BLE_CMD_CREATED(p, pp, data_len + 4);
UINT16_TO_STREAM(pp, HCI_BLE_SET_EXT_SCAN_RSP_DATA);
@@ -1369,12 +1373,6 @@ UINT8 btsnd_hcic_ble_set_ext_adv_scan_rsp_data(UINT8 adv_handle,
UINT8_TO_STREAM(pp, operation);
UINT8_TO_STREAM(pp, fragment_prefrence);
memset(pp, 0, data_len);
if (data_len > HCIC_PARAM_SIZE_EXT_ADV_WRITE_DATA) {
data_len = HCIC_PARAM_SIZE_EXT_ADV_WRITE_DATA;
}
UINT8_TO_STREAM (pp, data_len);
if (p_data != NULL && data_len > 0) {
ARRAY_TO_STREAM (pp, p_data, data_len);
@@ -1540,6 +1538,10 @@ UINT8 btsnd_hcic_ble_set_periodic_adv_data(UINT8 adv_handle,
HCI_TRACE_EVENT("%s, adv_handle = %d, operation = %d, len = %d",
__func__, adv_handle, operation, len);
if (len > HCIC_PARAM_SIZE_WRITE_PERIODIC_ADV_DATA) {
len = HCIC_PARAM_SIZE_WRITE_PERIODIC_ADV_DATA;
}
HCIC_BLE_CMD_CREATED(p, pp, len + 3);
UINT16_TO_STREAM(pp, HCI_BLE_SET_PERIOD_ADV_DATA);
@@ -1547,12 +1549,6 @@ UINT8 btsnd_hcic_ble_set_periodic_adv_data(UINT8 adv_handle,
UINT8_TO_STREAM(pp, adv_handle);
UINT8_TO_STREAM(pp, operation);
//memset(pp, 0, len);
if (len > HCIC_PARAM_SIZE_WRITE_PERIODIC_ADV_DATA) {
len = HCIC_PARAM_SIZE_WRITE_PERIODIC_ADV_DATA;
}
UINT8_TO_STREAM (pp, len);
if (p_data != NULL && len > 0) {
@@ -2268,17 +2264,19 @@ UINT8 btsnd_hcic_ble_big_sync_create(uint8_t big_handle, uint16_t sync_handle,
HCI_TRACE_DEBUG("big sync create: big_handle %d sync_handle %d encryption %d mse %d big_sync_timeout %d", big_handle, sync_handle, encryption, mse, big_sync_timeout);
// for (uint8_t i = 0; i < num_bis; i++)
// {
// HCI_TRACE_ERROR("i %d bis %d", bis[i]);
// }
#define HCIC_PARAM_SIZE_BIG_SYNC_CREATE_FIXED 24
if (num_bis > (HCIC_PARAM_SIZE_BIG_SYNC_CREATE_PARAMS - HCIC_PARAM_SIZE_BIG_SYNC_CREATE_FIXED)) {
HCI_TRACE_ERROR("%s: num_bis %u exceeds max", __func__, num_bis);
return FALSE;
}
HCIC_BLE_CMD_CREATED(p, pp, HCIC_PARAM_SIZE_BIG_SYNC_CREATE_PARAMS);
UINT8 param_size = HCIC_PARAM_SIZE_BIG_SYNC_CREATE_FIXED + num_bis;
HCIC_BLE_CMD_CREATED(p, pp, param_size);
pp = (UINT8 *)(p + 1);
UINT16_TO_STREAM(pp, HCI_BLE_BIG_CREATE_SYNC);
UINT8_TO_STREAM(pp, HCIC_PARAM_SIZE_BIG_SYNC_CREATE_PARAMS);
UINT8_TO_STREAM(pp, param_size);
UINT8_TO_STREAM(pp, big_handle);
UINT16_TO_STREAM(pp, sync_handle);