summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Olivier Gaillard <gaillard@google.com> 2018-12-06 12:47:45 +0000
committer Android (Google) Code Review <android-gerrit@google.com> 2018-12-06 12:47:45 +0000
commitf9bb7f79c1b69d7608a7a864ed7922935876231f (patch)
treef3a21b02e9e4a30fe2e54cc38c2b63e071546ed3
parent3a6306d4e47141e7731a1f622a9a03a6a5569893 (diff)
parentc17d280bab0913459b66f2198404133eaee905af (diff)
Merge "Use the calling worksource uid for trusted apps."
-rw-r--r--core/java/android/os/Binder.java12
-rw-r--r--core/java/com/android/internal/os/BinderCallsStats.java6
-rw-r--r--core/tests/coretests/src/com/android/internal/os/BinderCallsStatsTest.java16
-rw-r--r--services/core/java/com/android/server/BinderCallsStatsService.java144
-rw-r--r--services/tests/servicestests/src/com/android/server/BinderCallsStatsServiceTest.java125
5 files changed, 273 insertions, 30 deletions
diff --git a/core/java/android/os/Binder.java b/core/java/android/os/Binder.java
index 64314a7d8060..1efbc6b401c0 100644
--- a/core/java/android/os/Binder.java
+++ b/core/java/android/os/Binder.java
@@ -85,6 +85,15 @@ public class Binder implements IBinder {
public static boolean LOG_RUNTIME_EXCEPTION = false; // DO NOT SUBMIT WITH TRUE
/**
+ * Value to represents that a calling work source is not set.
+ *
+ * This constatnt needs to be kept in sync with IPCThreadState::kUnsetWorkSource.
+ *
+ * @hide
+ */
+ public static final int UNSET_WORKSOURCE = -1;
+
+ /**
* Control whether dump() calls are allowed.
*/
private static volatile String sDumpDisabled = null;
@@ -449,8 +458,6 @@ public class Binder implements IBinder {
* }
* </pre>
*
- * <p>The work source will be propagated for future outgoing binder transactions
- * executed on this thread.
* @hide
**/
@CriticalNative
@@ -972,7 +979,6 @@ public class Binder implements IBinder {
if (observer != null) {
observer.callEnded(callSession, requestSizeBytes, replySizeBytes);
}
-
return res;
}
}
diff --git a/core/java/com/android/internal/os/BinderCallsStats.java b/core/java/com/android/internal/os/BinderCallsStats.java
index a87bbf33ca9e..925dbdc3f367 100644
--- a/core/java/com/android/internal/os/BinderCallsStats.java
+++ b/core/java/com/android/internal/os/BinderCallsStats.java
@@ -21,6 +21,7 @@ import android.annotation.Nullable;
import android.os.Binder;
import android.os.Process;
import android.os.SystemClock;
+import android.os.ThreadLocalWorkSource;
import android.os.UserHandle;
import android.text.format.DateFormat;
import android.util.ArrayMap;
@@ -162,8 +163,7 @@ public class BinderCallsStats implements BinderInternal.Observer {
return;
}
- final boolean isWorkSourceSet = workSourceUid >= 0;
- final UidEntry uidEntry = getUidEntry(isWorkSourceSet ? workSourceUid : callingUid);
+ final UidEntry uidEntry = getUidEntry(workSourceUid);
uidEntry.callCount++;
if (recordCall) {
@@ -464,7 +464,7 @@ public class BinderCallsStats implements BinderInternal.Observer {
}
protected int getWorkSourceUid() {
- return Binder.getCallingWorkSourceUid();
+ return ThreadLocalWorkSource.getUid();
}
protected long getElapsedRealtimeMicro() {
diff --git a/core/tests/coretests/src/com/android/internal/os/BinderCallsStatsTest.java b/core/tests/coretests/src/com/android/internal/os/BinderCallsStatsTest.java
index 02a76f8f78af..e8ecc9a95ebe 100644
--- a/core/tests/coretests/src/com/android/internal/os/BinderCallsStatsTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/BinderCallsStatsTest.java
@@ -528,22 +528,6 @@ public class BinderCallsStatsTest {
}
@Test
- public void testCallingUidUsedWhenWorkSourceNotSet() {
- TestBinderCallsStats bcs = new TestBinderCallsStats();
- bcs.setDetailedTracking(true);
- bcs.workSourceUid = -1;
-
- Binder binder = new Binder();
- CallSession callSession = bcs.callStarted(binder, 1);
- bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE);
-
- assertEquals(1, bcs.getExportedCallStats().size());
- BinderCallsStats.ExportedCallStat stat = bcs.getExportedCallStats().get(0);
- assertEquals(CALLING_UID, stat.workSourceUid);
- assertEquals(CALLING_UID, stat.callingUid);
- }
-
- @Test
public void testGetExportedStatsWithoutCalls() {
TestBinderCallsStats bcs = new TestBinderCallsStats();
Binder binder = new Binder();
diff --git a/services/core/java/com/android/server/BinderCallsStatsService.java b/services/core/java/com/android/server/BinderCallsStatsService.java
index 81f0259bfd71..11a2fc9c1e45 100644
--- a/services/core/java/com/android/server/BinderCallsStatsService.java
+++ b/services/core/java/com/android/server/BinderCallsStatsService.java
@@ -16,23 +16,33 @@
package com.android.server;
+import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_AWARE;
+import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_UNAWARE;
+
import android.app.AppGlobals;
import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
import android.database.ContentObserver;
import android.net.Uri;
import android.os.Binder;
+import android.os.Process;
import android.os.RemoteException;
import android.os.SystemProperties;
+import android.os.ThreadLocalWorkSource;
import android.os.UserHandle;
import android.provider.Settings;
import android.util.ArrayMap;
+import android.util.ArraySet;
import android.util.KeyValueListParser;
import android.util.Slog;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.os.BackgroundThread;
import com.android.internal.os.BinderCallsStats;
+import com.android.internal.os.BinderInternal;
+import com.android.internal.os.BinderInternal.CallSession;
import com.android.internal.os.CachedDeviceState;
import java.io.FileDescriptor;
@@ -49,6 +59,106 @@ public class BinderCallsStatsService extends Binder {
private static final String PERSIST_SYS_BINDER_CALLS_DETAILED_TRACKING
= "persist.sys.binder_calls_detailed_tracking";
+ /** Resolves the work source of an incoming binder transaction. */
+ static class WorkSourceProvider {
+ private ArraySet<Integer> mAppIdWhitelist;
+
+ WorkSourceProvider() {
+ mAppIdWhitelist = new ArraySet<>();
+ }
+
+ public int resolveWorkSourceUid() {
+ final int callingUid = getCallingUid();
+ final int appId = UserHandle.getAppId(callingUid);
+ if (mAppIdWhitelist.contains(appId)) {
+ final int workSource = getCallingWorkSourceUid();
+ final boolean isWorkSourceSet = workSource != Binder.UNSET_WORKSOURCE;
+ return isWorkSourceSet ? workSource : callingUid;
+ }
+ return callingUid;
+ }
+
+ public void systemReady(Context context) {
+ mAppIdWhitelist = createAppidWhitelist(context);
+ }
+
+ public void dump(PrintWriter pw, Map<Integer, String> appIdToPackageName) {
+ pw.println("AppIds of apps that can set the work source:");
+ final ArraySet<Integer> whitelist = mAppIdWhitelist;
+ for (Integer appId : whitelist) {
+ pw.println("\t- " + appIdToPackageName.getOrDefault(appId, String.valueOf(appId)));
+ }
+ }
+
+ protected int getCallingUid() {
+ return Binder.getCallingUid();
+ }
+
+ protected int getCallingWorkSourceUid() {
+ return Binder.getCallingWorkSourceUid();
+ }
+
+ private ArraySet<Integer> createAppidWhitelist(Context context) {
+ // Use a local copy instead of mAppIdWhitelist to prevent concurrent read access.
+ final ArraySet<Integer> whitelist = new ArraySet<>();
+
+ // We trust our own process.
+ whitelist.add(Process.myUid());
+ // We only need to initialize it once. UPDATE_DEVICE_STATS is a system permission.
+ final PackageManager pm = context.getPackageManager();
+ final String[] permissions = { android.Manifest.permission.UPDATE_DEVICE_STATS };
+ final int queryFlags = MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE;
+ final List<PackageInfo> packages =
+ pm.getPackagesHoldingPermissions(permissions, queryFlags);
+ final int packagesSize = packages.size();
+ for (int i = 0; i < packagesSize; i++) {
+ final PackageInfo pkgInfo = packages.get(i);
+ try {
+ final int uid = pm.getPackageUid(pkgInfo.packageName, queryFlags);
+ final int appId = UserHandle.getAppId(uid);
+ whitelist.add(appId);
+ } catch (NameNotFoundException e) {
+ Slog.e(TAG, "Cannot find uid for package name " + pkgInfo.packageName, e);
+ }
+ }
+ return whitelist;
+ }
+ }
+
+ /** Observer for all system server incoming binder transactions. */
+ @VisibleForTesting
+ static class BinderCallsObserver implements BinderInternal.Observer {
+ private final BinderInternal.Observer mBinderCallsStats;
+ private final WorkSourceProvider mWorkSourceProvider;
+
+ BinderCallsObserver(BinderInternal.Observer callsStats,
+ WorkSourceProvider workSourceProvider) {
+ mBinderCallsStats = callsStats;
+ mWorkSourceProvider = workSourceProvider;
+ }
+
+ @Override
+ public CallSession callStarted(Binder binder, int code) {
+ // We depend on the code in Binder#execTransact to reset the state of
+ // ThreadLocalWorkSource
+ setThreadLocalWorkSourceUid(mWorkSourceProvider.resolveWorkSourceUid());
+ return mBinderCallsStats.callStarted(binder, code);
+ }
+
+ @Override
+ public void callEnded(CallSession s, int parcelRequestSize, int parcelReplySize) {
+ mBinderCallsStats.callEnded(s, parcelRequestSize, parcelReplySize);
+ }
+
+ @Override
+ public void callThrewException(CallSession s, Exception exception) {
+ mBinderCallsStats.callThrewException(s, exception);
+ }
+
+ protected void setThreadLocalWorkSourceUid(int uid) {
+ ThreadLocalWorkSource.setUid(uid);
+ }
+ }
/** Listens for flag changes. */
private static class SettingsObserver extends ContentObserver {
@@ -63,13 +173,16 @@ public class BinderCallsStatsService extends Binder {
private final Context mContext;
private final KeyValueListParser mParser = new KeyValueListParser(',');
private final BinderCallsStats mBinderCallsStats;
+ private final BinderCallsObserver mBinderCallsObserver;
- public SettingsObserver(Context context, BinderCallsStats binderCallsStats) {
+ SettingsObserver(Context context, BinderCallsStats binderCallsStats,
+ BinderCallsObserver observer) {
super(BackgroundThread.getHandler());
mContext = context;
context.getContentResolver().registerContentObserver(mUri, false, this,
UserHandle.USER_SYSTEM);
mBinderCallsStats = binderCallsStats;
+ mBinderCallsObserver = observer;
// Always kick once to ensure that we match current state
onChange();
}
@@ -107,7 +220,7 @@ public class BinderCallsStatsService extends Binder {
mParser.getBoolean(SETTINGS_ENABLED_KEY, BinderCallsStats.ENABLED_DEFAULT);
if (mEnabled != enabled) {
if (enabled) {
- Binder.setObserver(mBinderCallsStats);
+ Binder.setObserver(mBinderCallsObserver);
Binder.setProxyTransactListener(
new Binder.PropagateWorkSourceTransactListener());
} else {
@@ -155,6 +268,7 @@ public class BinderCallsStatsService extends Binder {
public static class LifeCycle extends SystemService {
private BinderCallsStatsService mService;
private BinderCallsStats mBinderCallsStats;
+ private WorkSourceProvider mWorkSourceProvider;
public LifeCycle(Context context) {
super(context);
@@ -163,7 +277,11 @@ public class BinderCallsStatsService extends Binder {
@Override
public void onStart() {
mBinderCallsStats = new BinderCallsStats(new BinderCallsStats.Injector());
- mService = new BinderCallsStatsService(mBinderCallsStats);
+ mWorkSourceProvider = new WorkSourceProvider();
+ BinderCallsObserver binderCallsObserver =
+ new BinderCallsObserver(mBinderCallsStats, mWorkSourceProvider);
+ mService = new BinderCallsStatsService(
+ mBinderCallsStats, binderCallsObserver, mWorkSourceProvider);
publishLocalService(Internal.class, new Internal(mBinderCallsStats));
publishBinderService("binder_calls_stats", mService);
boolean detailedTrackingEnabled = SystemProperties.getBoolean(
@@ -182,21 +300,29 @@ public class BinderCallsStatsService extends Binder {
if (SystemService.PHASE_SYSTEM_SERVICES_READY == phase) {
CachedDeviceState.Readonly deviceState = getLocalService(
CachedDeviceState.Readonly.class);
- mService.systemReady(getContext());
mBinderCallsStats.setDeviceState(deviceState);
+ // It needs to be called before mService.systemReady to make sure the observer is
+ // initialized before installing it.
+ mWorkSourceProvider.systemReady(getContext());
+ mService.systemReady(getContext());
}
}
}
private SettingsObserver mSettingsObserver;
private final BinderCallsStats mBinderCallsStats;
+ private final BinderCallsObserver mBinderCallsObserver;
+ private final WorkSourceProvider mWorkSourceProvider;
- BinderCallsStatsService(BinderCallsStats binderCallsStats) {
+ BinderCallsStatsService(BinderCallsStats binderCallsStats, BinderCallsObserver observer,
+ WorkSourceProvider workSourceProvider) {
mBinderCallsStats = binderCallsStats;
+ mBinderCallsObserver = observer;
+ mWorkSourceProvider = workSourceProvider;
}
public void systemReady(Context context) {
- mSettingsObserver = new SettingsObserver(context, mBinderCallsStats);
+ mSettingsObserver = new SettingsObserver(context, mBinderCallsStats, mBinderCallsObserver);
}
public void reset() {
@@ -216,7 +342,7 @@ public class BinderCallsStatsService extends Binder {
pw.println("binder_calls_stats reset.");
return;
} else if ("--enable".equals(arg)) {
- Binder.setObserver(mBinderCallsStats);
+ Binder.setObserver(mBinderCallsObserver);
return;
} else if ("--disable".equals(arg)) {
Binder.setObserver(null);
@@ -234,6 +360,9 @@ public class BinderCallsStatsService extends Binder {
mBinderCallsStats.setDetailedTracking(false);
pw.println("Detailed tracking disabled");
return;
+ } else if ("--dump-worksource-provider".equals(arg)) {
+ mWorkSourceProvider.dump(pw, getAppIdToPackagesMap());
+ return;
} else if ("-h".equals(arg)) {
pw.println("binder_calls_stats commands:");
pw.println(" --reset: Reset stats");
@@ -272,5 +401,4 @@ public class BinderCallsStatsService extends Binder {
}
return map;
}
-
}
diff --git a/services/tests/servicestests/src/com/android/server/BinderCallsStatsServiceTest.java b/services/tests/servicestests/src/com/android/server/BinderCallsStatsServiceTest.java
new file mode 100644
index 000000000000..f70efdfadfd7
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/BinderCallsStatsServiceTest.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server;
+
+import static org.junit.Assert.assertEquals;
+
+import android.os.Binder;
+import android.os.Process;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.internal.os.BinderInternal;
+import com.android.internal.os.BinderInternal.CallSession;
+import com.android.server.BinderCallsStatsService.WorkSourceProvider;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+@Presubmit
+public class BinderCallsStatsServiceTest {
+ @Test
+ public void weTrustOurselves() {
+ WorkSourceProvider workSourceProvider = new WorkSourceProvider() {
+ protected int getCallingUid() {
+ return Process.myUid();
+ }
+
+ protected int getCallingWorkSourceUid() {
+ return 1;
+ }
+ };
+ workSourceProvider.systemReady(InstrumentationRegistry.getContext());
+
+ assertEquals(1, workSourceProvider.resolveWorkSourceUid());
+ }
+
+ @Test
+ public void workSourceSetIfCallerHasPermission() {
+ WorkSourceProvider workSourceProvider = new WorkSourceProvider() {
+ protected int getCallingUid() {
+ // System process uid which as UPDATE_DEVICE_STATS.
+ return 1001;
+ }
+
+ protected int getCallingWorkSourceUid() {
+ return 1;
+ }
+ };
+ workSourceProvider.systemReady(InstrumentationRegistry.getContext());
+
+ assertEquals(1, workSourceProvider.resolveWorkSourceUid());
+ }
+
+ @Test
+ public void workSourceResolvedToCallingUid() {
+ WorkSourceProvider workSourceProvider = new WorkSourceProvider() {
+ protected int getCallingUid() {
+ // UID without permissions.
+ return Integer.MAX_VALUE;
+ }
+
+ protected int getCallingWorkSourceUid() {
+ return 1;
+ }
+ };
+ workSourceProvider.systemReady(InstrumentationRegistry.getContext());
+
+ assertEquals(Integer.MAX_VALUE, workSourceProvider.resolveWorkSourceUid());
+ }
+
+ @Test
+ public void workSourceSet() {
+ TestObserver observer = new TestObserver();
+ observer.callStarted(new Binder(), 0);
+ assertEquals(true, observer.workSourceSet);
+ }
+
+ static class TestObserver extends BinderCallsStatsService.BinderCallsObserver {
+ public boolean workSourceSet = false;
+
+ TestObserver() {
+ super(new NoopObserver(), new WorkSourceProvider());
+ }
+
+ @Override
+ protected void setThreadLocalWorkSourceUid(int uid) {
+ workSourceSet = true;
+ }
+ }
+
+
+ static class NoopObserver implements BinderInternal.Observer {
+ @Override
+ public CallSession callStarted(Binder binder, int code) {
+ return null;
+ }
+
+ @Override
+ public void callEnded(CallSession s, int parcelRequestSize, int parcelReplySize) {
+ }
+
+ @Override
+ public void callThrewException(CallSession s, Exception exception) {
+ }
+ }
+}