fix(ble/bluedroid): harden LE bond handling across encryption

Keep the existing bond until the new pairing is encrypted, and on encryption
failure drop the link instead of clearing keys. Recovering from a peer that
really deleted the bond is opt-in through BT_BLE_SMP_UNBOND_ON_KEY_MISSING.

Closes BLERP (NDSS 2026) V5, and stops an unauthenticated Pairing Request
from dropping the stored keys (V2 exploitation).
This commit is contained in:
zhiweijian
2026-08-21 19:17:48 +08:00
committed by BOT
parent 9aee704812
commit f864615d7d
5 changed files with 89 additions and 16 deletions
@@ -4251,12 +4251,36 @@ void btm_sec_encrypt_change (UINT16 handle, UINT8 status, UINT8 encr_enable)
}
if (p_acl && p_acl->transport == BT_TRANSPORT_LE) {
if (status == HCI_ERR_KEY_MISSING || status == HCI_ERR_AUTH_FAILURE ||
status == HCI_ERR_ENCRY_MODE_NOT_ACCEPTABLE) {
p_dev_rec->sec_flags &= ~ (BTM_SEC_LE_LINK_KEY_KNOWN);
BOOLEAN disconnect = (status != HCI_SUCCESS);
BD_ADDR pseudo_addr;
/* btm_ble_link_encrypted() runs the SMP state machine and the upper layer
callbacks, which may retire p_dev_rec, so keep our own copy of the address. */
memcpy(pseudo_addr, p_dev_rec->ble.pseudo_addr, BD_ADDR_LEN);
#if (BLE_SMP_UNBOND_ON_KEY_MISSING == TRUE)
/* The peer answered our LL_ENC_REQ saying it no longer holds the LTK. Forget our
copy and keep the link up so the application can pair again right away. This is
unauthenticated, an impersonator can report the same error to strip the stored
security level, which is why the option defaults to disabled. */
if (disconnect && status == HCI_ERR_KEY_MISSING && p_acl->link_role == BTM_ROLE_MASTER) {
BTM_TRACE_WARNING("%s peer reports no LTK, dropping the local LE keys\n", __func__);
p_dev_rec->sec_flags &= ~(BTM_SEC_LE_LINK_KEY_KNOWN);
p_dev_rec->ble.key_type = BTM_LE_KEY_NONE;
disconnect = FALSE;
}
#endif ///BLE_SMP_UNBOND_ON_KEY_MISSING == TRUE
btm_ble_link_encrypted(pseudo_addr, encr_enable);
/* A peer that refuses to encrypt must not be able to drop our bond, otherwise it
can strip the stored security level and then re-pair at a weaker one. Tear the
link down instead and keep the keys until the application unbonds. */
if (disconnect && BTM_IsAclConnectionUp(pseudo_addr, BT_TRANSPORT_LE)) {
BTM_TRACE_WARNING("%s LE encryption failed (status 0x%02x), disconnecting\n",
__func__, status);
btm_remove_acl(pseudo_addr, BT_TRANSPORT_LE);
}
btm_ble_link_encrypted(p_dev_rec->ble.pseudo_addr, encr_enable);
return;
} else {
/* BR/EDR connection, update the encryption key size to be 16 as always */
@@ -260,13 +260,9 @@ void smp_send_pair_req(tSMP_CB *p_cb, tSMP_INT_DATA *p_data)
{
SMP_TRACE_DEBUG("%s", __func__);
#if (BLE_INCLUDED == TRUE)
tBTM_SEC_DEV_REC *p_dev_rec = btm_find_dev (p_cb->pairing_bda);
/* erase all keys when master sends pairing req*/
if (p_dev_rec) {
btm_sec_clear_ble_keys(p_dev_rec);
}
#endif ///BLE_INCLUDED == TRUE
/* Any existing bond is replaced only once the new pairing is authenticated,
see smp_check_auth_req(). */
/* do not manipulate the key, let app decide,
leave out to BTM to mandate key distribution for bonding case */
smp_send_cmd(SMP_OPCODE_PAIRING_REQ, p_cb);
@@ -574,13 +570,11 @@ void smp_proc_pair_cmd(tSMP_CB *p_cb, tSMP_INT_DATA *p_data)
{
UINT8 *p = (UINT8 *)p_data;
UINT8 reason = SMP_ENC_KEY_SIZE;
tBTM_SEC_DEV_REC *p_dev_rec = btm_find_dev (p_cb->pairing_bda);
SMP_TRACE_DEBUG("%s", __func__);
/* erase all keys if it is slave proc pairing req*/
if (p_dev_rec && (p_cb->role == HCI_ROLE_SLAVE)) {
btm_sec_clear_ble_keys(p_dev_rec);
}
/* Any existing bond is replaced only once the new pairing is authenticated, see
smp_check_auth_req(). An unauthenticated Pairing Request must not drop the bond. */
p_cb->flags |= SMP_PAIR_FLAG_ENC_AFTER_PAIR;
@@ -596,6 +590,7 @@ void smp_proc_pair_cmd(tSMP_CB *p_cb, tSMP_INT_DATA *p_data)
smp_sm_event(p_cb, SMP_AUTH_CMPL_EVT, &reason);
return;
}
p_cb->accept_specified_sec_auth = bta_dm_co_ble_get_accept_auth_enable();
p_cb->origin_loc_auth_req = bta_dm_co_ble_get_auth_req();
if (p_cb->role == HCI_ROLE_SLAVE) {
@@ -1364,6 +1359,22 @@ void smp_check_auth_req(tSMP_CB *p_cb, tSMP_INT_DATA *p_data)
"(i-initiator r-responder)\n",
__func__, enc_enable, p_cb->local_i_key, p_cb->local_r_key);
if (enc_enable == 1) {
#if (BLE_INCLUDED == TRUE)
/* The new pairing has been authenticated and the link is now encrypted with the
freshly generated key, so the previous bond can be dropped. Doing it here rather
than when the Pairing Request is exchanged keeps the old key in place for a
pairing that never completes. */
tBTM_SEC_DEV_REC *p_dev_rec = btm_find_dev(p_cb->pairing_bda);
if (p_dev_rec) {
if (p_dev_rec->ble.key_type & (BTM_LE_KEY_PENC | BTM_LE_KEY_LENC)) {
SMP_TRACE_DEBUG("LE replace bond after enc, BDA:0x%02X%02X%02X%02X%02X%02X",
p_cb->pairing_bda[0], p_cb->pairing_bda[1], p_cb->pairing_bda[2],
p_cb->pairing_bda[3], p_cb->pairing_bda[4], p_cb->pairing_bda[5]);
}
btm_sec_clear_ble_keys(p_dev_rec);
}
#endif ///BLE_INCLUDED == TRUE
if (p_cb->le_secure_connections_mode_is_used) {
/* In LE SC mode LTK is used instead of STK and has to be always saved */
p_cb->local_i_key |= SMP_SEC_KEY_TYPE_ENC;