[RTT2] limit max number of outstanding requests per UID
Prevent spamming of service. Limit number of oustanding requests to
a large enough number that no correct use of the API will reach.
Bug: 65015291
Test: unit tests and integration tests
Change-Id: I675227911994f145ecea6638f049444c34fb031f
diff --git a/service/java/com/android/server/wifi/rtt/RttServiceImpl.java b/service/java/com/android/server/wifi/rtt/RttServiceImpl.java
index e3e6dcf..ff722ae 100644
--- a/service/java/com/android/server/wifi/rtt/RttServiceImpl.java
+++ b/service/java/com/android/server/wifi/rtt/RttServiceImpl.java
@@ -42,8 +42,8 @@
import android.os.UserHandle;
import android.os.WorkSource;
import android.util.Log;
+import android.util.SparseIntArray;
-import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.WakeupMessage;
import com.android.server.wifi.Clock;
import com.android.server.wifi.util.WifiPermissionsUtil;
@@ -77,14 +77,16 @@
private RttServiceSynchronized mRttServiceSynchronized;
- @VisibleForTesting
- public static final String HAL_RANGING_TIMEOUT_TAG = TAG + " HAL Ranging Timeout";
+ /* package */ static final String HAL_RANGING_TIMEOUT_TAG = TAG + " HAL Ranging Timeout";
private static final long HAL_RANGING_TIMEOUT_MS = 5_000; // 5 sec
// TODO: b/69323456 convert to a settable value
/* package */ static final long BACKGROUND_PROCESS_EXEC_GAP_MS = 1_800_000; // 30 min
+ // arbitrary, larger than anything reasonable
+ /* package */ static final int MAX_QUEUED_PER_UID = 20;
+
public RttServiceImpl(Context context) {
mContext = context;
}
@@ -463,6 +465,19 @@
private void queueRangingRequest(int uid, WorkSource workSource, IBinder binder,
IBinder.DeathRecipient dr, String callingPackage, RangingRequest request,
IRttCallback callback) {
+ if (isRequestorSpamming(workSource)) {
+ Log.w(TAG,
+ "Work source " + workSource + " is spamming, dropping request: " + request);
+ binder.unlinkToDeath(dr, 0);
+ try {
+ callback.onRangingFailure(RangingResultCallback.STATUS_CODE_FAIL);
+ } catch (RemoteException e) {
+ Log.e(TAG, "RttServiceSynchronized.queueRangingRequest: spamming, callback "
+ + "failed -- " + e);
+ }
+ return;
+ }
+
RttRequestInfo newRequest = new RttRequestInfo();
newRequest.uid = uid;
newRequest.workSource = workSource;
@@ -480,6 +495,30 @@
executeNextRangingRequestIfPossible(false);
}
+ private boolean isRequestorSpamming(WorkSource ws) {
+ if (VDBG) Log.v(TAG, "isRequestorSpamming: ws" + ws);
+
+ SparseIntArray counts = new SparseIntArray();
+
+ for (RttRequestInfo rri : mRttRequestQueue) {
+ for (int i = 0; i < rri.workSource.size(); ++i) {
+ int uid = rri.workSource.get(i);
+ counts.put(uid, counts.get(uid) + 1);
+ }
+ }
+
+ for (int i = 0; i < ws.size(); ++i) {
+ if (counts.get(ws.get(i)) < MAX_QUEUED_PER_UID) {
+ return false;
+ }
+ }
+
+ if (VDBG) {
+ Log.v(TAG, "isRequestorSpamming: ws=" + ws + ", someone is spamming: " + counts);
+ }
+ return true;
+ }
+
private void executeNextRangingRequestIfPossible(boolean popFirst) {
if (VDBG) Log.v(TAG, "executeNextRangingRequestIfPossible: popFirst=" + popFirst);
diff --git a/tests/wifitests/src/com/android/server/wifi/rtt/RttServiceImplTest.java b/tests/wifitests/src/com/android/server/wifi/rtt/RttServiceImplTest.java
index 01e3750..ee8c15f 100644
--- a/tests/wifitests/src/com/android/server/wifi/rtt/RttServiceImplTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/rtt/RttServiceImplTest.java
@@ -202,6 +202,9 @@
@After
public void tearDown() throws Exception {
+ assertEquals("Binder links != unlinks to death (size)",
+ mBinderLinkToDeathCounter.mUniqueExecs.size(),
+ mBinderUnlinkToDeathCounter.mUniqueExecs.size());
assertEquals("Binder links != unlinks to death", mBinderLinkToDeathCounter.mUniqueExecs,
mBinderUnlinkToDeathCounter.mUniqueExecs);
}
@@ -863,6 +866,125 @@
}
/**
+ * Validate that flooding the service with ranging requests will cause it to start rejecting
+ * rejects from the flooding uid. Single UID.
+ */
+ @Test
+ public void testRejectFloodingRequestsSingleUid() throws Exception {
+ runFloodRequestsTest(true);
+ }
+
+ /**
+ * Validate that flooding the service with ranging requests will cause it to start rejecting
+ * rejects from the flooding uid. WorkSource (all identical).
+ */
+ @Test
+ public void testRejectFloodingRequestsIdenticalWorksources() throws Exception {
+ runFloodRequestsTest(false);
+ }
+
+ /**
+ * Validate that flooding the service with ranging requests will cause it to start rejecting
+ * rejects from the flooding uid. WorkSource (with one constant UID but other varying UIDs -
+ * the varying UIDs should prevent the flood throttle)
+ */
+ @Test
+ public void testDontRejectFloodingRequestsVariousUids() throws Exception {
+ RangingRequest request = RttTestUtils.getDummyRangingRequest((byte) 1);
+ WorkSource ws = new WorkSource(10);
+
+ // 1. issue a request
+ mDut.startRanging(mockIbinder, mPackageName, ws, request, mockCallback);
+ mMockLooper.dispatchAll();
+
+ verify(mockNative).rangeRequest(mIntCaptor.capture(), eq(request));
+ verifyWakeupSet();
+
+ // 2. issue FLOOD LEVEL requests + 10 at various UIDs - no failure expected
+ for (int i = 0; i < RttServiceImpl.MAX_QUEUED_PER_UID + 10; ++i) {
+ WorkSource wsExtra = new WorkSource(ws);
+ wsExtra.add(11 + i);
+ mDut.startRanging(mockIbinder, mPackageName, wsExtra, request, mockCallback);
+ }
+ mMockLooper.dispatchAll();
+
+ // 3. clear queue
+ mDut.disable();
+ mMockLooper.dispatchAll();
+
+ verifyWakeupCancelled();
+ verify(mockNative).rangeCancel(eq(mIntCaptor.getValue()), any());
+ verify(mockCallback, times(RttServiceImpl.MAX_QUEUED_PER_UID + 11)).onRangingFailure(
+ RangingResultCallback.STATUS_CODE_FAIL_RTT_NOT_AVAILABLE);
+
+ verify(mockNative, atLeastOnce()).isReady();
+ verifyNoMoreInteractions(mockNative, mockCallback, mAlarmManager.getAlarmManager());
+ }
+
+ /**
+ * Utility to run configurable tests for flooding range requests.
+ * - Execute a single request
+ * - Flood service with requests: using same ID or same WorkSource
+ * - Provide results (to clear queue) and execute another test: validate succeeds
+ */
+ private void runFloodRequestsTest(boolean useUids) throws Exception {
+ RangingRequest request = RttTestUtils.getDummyRangingRequest((byte) 1);
+ Pair<List<RttResult>, List<RangingResult>> result = RttTestUtils.getDummyRangingResults(
+ request);
+
+ WorkSource ws = new WorkSource();
+ ws.add(10);
+ ws.add(20);
+ ws.add(30);
+
+ InOrder cbInorder = inOrder(mockCallback);
+ InOrder nativeInorder = inOrder(mockNative);
+
+ // 1. issue a request
+ mDut.startRanging(mockIbinder, mPackageName, useUids ? null : ws, request, mockCallback);
+ mMockLooper.dispatchAll();
+
+ nativeInorder.verify(mockNative).rangeRequest(mIntCaptor.capture(), eq(request));
+ verifyWakeupSet();
+
+ // 2. issue FLOOD LEVEL requests + 10: should get 11 failures (10 extra + 1 original)
+ for (int i = 0; i < RttServiceImpl.MAX_QUEUED_PER_UID + 10; ++i) {
+ mDut.startRanging(mockIbinder, mPackageName, useUids ? null : ws, request,
+ mockCallback);
+ }
+ mMockLooper.dispatchAll();
+
+ cbInorder.verify(mockCallback, times(11)).onRangingFailure(
+ RangingResultCallback.STATUS_CODE_FAIL);
+
+ // 3. provide results
+ mDut.onRangingResults(mIntCaptor.getValue(), result.first);
+ mMockLooper.dispatchAll();
+
+ cbInorder.verify(mockCallback).onRangingResults(result.second);
+ verifyWakeupCancelled();
+
+ nativeInorder.verify(mockNative).rangeRequest(mIntCaptor.capture(), eq(request));
+ verifyWakeupSet();
+
+ // 4. issue a request: don't expect a failure
+ mDut.startRanging(mockIbinder, mPackageName, useUids ? null : ws, request, mockCallback);
+ mMockLooper.dispatchAll();
+
+ // 5. clear queue
+ mDut.disable();
+ mMockLooper.dispatchAll();
+
+ verifyWakeupCancelled();
+ nativeInorder.verify(mockNative).rangeCancel(eq(mIntCaptor.getValue()), any());
+ cbInorder.verify(mockCallback, times(RttServiceImpl.MAX_QUEUED_PER_UID)).onRangingFailure(
+ RangingResultCallback.STATUS_CODE_FAIL_RTT_NOT_AVAILABLE);
+
+ verify(mockNative, atLeastOnce()).isReady();
+ verifyNoMoreInteractions(mockNative, mockCallback, mAlarmManager.getAlarmManager());
+ }
+
+ /**
* Validate that when Wi-Fi gets disabled (HAL level) the ranging queue gets cleared.
*/
@Test