diff options
8 files changed, 175 insertions, 74 deletions
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java index 9bd6c750fb13..187b54a6c3fc 100644 --- a/core/java/android/app/AppOpsManager.java +++ b/core/java/android/app/AppOpsManager.java @@ -742,6 +742,13 @@ public class AppOpsManager { public static final int ATTRIBUTION_FLAG_RECEIVER = 0x4; /** + * Attribution chain flag: Specifies that all attribution sources in the chain were trusted. + * Must only be set by system server. + * @hide + */ + public static final int ATTRIBUTION_FLAG_TRUSTED = 0x8; + + /** * No attribution flags. * @hide */ @@ -760,7 +767,8 @@ public class AppOpsManager { @IntDef(flag = true, prefix = { "FLAG_" }, value = { ATTRIBUTION_FLAG_ACCESSOR, ATTRIBUTION_FLAG_INTERMEDIARY, - ATTRIBUTION_FLAG_RECEIVER + ATTRIBUTION_FLAG_RECEIVER, + ATTRIBUTION_FLAG_TRUSTED }) public @interface AttributionFlags {} diff --git a/core/java/android/permission/PermissionUsageHelper.java b/core/java/android/permission/PermissionUsageHelper.java index 791764b4342f..03f94c549512 100644 --- a/core/java/android/permission/PermissionUsageHelper.java +++ b/core/java/android/permission/PermissionUsageHelper.java @@ -19,9 +19,11 @@ package android.permission; import static android.Manifest.permission_group.CAMERA; import static android.Manifest.permission_group.LOCATION; import static android.Manifest.permission_group.MICROPHONE; +import static android.app.AppOpsManager.ATTRIBUTION_CHAIN_ID_NONE; import static android.app.AppOpsManager.ATTRIBUTION_FLAGS_NONE; import static android.app.AppOpsManager.ATTRIBUTION_FLAG_ACCESSOR; import static android.app.AppOpsManager.ATTRIBUTION_FLAG_RECEIVER; +import static android.app.AppOpsManager.ATTRIBUTION_FLAG_TRUSTED; import static android.app.AppOpsManager.AttributionFlags; import static android.app.AppOpsManager.OPSTR_CAMERA; import static android.app.AppOpsManager.OPSTR_COARSE_LOCATION; @@ -180,7 +182,10 @@ public class PermissionUsageHelper implements AppOpsManager.OnOpActiveChangedLis public void onOpActiveChanged(@NonNull String op, int uid, @NonNull String packageName, @Nullable String attributionTag, boolean active, @AttributionFlags int attributionFlags, int attributionChainId) { - if ((attributionFlags & ATTRIBUTION_FLAGS_NONE) != 0) { + if (attributionChainId == ATTRIBUTION_CHAIN_ID_NONE + || attributionFlags == ATTRIBUTION_FLAGS_NONE + || (attributionFlags & ATTRIBUTION_FLAG_TRUSTED) == 0) { + // If this is not a chain, or it is untrusted, return return; } diff --git a/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java b/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java index 534f93ec0e47..9676a57b2df9 100644 --- a/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java @@ -64,7 +64,7 @@ import javax.inject.Inject; */ @SysUISingleton public class AppOpsControllerImpl extends BroadcastReceiver implements AppOpsController, - AppOpsManager.OnOpActiveChangedInternalListener, + AppOpsManager.OnOpActiveChangedListener, AppOpsManager.OnOpNotedListener, IndividualSensorPrivacyController.Callback, Dumpable { @@ -359,11 +359,29 @@ public class AppOpsControllerImpl extends BroadcastReceiver implements AppOpsCon mBGHandler.post(() -> notifySuscribersWorker(code, uid, packageName, active)); } + /** + * Required to override, delegate to other. Should not be called. + */ + public void onOpActiveChanged(String op, int uid, String packageName, boolean active) { + onOpActiveChanged(op, uid, packageName, null, active, + AppOpsManager.ATTRIBUTION_FLAGS_NONE, AppOpsManager.ATTRIBUTION_CHAIN_ID_NONE); + } + + // Get active app ops, and check if their attributions are trusted @Override - public void onOpActiveChanged(int code, int uid, String packageName, boolean active) { + public void onOpActiveChanged(String op, int uid, String packageName, String attributionTag, + boolean active, int attributionFlags, int attributionChainId) { + int code = AppOpsManager.strOpToOp(op); if (DEBUG) { - Log.w(TAG, String.format("onActiveChanged(%d,%d,%s,%s", code, uid, packageName, - Boolean.toString(active))); + Log.w(TAG, String.format("onActiveChanged(%d,%d,%s,%s,%d,%d)", code, uid, packageName, + Boolean.toString(active), attributionChainId, attributionFlags)); + } + if (attributionChainId != AppOpsManager.ATTRIBUTION_CHAIN_ID_NONE + && attributionFlags != AppOpsManager.ATTRIBUTION_FLAGS_NONE + && (attributionFlags & AppOpsManager.ATTRIBUTION_FLAG_ACCESSOR) == 0 + && (attributionFlags & AppOpsManager.ATTRIBUTION_FLAG_TRUSTED) == 0) { + // if this attribution chain isn't trusted, and this isn't the accessor, do not show it. + return; } boolean activeChanged = updateActives(code, uid, packageName, active); if (!activeChanged) return; // early return diff --git a/packages/SystemUI/tests/src/com/android/systemui/appops/AppOpsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/appops/AppOpsControllerTest.java index 78c67170d185..61a651234e0c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/appops/AppOpsControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/appops/AppOpsControllerTest.java @@ -77,6 +77,7 @@ public class AppOpsControllerTest extends SysuiTestCase { private static final int TEST_UID = UserHandle.getUid(0, 0); private static final int TEST_UID_OTHER = UserHandle.getUid(1, 0); private static final int TEST_UID_NON_USER_SENSITIVE = UserHandle.getUid(2, 0); + private static final int TEST_CHAIN_ID = 1; @Mock private AppOpsManager mAppOpsManager; @@ -162,7 +163,7 @@ public class AppOpsControllerTest extends SysuiTestCase { new int[]{AppOpsManager.OP_RECORD_AUDIO, AppOpsManager.OP_FINE_LOCATION}, mCallback); mController.onOpActiveChanged( - AppOpsManager.OP_RECORD_AUDIO, TEST_UID, TEST_PACKAGE_NAME, true); + AppOpsManager.OPSTR_RECORD_AUDIO, TEST_UID, TEST_PACKAGE_NAME, true); mController.onOpNoted(AppOpsManager.OP_FINE_LOCATION, TEST_UID, TEST_PACKAGE_NAME, TEST_ATTRIBUTION_NAME, AppOpsManager.OP_FLAG_SELF, AppOpsManager.MODE_ALLOWED); mTestableLooper.processAllMessages(); @@ -174,7 +175,7 @@ public class AppOpsControllerTest extends SysuiTestCase { public void addCallback_notIncludedCode() { mController.addCallback(new int[]{AppOpsManager.OP_FINE_LOCATION}, mCallback); mController.onOpActiveChanged( - AppOpsManager.OP_RECORD_AUDIO, TEST_UID, TEST_PACKAGE_NAME, true); + AppOpsManager.OPSTR_RECORD_AUDIO, TEST_UID, TEST_PACKAGE_NAME, true); mTestableLooper.processAllMessages(); verify(mCallback, never()).onActiveStateChanged( anyInt(), anyInt(), anyString(), anyBoolean()); @@ -185,7 +186,7 @@ public class AppOpsControllerTest extends SysuiTestCase { mController.addCallback(new int[]{AppOpsManager.OP_RECORD_AUDIO}, mCallback); mController.removeCallback(new int[]{AppOpsManager.OP_RECORD_AUDIO}, mCallback); mController.onOpActiveChanged( - AppOpsManager.OP_RECORD_AUDIO, TEST_UID, TEST_PACKAGE_NAME, true); + AppOpsManager.OPSTR_RECORD_AUDIO, TEST_UID, TEST_PACKAGE_NAME, true); mTestableLooper.processAllMessages(); verify(mCallback, never()).onActiveStateChanged( anyInt(), anyInt(), anyString(), anyBoolean()); @@ -196,7 +197,7 @@ public class AppOpsControllerTest extends SysuiTestCase { mController.addCallback(new int[]{AppOpsManager.OP_RECORD_AUDIO}, mCallback); mController.removeCallback(new int[]{AppOpsManager.OP_CAMERA}, mCallback); mController.onOpActiveChanged( - AppOpsManager.OP_RECORD_AUDIO, TEST_UID, TEST_PACKAGE_NAME, true); + AppOpsManager.OPSTR_RECORD_AUDIO, TEST_UID, TEST_PACKAGE_NAME, true); mTestableLooper.processAllMessages(); verify(mCallback).onActiveStateChanged(AppOpsManager.OP_RECORD_AUDIO, TEST_UID, TEST_PACKAGE_NAME, true); @@ -204,18 +205,18 @@ public class AppOpsControllerTest extends SysuiTestCase { @Test public void getActiveItems_sameDetails() { - mController.onOpActiveChanged(AppOpsManager.OP_RECORD_AUDIO, + mController.onOpActiveChanged(AppOpsManager.OPSTR_RECORD_AUDIO, TEST_UID, TEST_PACKAGE_NAME, true); - mController.onOpActiveChanged(AppOpsManager.OP_RECORD_AUDIO, + mController.onOpActiveChanged(AppOpsManager.OPSTR_RECORD_AUDIO, TEST_UID, TEST_PACKAGE_NAME, true); assertEquals(1, mController.getActiveAppOps().size()); } @Test public void getActiveItems_differentDetails() { - mController.onOpActiveChanged(AppOpsManager.OP_RECORD_AUDIO, + mController.onOpActiveChanged(AppOpsManager.OPSTR_RECORD_AUDIO, TEST_UID, TEST_PACKAGE_NAME, true); - mController.onOpActiveChanged(AppOpsManager.OP_CAMERA, + mController.onOpActiveChanged(AppOpsManager.OPSTR_CAMERA, TEST_UID, TEST_PACKAGE_NAME, true); mController.onOpNoted(AppOpsManager.OP_FINE_LOCATION, TEST_UID, TEST_PACKAGE_NAME, TEST_ATTRIBUTION_NAME, @@ -225,9 +226,9 @@ public class AppOpsControllerTest extends SysuiTestCase { @Test public void getActiveItemsForUser() { - mController.onOpActiveChanged(AppOpsManager.OP_RECORD_AUDIO, + mController.onOpActiveChanged(AppOpsManager.OPSTR_RECORD_AUDIO, TEST_UID, TEST_PACKAGE_NAME, true); - mController.onOpActiveChanged(AppOpsManager.OP_CAMERA, + mController.onOpActiveChanged(AppOpsManager.OPSTR_CAMERA, TEST_UID_OTHER, TEST_PACKAGE_NAME, true); mController.onOpNoted(AppOpsManager.OP_FINE_LOCATION, TEST_UID, TEST_PACKAGE_NAME, TEST_ATTRIBUTION_NAME, @@ -242,11 +243,11 @@ public class AppOpsControllerTest extends SysuiTestCase { public void systemAndExemptedRolesAreIgnored() { assumeFalse(mExemptedRolePkgName == null || mExemptedRolePkgName.equals("")); - mController.onOpActiveChanged(AppOpsManager.OP_RECORD_AUDIO, + mController.onOpActiveChanged(AppOpsManager.OPSTR_RECORD_AUDIO, TEST_UID_NON_USER_SENSITIVE, mExemptedRolePkgName, true); assertEquals(0, mController.getActiveAppOpsForUser( UserHandle.getUserId(TEST_UID_NON_USER_SENSITIVE), false).size()); - mController.onOpActiveChanged(AppOpsManager.OP_RECORD_AUDIO, + mController.onOpActiveChanged(AppOpsManager.OPSTR_RECORD_AUDIO, TEST_UID_NON_USER_SENSITIVE, SYSTEM_PKG, true); assertEquals(0, mController.getActiveAppOpsForUser( UserHandle.getUserId(TEST_UID_NON_USER_SENSITIVE), false).size()); @@ -257,7 +258,7 @@ public class AppOpsControllerTest extends SysuiTestCase { assumeFalse(mExemptedRolePkgName == null || mExemptedRolePkgName.equals("")); mController.addCallback(new int[]{AppOpsManager.OP_RECORD_AUDIO}, mCallback); - mController.onOpActiveChanged(AppOpsManager.OP_RECORD_AUDIO, + mController.onOpActiveChanged(AppOpsManager.OPSTR_RECORD_AUDIO, TEST_UID_NON_USER_SENSITIVE, mExemptedRolePkgName, true); mTestableLooper.processAllMessages(); @@ -279,8 +280,8 @@ public class AppOpsControllerTest extends SysuiTestCase { mController.setBGHandler(mMockHandler); mController.setListening(true); - mController.onOpActiveChanged(AppOpsManager.OP_FINE_LOCATION, TEST_UID, TEST_PACKAGE_NAME, - true); + mController.onOpActiveChanged(AppOpsManager.OPSTR_FINE_LOCATION, TEST_UID, + TEST_PACKAGE_NAME, true); mController.onOpNoted(AppOpsManager.OP_FINE_LOCATION, TEST_UID, TEST_PACKAGE_NAME, TEST_ATTRIBUTION_NAME, AppOpsManager.OP_FLAG_SELF, AppOpsManager.MODE_ALLOWED); assertFalse(mController.getActiveAppOps().isEmpty()); @@ -321,6 +322,36 @@ public class AppOpsControllerTest extends SysuiTestCase { } @Test + public void testUntrustedChainUsagesDiscarded() { + assertTrue(mController.getActiveAppOps().isEmpty()); + mController.setBGHandler(mMockHandler); + + //untrusted receiver access + mController.onOpActiveChanged(AppOpsManager.OPSTR_RECORD_AUDIO, TEST_UID, + TEST_PACKAGE_NAME, TEST_ATTRIBUTION_NAME, true, + AppOpsManager.ATTRIBUTION_FLAG_RECEIVER, TEST_CHAIN_ID); + //untrusted intermediary access + mController.onOpActiveChanged(AppOpsManager.OPSTR_RECORD_AUDIO, TEST_UID, + TEST_PACKAGE_NAME, TEST_ATTRIBUTION_NAME, true, + AppOpsManager.ATTRIBUTION_FLAG_INTERMEDIARY, TEST_CHAIN_ID); + assertTrue(mController.getActiveAppOps().isEmpty()); + } + + @Test + public void testTrustedChainUsagesKept() { + //untrusted accessor access + mController.onOpActiveChanged(AppOpsManager.OPSTR_RECORD_AUDIO, TEST_UID, + TEST_PACKAGE_NAME, TEST_ATTRIBUTION_NAME, true, + AppOpsManager.ATTRIBUTION_FLAG_ACCESSOR, TEST_CHAIN_ID); + //trusted access + mController.onOpActiveChanged(AppOpsManager.OPSTR_CAMERA, TEST_UID, + TEST_PACKAGE_NAME, TEST_ATTRIBUTION_NAME, true, + AppOpsManager.ATTRIBUTION_FLAG_RECEIVER | AppOpsManager.ATTRIBUTION_FLAG_TRUSTED, + TEST_CHAIN_ID); + assertEquals(2, mController.getActiveAppOps().size()); + } + + @Test public void testActiveOpNotRemovedAfterNoted() throws InterruptedException { // Replaces the timeout delay with 5 ms TestHandler testHandler = new TestHandler(mTestableLooper.getLooper()); @@ -329,7 +360,7 @@ public class AppOpsControllerTest extends SysuiTestCase { mController.setBGHandler(testHandler); mController.onOpActiveChanged( - AppOpsManager.OP_FINE_LOCATION, TEST_UID, TEST_PACKAGE_NAME, true); + AppOpsManager.OPSTR_FINE_LOCATION, TEST_UID, TEST_PACKAGE_NAME, true); mController.onOpNoted(AppOpsManager.OP_FINE_LOCATION, TEST_UID, TEST_PACKAGE_NAME, TEST_ATTRIBUTION_NAME, AppOpsManager.OP_FLAG_SELF, AppOpsManager.MODE_ALLOWED); @@ -364,7 +395,7 @@ public class AppOpsControllerTest extends SysuiTestCase { TEST_ATTRIBUTION_NAME, AppOpsManager.OP_FLAG_SELF, AppOpsManager.MODE_ALLOWED); mController.onOpActiveChanged( - AppOpsManager.OP_FINE_LOCATION, TEST_UID, TEST_PACKAGE_NAME, true); + AppOpsManager.OPSTR_FINE_LOCATION, TEST_UID, TEST_PACKAGE_NAME, true); mTestableLooper.processAllMessages(); List<AppOpItem> list = mController.getActiveAppOps(); @@ -375,7 +406,7 @@ public class AppOpsControllerTest extends SysuiTestCase { assertEquals(2, list.size()); mController.onOpActiveChanged( - AppOpsManager.OP_FINE_LOCATION, TEST_UID, TEST_PACKAGE_NAME, false); + AppOpsManager.OPSTR_FINE_LOCATION, TEST_UID, TEST_PACKAGE_NAME, false); mTestableLooper.processAllMessages(); @@ -393,7 +424,7 @@ public class AppOpsControllerTest extends SysuiTestCase { TEST_ATTRIBUTION_NAME, AppOpsManager.OP_FLAG_SELF, AppOpsManager.MODE_ALLOWED); mController.onOpActiveChanged( - AppOpsManager.OP_FINE_LOCATION, TEST_UID, TEST_PACKAGE_NAME, true); + AppOpsManager.OPSTR_FINE_LOCATION, TEST_UID, TEST_PACKAGE_NAME, true); mTestableLooper.processAllMessages(); verify(mCallback).onActiveStateChanged( @@ -405,7 +436,7 @@ public class AppOpsControllerTest extends SysuiTestCase { mController.addCallback(new int[]{AppOpsManager.OP_FINE_LOCATION}, mCallback); mController.onOpActiveChanged( - AppOpsManager.OP_FINE_LOCATION, TEST_UID, TEST_PACKAGE_NAME, true); + AppOpsManager.OPSTR_FINE_LOCATION, TEST_UID, TEST_PACKAGE_NAME, true); mController.onOpNoted(AppOpsManager.OP_FINE_LOCATION, TEST_UID, TEST_PACKAGE_NAME, TEST_ATTRIBUTION_NAME, AppOpsManager.OP_FLAG_SELF, AppOpsManager.MODE_ALLOWED); @@ -421,7 +452,7 @@ public class AppOpsControllerTest extends SysuiTestCase { mTestableLooper.processAllMessages(); mController.onOpActiveChanged( - AppOpsManager.OP_RECORD_AUDIO, TEST_UID, TEST_PACKAGE_NAME, true); + AppOpsManager.OPSTR_RECORD_AUDIO, TEST_UID, TEST_PACKAGE_NAME, true); mTestableLooper.processAllMessages(); verify(mCallback, never()) @@ -434,7 +465,7 @@ public class AppOpsControllerTest extends SysuiTestCase { mTestableLooper.processAllMessages(); mController.onOpActiveChanged( - AppOpsManager.OP_RECORD_AUDIO, TEST_UID, TEST_PACKAGE_NAME, true); + AppOpsManager.OPSTR_RECORD_AUDIO, TEST_UID, TEST_PACKAGE_NAME, true); mTestableLooper.processAllMessages(); assertTrue(mController.getActiveAppOps().isEmpty()); @@ -446,7 +477,7 @@ public class AppOpsControllerTest extends SysuiTestCase { mTestableLooper.processAllMessages(); mController.onOpActiveChanged( - AppOpsManager.OP_PHONE_CALL_MICROPHONE, TEST_UID, TEST_PACKAGE_NAME, true); + AppOpsManager.OPSTR_PHONE_CALL_MICROPHONE, TEST_UID, TEST_PACKAGE_NAME, true); mTestableLooper.processAllMessages(); assertTrue(mController.getActiveAppOps().isEmpty()); @@ -461,7 +492,7 @@ public class AppOpsControllerTest extends SysuiTestCase { mTestableLooper.processAllMessages(); mController.onOpActiveChanged( - AppOpsManager.OP_CAMERA, TEST_UID, TEST_PACKAGE_NAME, true); + AppOpsManager.OPSTR_CAMERA, TEST_UID, TEST_PACKAGE_NAME, true); mTestableLooper.processAllMessages(); verify(mCallback).onActiveStateChanged( @@ -477,7 +508,7 @@ public class AppOpsControllerTest extends SysuiTestCase { mController.addCallback(new int[]{AppOpsManager.OP_RECORD_AUDIO}, mCallback); mTestableLooper.processAllMessages(); mController.onOpActiveChanged( - AppOpsManager.OP_RECORD_AUDIO, TEST_UID, TEST_PACKAGE_NAME, true); + AppOpsManager.OPSTR_RECORD_AUDIO, TEST_UID, TEST_PACKAGE_NAME, true); mTestableLooper.processAllMessages(); mRecordingCallback.onRecordingConfigChanged(Collections.emptyList()); @@ -493,7 +524,7 @@ public class AppOpsControllerTest extends SysuiTestCase { mController.addCallback(new int[]{AppOpsManager.OP_RECORD_AUDIO}, mCallback); mTestableLooper.processAllMessages(); mController.onOpActiveChanged( - AppOpsManager.OP_RECORD_AUDIO, TEST_UID_OTHER, TEST_PACKAGE_NAME, true); + AppOpsManager.OPSTR_RECORD_AUDIO, TEST_UID_OTHER, TEST_PACKAGE_NAME, true); mTestableLooper.processAllMessages(); AudioRecordingConfiguration mockARC = mock(AudioRecordingConfiguration.class); @@ -516,7 +547,7 @@ public class AppOpsControllerTest extends SysuiTestCase { mCallback); mTestableLooper.processAllMessages(); mController.onOpActiveChanged( - AppOpsManager.OP_RECORD_AUDIO, TEST_UID_OTHER, TEST_PACKAGE_NAME, true); + AppOpsManager.OPSTR_RECORD_AUDIO, TEST_UID_OTHER, TEST_PACKAGE_NAME, true); mTestableLooper.processAllMessages(); List<AppOpItem> list = mController.getActiveAppOps(); assertEquals(1, list.size()); @@ -526,7 +557,7 @@ public class AppOpsControllerTest extends SysuiTestCase { // Add a camera op, and disable the microphone. The camera op should be the only op returned mController.onSensorBlockedChanged(MICROPHONE, true); mController.onOpActiveChanged( - AppOpsManager.OP_CAMERA, TEST_UID_OTHER, TEST_PACKAGE_NAME, true); + AppOpsManager.OPSTR_CAMERA, TEST_UID_OTHER, TEST_PACKAGE_NAME, true); mTestableLooper.processAllMessages(); list = mController.getActiveAppOps(); assertEquals(1, list.size()); @@ -550,7 +581,7 @@ public class AppOpsControllerTest extends SysuiTestCase { mCallback); mTestableLooper.processAllMessages(); mController.onOpActiveChanged( - AppOpsManager.OP_PHONE_CALL_MICROPHONE, TEST_UID_OTHER, TEST_PACKAGE_NAME, true); + AppOpsManager.OPSTR_PHONE_CALL_MICROPHONE, TEST_UID_OTHER, TEST_PACKAGE_NAME, true); mTestableLooper.processAllMessages(); List<AppOpItem> list = mController.getActiveAppOps(); assertEquals(1, list.size()); @@ -560,7 +591,7 @@ public class AppOpsControllerTest extends SysuiTestCase { // Add a camera op, and disable the microphone. The camera op should be the only op returned mController.onSensorBlockedChanged(MICROPHONE, true); mController.onOpActiveChanged( - AppOpsManager.OP_CAMERA, TEST_UID_OTHER, TEST_PACKAGE_NAME, true); + AppOpsManager.OPSTR_CAMERA, TEST_UID_OTHER, TEST_PACKAGE_NAME, true); mTestableLooper.processAllMessages(); list = mController.getActiveAppOps(); assertEquals(1, list.size()); @@ -583,7 +614,7 @@ public class AppOpsControllerTest extends SysuiTestCase { mCallback); mTestableLooper.processAllMessages(); mController.onOpActiveChanged( - AppOpsManager.OP_CAMERA, TEST_UID_OTHER, TEST_PACKAGE_NAME, true); + AppOpsManager.OPSTR_CAMERA, TEST_UID_OTHER, TEST_PACKAGE_NAME, true); mTestableLooper.processAllMessages(); List<AppOpItem> list = mController.getActiveAppOps(); assertEquals(1, list.size()); @@ -593,7 +624,7 @@ public class AppOpsControllerTest extends SysuiTestCase { // Add an audio op, and disable the camera. The audio op should be the only op returned mController.onSensorBlockedChanged(CAMERA, true); mController.onOpActiveChanged( - AppOpsManager.OP_RECORD_AUDIO, TEST_UID_OTHER, TEST_PACKAGE_NAME, true); + AppOpsManager.OPSTR_RECORD_AUDIO, TEST_UID_OTHER, TEST_PACKAGE_NAME, true); mTestableLooper.processAllMessages(); list = mController.getActiveAppOps(); assertEquals(1, list.size()); @@ -616,7 +647,7 @@ public class AppOpsControllerTest extends SysuiTestCase { mCallback); mTestableLooper.processAllMessages(); mController.onOpActiveChanged( - AppOpsManager.OP_PHONE_CALL_CAMERA, TEST_UID_OTHER, TEST_PACKAGE_NAME, true); + AppOpsManager.OPSTR_PHONE_CALL_CAMERA, TEST_UID_OTHER, TEST_PACKAGE_NAME, true); mTestableLooper.processAllMessages(); List<AppOpItem> list = mController.getActiveAppOps(); assertEquals(1, list.size()); @@ -626,7 +657,7 @@ public class AppOpsControllerTest extends SysuiTestCase { // Add an audio op, and disable the camera. The audio op should be the only op returned mController.onSensorBlockedChanged(CAMERA, true); mController.onOpActiveChanged( - AppOpsManager.OP_RECORD_AUDIO, TEST_UID_OTHER, TEST_PACKAGE_NAME, true); + AppOpsManager.OPSTR_RECORD_AUDIO, TEST_UID_OTHER, TEST_PACKAGE_NAME, true); mTestableLooper.processAllMessages(); list = mController.getActiveAppOps(); assertEquals(1, list.size()); diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java index e001b059b85e..7789d604461c 100644 --- a/services/core/java/com/android/server/appop/AppOpsService.java +++ b/services/core/java/com/android/server/appop/AppOpsService.java @@ -20,6 +20,7 @@ import static android.app.ActivityManager.PROCESS_CAPABILITY_FOREGROUND_CAMERA; import static android.app.ActivityManager.PROCESS_CAPABILITY_FOREGROUND_LOCATION; import static android.app.ActivityManager.PROCESS_CAPABILITY_FOREGROUND_MICROPHONE; import static android.app.AppOpsManager.ATTRIBUTION_CHAIN_ID_NONE; +import static android.app.AppOpsManager.ATTRIBUTION_FLAG_TRUSTED; import static android.app.AppOpsManager.CALL_BACK_ON_SWITCHED_OP; import static android.app.AppOpsManager.FILTER_BY_ATTRIBUTION_TAG; import static android.app.AppOpsManager.FILTER_BY_OP_NAMES; @@ -3332,7 +3333,8 @@ public class AppOpsService extends IAppOpsService.Stub { verifyIncomingPackage(proxiedPackageName, UserHandle.getUserId(proxiedUid)); verifyIncomingPackage(proxyPackageName, UserHandle.getUserId(proxyUid)); - skipProxyOperation = resolveSkipProxyOperation(skipProxyOperation, attributionSource); + skipProxyOperation = skipProxyOperation + && isCallerAndAttributionTrusted(attributionSource); String resolveProxyPackageName = AppOpsManager.resolvePackageName(proxyUid, proxyPackageName); @@ -3849,7 +3851,8 @@ public class AppOpsService extends IAppOpsService.Stub { verifyIncomingPackage(proxyPackageName, UserHandle.getUserId(proxyUid)); verifyIncomingPackage(proxiedPackageName, UserHandle.getUserId(proxiedUid)); - skipProxyOperation = resolveSkipProxyOperation(skipProxyOperation, attributionSource); + boolean isCallerTrusted = isCallerAndAttributionTrusted(attributionSource); + skipProxyOperation = isCallerTrusted && skipProxyOperation; String resolvedProxyPackageName = AppOpsManager.resolvePackageName(proxyUid, proxyPackageName); @@ -3858,11 +3861,15 @@ public class AppOpsService extends IAppOpsService.Stub { proxiedPackageName); } + final boolean isChainTrusted = isCallerTrusted + && attributionChainId != ATTRIBUTION_CHAIN_ID_NONE + && ((proxyAttributionFlags & ATTRIBUTION_FLAG_TRUSTED) != 0 + || (proxiedAttributionFlags & ATTRIBUTION_FLAG_TRUSTED) != 0); final boolean isSelfBlame = Binder.getCallingUid() == proxiedUid; final boolean isProxyTrusted = mContext.checkPermission( Manifest.permission.UPDATE_APP_OPS_STATS, -1, proxyUid) == PackageManager.PERMISSION_GRANTED || isSelfBlame - || attributionChainId != ATTRIBUTION_CHAIN_ID_NONE; + || isChainTrusted; String resolvedProxiedPackageName = AppOpsManager.resolvePackageName(proxiedUid, proxiedPackageName); @@ -4051,7 +4058,8 @@ public class AppOpsService extends IAppOpsService.Stub { final String proxiedAttributionTag = attributionSource.getNextAttributionTag(); final IBinder proxiedToken = attributionSource.getNextToken(); - skipProxyOperation = resolveSkipProxyOperation(skipProxyOperation, attributionSource); + skipProxyOperation = skipProxyOperation + && isCallerAndAttributionTrusted(attributionSource); verifyIncomingProxyUid(attributionSource); verifyIncomingOp(code); @@ -4334,11 +4342,7 @@ public class AppOpsService extends IAppOpsService.Stub { } } - private boolean resolveSkipProxyOperation(boolean requestsSkipProxyOperation, - @NonNull AttributionSource attributionSource) { - if (!requestsSkipProxyOperation) { - return false; - } + private boolean isCallerAndAttributionTrusted(@NonNull AttributionSource attributionSource) { if (attributionSource.getUid() != Binder.getCallingUid() && attributionSource.isTrusted(mContext)) { return true; diff --git a/services/core/java/com/android/server/appop/DiscreteRegistry.java b/services/core/java/com/android/server/appop/DiscreteRegistry.java index 0439660f0c97..b9cc992b438e 100644 --- a/services/core/java/com/android/server/appop/DiscreteRegistry.java +++ b/services/core/java/com/android/server/appop/DiscreteRegistry.java @@ -19,6 +19,7 @@ package com.android.server.appop; import static android.app.AppOpsManager.ATTRIBUTION_CHAIN_ID_NONE; import static android.app.AppOpsManager.ATTRIBUTION_FLAG_ACCESSOR; import static android.app.AppOpsManager.ATTRIBUTION_FLAG_RECEIVER; +import static android.app.AppOpsManager.ATTRIBUTION_FLAG_TRUSTED; import static android.app.AppOpsManager.FILTER_BY_ATTRIBUTION_TAG; import static android.app.AppOpsManager.FILTER_BY_OP_NAMES; import static android.app.AppOpsManager.FILTER_BY_PACKAGE_NAME; @@ -359,7 +360,8 @@ final class DiscreteRegistry { for (int opEventNum = 0; opEventNum < nOpEvents; opEventNum++) { DiscreteOpEvent event = opEvents.get(opEventNum); if (event == null - || event.mAttributionChainId == ATTRIBUTION_CHAIN_ID_NONE) { + || event.mAttributionChainId == ATTRIBUTION_CHAIN_ID_NONE + || (event.mAttributionFlags & ATTRIBUTION_FLAG_TRUSTED) == 0) { continue; } diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java index 30eccca48ee9..fa4c985a1c44 100644 --- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java +++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java @@ -20,7 +20,10 @@ import static android.Manifest.permission.ADJUST_RUNTIME_PERMISSIONS_POLICY; import static android.Manifest.permission.CAPTURE_AUDIO_HOTWORD; import static android.Manifest.permission.READ_EXTERNAL_STORAGE; import static android.Manifest.permission.RECORD_AUDIO; +import static android.Manifest.permission.UPDATE_APP_OPS_STATS; import static android.Manifest.permission.WRITE_EXTERNAL_STORAGE; +import static android.app.AppOpsManager.ATTRIBUTION_CHAIN_ID_NONE; +import static android.app.AppOpsManager.ATTRIBUTION_FLAGS_NONE; import static android.app.AppOpsManager.MODE_ALLOWED; import static android.app.AppOpsManager.MODE_ERRORED; import static android.app.AppOpsManager.MODE_IGNORED; @@ -5601,13 +5604,14 @@ public class PermissionManagerService extends IPermissionManager.Stub { @PermissionCheckerManager.PermissionResult public int checkOp(int op, AttributionSourceState attributionSource, String message, boolean forDataDelivery, boolean startDataDelivery) { - int result = checkOp(mContext, op, new AttributionSource(attributionSource), message, - forDataDelivery, startDataDelivery); + int result = checkOp(mContext, op, mPermissionManagerServiceInternal, + new AttributionSource(attributionSource), message, forDataDelivery, + startDataDelivery); if (result != PermissionChecker.PERMISSION_GRANTED && startDataDelivery) { // Finish any started op if some step in the attribution chain failed. finishDataDelivery(op, attributionSource, /*fromDatasource*/ false); } - return result; + return result; } @PermissionCheckerManager.PermissionResult @@ -5731,8 +5735,14 @@ public class PermissionManagerService extends IPermissionManager.Stub { final int op = AppOpsManager.permissionToOpCode(permission); final int attributionChainId = getAttributionChainId(startDataDelivery, attributionSource); + final boolean hasChain = attributionChainId != ATTRIBUTION_CHAIN_ID_NONE; AttributionSource current = attributionSource; AttributionSource next = null; + // We consider the chain trusted if the start node has UPDATE_APP_OPS_STATS, and + // every attributionSource in the chain is registered with the system. + final boolean isChainStartTrusted = !hasChain || checkPermission(context, + permissionManagerServiceInt, UPDATE_APP_OPS_STATS, current.getUid(), + current.getRenouncedPermissions()); while (true) { final boolean skipCurrentChecks = (fromDatasource || next != null); @@ -5777,13 +5787,17 @@ public class PermissionManagerService extends IPermissionManager.Stub { && current.equals(attributionSource) && next != null && next.getNext() == null); final boolean selfAccess = singleReceiverFromDatasource || next == null; + final boolean isLinkTrusted = isChainStartTrusted + && (current.isTrusted(context) || current.equals(attributionSource)) + && (next == null || next.isTrusted(context)); - final int proxyAttributionFlags = (!skipCurrentChecks) + final int proxyAttributionFlags = (!skipCurrentChecks && hasChain) ? resolveProxyAttributionFlags(attributionSource, current, fromDatasource, - startDataDelivery, selfAccess) - : AppOpsManager.ATTRIBUTION_FLAGS_NONE; - final int proxiedAttributionFlags = resolveProxiedAttributionFlags( - attributionSource, next, fromDatasource, startDataDelivery, selfAccess); + startDataDelivery, selfAccess, isLinkTrusted) + : ATTRIBUTION_FLAGS_NONE; + final int proxiedAttributionFlags = hasChain ? resolveProxiedAttributionFlags( + attributionSource, next, fromDatasource, startDataDelivery, selfAccess, + isLinkTrusted) : ATTRIBUTION_FLAGS_NONE; final int opMode = performOpTransaction(context, op, current, message, forDataDelivery, startDataDelivery, skipCurrentChecks, selfAccess, @@ -5838,48 +5852,52 @@ public class PermissionManagerService extends IPermissionManager.Stub { private static @AttributionFlags int resolveProxyAttributionFlags( @NonNull AttributionSource attributionChain, @NonNull AttributionSource current, boolean fromDatasource, - boolean startDataDelivery, boolean selfAccess) { + boolean startDataDelivery, boolean selfAccess, boolean isTrusted) { return resolveAttributionFlags(attributionChain, current, fromDatasource, - startDataDelivery, selfAccess, /*flagsForProxy*/ true); + startDataDelivery, selfAccess, isTrusted, /*flagsForProxy*/ true); } private static @AttributionFlags int resolveProxiedAttributionFlags( @NonNull AttributionSource attributionChain, @NonNull AttributionSource current, boolean fromDatasource, - boolean startDataDelivery, boolean selfAccess) { + boolean startDataDelivery, boolean selfAccess, boolean isTrusted) { return resolveAttributionFlags(attributionChain, current, fromDatasource, - startDataDelivery, selfAccess, /*flagsForProxy*/ false); + startDataDelivery, selfAccess, isTrusted, /*flagsForProxy*/ false); } private static @AttributionFlags int resolveAttributionFlags( @NonNull AttributionSource attributionChain, @NonNull AttributionSource current, boolean fromDatasource, - boolean startDataDelivery, boolean selfAccess, boolean flagsForProxy) { + boolean startDataDelivery, boolean selfAccess, boolean isTrusted, + boolean flagsForProxy) { if (current == null || !startDataDelivery) { return AppOpsManager.ATTRIBUTION_FLAGS_NONE; } + int trustedFlag = isTrusted + ? AppOpsManager.ATTRIBUTION_FLAG_TRUSTED : AppOpsManager.ATTRIBUTION_FLAGS_NONE; if (flagsForProxy) { if (selfAccess) { - return AppOpsManager.ATTRIBUTION_FLAG_ACCESSOR; + return trustedFlag | AppOpsManager.ATTRIBUTION_FLAG_ACCESSOR; } else if (!fromDatasource && current.equals(attributionChain)) { - return AppOpsManager.ATTRIBUTION_FLAG_ACCESSOR; + return trustedFlag | AppOpsManager.ATTRIBUTION_FLAG_ACCESSOR; } } else { if (selfAccess) { - return AppOpsManager.ATTRIBUTION_FLAG_RECEIVER; + return trustedFlag | AppOpsManager.ATTRIBUTION_FLAG_RECEIVER; } else if (fromDatasource && current.equals(attributionChain.getNext())) { - return AppOpsManager.ATTRIBUTION_FLAG_ACCESSOR; + return trustedFlag | AppOpsManager.ATTRIBUTION_FLAG_ACCESSOR; } else if (current.getNext() == null) { - return AppOpsManager.ATTRIBUTION_FLAG_RECEIVER; + return trustedFlag | AppOpsManager.ATTRIBUTION_FLAG_RECEIVER; } } if (fromDatasource && current.equals(attributionChain)) { return AppOpsManager.ATTRIBUTION_FLAGS_NONE; } - return AppOpsManager.ATTRIBUTION_FLAG_INTERMEDIARY; + return trustedFlag | AppOpsManager.ATTRIBUTION_FLAG_INTERMEDIARY; } private static int checkOp(@NonNull Context context, @NonNull int op, + @NonNull PermissionManagerServiceInternal permissionManagerServiceInt, @NonNull AttributionSource attributionSource, @Nullable String message, boolean forDataDelivery, boolean startDataDelivery) { if (op < 0 || attributionSource.getPackageName() == null) { @@ -5888,10 +5906,17 @@ public class PermissionManagerService extends IPermissionManager.Stub { final int attributionChainId = getAttributionChainId(startDataDelivery, attributionSource); + final boolean hasChain = attributionChainId != ATTRIBUTION_CHAIN_ID_NONE; AttributionSource current = attributionSource; AttributionSource next = null; + // We consider the chain trusted if the start node has UPDATE_APP_OPS_STATS, and + // every attributionSource in the chain is registered with the system. + final boolean isChainStartTrusted = !hasChain || checkPermission(context, + permissionManagerServiceInt, UPDATE_APP_OPS_STATS, current.getUid(), + current.getRenouncedPermissions()); + while (true) { final boolean skipCurrentChecks = (next != null); next = current.getNext(); @@ -5904,14 +5929,17 @@ public class PermissionManagerService extends IPermissionManager.Stub { // The access is for oneself if this is the single attribution source in the chain. final boolean selfAccess = (next == null); + final boolean isLinkTrusted = isChainStartTrusted + && (current.isTrusted(context) || current.equals(attributionSource)) + && (next == null || next.isTrusted(context)); - final int proxyAttributionFlags = (!skipCurrentChecks) + final int proxyAttributionFlags = (!skipCurrentChecks && hasChain) ? resolveProxyAttributionFlags(attributionSource, current, - /*fromDatasource*/ false, startDataDelivery, selfAccess) - : AppOpsManager.ATTRIBUTION_FLAGS_NONE; - final int proxiedAttributionFlags = resolveProxiedAttributionFlags( + /*fromDatasource*/ false, startDataDelivery, selfAccess, + isLinkTrusted) : ATTRIBUTION_FLAGS_NONE; + final int proxiedAttributionFlags = hasChain ? resolveProxiedAttributionFlags( attributionSource, next, /*fromDatasource*/ false, startDataDelivery, - selfAccess); + selfAccess, isLinkTrusted) : ATTRIBUTION_FLAGS_NONE; final int opMode = performOpTransaction(context, op, current, message, forDataDelivery, startDataDelivery, skipCurrentChecks, selfAccess, diff --git a/services/core/java/com/android/server/speech/SpeechRecognitionManagerServiceImpl.java b/services/core/java/com/android/server/speech/SpeechRecognitionManagerServiceImpl.java index 264ef87c97bf..f7950aacedc3 100644 --- a/services/core/java/com/android/server/speech/SpeechRecognitionManagerServiceImpl.java +++ b/services/core/java/com/android/server/speech/SpeechRecognitionManagerServiceImpl.java @@ -29,6 +29,7 @@ import android.content.pm.ServiceInfo; import android.os.Binder; import android.os.IBinder; import android.os.RemoteException; +import android.permission.PermissionManager; import android.speech.IRecognitionListener; import android.speech.IRecognitionService; import android.speech.IRecognitionServiceManagerCallback; @@ -139,6 +140,10 @@ final class SpeechRecognitionManagerServiceImpl extends @NonNull AttributionSource attributionSource) throws RemoteException { attributionSource.enforceCallingUid(); + if (!attributionSource.isTrusted(mMaster.getContext())) { + mMaster.getContext().getSystemService(PermissionManager.class) + .registerAttributionSource(attributionSource); + } service.startListening(recognizerIntent, listener, attributionSource); } |