Audio policy: anonymize Bluetooth MAC addresses

Make sure APIs returning audio device descriptors from the native
audioserver anonymize the Bluetooth MAC addresses because those are considered
privacy sensitive.
Only expose the full MAC address to system and apps with BLUETOOTH_CONNECT
permission.

APIs modified: listAudioPorts, listAudioPatches, getAudioPort
APIs that can only be called from system server or only convey port IDs
are not modified.

Bug: 285588444
Test: atest AudioManagerTest
Test: atest RoutingTest
Test: atest AudioCommunicationDeviceTest
Change-Id: Ia6bac184f5f39ed9d538f762ebb89bcceb44ae50
diff --git a/media/audio/aconfig/audio_flags.aconfig b/media/audio/aconfig/audio_flags.aconfig
index 968a065..d1fb6bc 100644
--- a/media/audio/aconfig/audio_flags.aconfig
+++ b/media/audio/aconfig/audio_flags.aconfig
@@ -22,6 +22,15 @@
 }
 
 flag {
+    name: "bluetooth_mac_address_anonymization"
+    namespace: "media_audio"
+    description: "\
+Enable Bluetooth MAC address anonymization when reporting \
+audio device descriptors to non privileged apps."
+    bug: "285588444"
+}
+
+flag {
     name: "mutex_priority_inheritance"
     namespace: "media_audio"
     description: "\
diff --git a/media/audioaidlconversion/AidlConversionCppNdk.cpp b/media/audioaidlconversion/AidlConversionCppNdk.cpp
index 5558259..394d9b5 100644
--- a/media/audioaidlconversion/AidlConversionCppNdk.cpp
+++ b/media/audioaidlconversion/AidlConversionCppNdk.cpp
@@ -1069,6 +1069,13 @@
             if (mac.size() != 6) return BAD_VALUE;
             snprintf(addressBuffer, AUDIO_DEVICE_MAX_ADDRESS_LEN, "%02X:%02X:%02X:%02X:%02X:%02X",
                     mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
+            // special case for anonymized mac address:
+            // change anonymized bytes back from FD:FF:FF:FF to XX:XX:XX:XX
+            std::string address(addressBuffer);
+            if (address.compare(0, strlen("FD:FF:FF:FF"), "FD:FF:FF:FF") == 0) {
+                address.replace(0, strlen("FD:FF:FF:FF"), "XX:XX:XX:XX");
+            }
+            strcpy(addressBuffer, address.c_str());
         } break;
         case Tag::ipv4: {
             const std::vector<uint8_t>& ipv4 = aidl.address.get<AudioDeviceAddress::ipv4>();
@@ -1125,11 +1132,20 @@
     if (!legacyAddress.empty()) {
         switch (suggestDeviceAddressTag(aidl.type)) {
             case Tag::mac: {
+                // special case for anonymized mac address:
+                // change anonymized bytes so that they can be scanned as HEX bytes
+                // Use '01' for LSB bits 0 and 1 as Bluetooth MAC addresses are never multicast
+                // and universaly administered
+                std::string address = legacyAddress;
+                if (address.compare(0, strlen("XX:XX:XX:XX"), "XX:XX:XX:XX") == 0) {
+                    address.replace(0, strlen("XX:XX:XX:XX"), "FD:FF:FF:FF");
+                }
+
                 std::vector<uint8_t> mac(6);
-                int status = sscanf(legacyAddress.c_str(), "%hhX:%hhX:%hhX:%hhX:%hhX:%hhX",
+                int status = sscanf(address.c_str(), "%hhX:%hhX:%hhX:%hhX:%hhX:%hhX",
                         &mac[0], &mac[1], &mac[2], &mac[3], &mac[4], &mac[5]);
                 if (status != mac.size()) {
-                    ALOGE("%s: malformed MAC address: \"%s\"", __func__, legacyAddress.c_str());
+                    ALOGE("%s: malformed MAC address: \"%s\"", __func__, address.c_str());
                     return unexpected(BAD_VALUE);
                 }
                 aidl.address = AudioDeviceAddress::make<AudioDeviceAddress::mac>(std::move(mac));
diff --git a/media/libaudioclient/tests/audio_aidl_legacy_conversion_tests.cpp b/media/libaudioclient/tests/audio_aidl_legacy_conversion_tests.cpp
index 9a46b20..dc37785 100644
--- a/media/libaudioclient/tests/audio_aidl_legacy_conversion_tests.cpp
+++ b/media/libaudioclient/tests/audio_aidl_legacy_conversion_tests.cpp
@@ -481,8 +481,28 @@
                                  AudioDeviceAddress::make<AudioDeviceAddress::Tag::alsa>(
                                          std::vector<int32_t>{1, 2}))));
 
+TEST(AnonymizedBluetoothAddressRoundTripTest, Legacy2Aidl2Legacy) {
+    const std::vector<uint8_t> sAnonymizedAidlAddress =
+            std::vector<uint8_t>{0xFD, 0xFF, 0xFF, 0xFF, 0xAB, 0xCD};
+    const std::string sAnonymizedLegacyAddress = std::string("XX:XX:XX:XX:AB:CD");
+    auto device = legacy2aidl_audio_device_AudioDevice(AUDIO_DEVICE_OUT_BLUETOOTH_A2DP,
+                                                       sAnonymizedLegacyAddress);
+    ASSERT_TRUE(device.ok());
+    ASSERT_EQ(AudioDeviceAddress::Tag::mac, device.value().address.getTag());
+    ASSERT_EQ(sAnonymizedAidlAddress, device.value().address.get<AudioDeviceAddress::mac>());
+
+    audio_devices_t legacyType;
+    std::string legacyAddress;
+    status_t status =
+            aidl2legacy_AudioDevice_audio_device(device.value(), &legacyType, &legacyAddress);
+    ASSERT_EQ(OK, status);
+    EXPECT_EQ(legacyType, AUDIO_DEVICE_OUT_BLUETOOTH_A2DP);
+    EXPECT_EQ(sAnonymizedLegacyAddress, legacyAddress);
+}
+
 class AudioFormatDescriptionRoundTripTest : public testing::TestWithParam<AudioFormatDescription> {
 };
+
 TEST_P(AudioFormatDescriptionRoundTripTest, Aidl2Legacy2Aidl) {
     const auto initial = GetParam();
     auto conv = aidl2legacy_AudioFormatDescription_audio_format_t(initial);
diff --git a/media/utils/Android.bp b/media/utils/Android.bp
index 07dac5e..56b11b5 100644
--- a/media/utils/Android.bp
+++ b/media/utils/Android.bp
@@ -93,6 +93,7 @@
         "liblog",
         "libpermission",
         "libutils",
+        "aconfig_audio_flags_c_lib",
         "android.hardware.graphics.bufferqueue@1.0",
         "android.hidl.token@1.0-utils",
         "packagemanager_aidl-cpp",
diff --git a/media/utils/ServiceUtilities.cpp b/media/utils/ServiceUtilities.cpp
index eef460f..245811b 100644
--- a/media/utils/ServiceUtilities.cpp
+++ b/media/utils/ServiceUtilities.cpp
@@ -27,6 +27,7 @@
 #include <media/AidlConversionUtil.h>
 #include <android/content/AttributionSourceState.h>
 
+#include <com_android_media_audio_flags.h>
 #include <iterator>
 #include <algorithm>
 #include <pwd.h>
@@ -46,6 +47,7 @@
 static const String16 sModifyPhoneState("android.permission.MODIFY_PHONE_STATE");
 static const String16 sModifyAudioRouting("android.permission.MODIFY_AUDIO_ROUTING");
 static const String16 sCallAudioInterception("android.permission.CALL_AUDIO_INTERCEPTION");
+static const String16 sAndroidPermissionBluetoothConnect("android.permission.BLUETOOTH_CONNECT");
 
 static String16 resolveCallingPackage(PermissionController& permissionController,
         const std::optional<String16> opPackageName, uid_t uid) {
@@ -374,6 +376,52 @@
     return NO_ERROR;
 }
 
+/**
+ * Determines if the MAC address in Bluetooth device descriptors returned by APIs of
+ * a native audio service (audio flinger, audio policy) must be anonymized.
+ * MAC addresses returned to system server or apps with BLUETOOTH_CONNECT permission
+ * are not anonymized.
+ *
+ * @param attributionSource The attribution source of the calling app.
+ * @param caller string identifying the caller for logging.
+ * @return true if the MAC addresses must be anonymized, false otherwise.
+ */
+bool mustAnonymizeBluetoothAddress(
+        const AttributionSourceState& attributionSource, const String16& caller) {
+    if (!com::android::media::audio::flags::bluetooth_mac_address_anonymization()) {
+        return false;
+    }
+
+    uid_t uid = VALUE_OR_FATAL(aidl2legacy_int32_t_uid_t(attributionSource.uid));
+    if (isAudioServerOrSystemServerUid(uid)) {
+        return false;
+    }
+    const std::optional<AttributionSourceState> resolvedAttributionSource =
+            resolveAttributionSource(attributionSource);
+    if (!resolvedAttributionSource.has_value()) {
+        return true;
+    }
+    permission::PermissionChecker permissionChecker;
+    return permissionChecker.checkPermissionForPreflightFromDatasource(
+            sAndroidPermissionBluetoothConnect, resolvedAttributionSource.value(), caller,
+            AppOpsManager::OP_BLUETOOTH_CONNECT)
+                != permission::PermissionChecker::PERMISSION_GRANTED;
+}
+
+/**
+ * Modifies the passed MAC address string in place for consumption by unprivileged clients.
+ * the string is assumed to have a valid MAC address format.
+ * the anonymzation must be kept in sync with toAnonymizedAddress() in BluetoothUtils.java
+ *
+ * @param address input/output the char string contining the MAC address to anonymize.
+ */
+void anonymizeBluetoothAddress(char *address) {
+    if (address == nullptr || strlen(address) != strlen("AA:BB:CC:DD:EE:FF")) {
+        return;
+    }
+    memcpy(address, "XX:XX:XX:XX", strlen("XX:XX:XX:XX"));
+}
+
 sp<content::pm::IPackageManagerNative> MediaPackageManager::retrievePackageManager() {
     const sp<IServiceManager> sm = defaultServiceManager();
     if (sm == nullptr) {
diff --git a/media/utils/include/mediautils/ServiceUtilities.h b/media/utils/include/mediautils/ServiceUtilities.h
index 3d7981a..0b3a3f9 100644
--- a/media/utils/include/mediautils/ServiceUtilities.h
+++ b/media/utils/include/mediautils/ServiceUtilities.h
@@ -108,6 +108,10 @@
 bool bypassInterruptionPolicyAllowed(const AttributionSourceState& attributionSource);
 bool callAudioInterceptionAllowed(const AttributionSourceState& attributionSource);
 void purgePermissionCache();
+bool mustAnonymizeBluetoothAddress(
+        const AttributionSourceState& attributionSource, const String16& caller);
+void anonymizeBluetoothAddress(char *address);
+
 int32_t getOpForSource(audio_source_t source);
 
 AttributionSourceState getCallingAttributionSource();
diff --git a/services/audiopolicy/service/AudioPolicyInterfaceImpl.cpp b/services/audiopolicy/service/AudioPolicyInterfaceImpl.cpp
index ce9fb92..509b673 100644
--- a/services/audiopolicy/service/AudioPolicyInterfaceImpl.cpp
+++ b/services/audiopolicy/service/AudioPolicyInterfaceImpl.cpp
@@ -1530,6 +1530,19 @@
     return Status::ok();
 }
 
+template <typename Port>
+void anonymizePortBluetoothAddress(Port *port) {
+    if (port->type != AUDIO_PORT_TYPE_DEVICE) {
+        return;
+    }
+    if (!(audio_is_a2dp_device(port->ext.device.type)
+            || audio_is_ble_device(port->ext.device.type)
+            || audio_is_bluetooth_sco_device(port->ext.device.type)
+            || audio_is_hearing_aid_out_device(port->ext.device.type))) {
+        return;
+    }
+    anonymizeBluetoothAddress(port->ext.device.address);
+}
 
 Status AudioPolicyService::listAudioPorts(media::AudioPortRole roleAidl,
                                           media::AudioPortType typeAidl, Int* count,
@@ -1552,10 +1565,20 @@
     if (mAudioPolicyManager == NULL) {
         return binderStatusFromStatusT(NO_INIT);
     }
+
+    const AttributionSourceState attributionSource = getCallingAttributionSource();
+
     AutoCallerClear acc;
     RETURN_IF_BINDER_ERROR(binderStatusFromStatusT(
             mAudioPolicyManager->listAudioPorts(role, type, &num_ports, ports.get(), &generation)));
     numPortsReq = std::min(numPortsReq, num_ports);
+
+    if (mustAnonymizeBluetoothAddress(attributionSource, String16(__func__))) {
+        for (size_t i = 0; i < numPortsReq; ++i) {
+            anonymizePortBluetoothAddress(&ports[i]);
+        }
+    }
+
     RETURN_IF_BINDER_ERROR(binderStatusFromStatusT(
             convertRange(ports.get(), ports.get() + numPortsReq, std::back_inserter(*portsAidl),
                          legacy2aidl_audio_port_v7_AudioPortFw)));
@@ -1582,8 +1605,16 @@
     if (mAudioPolicyManager == NULL) {
         return binderStatusFromStatusT(NO_INIT);
     }
+
+    const AttributionSourceState attributionSource = getCallingAttributionSource();
+
     AutoCallerClear acc;
     RETURN_IF_BINDER_ERROR(binderStatusFromStatusT(mAudioPolicyManager->getAudioPort(&port)));
+
+    if (mustAnonymizeBluetoothAddress(attributionSource, String16(__func__))) {
+        anonymizePortBluetoothAddress(&port);
+    }
+
     *_aidl_return = VALUE_OR_RETURN_BINDER_STATUS(legacy2aidl_audio_port_v7_AudioPortFw(port));
     return Status::ok();
 }
@@ -1645,10 +1676,25 @@
     if (mAudioPolicyManager == NULL) {
         return binderStatusFromStatusT(NO_INIT);
     }
+
+    const AttributionSourceState attributionSource = getCallingAttributionSource();
+
     AutoCallerClear acc;
     RETURN_IF_BINDER_ERROR(binderStatusFromStatusT(
             mAudioPolicyManager->listAudioPatches(&num_patches, patches.get(), &generation)));
     numPatchesReq = std::min(numPatchesReq, num_patches);
+
+    if (mustAnonymizeBluetoothAddress(attributionSource, String16(__func__))) {
+        for (size_t i = 0; i < numPatchesReq; ++i) {
+            for (size_t j = 0; j < patches[i].num_sources; ++j) {
+                anonymizePortBluetoothAddress(&patches[i].sources[j]);
+            }
+            for (size_t j = 0; j < patches[i].num_sinks; ++j) {
+                anonymizePortBluetoothAddress(&patches[i].sinks[j]);
+            }
+        }
+    }
+
     RETURN_IF_BINDER_ERROR(binderStatusFromStatusT(
             convertRange(patches.get(), patches.get() + numPatchesReq,
                          std::back_inserter(*patchesAidl), legacy2aidl_audio_patch_AudioPatchFw)));