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

Commit 5f5053da authored by Treehugger Robot's avatar Treehugger Robot Committed by Gerrit Code Review
Browse files

Merge "Add protection against LE scanning abuse"

parents 90f88533 7be022e7
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.
@@ -1300,7 +1301,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
@@ -43,6 +43,8 @@ import java.util.UUID;
    // Pre-M apps are allowed to get scan results even if location is disabled
    boolean legacyForegroundApp;

    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
@@ -64,6 +64,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";
@@ -188,6 +192,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);
@@ -216,6 +223,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);
                    }
                }
            }
        }
@@ -225,6 +240,11 @@ public class ScanManager {
            if (client == null) return;
            if (mRegularScanClients.contains(client)) {
                mScanNative.stopRegularScan(client);

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

                if (!mScanNative.isOpportunisticScanClient(client)) {
                    mScanNative.configureRegularScanParams();
                }
@@ -484,6 +504,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();
@@ -624,6 +648,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 scan client information
        ScanClient getClient(int clientIf) {
            for (ScanClient client : mRegularScanClients) {