fix(nimble): Fix for BLERP attack

This commit is contained in:
Rahul Tank
2026-09-07 12:29:08 +05:30
parent 233e065142
commit 1cfacb92c4
4 changed files with 267 additions and 31 deletions
+18 -3
View File
@@ -10840,8 +10840,8 @@ ble_gap_reset_irk(void)
}
#endif
int
ble_gap_unpair(const ble_addr_t *peer_addr)
static int
ble_gap_unpair_ex(const ble_addr_t *peer_addr, bool terminate)
{
#if NIMBLE_BLE_SM
@@ -10882,7 +10882,9 @@ ble_gap_unpair(const ble_addr_t *peer_addr)
defer_add_pending = 1;
}
#endif
ble_gap_terminate_with_conn(conn, BLE_ERR_REM_USER_CONN_TERM);
if (terminate) {
ble_gap_terminate_with_conn(conn, BLE_ERR_REM_USER_CONN_TERM);
}
}
ble_hs_unlock();
@@ -10977,10 +10979,23 @@ ble_gap_unpair(const ble_addr_t *peer_addr)
return 0;
#else
(void)terminate;
return BLE_HS_ENOTSUP;
#endif
}
int
ble_gap_unpair(const ble_addr_t *peer_addr)
{
return ble_gap_unpair_ex(peer_addr, true);
}
int
ble_gap_unpair_keep_conn(const ble_addr_t *peer_addr)
{
return ble_gap_unpair_ex(peer_addr, false);
}
int
ble_gap_unpair_oldest_peer(void)
{
+1
View File
@@ -194,6 +194,7 @@ void ble_gap_preempt(void);
void ble_gap_preempt_done(void);
int ble_gap_terminate_with_conn(struct ble_hs_conn *conn, uint8_t hci_reason);
int ble_gap_unpair_keep_conn(const ble_addr_t *peer_addr);
void ble_gap_reset_state(int reason);
void ble_gap_conn_broken(uint16_t conn_handle, int reason);
int32_t ble_gap_timer(void);
+243 -28
View File
@@ -46,6 +46,7 @@
#include "nimble/ble.h"
#include "nimble/nimble_opt.h"
#include "host/ble_sm.h"
#include "host/ble_store.h"
#include "ble_hs_priv.h"
#include "ble_hs_resolv_priv.h"
#include "../store/config/src/ble_store_config_priv.h"
@@ -994,13 +995,20 @@ ble_sm_read_bond(uint16_t conn_handle, struct ble_store_value_sec *out_bond)
static int
ble_sm_chk_repeat_pairing(uint16_t conn_handle,
ble_sm_proc_flags proc_flags,
uint8_t key_size)
uint8_t key_size, uint8_t *out_sm_err,
bool *out_terminate, bool *out_keep_bond)
{
struct ble_gap_repeat_pairing rp;
struct ble_store_value_sec bond;
struct ble_hs_conn *conn;
bool link_encrypted;
int rc;
int delete_retries;
*out_sm_err = 0;
*out_terminate = false;
*out_keep_bond = false;
delete_retries = 0;
do {
#if MYNEWT_VAL(BLE_DEFER_CONN_EVENTS)
@@ -1033,6 +1041,21 @@ ble_sm_chk_repeat_pairing(uint16_t conn_handle,
return rc;
}
ble_hs_lock();
conn = ble_hs_conn_find(conn_handle);
link_encrypted = conn != NULL && conn->bhc_sec_state.encrypted;
ble_hs_unlock();
#if MYNEWT_VAL(BLE_SMP_REQUIRE_ENC_BEFORE_REPAIR)
if (!link_encrypted) {
*out_sm_err = BLE_SM_ERR_AUTHREQ;
*out_terminate = true;
*out_keep_bond = true;
return BLE_HS_EALREADY;
}
#else
(void)link_encrypted;
#endif
#if MYNEWT_VAL(BLE_DEFER_CONN_EVENTS)
/*
* Repeat pairing needs a synchronous app decision and cannot be
@@ -1063,6 +1086,22 @@ ble_sm_chk_repeat_pairing(uint16_t conn_handle,
rp.new_sc = !!(proc_flags & BLE_SM_PROC_F_SC);
rp.new_bonding = !!(proc_flags & BLE_SM_PROC_F_BONDING);
/*
* A repeat pairing must not reduce the security of an existing bond.
* Applications may still explicitly delete the bond and retry when a
* replacement with equal or stronger security is intentional.
*/
if (MYNEWT_VAL(BLE_SMP_HARDENED_REPAIRING) &&
((bond.sc && !rp.new_sc) ||
(bond.authenticated && !rp.new_authenticated) ||
(key_size < bond.key_size))) {
*out_sm_err = key_size < bond.key_size ? BLE_SM_ERR_ENC_KEY_SZ
: BLE_SM_ERR_AUTHREQ;
*out_terminate = true;
*out_keep_bond = true;
return BLE_HS_EALREADY;
}
rc = ble_gap_repeat_pairing_event(&rp);
if (rc == BLE_GAP_REPEAT_PAIRING_RETRY) {
delete_retries++;
@@ -1084,6 +1123,57 @@ ble_sm_chk_repeat_pairing(uint16_t conn_handle,
return BLE_HS_EALREADY;
}
static void
ble_sm_maybe_remove_bond_on_fail(uint16_t conn_handle,
const struct ble_sm_result *res)
{
struct ble_hs_conn_addrs addrs;
struct ble_hs_conn *conn;
ble_addr_t peer;
int is_master;
if (res == NULL ||
res->keep_bond ||
!res->enc_cb ||
res->app_status == 0 ||
res->app_status == BLE_HS_ENOTCONN) {
/* Link drop is not a pairing failure. ble_sm_connection_broken()
* still sets enc_cb with ENOTCONN while the conn object exists;
* unpairing here would wipe IRK after a successful bond.
*/
return;
}
#if !MYNEWT_VAL(BLE_SMP_REMOVE_BOND_ON_PAIR_FAIL_AS_CENTRAL) && \
!MYNEWT_VAL(BLE_SMP_REMOVE_BOND_ON_PAIR_FAIL_AS_PERIPHERAL)
return;
#else
ble_hs_lock();
conn = ble_hs_conn_find(conn_handle);
if (conn == NULL) {
ble_hs_unlock();
return;
}
is_master = !!(conn->bhc_flags & BLE_HS_CONN_F_MASTER);
ble_hs_conn_addrs(conn, &addrs);
peer = addrs.peer_id_addr;
ble_hs_unlock();
if (is_master) {
#if !MYNEWT_VAL(BLE_SMP_REMOVE_BOND_ON_PAIR_FAIL_AS_CENTRAL)
return;
#endif
} else {
#if !MYNEWT_VAL(BLE_SMP_REMOVE_BOND_ON_PAIR_FAIL_AS_PERIPHERAL)
return;
#endif
}
ble_gap_unpair_keep_conn(&peer);
#endif
}
void
ble_sm_process_result(uint16_t conn_handle, struct ble_sm_result *res,
bool tx_fail)
@@ -1139,12 +1229,21 @@ ble_sm_process_result(uint16_t conn_handle, struct ble_sm_result *res,
ble_hs_unlock();
if (res && res->terminate) {
ble_gap_terminate(conn_handle, BLE_ERR_AUTH_FAIL);
}
/* Erase bond before BLE_RESTART_PAIR so repeat-pairing sees no bond. */
ble_sm_maybe_remove_bond_on_fail(conn_handle, res);
if (proc == NULL) {
break;
}
#if MYNEWT_VAL(BLE_RESTART_PAIR)
if (res->app_status == BLE_HS_HCI_ERR(BLE_ERR_PINKEY_MISSING)) {
if (!res->terminate &&
!res->restore &&
res->app_status == BLE_HS_HCI_ERR(BLE_ERR_PINKEY_MISSING)) {
ble_hs_lock();
conn = ble_hs_conn_find(conn_handle);
if (conn == NULL) {
@@ -1349,6 +1448,7 @@ ble_sm_enc_event_rx(uint16_t conn_handle, uint8_t evt_status, int encrypted)
{
struct ble_sm_result res;
struct ble_sm_proc *proc;
bool terminate_conn;
int authenticated;
int bonded;
int key_size;
@@ -1359,6 +1459,7 @@ ble_sm_enc_event_rx(uint16_t conn_handle, uint8_t evt_status, int encrypted)
authenticated = 0;
bonded = 0;
key_size = 0;
terminate_conn = false;
ble_hs_lock();
@@ -1402,6 +1503,21 @@ ble_sm_enc_event_rx(uint16_t conn_handle, uint8_t evt_status, int encrypted)
}
bonded = 1;
res.restore = 1;
if (evt_status != 0) {
if (evt_status == BLE_ERR_PINKEY_MISSING &&
(proc->flags & BLE_SM_PROC_F_INITIATOR) &&
(MYNEWT_VAL(BLE_SMP_UNBOND_ON_KEY_MISSING) ||
MYNEWT_VAL(BLE_SMP_REMOVE_BOND_ON_PAIR_FAIL_AS_CENTRAL))) {
/* Peer reports no LTK. Erase our bond before restart-pair. */
res.restore = 0;
res.enc_cb = 1;
res.app_status = BLE_HS_HCI_ERR(evt_status);
res.keep_bond = 0;
} else {
terminate_conn = true;
res.keep_bond = 1;
}
}
key_size = proc->key_size;
break;
@@ -1432,10 +1548,18 @@ ble_sm_enc_event_rx(uint16_t conn_handle, uint8_t evt_status, int encrypted)
if (proc == NULL || proc->state == BLE_SM_PROC_STATE_NONE) {
res.enc_cb = 1;
res.app_status = BLE_HS_HCI_ERR(evt_status);
if (proc == NULL && evt_status != 0) {
/* No active security procedure owns this controller failure. */
res.keep_bond = 1;
}
}
ble_hs_unlock();
if (terminate_conn) {
ble_gap_terminate(conn_handle, BLE_ERR_AUTH_FAIL);
}
res.bonded = bonded;
ble_sm_process_result(conn_handle, &res, true);
}
@@ -1546,15 +1670,22 @@ ble_sm_ltk_start_exec(struct ble_sm_proc *proc, struct ble_sm_result *res,
}
}
struct ble_sm_ltk_restore_ctx {
const struct ble_store_value_sec *value_sec;
bool bond_present;
};
static void
ble_sm_ltk_restore_exec(struct ble_sm_proc *proc, struct ble_sm_result *res,
void *arg)
{
struct ble_store_value_sec *value_sec;
const struct ble_sm_ltk_restore_ctx *ctx;
const struct ble_store_value_sec *value_sec;
BLE_HS_DBG_ASSERT(!(proc->flags & BLE_SM_PROC_F_INITIATOR));
value_sec = arg;
ctx = arg;
value_sec = ctx != NULL ? ctx->value_sec : NULL;
if (value_sec != NULL) {
/* Store provided a key; send it to the controller. */
@@ -1569,24 +1700,32 @@ ble_sm_ltk_restore_exec(struct ble_sm_proc *proc, struct ble_sm_result *res,
} else {
/* Notify the app if it provided a key and the procedure failed. */
res->enc_cb = 1;
res->restore = 1;
res->terminate = 1;
res->keep_bond = 1;
}
} else {
/* Application does not have the requested key in its database. Send a
* negative reply to the controller.
/*
* No LTK matched ediv/rand. If we still hold a bond for this peer,
* treat it as a failed restore (keep bond, drop link, block
* BLE_RESTART_PAIR). If we hold no bond, this is one-sided deletion
* on our side; allow BLE_RESTART_PAIR so the peer can re-pair.
*/
int rc;
rc = ble_sm_ltk_req_neg_reply_tx(proc->conn_handle);
if (rc != 0) {
res->app_status = rc;
res->enc_cb = 1; /* Notify application of failure, similar to reply branch */
res->enc_cb = 1;
} else {
/* Neg-reply sent: do not treat as restore success. app_status=0
* would enter ENC_RESTORE and wait for an enc-change that never
* arrives. PINKEY_MISSING also drives BLE_RESTART_PAIR.
*/
res->app_status = BLE_HS_HCI_ERR(BLE_ERR_PINKEY_MISSING);
res->enc_cb = 1;
}
if (ctx != NULL && ctx->bond_present) {
res->restore = 1;
res->terminate = 1;
res->keep_bond = 1;
}
}
if (res->app_status == 0) {
@@ -1599,7 +1738,10 @@ ble_sm_ltk_restore_exec(struct ble_sm_proc *proc, struct ble_sm_result *res,
int
ble_sm_ltk_req_rx(const struct ble_hci_ev_le_subev_lt_key_req *ev)
{
struct ble_sm_ltk_restore_ctx restore_ctx;
struct ble_store_value_sec value_sec;
struct ble_store_value_sec bond;
struct ble_store_key_sec key_sec;
struct ble_hs_conn_addrs addrs;
struct ble_sm_result res;
struct ble_sm_proc *proc;
@@ -1611,6 +1753,7 @@ ble_sm_ltk_req_rx(const struct ble_hci_ev_le_subev_lt_key_req *ev)
uint16_t conn_handle = le16toh(ev->conn_handle);
memset(&res, 0, sizeof res);
memset(&restore_ctx, 0, sizeof restore_ctx);
ble_hs_lock();
proc = ble_sm_proc_find(conn_handle, BLE_SM_PROC_STATE_NONE, 0, NULL);
@@ -1682,11 +1825,21 @@ ble_sm_ltk_req_rx(const struct ble_hci_ev_le_subev_lt_key_req *ev)
peer_id_addr, &value_sec);
if (store_rc == 0) {
/* Send the key to the controller. */
res.state_arg = &value_sec;
restore_ctx.value_sec = &value_sec;
} else {
/* Send a nack to the controller. */
res.state_arg = NULL;
/*
* A nonmatching ediv/rand does not imply that no bond exists.
* Read the peer record here, outside the host lock, so the
* LTK restore state can distinguish a stale request from
* one-sided bond deletion.
*/
memset(&key_sec, 0, sizeof key_sec);
key_sec.peer_addr = addrs.peer_id_addr;
store_rc = ble_store_read_peer_sec(&key_sec, &bond);
restore_ctx.bond_present =
store_rc == 0 && bond.ltk_present;
}
res.state_arg = &restore_ctx;
}
}
@@ -2051,6 +2204,9 @@ ble_sm_pair_req_rx(uint16_t conn_handle, struct os_mbuf **om,
struct ble_hs_conn *conn;
ble_sm_proc_flags proc_flags;
uint8_t key_size;
uint8_t sm_err;
bool terminate;
bool keep_bond;
int rc;
/* Silence spurious unused-variable warnings. */
@@ -2174,11 +2330,18 @@ ble_sm_pair_req_rx(uint16_t conn_handle, struct os_mbuf **om,
* application an opportunity to delete the old bond.
*/
if (res->app_status == 0) {
rc = ble_sm_chk_repeat_pairing(conn_handle, proc_flags, key_size);
rc = ble_sm_chk_repeat_pairing(conn_handle, proc_flags, key_size,
&sm_err, &terminate, &keep_bond);
if (rc != 0) {
/* The app indicated that the pairing request should be ignored. */
res->app_status = rc;
res->execute = 0;
res->sm_err = sm_err;
res->terminate = terminate;
res->keep_bond = keep_bond;
if (terminate) {
res->enc_cb = 1;
}
if (rc == BLE_HS_EBUSY) {
/*
* CONNECT not yet delivered; send Pairing Failed so the peer
@@ -2195,8 +2358,10 @@ ble_sm_pair_rsp_rx(uint16_t conn_handle, struct os_mbuf **om,
struct ble_sm_result *res)
{
struct ble_sm_pair_cmd *rsp;
struct ble_store_value_sec bond;
struct ble_sm_proc *proc;
uint8_t ioact;
int bond_rc;
int rc;
res->app_status = ble_hs_mbuf_pullup_base(om, sizeof(*rsp));
@@ -2206,6 +2371,7 @@ ble_sm_pair_rsp_rx(uint16_t conn_handle, struct os_mbuf **om,
}
rsp = (struct ble_sm_pair_cmd *)(*om)->om_data;
bond_rc = ble_sm_read_bond(conn_handle, &bond);
ble_hs_lock();
proc = ble_sm_proc_find(conn_handle, BLE_SM_PROC_STATE_PAIR, 1, NULL);
@@ -2232,21 +2398,56 @@ ble_sm_pair_rsp_rx(uint16_t conn_handle, struct os_mbuf **om,
res->sm_err = BLE_SM_ERR_AUTHREQ;
res->app_status = BLE_HS_SM_US_ERR(BLE_SM_ERR_AUTHREQ);
res->enc_cb = 1;
} else if (MYNEWT_VAL(BLE_SMP_HARDENED_REPAIRING) &&
bond_rc == 0 && bond.ltk_present &&
proc->sec_req_valid &&
((rsp->authreq &
proc->sec_req_authreq &
(BLE_SM_PAIR_AUTHREQ_MITM | BLE_SM_PAIR_AUTHREQ_SC)) !=
(proc->sec_req_authreq &
(BLE_SM_PAIR_AUTHREQ_MITM | BLE_SM_PAIR_AUTHREQ_SC)))) {
res->sm_err = BLE_SM_ERR_AUTHREQ;
res->app_status = BLE_HS_SM_US_ERR(BLE_SM_ERR_AUTHREQ);
res->enc_cb = 1;
res->terminate = 1;
res->keep_bond = 1;
} else {
ble_sm_pair_cfg(proc);
rc = ble_sm_io_action(proc, &ioact);
if (rc != 0) {
res->sm_err = BLE_SM_ERR_AUTHREQ;
res->app_status = BLE_HS_SM_US_ERR(BLE_SM_ERR_AUTHREQ);
if (bond_rc == 0 &&
MYNEWT_VAL(BLE_SMP_HARDENED_REPAIRING) &&
((bond.sc && !(proc->flags & BLE_SM_PROC_F_SC)) ||
(bond.authenticated &&
!(proc->flags & BLE_SM_PROC_F_AUTHENTICATED)) ||
(proc->key_size < bond.key_size))) {
res->sm_err = proc->key_size < bond.key_size ? BLE_SM_ERR_ENC_KEY_SZ
: BLE_SM_ERR_AUTHREQ;
res->app_status = BLE_HS_SM_US_ERR(res->sm_err);
res->enc_cb = 1;
} else {
proc->state = ble_sm_state_after_pair(proc);
if (ble_sm_ioact_state(ioact) == proc->state) {
res->passkey_params.action = ioact;
}
if (ble_sm_proc_can_advance(proc)) {
res->execute = 1;
res->terminate = 1;
res->keep_bond = 1;
} else if (bond_rc != 0 &&
bond_rc != BLE_HS_ENOENT &&
bond_rc != BLE_HS_ENOTSUP) {
res->sm_err = BLE_SM_ERR_UNSPECIFIED;
res->app_status = bond_rc;
res->enc_cb = 1;
}
if (res->app_status == 0) {
rc = ble_sm_io_action(proc, &ioact);
if (rc != 0) {
res->sm_err = BLE_SM_ERR_AUTHREQ;
res->app_status = BLE_HS_SM_US_ERR(BLE_SM_ERR_AUTHREQ);
res->enc_cb = 1;
} else {
proc->state = ble_sm_state_after_pair(proc);
if (ble_sm_ioact_state(ioact) == proc->state) {
res->passkey_params.action = ioact;
}
if (ble_sm_proc_can_advance(proc)) {
res->execute = 1;
}
}
}
}
@@ -2350,7 +2551,7 @@ ble_sm_sec_req_rx(uint16_t conn_handle, struct os_mbuf **om,
/* always check if we have keys for this peer */
res->app_status = ble_store_read_peer_sec(&key_sec, &value_sec);
if (res->app_status == 0) {
if (res->app_status == 0 && value_sec.ltk_present) {
/* if keys are present and link is already encrypted check if
* pairing should be started for security level elevation.
* Otherwise we first require link encryption.
@@ -2371,9 +2572,13 @@ ble_sm_sec_req_rx(uint16_t conn_handle, struct os_mbuf **om,
start_pairing = true;
}
}
} else {
} else if (res->app_status == BLE_HS_ENOENT ||
res->app_status == BLE_HS_ENOTSUP ||
(res->app_status == 0 && !value_sec.ltk_present)) {
/* no keys present, start pairing */
start_pairing = true;
} else {
return;
}
/** Make sure a procedure isn't already in progress for this connection. */
@@ -2388,6 +2593,16 @@ ble_sm_sec_req_rx(uint16_t conn_handle, struct os_mbuf **om,
if (start_pairing) {
res->app_status = ble_sm_pair_initiate(conn_handle);
if (res->app_status == 0) {
ble_hs_lock();
proc = ble_sm_proc_find(conn_handle, BLE_SM_PROC_STATE_PAIR, 1,
NULL);
if (proc != NULL) {
proc->sec_req_authreq = cmd->authreq;
proc->sec_req_valid = 1;
}
ble_hs_unlock();
}
} else {
res->app_status = ble_sm_enc_initiate(conn_handle,
value_sec.key_size,
+5
View File
@@ -255,6 +255,8 @@ struct ble_sm_proc {
uint8_t state;
uint8_t rx_key_flags;
uint8_t key_size;
uint8_t sec_req_authreq;
uint8_t sec_req_valid;
uint8_t pair_req[sizeof(struct ble_sm_hdr) + sizeof(struct ble_sm_pair_cmd)];
uint8_t pair_rsp[sizeof(struct ble_sm_hdr) + sizeof(struct ble_sm_pair_cmd)];
@@ -287,7 +289,10 @@ struct ble_sm_result {
unsigned enc_cb : 1;
unsigned bonded : 1;
unsigned restore : 1;
unsigned terminate : 1;
unsigned out_of_order : 1;
/* Hardened re-pair refusal: keep the stored bond despite remove-on-fail. */
unsigned keep_bond : 1;
};
#if MYNEWT_VAL(BLE_HS_DEBUG)