BLE_GAP_EVENT_REPEAT_PAIRING forces bond deletion before authentication completes
- Dominant language
- C
- Stars
- 893
- Forks
- 512
- Avg merge
- 13d 31m
- Merged PRs (30d)
- 7
Description
### Summary
When a peer initiates re-pairing, the application is forced to delete the existing bond via `BLE_GAP_EVENT_REPEAT_PAIRING` before Phase 2 authentication has completed. This allows an unauthenticated attacker to permanently evict a legitimate bond from the store simply by initiating a pairing request. No knowledge of the passkey, OOB data, or existing keys is required.
### Security Impact
Any device within radio range can send a Pairing Request to a peripheral that has bonded peers. Because `BLE_GAP_EVENT_REPEAT_PAIRING` fires during Phase 1 (feature exchange), before any authentication has taken place, the recommended handler pattern of deleting the old bond and returning `BLE_GAP_REPEAT_PAIRING_RETRY` exposes the following attack:
1. Attacker connects to a peripheral that has a legitimate bond with a trusted peer
2. Attacker sends a Pairing Request
3. Peripheral fires `BLE_GAP_EVENT_REPEAT_PAIRING` and application deletes the legitimate bond
4. Attacker disconnects or allows Phase 2 to fail
5. The legitimate bond is permanently gone; the trusted peer can no longer reconnect using its stored keys
This requires no credentials and is trivially repeatable.
The attack applies to all Phase 2 authentication methods that provide MITM protection:
- **Passkey Entry**: passkey is verified in Phase 2, after the bond is already evicted
- **Numeric Comparison**: user confirmation occurs in Phase 2, after the bond is already evicted
- **OOB**: OOB data is verified in Phase 2, after the bond is already evicted
(The issue obviously does not apply to **Just Works**, which provides no MITM protection and has no authentication step in Phase 2.)
### Background
`BLE_GAP_EVENT_REPEAT_PAIRING` is fired from `ble_sm_chk_repeat_pairing()` in `ble_sm.c`, which is called from `ble_sm_pair_req_rx()` during Phase 1. The function blocks pairing progression inside a `do...while` loop until the application either returns `BLE_GAP_REPEAT_PAIRING_RETRY` (after deleting the bond) or `BLE_GAP_REPEAT_PAIRING_IGNORE`. There is no option to defer the decision until after authentication succeeds.
### Current behavior
1. Attacker or peer initiates re-pairing by sending a Pairing Request (Phase 1)
2. Stack fires `BLE_GAP_EVENT_REPEAT_PAIRING` **before any Phase 2 authentication**
3. Application must delete the existing bond and return `BLE_GAP_REPEAT_PAIRING_RETRY` to proceed
4. Authentication occurs in Phase 2; if it **fails or is abandoned**, the old bond is already gone
5. The previously bonded peer can no longer reconnect
### Expected behavior
The existing bond should only be evicted once the new pairing has been confirmed as authenticated (i.e. after Phase 2 completes successfully, before Phase 3 key distribution). Specifically, `ble_sm_chk_repeat_pairing()` should be called from `ble_sm_key_exchange_complete()` rather than from `ble_sm_pair_req_rx()`.
### Suggested fix
Move the `ble_gap_repeat_pairing_event()` call to Phase 3, immediately before the new keys are written to the store. At that point authentication has already succeeded, so bond eviction is safe.
Alternatively, add a `BLE_GAP_REPEAT_PAIRING_DEFER` return value that causes the stack to shadow the old bond internally and only commit the eviction on pairing success, leaving the original bond intact if Phase 2 fails or the connection drops.
### Workaround
Applications can manually save the existing bond before deleting it and restore it in `BLE_GAP_EVENT_ENC_CHANGE` if `status != 0`, and in `BLE_GAP_EVENT_DISCONNECT` if the connection drops before encryption completes. However this requires using internal store APIs (`ble_store_write_peer_sec` / `ble_store_write_our_sec`), leaves a window where the bond is absent, and does not fully close the attack surface since the attacker only needs to trigger the event — not complete the pairing.
### Environment
- NimBLE version: verified for 1.9.0 and current master. Probably applies to older versions too.
- Relevant source: `nimble/host/src/ble_sm.c`, `ble_sm_chk_repeat_pairing()`, `ble_sm_pair_req_rx()`
Contributor guide
No contributing guide indexed for this repository
Research direction
Start in nimble/host/src/ble_sm.c by tracing ble_sm_pair_req_rx(), ble_sm_chk_repeat_pairing(), and ble_sm_key_exchange_complete(). Verify the pairing flow and existing bond handling, then ensure an old bond remains available when Phase 2 fails or disconnects and is evicted only after authentication succeeds before new keys are stored.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- c
- Domain
- embedded-iot, networking, security
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Clearly specified
- Newbie friendliness
- 45/100