Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 0a5dbb60 authored by Jeff Sharkey's avatar Jeff Sharkey
Browse files

Revision of denylist filtering.

Since the denylist is designed to match both Bluetooth Classic and LE
devices, this change shifts the DeviceConfig logic to a central place
in AdapterService.  Then the Classic and LE stacks reach back into
AdapterService to obtain the latest Predicates each time they perform
their own filtering.

It might seem inefficient to obtain the detailed Predicates each time,
but the mDeviceConfigLock will have low contention.

This change is a no-op due the disabled feature flag.  Also apply
filtering to LE bulk results, which were previously missed.

Bug: 181812624
Tag: #feature
Test: atest BluetoothInstrumentationTests
Change-Id: Ia2482aca2eb149ca5db676e979ae011e1f5236f7
parent fbe3410b
Loading
Loading
Loading
Loading
+81 −1
Original line number Diff line number Diff line
@@ -28,6 +28,7 @@ import static com.android.bluetooth.Utils.enforceLocalMacAddressPermission;
import static com.android.bluetooth.Utils.hasBluetoothPrivilegedPermission;
import static com.android.bluetooth.Utils.isPackageNameAccurate;

import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityManager;
import android.app.AlarmManager;
@@ -63,6 +64,7 @@ import android.os.AsyncTask;
import android.os.BatteryStats;
import android.os.Binder;
import android.os.Bundle;
import android.os.BytesMatcher;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
@@ -109,8 +111,10 @@ import com.android.bluetooth.sap.SapService;
import com.android.bluetooth.sdp.SdpManager;
import com.android.bluetooth.telephony.BluetoothInCallService;
import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.app.IBatteryStats;
import com.android.internal.os.BackgroundThread;
import com.android.internal.util.ArrayUtils;

import com.google.protobuf.InvalidProtocolBufferException;
@@ -125,6 +129,8 @@ import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.function.Predicate;
import java.util.regex.Pattern;

public class AdapterService extends Service {
    private static final String TAG = "BluetoothAdapterService";
@@ -478,6 +484,7 @@ public class AdapterService extends Service {
    public void onCreate() {
        super.onCreate();
        debugLog("onCreate()");
        mDeviceConfigListener.start();
        mRemoteDevices = new RemoteDevices(this, Looper.getMainLooper());
        mRemoteDevices.init();
        clearDiscoveringPackages();
@@ -2421,7 +2428,8 @@ public class AdapterService extends Service {
        }

        synchronized (mDiscoveringPackages) {
            mDiscoveringPackages.add(new DiscoveringPackage(callingPackage, permission));
            mDiscoveringPackages.add(
                    new DiscoveringPackage(callingPackage, permission, hasDisavowedLocation));
        }
        return startDiscoveryNative();
    }
@@ -3435,6 +3443,78 @@ public class AdapterService extends Service {
        return initFlags.toArray(new String[0]);
    }

    private final Object mDeviceConfigLock = new Object();

    /**
     * Predicate that can be applied to names to determine if a device is
     * well-known to be used for physical location.
     */
    @GuardedBy("mDeviceConfigLock")
    private Predicate<String> mLocationDenylistName = (v) -> false;

    /**
     * Predicate that can be applied to MAC addresses to determine if a device
     * is well-known to be used for physical location.
     */
    @GuardedBy("mDeviceConfigLock")
    private Predicate<byte[]> mLocationDenylistMac = (v) -> false;

    /**
     * Predicate that can be applied to Advertising Data payloads to determine
     * if a device is well-known to be used for physical location.
     */
    @GuardedBy("mDeviceConfigLock")
    private Predicate<byte[]> mLocationDenylistAdvertisingData = (v) -> false;

    public @NonNull Predicate<String> getLocationDenylistName() {
        synchronized (mDeviceConfigLock) {
            return mLocationDenylistName;
        }
    }

    public @NonNull Predicate<byte[]> getLocationDenylistMac() {
        synchronized (mDeviceConfigLock) {
            return mLocationDenylistMac;
        }
    }

    public @NonNull Predicate<byte[]> getLocationDenylistAdvertisingData() {
        synchronized (mDeviceConfigLock) {
            return mLocationDenylistAdvertisingData;
        }
    }

    private final DeviceConfigListener mDeviceConfigListener = new DeviceConfigListener();

    private class DeviceConfigListener implements DeviceConfig.OnPropertiesChangedListener {
        private static final String LOCATION_DENYLIST_NAME =
                "location_denylist_name";
        private static final String LOCATION_DENYLIST_MAC =
                "location_denylist_mac";
        private static final String LOCATION_DENYLIST_ADVERTISING_DATA =
                "location_denylist_advertising_data";

        public void start() {
            DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_BLUETOOTH,
                    BackgroundThread.getExecutor(), this);
            onPropertiesChanged(DeviceConfig.getProperties(DeviceConfig.NAMESPACE_BLUETOOTH));
        }

        @Override
        public void onPropertiesChanged(DeviceConfig.Properties properties) {
            synchronized (mDeviceConfigLock) {
                final String name = properties.getString(LOCATION_DENYLIST_NAME, null);
                mLocationDenylistName = !TextUtils.isEmpty(name)
                        ? Pattern.compile(name).asPredicate()
                        : (v) -> false;
                mLocationDenylistMac = BytesMatcher
                        .decode(properties.getString(LOCATION_DENYLIST_MAC, null));
                mLocationDenylistAdvertisingData = BytesMatcher
                        .decode(properties.getString(LOCATION_DENYLIST_ADVERTISING_DATA, null));
            }
        }
    }

    /**
     *  Obfuscate Bluetooth MAC address into a PII free ID string
     *
+13 −8
Original line number Diff line number Diff line
@@ -16,25 +16,30 @@

package com.android.bluetooth.btservice;

import android.annotation.NonNull;
import android.annotation.Nullable;

final class DiscoveringPackage {
    private String mPackageName;
    private @NonNull String mPackageName;
    private @Nullable String mPermission;
    private boolean mHasDisavowedLocation;

    @Nullable
    private String mPermission;

    DiscoveringPackage(String packageName, String permission) {
    DiscoveringPackage(@NonNull String packageName, @Nullable String permission,
            boolean hasDisavowedLocation) {
        mPackageName = packageName;
        mPermission = permission;
        mHasDisavowedLocation = hasDisavowedLocation;
    }

    public String getPackageName() {
    public @NonNull String getPackageName() {
        return mPackageName;
    }

    @Nullable
    public String getPermission() {
    public @Nullable String getPermission() {
        return mPermission;
    }

    public boolean hasDisavowedLocation() {
        return mHasDisavowedLocation;
    }
}
+26 −0
Original line number Diff line number Diff line
@@ -29,6 +29,7 @@ import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.net.MacAddress;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
@@ -48,6 +49,7 @@ import java.util.HashSet;
import java.util.LinkedList;
import java.util.Queue;
import java.util.Set;
import java.util.function.Predicate;

final class RemoteDevices {
    private static final boolean DBG = false;
@@ -113,6 +115,24 @@ final class RemoteDevices {
        }
    };

    /**
     * Predicate that tests if the given {@link BluetoothDevice} is well-known
     * to be used for physical location.
     */
    private final Predicate<BluetoothDevice> mLocationDenylistPredicate = (device) -> {
        final MacAddress parsedAddress = MacAddress.fromString(device.getAddress());
        if (sAdapterService.getLocationDenylistMac().test(parsedAddress.toByteArray())) {
            Log.v(TAG, "Skipping device matching denylist: " + parsedAddress);
            return true;
        }
        final String name = device.getName();
        if (sAdapterService.getLocationDenylistName().test(name)) {
            Log.v(TAG, "Skipping name matching denylist: " + name);
            return true;
        }
        return false;
    };

    RemoteDevices(AdapterService service, Looper looper) {
        sAdapter = BluetoothAdapter.getDefaultAdapter();
        sAdapterService = service;
@@ -602,6 +622,12 @@ final class RemoteDevices {
        final ArrayList<DiscoveringPackage> packages = sAdapterService.getDiscoveringPackages();
        synchronized (packages) {
            for (DiscoveringPackage pkg : packages) {
                if (pkg.hasDisavowedLocation()) {
                    if (mLocationDenylistPredicate.test(device)) {
                        continue;
                    }
                }

                intent.setPackage(pkg.getPackageName());

                String[] perms;
+24 −56
Original line number Diff line number Diff line
@@ -50,7 +50,6 @@ import android.content.Context;
import android.content.Intent;
import android.net.MacAddress;
import android.os.Binder;
import android.os.BytesMatcher;
import android.os.IBinder;
import android.os.ParcelUuid;
import android.os.RemoteException;
@@ -58,7 +57,6 @@ import android.os.ServiceManager;
import android.os.SystemClock;
import android.os.UserHandle;
import android.os.WorkSource;
import android.provider.DeviceConfig;
import android.provider.Settings;
import android.util.Log;

@@ -69,9 +67,7 @@ import com.android.bluetooth.btservice.AbstractionLayer;
import com.android.bluetooth.btservice.AdapterService;
import com.android.bluetooth.btservice.ProfileService;
import com.android.bluetooth.util.NumberUtils;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.os.BackgroundThread;

import java.util.ArrayDeque;
import java.util.ArrayList;
@@ -84,6 +80,7 @@ import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.function.Predicate;

/**
 * Provides Bluetooth Gatt profile, as a service in
@@ -195,46 +192,23 @@ public class GattService extends ProfileService {
    private ICompanionDeviceManager mCompanionManager;
    private String mExposureNotificationPackage;

    private final Object mDeviceConfigLock = new Object();

    /**
     * Matcher that can be applied to MAC addresses to determine if a
     * {@link BluetoothDevice} is well-known to be used for physical location.
     */
    @GuardedBy("mDeviceConfigLock")
    private BytesMatcher mLocationDenylistMac = new BytesMatcher();

    /**
     * Matcher that can be applied to Advertising Data payloads to determine if
     * a {@link ScanRecord} is well-known to be used for physical location.
     */
    @GuardedBy("mDeviceConfigLock")
    private BytesMatcher mLocationDenylistAdvertisingData = new BytesMatcher();

    private final DeviceConfigListener mDeviceConfigListener = new DeviceConfigListener();

    private class DeviceConfigListener implements DeviceConfig.OnPropertiesChangedListener {
        private static final String LOCATION_DENYLIST_MAC =
                "location_denylist_mac";
        private static final String LOCATION_DENYLIST_ADVERTISING_DATA =
                "location_denylist_advertising_data";

        public void start() {
            DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_BLUETOOTH,
                    BackgroundThread.getExecutor(), this);
            onPropertiesChanged(DeviceConfig.getProperties(DeviceConfig.NAMESPACE_BLUETOOTH));
        }

        @Override
        public void onPropertiesChanged(DeviceConfig.Properties properties) {
            synchronized (mDeviceConfigLock) {
                mLocationDenylistMac = BytesMatcher
                        .decode(properties.getString(LOCATION_DENYLIST_MAC, null));
                mLocationDenylistAdvertisingData = BytesMatcher
                        .decode(properties.getString(LOCATION_DENYLIST_ADVERTISING_DATA, null));
            }
    private final Predicate<ScanResult> mLocationDenylistPredicate = (scanResult) -> {
        final AdapterService adapterService = AdapterService.getAdapterService();
        final MacAddress parsedAddress = MacAddress
                .fromString(scanResult.getDevice().getAddress());
        if (adapterService.getLocationDenylistMac().test(parsedAddress.toByteArray())) {
            Log.v(TAG, "Skipping device matching denylist: " + parsedAddress);
            return true;
        }
        final ScanRecord scanRecord = scanResult.getScanRecord();
        if (scanRecord.matchesAnyField(adapterService.getLocationDenylistAdvertisingData())) {
            Log.v(TAG, "Skipping data matching denylist: " + scanRecord);
            return true;
        }
        return false;
    };

    private static GattService sGattService;

@@ -261,8 +235,6 @@ public class GattService extends ProfileService {
        Settings.Global.putInt(
                getContentResolver(), "bluetooth_sanitized_exposure_notification_supported", 1);

        mDeviceConfigListener.start();

        initializeNative();
        mAdapter = BluetoothAdapter.getDefaultAdapter();
        mCompanionManager = ICompanionDeviceManager.Stub.asInterface(
@@ -1104,25 +1076,17 @@ public class GattService extends ProfileService {
            }

            ScanRecord scanRecord = ScanRecord.parseFromBytes(scanRecordData);
            ScanResult result =
                    new ScanResult(device, eventType, primaryPhy, secondaryPhy, advertisingSid,
                            txPower, rssi, periodicAdvInt, scanRecord,
                            SystemClock.elapsedRealtimeNanos());

            if (client.hasDisavowedLocation) {
                synchronized (mDeviceConfigLock) {
                    final MacAddress parsedAddress = MacAddress.fromString(address);
                    if (mLocationDenylistMac.testMacAddress(parsedAddress)) {
                        Log.v(TAG, "Skipping device matching denylist: " + parsedAddress);
                        continue;
                    }
                    if (scanRecord.matchesAnyField(mLocationDenylistAdvertisingData)) {
                        Log.v(TAG, "Skipping data matching denylist: " + scanRecord);
                if (mLocationDenylistPredicate.test(result)) {
                    continue;
                }
            }
            }

            ScanResult result =
                    new ScanResult(device, eventType, primaryPhy, secondaryPhy, advertisingSid,
                            txPower, rssi, periodicAdvInt, scanRecord,
                            SystemClock.elapsedRealtimeNanos());
            boolean hasPermission = hasScanResultPermission(client);
            if (!hasPermission) {
                for (String associatedDevice : client.associatedDevices) {
@@ -1751,6 +1715,10 @@ public class GattService extends ProfileService {
                }
            }

            if (client.hasDisavowedLocation) {
                permittedResults.removeIf(mLocationDenylistPredicate);
            }

            if (app.callback != null) {
                app.callback.onBatchScanResults(permittedResults);
            } else {