CVE-2026-0097 AI-generated analysis
platform/packages/modules/Bluetooth: system/stack/smp (Security Manager Protocol), system/stack/btm/btm_sec.cc, system/btif/src/btif_dm.cc
In the LE Secure Connections Passkey Display and Key Notification association models, the fix makes the legacy SMP layer wait for both the local user's approval (SMP_SecurityGrant) and the remote device's passkey confirmation before emitting SMP_KEY_READY_EVT. It treats a user rejection as an authentication failure, cancels LE pairing on authentication timeout, and sets the default LE pairing flags (is_le_only/is_le_nc) in btif for passkey notification.
Root cause
In system/stack/smp/smp_keys.cc, smp_process_stk always emitted SMP_KEY_READY_EVT (SMP_KEY_TYPE_STK) as soon as the key was computed. For the SMP_MODEL_SEC_CONN_PASSKEY_DISP and SMP_MODEL_KEY_NOTIF models, no state tied this step to the local user's approval. SMP_SecurityGrant had no handling for these models, so the user's accept or reject did not gate the SMP state machine. Also, btif_dm_ble_key_notif_evt did not set pairing_cb.is_le_only / is_le_nc. In BTM_PAIR_STATE_WAIT_AUTH_COMPLETE, btm_sec_pairing_timeout did not cancel an active LE SMP pairing, so a timeout did not tear that pairing down.
How the fix works
- system/stack/smp/smp_int.h: tSMP_CB gains a passkey_display_state struct with two booleans: approved (the user approved the pairing) and confirmed (the remote device confirmed the passkey).
- system/stack/smp/smp_keys.cc:smp_process_stk: after smp_mask_enc_key, if selected_association_model is SMP_MODEL_SEC_CONN_PASSKEY_DISP or SMP_MODEL_KEY_NOTIF, it sets passkey_display_state.confirmed = true and stores the masked key in p_cb->tk. If passkey_display_state.approved is not yet set, it logs 'Waiting for user to approve pairing' and returns early. Otherwise (other models, or already approved) it emits SMP_KEY_READY_EVT with SMP_KEY_TYPE_STK as before.
- system/stack/smp/smp_api.cc:SMP_SecurityGrant: new early branch when bd_addr matches smp_cb.pairing_bda and the model is SMP_MODEL_SEC_CONN_PASSKEY_DISP or SMP_MODEL_KEY_NOTIF. If res == SMP_SUCCESS, it sets approved = true. If confirmed is already set, it then emits SMP_KEY_READY_EVT with key_type SMP_KEY_TYPE_TK and p_data = smp_cb.tk.data(); if not, it only logs and waits. For any other res, it emits SMP_AUTH_CMPL_EVT with status SMP_PAIR_AUTH_FAIL. The branch returns before the pre-existing handling, which begins with the SMP_CONSENT_REQ_EVT check. Logging now includes bd_addr, res, br_state, cb_evt, pairing_bda and the association model.
- system/stack/btm/btm_sec.cc:btm_sec_pairing_timeout: in case BTM_PAIR_STATE_WAIT_AUTH_COMPLETE, if pairing_flags has BTM_PAIR_FLAGS_LE_ACTIVE, it calls SMP_PairCancel(p_cb->pairing_bda). When SMP is not idle for that address, SMP_PairCancel raises SMP_AUTH_CMPL_EVT with SMP_PAIR_FAIL_UNKNOWN. The code then falls through to the existing BTM_PAIR_STATE_GET_REM_NAME UI notification. The trace level is raised from EVENT to WARNING.
- system/btif/src/btif_dm.cc:btif_dm_ble_key_notif_evt: sets pairing_cb.is_le_only = true and pairing_cb.is_le_nc = false before invoking the SSP request callback with BT_SSP_VARIANT_PASSKEY_NOTIFICATION. The commit describes this as initializing the default LE pairing flags.
Completeness
- The gating applies only to SMP_MODEL_SEC_CONN_PASSKEY_DISP and SMP_MODEL_KEY_NOTIF. For other association models, smp_process_stk still emits SMP_KEY_READY_EVT immediately.
- The diff does not reset passkey_display_state (approved/confirmed) at pairing start or completion. The material does not show whether the control block is cleared elsewhere between pairings.
- The deferred path in SMP_SecurityGrant emits SMP_KEY_READY_EVT with key_type SMP_KEY_TYPE_TK, while the direct path in smp_process_stk uses SMP_KEY_TYPE_STK for the same data. The material does not show how the state machine handles this difference.
- The commit message lists Flag: com.android.bluetooth.flags.passkey_entry_pairing_approval, but the shown diff has no flag check, so the new behaviour is unconditional in the shown code.
- The timeout cancellation applies only to BTM_PAIR_STATE_WAIT_AUTH_COMPLETE with BTM_PAIR_FLAGS_LE_ACTIVE. Other pairing states are unchanged.
- On Android 14, SMP_SecurityGrant and SMP_PairCancel have a LOG_ASSERT that the GD shim is not enabled, so there the fix is in the legacy SMP stack.
Behavior changes
- In passkey display / key notification LE pairings, key readiness is deferred until the user approves. If the remote finishes first, the stack logs 'Waiting for user to approve pairing' and waits.
- For these models, a user rejection via SMP_SecurityGrant with res != SMP_SUCCESS now fails the pairing with SMP_PAIR_AUTH_FAIL, and the pre-existing SMP_SecurityGrant handling is skipped.
- An authentication timeout during an active LE pairing now cancels the SMP pairing (SMP_PairCancel, failure reason SMP_PAIR_FAIL_UNKNOWN) in addition to notifying the UI.
- For BLE passkey notification requests, pairing_cb.is_le_only is set to true and is_le_nc to false.
- More logging, at higher levels, in SMP_SecurityGrant, smp_process_stk and btm_sec_pairing_timeout.
Per major
- Android 14: Same fix: combined backport 725df035dfe2 in android-security-14.0.0_r28 (BTM_TRACE_*/SMP_TRACE_* logging, btm_cb, btif_dm_ble_key_notif_evt). 7/8 lines of the patched region are present at android-security-14.0.0_r29.
- Android 15: Adapted fix: c18ac65121e1 in android-security-15.0.0_r16. Same logic, using log::verbose/log::warn/log::info, btm_sec_cb.pairing_flags, and tSMP_CB as a class. 7/8 lines present at android-security-15.0.0_r17.
- Android 16: Adapted fix: ce76f82dac2a in android-security-16.0.0_r7, same logic. The btif change is in btif_dm_ble_passkey_notif_evt, and the surrounding code uses std::format. 7/8 lines present at android-security-16.0.0_r8.
- Android 17: Resolved in base (android-17.0.0_r1, commit 57541dad9e1f). Only 3/8 lines of the patched region are present at android-security-17.0.0_r1, so the surrounding code differs on 17. The 17 diff in the material is identical to the 14 diff (legacy macros), so the exact form on 17 is uncertain.
Unknowns
- Whether passkey_display_state is reset between pairings (the diff has no reset, and the SMP control-block reset code is not shown).
- Why the deferred SMP_KEY_READY_EVT uses SMP_KEY_TYPE_TK while the direct path uses SMP_KEY_TYPE_STK, and how smp_sm_event handles each.
- How smp_process_stk is reached in the LE Secure Connections flow (its callers are not shown).
- How pairing_cb.is_le_only / is_le_nc affect the processing of the user's reply in btif.
- The actual code on Android 17 (android-security-17.0.0_r1 has only 3/8 lines of the patched region, and the 17 diff in the material duplicates the 14 diff).
- The concrete trigger and attacker position. The bulletin states only Critical EoP.