From 9529bb85c051596d8cc34ddac5fd5318e9b6d009 Mon Sep 17 00:00:00 2001
From: Himanshu Rawat <rwt@google.com>
Date: Fri, 5 Apr 2024 17:33:53 +0000
Subject: [PATCH] RESTRICT AUTOMERGE Disallow unexpected incoming HID
 connections

HID profile accepted any new incoming HID connection. Even when the
connection policy disabled HID connection, remote devices could initiate
HID connection.
This change ensures that incoming HID connection are accepted only if
application was interested in that HID connection.
This vulnerarbility no longer exists on the main because of feature
request b/324093729.

Test: mmm packages/modules/Bluetooth
Test: Manual | Pair and connect a HID device, disable HID connection
from Bluetooth device setting, attempt to connect from the HID device.
Bug: 308429049
Ignore-AOSP-First: security
(cherry picked from https://googleplex-android-review.googlesource.com/q/commit:bdd92020a9c14c3f541b39624c5b1e0af599acc5)
(cherry picked from https://googleplex-android-review.googlesource.com/q/commit:358b66af175f423523c5d90bb8aea4b3eb084172)
Merged-In: Iba2ac3502bf1e6e4ac1f60ed64b1b074facd880b
Change-Id: Iba2ac3502bf1e6e4ac1f60ed64b1b074facd880b
---
 .../jni/com_android_bluetooth_hid_host.cpp    |  8 +-
 .../android/bluetooth/hid/HidHostService.java | 11 +--
 system/btif/include/btif_hh.h                 |  4 +-
 system/btif/include/btif_storage.h            | 23 ++++++
 system/btif/src/btif_hh.cc                    | 81 +++++++++++++++++--
 system/btif/src/btif_profile_storage.cc       | 50 +++++++++++-
 system/gd/rust/linux/stack/src/bluetooth.rs   |  4 +-
 .../gd/rust/topshim/src/profiles/hid_host.rs  |  2 +-
 system/include/hardware/bt_hh.h               |  2 +-
 9 files changed, 166 insertions(+), 19 deletions(-)

diff --git a/android/app/jni/com_android_bluetooth_hid_host.cpp b/android/app/jni/com_android_bluetooth_hid_host.cpp
index 7a164233bc1..18ba315129c 100644
--- a/android/app/jni/com_android_bluetooth_hid_host.cpp
+++ b/android/app/jni/com_android_bluetooth_hid_host.cpp
@@ -282,7 +282,8 @@ static jboolean connectHidNative(JNIEnv* env, jobject object,
 }
 
 static jboolean disconnectHidNative(JNIEnv* env, jobject object,
-                                    jbyteArray address) {
+                                    jbyteArray address,
+                                    jboolean reconnect_allowed) {
   jbyte* addr;
   jboolean ret = JNI_TRUE;
   if (!sBluetoothHidInterface) return JNI_FALSE;
@@ -293,7 +294,8 @@ static jboolean disconnectHidNative(JNIEnv* env, jobject object,
     return JNI_FALSE;
   }
 
-  bt_status_t status = sBluetoothHidInterface->disconnect((RawAddress*)addr);
+  bt_status_t status =
+      sBluetoothHidInterface->disconnect((RawAddress*)addr, reconnect_allowed);
   if (status != BT_STATUS_SUCCESS) {
     ALOGE("Failed disconnect hid channel, status: %d", status);
     ret = JNI_FALSE;
@@ -509,7 +511,7 @@ static JNINativeMethod sMethods[] = {
     {"initializeNative", "()V", (void*)initializeNative},
     {"cleanupNative", "()V", (void*)cleanupNative},
     {"connectHidNative", "([B)Z", (void*)connectHidNative},
-    {"disconnectHidNative", "([B)Z", (void*)disconnectHidNative},
+    {"disconnectHidNative", "([BZ)Z", (void*)disconnectHidNative},
     {"getProtocolModeNative", "([B)Z", (void*)getProtocolModeNative},
     {"virtualUnPlugNative", "([B)Z", (void*)virtualUnPlugNative},
     {"setProtocolModeNative", "([BB)Z", (void*)setProtocolModeNative},
diff --git a/android/app/src/com/android/bluetooth/hid/HidHostService.java b/android/app/src/com/android/bluetooth/hid/HidHostService.java
index dfd7dd20028..eae47c18811 100644
--- a/android/app/src/com/android/bluetooth/hid/HidHostService.java
+++ b/android/app/src/com/android/bluetooth/hid/HidHostService.java
@@ -190,7 +190,10 @@ public class HidHostService extends ProfileService {
                 break;
                 case MESSAGE_DISCONNECT: {
                     BluetoothDevice device = (BluetoothDevice) msg.obj;
-                    if (!disconnectHidNative(getByteAddress(device))) {
+                    int connectionPolicy = getConnectionPolicy(device);
+                    boolean reconnectAllowed =
+                            connectionPolicy == BluetoothProfile.CONNECTION_POLICY_ALLOWED;
+                    if (!disconnectHidNative(getByteAddress(device), reconnectAllowed)) {
                         broadcastConnectionState(device, BluetoothProfile.STATE_DISCONNECTING);
                         broadcastConnectionState(device, BluetoothProfile.STATE_DISCONNECTED);
                         break;
@@ -326,9 +329,7 @@ public class HidHostService extends ProfileService {
         }
     };
 
-    /**
-     * Handlers for incoming service calls
-     */
+    /** Handlers for incoming service calls */
     @VisibleForTesting
     static class BluetoothHidHostBinder extends IBluetoothHidHost.Stub
             implements IProfileServiceBinder {
@@ -1032,7 +1033,7 @@ public class HidHostService extends ProfileService {
 
     private native boolean connectHidNative(byte[] btAddress);
 
-    private native boolean disconnectHidNative(byte[] btAddress);
+    private native boolean disconnectHidNative(byte[] btAddress, boolean reconnect_allowed);
 
     private native boolean getProtocolModeNative(byte[] btAddress);
 
diff --git a/system/btif/include/btif_hh.h b/system/btif/include/btif_hh.h
index d5246871499..6ee84877122 100644
--- a/system/btif/include/btif_hh.h
+++ b/system/btif/include/btif_hh.h
@@ -111,6 +111,7 @@ typedef struct {
   uint8_t dev_handle;
   RawAddress bd_addr;
   tBTA_HH_ATTR_MASK attr_mask;
+  bool reconnect_allowed;
 } btif_hh_added_device_t;
 
 /**
@@ -134,7 +135,8 @@ extern btif_hh_cb_t btif_hh_cb;
 
 btif_hh_device_t* btif_hh_find_connected_dev_by_handle(uint8_t handle);
 void btif_hh_remove_device(RawAddress bd_addr);
-bool btif_hh_add_added_dev(const RawAddress& bda, tBTA_HH_ATTR_MASK attr_mask);
+bool btif_hh_add_added_dev(const RawAddress& bda, tBTA_HH_ATTR_MASK attr_mask,
+                           bool reconnect_allowed);
 bt_status_t btif_hh_virtual_unplug(const RawAddress* bd_addr);
 void btif_hh_disconnect(RawAddress* bd_addr);
 void btif_hh_setreport(btif_hh_device_t* p_dev, bthh_report_type_t r_type,
diff --git a/system/btif/include/btif_storage.h b/system/btif/include/btif_storage.h
index a8bf67eac15..392d99e4a3f 100644
--- a/system/btif/include/btif_storage.h
+++ b/system/btif/include/btif_storage.h
@@ -216,6 +216,29 @@ void btif_storage_load_le_devices(void);
  ******************************************************************************/
 bt_status_t btif_storage_load_bonded_devices(void);
 
+/*******************************************************************************
+ *
+ * Function         btif_storage_set_hid_connection_policy
+ *
+ * Description      Stores connection policy info in nvram
+ *
+ * Returns          BT_STATUS_SUCCESS
+ *
+ ******************************************************************************/
+bt_status_t btif_storage_set_hid_connection_policy(const RawAddress& addr,
+                                                   bool reconnect_allowed);
+/*******************************************************************************
+ *
+ * Function         btif_storage_get_hid_connection_policy
+ *
+ * Description      get connection policy info from nvram
+ *
+ * Returns          BT_STATUS_SUCCESS
+ *
+ ******************************************************************************/
+bt_status_t btif_storage_get_hid_connection_policy(const RawAddress& addr,
+                                                   bool* reconnect_allowed);
+
 /*******************************************************************************
  *
  * Function         btif_storage_add_hid_device_info
diff --git a/system/btif/src/btif_hh.cc b/system/btif/src/btif_hh.cc
index fea75ef0cbb..e9d157b3eef 100644
--- a/system/btif/src/btif_hh.cc
+++ b/system/btif/src/btif_hh.cc
@@ -309,6 +309,24 @@ btif_hh_device_t* btif_hh_find_connected_dev_by_handle(uint8_t handle) {
   return NULL;
 }
 
+/*******************************************************************************
+ *
+ * Function         btif_hh_find_added_dev
+ *
+ * Description      Return the added device pointer of the specified address
+ *
+ * Returns          Added device entry
+ ******************************************************************************/
+btif_hh_added_device_t* btif_hh_find_added_dev(const RawAddress& addr) {
+  for (int i = 0; i < BTIF_HH_MAX_ADDED_DEV; i++) {
+    btif_hh_added_device_t* added_dev = &btif_hh_cb.added_devices[i];
+    if (added_dev->bd_addr == addr) {
+      return added_dev;
+    }
+  }
+  return nullptr;
+}
+
 /*******************************************************************************
  *
  * Function         btif_hh_find_dev_by_bda
@@ -398,9 +416,38 @@ static void hh_connect_complete(uint8_t handle, RawAddress& bda,
   HAL_CBACK(bt_hh_callbacks, connection_state_cb, &bda, state);
 }
 
+static bool hh_connection_allowed(const RawAddress& bda) {
+  /* Accept connection only if reconnection is allowed for the known device, or
+   * outgoing connection was requested */
+  btif_hh_added_device_t* added_dev = btif_hh_find_added_dev(bda);
+  if (added_dev != nullptr && added_dev->reconnect_allowed) {
+    LOG_VERBOSE("Connection allowed %s", ADDRESS_TO_LOGGABLE_CSTR(bda));
+    return true;
+  } else if (btif_hh_cb.pending_conn_address == bda) {
+    LOG_VERBOSE("Device connection was pending for: %s, status: %s",
+                ADDRESS_TO_LOGGABLE_CSTR(bda),
+                btif_hh_status_text(btif_hh_cb.status).c_str());
+    return true;
+  }
+
+  return false;
+}
+
 static void hh_open_handler(tBTA_HH_CONN& conn) {
   LOG_DEBUG("status = %d, handle = %d", conn.status, conn.handle);
 
+  if (!hh_connection_allowed(conn.bda)) {
+    LOG_WARN("Reject unexpected incoming HID Connection, device: %s",
+             ADDRESS_TO_LOGGABLE_CSTR(conn.bda));
+    btif_hh_device_t* p_dev = btif_hh_find_connected_dev_by_handle(conn.handle);
+    if (p_dev != nullptr) {
+      p_dev->dev_status = BTHH_CONN_STATE_DISCONNECTED;
+    }
+
+    hh_connect_complete(conn.handle, conn.bda, BTIF_HH_DEV_DISCONNECTED);
+    return;
+  }
+
   HAL_CBACK(bt_hh_callbacks, connection_state_cb, (RawAddress*)&conn.bda,
             BTHH_CONN_STATE_CONNECTING);
   btif_hh_cb.pending_conn_address = RawAddress::kEmpty;
@@ -454,7 +501,8 @@ static void hh_open_handler(tBTA_HH_CONN& conn) {
  *
  * Returns          true if add successfully, otherwise false.
  ******************************************************************************/
-bool btif_hh_add_added_dev(const RawAddress& bda, tBTA_HH_ATTR_MASK attr_mask) {
+bool btif_hh_add_added_dev(const RawAddress& bda, tBTA_HH_ATTR_MASK attr_mask,
+                           bool reconnect_allowed) {
   int i;
   for (i = 0; i < BTIF_HH_MAX_ADDED_DEV; i++) {
     if (btif_hh_cb.added_devices[i].bd_addr == bda) {
@@ -464,10 +512,12 @@ bool btif_hh_add_added_dev(const RawAddress& bda, tBTA_HH_ATTR_MASK attr_mask) {
   }
   for (i = 0; i < BTIF_HH_MAX_ADDED_DEV; i++) {
     if (btif_hh_cb.added_devices[i].bd_addr.IsEmpty()) {
-      LOG(WARNING) << " Added device " << ADDRESS_TO_LOGGABLE_STR(bda);
+      LOG(WARNING) << " Added device " << ADDRESS_TO_LOGGABLE_STR(bda)
+                   << " reconnection allowed: " << reconnect_allowed;
       btif_hh_cb.added_devices[i].bd_addr = bda;
       btif_hh_cb.added_devices[i].dev_handle = BTA_HH_INVALID_HANDLE;
       btif_hh_cb.added_devices[i].attr_mask = attr_mask;
+      btif_hh_cb.added_devices[i].reconnect_allowed = reconnect_allowed;
       return true;
     }
   }
@@ -1025,7 +1075,7 @@ static void btif_hh_upstreams_evt(uint16_t event, char* p_param) {
                                 p_data->dscp_info.version,
                                 p_data->dscp_info.ctry_code, len,
                                 p_data->dscp_info.descriptor.dsc_list);
-        if (btif_hh_add_added_dev(p_dev->bd_addr, p_dev->attr_mask)) {
+        if (btif_hh_add_added_dev(p_dev->bd_addr, p_dev->attr_mask, true)) {
           tBTA_HH_DEV_DSCP_INFO dscp_info;
           bt_status_t ret;
           btif_hh_copy_hid_info(&dscp_info, &p_data->dscp_info);
@@ -1042,6 +1092,8 @@ static void btif_hh_upstreams_evt(uint16_t event, char* p_param) {
               p_data->dscp_info.ssr_min_tout, len,
               p_data->dscp_info.descriptor.dsc_list);
 
+          btif_storage_set_hid_connection_policy(p_dev->bd_addr, true);
+
           ASSERTC(ret == BT_STATUS_SUCCESS, "storing hid info failed", ret);
           BTIF_TRACE_WARNING("BTA_HH_GET_DSCP_EVT: Called add device");
 
@@ -1341,12 +1393,20 @@ static bt_status_t connect(RawAddress* bd_addr) {
     return BT_STATUS_NOT_READY;
   }
 
+  /* If the device was already added, ensure that reconnections are allowed */
+  btif_hh_added_device_t* added_dev = btif_hh_find_added_dev(*bd_addr);
+  if (added_dev != nullptr && !added_dev->reconnect_allowed) {
+    added_dev->reconnect_allowed = true;
+    btif_storage_set_hid_connection_policy(*bd_addr, true);
+  }
+
   p_dev = btif_hh_find_connected_dev_by_bda(*bd_addr);
   if (p_dev) {
     if (p_dev->dev_status == BTHH_CONN_STATE_CONNECTED ||
         p_dev->dev_status == BTHH_CONN_STATE_CONNECTING) {
       BTIF_TRACE_ERROR("%s: Error, device %s already connected.", __func__,
                        ADDRESS_TO_LOGGABLE_CSTR(*bd_addr));
+
       return BT_STATUS_DONE;
     } else if (p_dev->dev_status == BTHH_CONN_STATE_DISCONNECTING) {
       BTIF_TRACE_ERROR("%s: Error, device %s is busy with (dis)connecting.",
@@ -1368,7 +1428,7 @@ static bt_status_t connect(RawAddress* bd_addr) {
  * Returns         bt_status_t
  *
  ******************************************************************************/
-static bt_status_t disconnect(RawAddress* bd_addr) {
+static bt_status_t disconnect(RawAddress* bd_addr, bool reconnect_allowed) {
   CHECK_BTHH_INIT();
   BTIF_TRACE_EVENT("BTHH: %s", __func__);
   btif_hh_device_t* p_dev;
@@ -1380,6 +1440,16 @@ static bt_status_t disconnect(RawAddress* bd_addr) {
     return BT_STATUS_UNHANDLED;
   }
 
+  if (!reconnect_allowed) {
+    LOG_INFO("Incoming reconnections disabled for device %s",
+             ADDRESS_TO_LOGGABLE_CSTR(*bd_addr));
+    btif_hh_added_device_t* added_dev = btif_hh_find_added_dev(*bd_addr);
+    if (added_dev != nullptr && added_dev->reconnect_allowed) {
+      added_dev->reconnect_allowed = false;
+      btif_storage_set_hid_connection_policy(added_dev->bd_addr, false);
+    }
+  }
+
   p_dev = btif_hh_find_connected_dev_by_bda(*bd_addr);
   if (!p_dev) {
     BTIF_TRACE_ERROR("%s: Error, device %s not opened.", __func__,
@@ -1524,9 +1594,10 @@ static bt_status_t set_info(RawAddress* bd_addr, bthh_hid_info_t hid_info) {
       (uint8_t*)osi_malloc(dscp_info.descriptor.dl_len);
   memcpy(dscp_info.descriptor.dsc_list, &(hid_info.dsc_list), hid_info.dl_len);
 
-  if (btif_hh_add_added_dev(*bd_addr, hid_info.attr_mask)) {
+  if (btif_hh_add_added_dev(*bd_addr, hid_info.attr_mask, true)) {
     BTA_HhAddDev(*bd_addr, hid_info.attr_mask, hid_info.sub_class,
                  hid_info.app_id, dscp_info);
+    btif_storage_set_hid_connection_policy(*bd_addr, true);
   }
 
   osi_free_and_reset((void**)&dscp_info.descriptor.dsc_list);
diff --git a/system/btif/src/btif_profile_storage.cc b/system/btif/src/btif_profile_storage.cc
index eb423ba06da..92ad612c9f1 100644
--- a/system/btif/src/btif_profile_storage.cc
+++ b/system/btif/src/btif_profile_storage.cc
@@ -80,6 +80,7 @@ using bluetooth::groups::DeviceGroups;
 #define BTIF_STORAGE_LEAUDIO_SOURCE_SUPPORTED_CONTEXT_TYPE \
   "SourceSupportedContextType"
 #define BTIF_STORAGE_DEVICE_GROUP_BIN "DeviceGroupBin"
+#define BTIF_STORAGE_KEY_HID_RECONNECT_ALLOWED "HidReConnectAllowed"
 
 #define STORAGE_HID_ATRR_MASK_SIZE (4)
 #define STORAGE_HID_SUB_CLASS_SIZE (2)
@@ -104,6 +105,49 @@ using bluetooth::groups::DeviceGroups;
    STORAGE_HID_VERSION_SIZE + 1 + STORAGE_HID_CTRY_CODE_SIZE + 1 +    \
    STORAGE_HID_DESC_LEN_SIZE + 1 + STORAGE_HID_DESC_MAX_SIZE + 1)
 
+/*******************************************************************************
+ *
+ * Function         btif_storage_set_hid_connection_policy
+ *
+ * Description      Stores connection policy info in nvram
+ *
+ * Returns          BT_STATUS_SUCCESS
+ *
+ ******************************************************************************/
+bt_status_t btif_storage_set_hid_connection_policy(const RawAddress& addr,
+                                                   bool reconnect_allowed) {
+  std::string bdstr = addr.ToString();
+
+  if (btif_config_set_int(bdstr, BTIF_STORAGE_KEY_HID_RECONNECT_ALLOWED,
+                          reconnect_allowed)) {
+    return BT_STATUS_SUCCESS;
+  } else {
+    return BT_STATUS_FAIL;
+  }
+}
+
+/*******************************************************************************
+ *
+ * Function         btif_storage_get_hid_connection_policy
+ *
+ * Description      get connection policy info from nvram
+ *
+ * Returns          BT_STATUS_SUCCESS
+ *
+ ******************************************************************************/
+bt_status_t btif_storage_get_hid_connection_policy(const RawAddress& addr,
+                                                   bool* reconnect_allowed) {
+  std::string bdstr = addr.ToString();
+
+  // For backward compatibility, assume that the reconnection is allowed in the
+  // absence of the key
+  int value = 1;
+  btif_config_get_int(bdstr, BTIF_STORAGE_KEY_HID_RECONNECT_ALLOWED, &value);
+  *reconnect_allowed = (value != 0);
+
+  return BT_STATUS_SUCCESS;
+}
+
 /*******************************************************************************
  *
  * Function         btif_storage_add_hid_device_info
@@ -198,8 +242,11 @@ bt_status_t btif_storage_load_bonded_hid_info(void) {
                           (uint8_t*)dscp_info.descriptor.dsc_list, &len);
     }
 
+    bool reconnect_allowed = false;
+    btif_storage_get_hid_connection_policy(bd_addr, &reconnect_allowed);
+
     // add extracted information to BTA HH
-    if (btif_hh_add_added_dev(bd_addr, attr_mask)) {
+    if (btif_hh_add_added_dev(bd_addr, attr_mask, reconnect_allowed)) {
       BTA_HhAddDev(bd_addr, attr_mask, sub_class, app_id, dscp_info);
     }
   }
@@ -231,6 +278,7 @@ bt_status_t btif_storage_remove_hid_info(const RawAddress& remote_bd_addr) {
   btif_config_remove(bdstr, "HidSSRMaxLatency");
   btif_config_remove(bdstr, "HidSSRMinTimeout");
   btif_config_remove(bdstr, "HidDescriptor");
+  btif_config_remove(bdstr, BTIF_STORAGE_KEY_HID_RECONNECT_ALLOWED);
   return BT_STATUS_SUCCESS;
 }
 
diff --git a/system/gd/rust/linux/stack/src/bluetooth.rs b/system/gd/rust/linux/stack/src/bluetooth.rs
index 910d04af784..bec54c5f441 100644
--- a/system/gd/rust/linux/stack/src/bluetooth.rs
+++ b/system/gd/rust/linux/stack/src/bluetooth.rs
@@ -2446,7 +2446,7 @@ impl IBluetooth for Bluetooth {
                     if UuidHelper::is_profile_supported(&p) {
                         match p {
                             Profile::Hid | Profile::Hogp => {
-                                self.hh.as_ref().unwrap().disconnect(&mut addr.unwrap());
+                                self.hh.as_ref().unwrap().disconnect(&mut addr.unwrap(), true);
                             }
 
                             Profile::A2dpSink
@@ -2574,7 +2574,7 @@ impl BtifHHCallbacks for Bluetooth {
                 "[{}]: Rejecting a unbonded device's attempt to connect to HID/HOG profiles",
                 DisplayAddress(&address)
             );
-            self.hh.as_ref().unwrap().disconnect(&mut address);
+            self.hh.as_ref().unwrap().disconnect(&mut address, true);
         }
     }
 
diff --git a/system/gd/rust/topshim/src/profiles/hid_host.rs b/system/gd/rust/topshim/src/profiles/hid_host.rs
index ce60d93354c..76c5c685c46 100644
--- a/system/gd/rust/topshim/src/profiles/hid_host.rs
+++ b/system/gd/rust/topshim/src/profiles/hid_host.rs
@@ -238,7 +238,7 @@ impl HidHost {
     #[profile_enabled_or(BtStatus::NotReady)]
     pub fn disconnect(&self, addr: &mut RawAddress) -> BtStatus {
         let addr_ptr = LTCheckedPtrMut::from_ref(addr);
-        BtStatus::from(ccall!(self, disconnect, addr_ptr.into()))
+        BtStatus::from(ccall!(self, disconnect, addr_ptr.into(), true))
     }
 
     #[profile_enabled_or(BtStatus::NotReady)]
diff --git a/system/include/hardware/bt_hh.h b/system/include/hardware/bt_hh.h
index f6e6e83abd4..0700ed4a204 100644
--- a/system/include/hardware/bt_hh.h
+++ b/system/include/hardware/bt_hh.h
@@ -179,7 +179,7 @@ typedef struct {
   bt_status_t (*connect)(RawAddress* bd_addr);
 
   /** dis-connect from hid device */
-  bt_status_t (*disconnect)(RawAddress* bd_addr);
+  bt_status_t (*disconnect)(RawAddress* bd_addr, bool reconnect_allowed);
 
   /** Virtual UnPlug (VUP) the specified HID device */
   bt_status_t (*virtual_unplug)(RawAddress* bd_addr);
-- 
GitLab