apache / apache/mynewt-nimble

BLE_GAP_EVENT_REPEAT_PAIRING forces bond deletion before authentication completes

Open
#2,206 0 comments 0 reactions 0 assignees View on GitHub
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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.