CVE-2024-34730 AI-generated analysis
platform/packages/modules/Bluetooth, HID Host profile (btif_hh, btif_profile_storage, HidHostService JNI, Floss topshim)
Adds a per-device "reconnect_allowed" flag to the HID host btif layer (persisted as HidReConnectAllowed) and makes hh_open_handler in system/btif/src/btif_hh.cc reject incoming HID connections unless the device is an added device with reconnection allowed or an outgoing connection to it is pending.
Root cause
In system/btif/src/btif_hh.cc, hh_open_handler processed every BTA_HH open event as an accepted connection: it emitted BTHH_CONN_STATE_CONNECTING and proceeded to set up the device without checking whether the application had allowed connections from that device. The HID connection policy (BluetoothProfile.CONNECTION_POLICY_ALLOWED) was evaluated only in the Java HidHostService and was not propagated to the native stack; the native disconnect() took only an address. The only guard visible in the material is the Java-side okToConnect check in MESSAGE_CONNECT_STATE_CHANGED, which acts once the HAL reports CONNECTED (virtualUnPlugNative). Per the commit message, a remote device could initiate a HID connection even when HID connection was disabled by the connection policy.
How the fix works
- system/include/hardware/bt_hh.h: bthh_interface_t.disconnect gains a second parameter bool reconnect_allowed so the application can tell the native stack whether future incoming connections from this device are acceptable.
- android/app/src/com/android/bluetooth/hid/HidHostService.java (MESSAGE_DISCONNECT handler): computes reconnectAllowed = getConnectionPolicy(device) == BluetoothProfile.CONNECTION_POLICY_ALLOWED and passes it to disconnectHidNative(byte[], boolean). android/app/jni/com_android_bluetooth_hid_host.cpp: disconnectHidNative takes jboolean reconnect_allowed, forwards it to sBluetoothHidInterface->disconnect, JNI signature changed to "([BZ)Z".
- system/btif/include/btif_hh.h: btif_hh_added_device_t gains bool reconnect_allowed; btif_hh_add_added_dev gains a reconnect_allowed parameter.
- system/btif/src/btif_hh.cc: new btif_hh_find_added_dev(addr) looks up the added-device table; new static hh_connection_allowed(bda) returns true only if the device is an added device with reconnect_allowed == true, or btif_hh_cb.pending_conn_address == bda (an outgoing connect was requested).
- system/btif/src/btif_hh.cc:hh_open_handler: at entry, if !hh_connection_allowed(conn.bda), logs "Reject unexpected incoming HID Connection", sets dev_status = BTHH_CONN_STATE_DISCONNECTED on the device found by btif_hh_find_connected_dev_by_handle (if any), calls hh_connect_complete(conn.handle, conn.bda, BTIF_HH_DEV_DISCONNECTED) and returns, skipping the CONNECTING callback and device setup.
- system/btif/src/btif_hh.cc:disconnect(bd_addr, reconnect_allowed): when reconnect_allowed is false, finds the added device and if it currently allows reconnection, clears reconnect_allowed and persists via btif_storage_set_hid_connection_policy(addr, false). This happens before the connected-device lookup, so the policy is recorded even if the device is not currently opened.
- system/btif/src/btif_hh.cc:connect(bd_addr): if the device is already added with reconnect_allowed == false, sets it to true and persists it, so an application-initiated connect re-enables incoming reconnections.
- system/btif/src/btif_hh.cc: BTA_HH_GET_DSCP_EVT handling in btif_hh_upstreams_evt and set_info() now call btif_hh_add_added_dev(..., true) and btif_storage_set_hid_connection_policy(addr, true), so newly added devices default to allowed.
- system/btif/src/btif_profile_storage.cc: adds key BTIF_STORAGE_KEY_HID_RECONNECT_ALLOWED ("HidReConnectAllowed"), btif_storage_set_hid_connection_policy (btif_config_set_int) and btif_storage_get_hid_connection_policy (btif_config_get_int, defaulting to 1 when the key is absent for backward compatibility). btif_storage_load_bonded_hid_info reads the policy and passes it to btif_hh_add_added_dev; btif_storage_remove_hid_info removes the key.
- system/gd/rust/topshim/src/profiles/hid_host.rs and system/gd/rust/linux/stack/src/bluetooth.rs (Floss): disconnect callers updated to pass true for the new parameter, i.e. Floss never marks a device as disallowed; this is an interface-compatibility change.
Completeness
- Enforcement is placed in hh_open_handler, which handles the BTA_HH open event; outgoing connections are allowed via the pending_conn_address check, so only connections without a pending outgoing request from devices not marked allowed are rejected.
- The policy only reaches the native layer through the Java MESSAGE_DISCONNECT path (disconnectHidNative with reconnectAllowed). The diff does not show any other propagation of a connection-policy change (for example setting the policy to FORBIDDEN while the device is not connected) to the native flag; whether such paths exist is not visible in the material.
- Backward compatibility default: btif_storage_get_hid_connection_policy returns allowed when the HidReConnectAllowed key is absent, so devices bonded before the fix are treated as allowed at the btif layer until a disconnect with a policy other than ALLOWED records the flag. The existing Java okToConnect/virtualUnPlugNative check in MESSAGE_CONNECT_STATE_CHANGED remains for those devices.
- The rejection path sets dev_status to DISCONNECTED and reports BTIF_HH_DEV_DISCONNECTED via hh_connect_complete; the diff does not show an explicit lower-layer close in that path, so what hh_connect_complete does with the underlying channel is not visible in the material.
- Floss (Rust) callers pass reconnect_allowed = true unconditionally, so on Floss added devices are never marked disallowed by this fix; the native gate still rejects devices that are not in the added-device table and have no pending outgoing connect. Floss separately rejects unbonded devices in its BtifHHCallbacks implementation.
Behavior changes
- Incoming HID connections from devices that are not added with reconnect_allowed and have no pending outgoing connect are now rejected in btif with a connection_state_cb of DISCONNECTED (log: "Reject unexpected incoming HID Connection") instead of proceeding to CONNECTING/CONNECTED.
- New persisted per-device config key "HidReConnectAllowed" written on device add (true), on connect() when previously disallowed (true) and on disconnect() with policy not ALLOWED when previously allowed (false); removed with the device's HID info.
- HAL interface change: bthh_interface_t.disconnect now takes (RawAddress*, bool); JNI method disconnectHidNative signature changed to ([BZ)Z. Any out-of-tree caller of the old signature must be updated.
- Disconnecting a device whose Java connection policy is not CONNECTION_POLICY_ALLOWED now also disables native acceptance of its future incoming HID connections; a subsequent application-initiated connect re-enables it.
Per major
- Android 14: same fix: commit 9529bb85c051 (RESTRICT AUTOMERGE, Change-Id Iba2ac3502bf1e6e4ac1f60ed64b1b074facd880b) in android-security-14.0.0_r13; at android-security-14.0.0_r29 7/8 lines of the patched region are present and the context shows the reconnect_allowed plumbing (bt_hh.h, JNI, HidHostService, btif_hh.h, storage key) in place.
- Android 15: not affected per the bulletin; the commit message states the vulnerability no longer exists on main because of feature request b/324093729. 5/8 lines of the patched region present at android-security-15.0.0_r17.
- Android 16: not affected per the bulletin; 4/8 lines of the patched region present at android-security-16.0.0_r8.
- Android 17: not affected per the bulletin; 4/8 lines of the patched region present at android-security-17.0.0_r1.
Unknowns
- Whether hh_connect_complete with BTIF_HH_DEV_DISCONNECTED closes the underlying HID/L2CAP channel, or only reports state; the function body is not in the material.
- Whether any Java path other than MESSAGE_DISCONNECT propagates a connection-policy change to the native reconnect_allowed flag.
- The exact content of feature b/324093729 that made Android 15+ not affected, and what the partial line matches on 15/16/17 reflect.
- How the fix was applied to Android 12, 12L and 13 (not in the material).