diff options
| author | 2018-12-06 12:47:45 +0000 | |
|---|---|---|
| committer | 2018-12-06 12:47:45 +0000 | |
| commit | f9bb7f79c1b69d7608a7a864ed7922935876231f (patch) | |
| tree | f3a21b02e9e4a30fe2e54cc38c2b63e071546ed3 | |
| parent | 3a6306d4e47141e7731a1f622a9a03a6a5569893 (diff) | |
| parent | c17d280bab0913459b66f2198404133eaee905af (diff) | |
Merge "Use the calling worksource uid for trusted apps."
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) { + } + } +} |