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

Commit eaf0ae01 authored by Ajay Panicker's avatar Ajay Panicker Committed by android-build-merger
Browse files

Add protection against LE scanning abuse

am: ed2ff608

* commit 'ed2ff608':
  Add protection against LE scanning abuse

Change-Id: Ifb57541579eed30d4d34d3ec407987c2ea02d56e
parents ce731001 ed2ff608
Loading
Loading
Loading
Loading
+28 −0
Original line number Diff line number Diff line
@@ -42,6 +42,7 @@ import com.android.bluetooth.btservice.BluetoothProto;
        long duration;
        long timestamp;
        boolean opportunistic;
        boolean timeout;
        boolean background;
        int results;

@@ -57,6 +58,12 @@ import com.android.bluetooth.btservice.BluetoothProto;

    static final int NUM_SCAN_DURATIONS_KEPT = 5;

    // This constant defines the time window an app can scan multiple times.
    // Any single app can scan up to |NUM_SCAN_DURATIONS_KEPT| times during
    // this window. Once they reach this limit, they must wait until their
    // earliest recorded scan exits this window.
    static final long EXCESSIVE_SCANNING_PERIOD_MS = 30 * 1000;

    String appName;
    int scansStarted = 0;
    int scansStopped = 0;
@@ -134,6 +141,25 @@ import com.android.bluetooth.btservice.BluetoothProto;
        gattService.addScanEvent(scanEvent);
    }

    synchronized void setScanTimeout() {
        if (!isScanning)
          return;

        if (!lastScans.isEmpty()) {
            LastScan curr = lastScans.get(lastScans.size() - 1);
            curr.timeout = true;
        }
    }

    synchronized boolean isScanningTooFrequently() {
        if (lastScans.size() < NUM_SCAN_DURATIONS_KEPT) {
            return false;
        }

        return (System.currentTimeMillis() - lastScans.get(0).timestamp) <
            EXCESSIVE_SCANNING_PERIOD_MS;
    }

    // This function truncates the app name for privacy reasons. Apps with
    // four part package names or more get truncated to three parts, and apps
    // with three part package names names get truncated to two. Apps with two
@@ -183,6 +209,7 @@ import com.android.bluetooth.btservice.BluetoothProto;
        if (isRegistered) sb.append(" (Registered)");
        if (lastScan.opportunistic) sb.append(" (Opportunistic)");
        if (lastScan.background) sb.append(" (Background)");
        if (lastScan.timeout) sb.append(" (Forced-Opportunistic)");
        sb.append("\n");

        sb.append("  LE scans (started/stopped)         : " +
@@ -209,6 +236,7 @@ import com.android.bluetooth.btservice.BluetoothProto;
                sb.append(scan.duration + "ms ");
                if (scan.opportunistic) sb.append("Opp ");
                if (scan.background) sb.append("Back ");
                if (scan.timeout) sb.append("Forced ");
                sb.append(scan.results + " results");
                sb.append("\n");
            }
+11 −1
Original line number Diff line number Diff line
@@ -66,6 +66,7 @@ import java.util.Set;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

import static android.content.pm.PackageManager.PERMISSION_GRANTED;
/**
 * Provides Bluetooth Gatt profile, as a service in
 * the Bluetooth application.
@@ -1309,7 +1310,16 @@ public class GattService extends ProfileService {
        } else {
            app = mClientMap.getAppScanStatsById(appIf);
        }
        if (app != null) app.recordScanStart(settings);

        if (app != null) {
            if (app.isScanningTooFrequently() &&
                checkCallingOrSelfPermission(BLUETOOTH_PRIVILEGED) != PERMISSION_GRANTED) {
                Log.e(TAG, "App '" + app.appName + "' is scanning too frequently");
                return;
            }
            scanClient.stats = app;
            app.recordScanStart(settings);
        }

        mScanManager.startScan(scanClient);
    }
+2 −0
Original line number Diff line number Diff line
@@ -47,6 +47,8 @@ import java.util.UUID;
    // Who is responsible for this scan.
    WorkSource workSource;

    AppScanStats stats = null;

    private static final ScanSettings DEFAULT_SCAN_SETTINGS = new ScanSettings.Builder()
            .setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY).build();

+54 −0
Original line number Diff line number Diff line
@@ -66,6 +66,10 @@ public class ScanManager {
    private static final int MSG_START_BLE_SCAN = 0;
    private static final int MSG_STOP_BLE_SCAN = 1;
    private static final int MSG_FLUSH_BATCH_RESULTS = 2;
    private static final int MSG_SCAN_TIMEOUT = 3;

    // Maximum msec before scan gets downgraded to opportunistic
    private static final int SCAN_TIMEOUT_MS = 5 * 60 * 1000;

    private static final String ACTION_REFRESH_BATCHED_SCAN =
            "com.android.bluetooth.gatt.REFRESH_BATCHED_SCAN";
@@ -192,6 +196,9 @@ public class ScanManager {
                case MSG_FLUSH_BATCH_RESULTS:
                    handleFlushBatchResults(client);
                    break;
                case MSG_SCAN_TIMEOUT:
                    mScanNative.regularScanTimeout();
                    break;
                default:
                    // Shouldn't happen.
                    Log.e(TAG, "received an unkown message : " + msg.what);
@@ -220,6 +227,14 @@ public class ScanManager {
                mScanNative.startRegularScan(client);
                if (!mScanNative.isOpportunisticScanClient(client)) {
                    mScanNative.configureRegularScanParams();

                    if (!mScanNative.isFirstMatchScanClient(client)) {
                        Message msg = mHandler.obtainMessage(MSG_SCAN_TIMEOUT);
                        msg.obj = client;
                        // Only one timeout message should exist at any time
                        mHandler.removeMessages(SCAN_TIMEOUT_MS);
                        mHandler.sendMessageDelayed(msg, SCAN_TIMEOUT_MS);
                    }
                }

                // Update BatteryStats with this workload.
@@ -242,6 +257,11 @@ public class ScanManager {
                if (client == null) return;

                mScanNative.stopRegularScan(client);

                if (mScanNative.numRegularScanClients() == 0) {
                    mHandler.removeMessages(MSG_SCAN_TIMEOUT);
                }

                if (!mScanNative.isOpportunisticScanClient(client)) {
                    mScanNative.configureRegularScanParams();
                }
@@ -508,6 +528,10 @@ public class ScanManager {
            return client.settings.getScanMode() == ScanSettings.SCAN_MODE_OPPORTUNISTIC;
        }

        private boolean isFirstMatchScanClient(ScanClient client) {
            return (client.settings.getCallbackType() & ScanSettings.CALLBACK_TYPE_FIRST_MATCH) != 0;
        }

        private void resetBatchScan(ScanClient client) {
            int clientIf = client.clientIf;
            BatchScanParams batchScanParams = getBatchScanParams();
@@ -647,6 +671,36 @@ public class ScanManager {
            removeScanFilters(client.clientIf);
        }

        void regularScanTimeout() {
            for (ScanClient client : mRegularScanClients) {
                if (!isOpportunisticScanClient(client) && !isFirstMatchScanClient(client)) {
                    logd("clientIf set to scan opportunisticly: " + client.clientIf);
                    setOpportunisticScanClient(client);
                    client.stats.setScanTimeout();
                }
            }

            // The scan should continue for background scans
            configureRegularScanParams();
            if (numRegularScanClients() == 0) {
                logd("stop scan");
                gattClientScanNative(false);
            }
        }

        void setOpportunisticScanClient(ScanClient client) {
            // TODO: Add constructor to ScanSettings.Builder
            // that can copy values from an existing ScanSettings object
            ScanSettings.Builder builder = new ScanSettings.Builder();
            ScanSettings settings = client.settings;
            builder.setScanMode(ScanSettings.SCAN_MODE_OPPORTUNISTIC);
            builder.setCallbackType(settings.getCallbackType());
            builder.setScanResultType(settings.getScanResultType());
            builder.setReportDelay(settings.getReportDelayMillis());
            builder.setNumOfMatches(settings.getNumOfMatches());
            client.settings = builder.build();
        }

        // Find the regular scan client information.
        ScanClient getRegularScanClient(int clientIf) {
            for (ScanClient client : mRegularScanClients) {