Skip to content

nimble/host: Evict stale resolving list entry before adding - #2282

Open
sanastasiou wants to merge 1 commit into
apache:masterfrom
sanastasiou:host-pvcy-evict-stale-rl-entry
Open

nimble/host: Evict stale resolving list entry before adding#2282
sanastasiou wants to merge 1 commit into
apache:masterfrom
sanastasiou:host-pvcy-evict-stale-rl-entry

Conversation

@sanastasiou

Copy link
Copy Markdown

Problem

Some controllers reject LE Add Device To Resolving List with Invalid HCI Command Parameters (0x12) when an entry for that peer identity is already present.

ble_hs_pvcy_add_entry() does not handle this. The error propagates to ble_store_write_peer_sec(), which discards it:

/* There is not much to do here if it fails */

The controller is therefore left resolving the old IRK, and the host has no way to notice — the resolving list cannot be read back.

Impact

Reachable whenever a peer re-pairs over an existing bond.

Peers that rotate their IRK on unpair — Android does — become permanently unresolvable afterwards. The controller silently drops their CONNECT_IND because it still holds the previous IRK. There is no error anywhere: the peer simply stops being able to reconnect. The only recovery is a reset that rebuilds the resolving list from the bond store.

Peers with a stable IRK are unaffected, which is why this typically presents as "Android cannot reconnect after re-pairing, iOS is fine".

Fix

Remove any existing entry for the identity before the add. Removing an absent entry is harmless, so no prior lookup is needed — and since the resolving list is not readable, a lookup is not generally possible anyway.

Note the call is to ble_hs_pvcy_remove_entry_hci() rather than ble_hs_pvcy_remove_entry(). The latter wraps the command in its own ble_gap_preempt() / ble_gap_preempt_done() pair, and ble_gap_preempt_done() is not depth counted — using it here would end the preemption established a few lines above, before the add is issued. That preemption is required by Vol 2, Part E, 7.8.38.

Testing

Observed and fixed on an ESP32-C3 (NimBLE-Arduino, BLE_HOST_BASED_PRIVACY=0, so the resolving list lives in the controller).

Before: every re-pair over an existing bond logged rc=530 (BLE_HS_HCI_ERR(0x12)) from the add path, after which the Android peer could no longer reconnect until reboot.

After: the add succeeds, and repeated OS-forget + re-pair cycles reconnect normally. Verified across both an Android peer (rotating IRK) and an iOS peer (stable IRK) to confirm no regression on the previously-working path.

I do not have a multi-controller test bed, so I can only confirm the 0x12 behaviour on the ESP32-C3. The change should be a no-op on controllers that already tolerate a duplicate add.

@sjanc sjanc left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looks ok to me, however you probably need to adjust unittests to this change
(and fix compliance check)

Some controllers reject LE Add Device To Resolving List with Invalid
HCI Command Parameters (0x12) when an entry for that peer identity
already exists. ble_hs_pvcy_add_entry() does not handle this: the error
is returned to ble_store_write_peer_sec(), which discards it ("There is
not much to do here if it fails"). The controller is then left
resolving the OLD IRK.

This is reachable whenever a peer re-pairs over an existing bond.
Peers that rotate their IRK on unpair -- Android does -- become
permanently unresolvable afterwards: the controller silently drops
their CONNECT_IND because it still holds the previous IRK, and the host
never learns why. The only recovery is a reset that rebuilds the
resolving list from the bond store. Peers with a stable IRK are
unaffected, which is why this tends to show up as "Android cannot
reconnect after re-pairing, iOS is fine".

Remove any existing entry for the identity before the add. Removing an
absent entry is harmless, so it needs no prior lookup -- and the
resolving list cannot be read back, so a lookup is not possible in the
general case anyway.

Note this calls ble_hs_pvcy_remove_entry_hci() rather than
ble_hs_pvcy_remove_entry(). The latter wraps the command in its own
ble_gap_preempt()/ble_gap_preempt_done() pair, and
ble_gap_preempt_done() is not depth counted, so using it here would end
the preemption established above before the add is issued.

Observed on an ESP32-C3 (NimBLE-Arduino, host based privacy disabled)
with rc=530 == BLE_HS_HCI_ERR(0x12) logged from the add path on every
re-pair, and fixed by this change on the same hardware.

Adjust the host unit tests for the extra LE Remove Device From
Resolving List command that now precedes every add.
@sanastasiou
sanastasiou force-pushed the host-pvcy-evict-stale-rl-entry branch from 98347d7 to 351b3d5 Compare September 8, 2026 22:07
@sanastasiou

Copy link
Copy Markdown
Author

Thanks for the review. Updated: the host unit tests now expect the LE Remove Device From Resolving List that precedes every add (params verified where the add's are), and the commit body is rewrapped to 72 columns. nimble/host/test passes locally with the newt_test_all recipe.

The CodeQL startup failure is the org's allowed-actions policy rejecting advanced-security/filter-sarif, unrelated to this change. The remaining workflows need approval to run.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants