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

Commit ed2ff608 authored by Ajay Panicker's avatar Ajay Panicker
Browse files

Add protection against LE scanning abuse

Added two checks to prevent abuse. The first check ensures that an
app doesn't scan too frequently in a certain time period. It is
allowed to scan again after its oldest scan exceedes said time
period. The second check ensures that an app doesn't scan for too
long. Upon starting a scan, this code waits a certain amount of time.
If the app is still scanning by that point, this code stops
the scan and forces the app to use opportunistic scanning instead.

Bug: 27357274
Change-Id: Ic99ac1f838e15ed99fe2fae643ef073d74b5c96b
parent 7de93b45
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) {