From c7988a90c749e5c2f8a19d50661492a39c3ec394 Mon Sep 17 00:00:00 2001 From: Bernardo Rufino Date: Wed, 1 Apr 2020 15:19:49 +0100 Subject: [PATCH] Allow sysUI to send a11y events for other package and user As discussed, we: * Allow apps with INTERACT_ACCROSS_USERS(_FULL) to specify an explicit user (instead of just USER_CURRENT). * Introduce new signature permission ACT_AS_PACKAGE_FOR_ACCESSIBILITY and grant it to sysUI. This permissions allow holders to specify another package on behalf of which they can perform a11y operations. This is for toasts since now sysUI renders toasts on behalf of the app for apps targeting R+. Bug: 152839254 Test: atest FrameworksServicesTests:AccessibilitySecurityPolicyTest FrameworksServicesTests:AccessibilityWindowManagerTest android.widget.cts.ToastTest Change-Id: I3541045d574518571f348051d53e24ff1a4a67ef --- core/res/AndroidManifest.xml | 5 + packages/SystemUI/AndroidManifest.xml | 1 + .../AccessibilityManagerService.java | 3 +- .../AccessibilitySecurityPolicy.java | 11 +- .../AccessibilityWindowManager.java | 3 +- .../AccessibilitySecurityPolicyTest.java | 109 ++++++++++++++++-- .../AccessibilityWindowManagerTest.java | 2 +- 7 files changed, 116 insertions(+), 18 deletions(-) diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index ee25ac27b25c7..62c2c5124ae27 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -3201,6 +3201,11 @@ + + + diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml index 133d375b8c6e1..37900fb134960 100644 --- a/packages/SystemUI/AndroidManifest.xml +++ b/packages/SystemUI/AndroidManifest.xml @@ -219,6 +219,7 @@ + diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java index 1a72cf023453d..c7eb3ce37f4f2 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java @@ -599,7 +599,8 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub // Make sure the reported package is one the caller has access to. event.setPackageName(mSecurityPolicy.resolveValidReportedPackageLocked( - event.getPackageName(), UserHandle.getCallingAppId(), resolvedUserId)); + event.getPackageName(), UserHandle.getCallingAppId(), resolvedUserId, + getCallingPid())); // This method does nothing for a background user. if (resolvedUserId == mCurrentUserId) { diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilitySecurityPolicy.java b/services/accessibility/java/com/android/server/accessibility/AccessibilitySecurityPolicy.java index d98e31eadb225..41f32075fb77b 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilitySecurityPolicy.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilitySecurityPolicy.java @@ -167,11 +167,12 @@ public class AccessibilitySecurityPolicy { * @param packageName The package name the app wants to expose * @param appId The app's id * @param userId The app's user id + * @param pid The app's process pid that requested this * @return A package name that is valid to report */ @Nullable public String resolveValidReportedPackageLocked( - @Nullable CharSequence packageName, int appId, int userId) { + @Nullable CharSequence packageName, int appId, int userId, int pid) { // Okay to pass no package if (packageName == null) { return null; @@ -191,6 +192,11 @@ public class AccessibilitySecurityPolicy { .getHostedWidgetPackages(resolvedUid), packageNameStr)) { return packageName.toString(); } + // If app has the targeted permission to act as another package + if (mContext.checkPermission(Manifest.permission.ACT_AS_PACKAGE_FOR_ACCESSIBILITY, + pid, resolvedUid) == PackageManager.PERMISSION_GRANTED) { + return packageName.toString(); + } // Otherwise, set the package to the first one in the UID final String[] packageNames = mPackageManager.getPackagesForUid(resolvedUid); if (ArrayUtils.isEmpty(packageNames)) { @@ -403,8 +409,7 @@ public class AccessibilitySecurityPolicy { || userId == UserHandle.USER_CURRENT_OR_SELF) { return currentUserId; } - throw new IllegalArgumentException("Calling user can be changed to only " - + "UserHandle.USER_CURRENT or UserHandle.USER_CURRENT_OR_SELF."); + return resolveProfileParentLocked(userId); } /** diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java index 5d97d213928f3..468e93a8f683c 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java @@ -955,7 +955,8 @@ public class AccessibilityWindowManager { // Makes sure the reported package is one the caller has access to. packageName = mSecurityPolicy.resolveValidReportedPackageLocked( - packageName, UserHandle.getCallingAppId(), resolvedUserId); + packageName, UserHandle.getCallingAppId(), resolvedUserId, + Binder.getCallingPid()); windowId = sNextWindowId++; // If the window is from a process that runs across users such as diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilitySecurityPolicyTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilitySecurityPolicyTest.java index 5a96347c4ae1a..cc8ac86d6b59c 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilitySecurityPolicyTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilitySecurityPolicyTest.java @@ -28,10 +28,12 @@ import static org.junit.Assert.assertThat; import static org.mockito.AdditionalAnswers.returnsFirstArg; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import android.Manifest; import android.accessibilityservice.AccessibilityServiceInfo; import android.app.AppOpsManager; import android.appwidget.AppWidgetManagerInternal; @@ -71,6 +73,8 @@ public class AccessibilitySecurityPolicyTest { private static final int WINDOWID = 0x000a; private static final int WINDOWID2 = 0x000b; private static final int APP_UID = 10400; + private static final int APP_PID = 2000; + private static final int SYSTEM_PID = 558; private static final String PERMISSION = "test-permission"; private static final String FUNCTION = "test-function-name"; @@ -196,13 +200,13 @@ public class AccessibilitySecurityPolicyTest { @Test public void resolveValidReportedPackage_nullPkgName_returnNull() { assertNull(mA11ySecurityPolicy.resolveValidReportedPackageLocked( - null, Process.SYSTEM_UID, UserHandle.USER_SYSTEM)); + null, Process.SYSTEM_UID, UserHandle.USER_SYSTEM, SYSTEM_PID)); } @Test public void resolveValidReportedPackage_uidIsSystem_returnPkgName() { assertEquals(mA11ySecurityPolicy.resolveValidReportedPackageLocked( - PACKAGE_NAME, Process.SYSTEM_UID, UserHandle.USER_SYSTEM), + PACKAGE_NAME, Process.SYSTEM_UID, UserHandle.USER_SYSTEM, SYSTEM_PID), PACKAGE_NAME); } @@ -213,7 +217,7 @@ public class AccessibilitySecurityPolicyTest { .thenReturn(APP_UID); assertEquals(mA11ySecurityPolicy.resolveValidReportedPackageLocked( - PACKAGE_NAME, APP_UID, UserHandle.USER_SYSTEM), + PACKAGE_NAME, APP_UID, UserHandle.USER_SYSTEM, APP_PID), PACKAGE_NAME); } @@ -221,6 +225,7 @@ public class AccessibilitySecurityPolicyTest { public void resolveValidReportedPackage_uidIsWidgetHost_pkgNameIsAppWidget_returnPkgName() throws PackageManager.NameNotFoundException { final int widgetHostUid = APP_UID; + final int widgetHostPid = APP_PID; final String hostPackageName = PACKAGE_NAME; final String widgetPackageName = PACKAGE_NAME2; final ArraySet widgetPackages = new ArraySet<>(); @@ -232,7 +237,7 @@ public class AccessibilitySecurityPolicyTest { .thenReturn(widgetHostUid); assertEquals(mA11ySecurityPolicy.resolveValidReportedPackageLocked( - widgetPackageName, widgetHostUid, UserHandle.USER_SYSTEM), + widgetPackageName, widgetHostUid, UserHandle.USER_SYSTEM, widgetHostPid), widgetPackageName); } @@ -247,10 +252,52 @@ public class AccessibilitySecurityPolicyTest { .thenThrow(PackageManager.NameNotFoundException.class); when(mMockAppWidgetManager.getHostedWidgetPackages(APP_UID)) .thenReturn(new ArraySet<>()); + when(mMockContext.checkPermission( + eq(Manifest.permission.ACT_AS_PACKAGE_FOR_ACCESSIBILITY), anyInt(), eq(APP_UID))) + .thenReturn(PackageManager.PERMISSION_DENIED); - assertEquals(mA11ySecurityPolicy.resolveValidReportedPackageLocked( - invalidPackageName, APP_UID, UserHandle.USER_SYSTEM), - PACKAGE_NAME); + assertEquals(PACKAGE_NAME, mA11ySecurityPolicy.resolveValidReportedPackageLocked( + invalidPackageName, APP_UID, UserHandle.USER_SYSTEM, APP_PID)); + } + + @Test + public void resolveValidReportedPackage_anotherPkgNameWithActAsPkgPermission_returnPkg() + throws PackageManager.NameNotFoundException { + final String wantedPackageName = PACKAGE_NAME2; + final int wantedUid = APP_UID + 1; + final String[] uidPackages = {PACKAGE_NAME}; + when(mMockPackageManager.getPackagesForUid(APP_UID)) + .thenReturn(uidPackages); + when(mMockPackageManager.getPackageUidAsUser(wantedPackageName, UserHandle.USER_SYSTEM)) + .thenReturn(wantedUid); + when(mMockAppWidgetManager.getHostedWidgetPackages(APP_UID)) + .thenReturn(new ArraySet<>()); + when(mMockContext.checkPermission( + eq(Manifest.permission.ACT_AS_PACKAGE_FOR_ACCESSIBILITY), anyInt(), eq(APP_UID))) + .thenReturn(PackageManager.PERMISSION_GRANTED); + + assertEquals(wantedPackageName, mA11ySecurityPolicy.resolveValidReportedPackageLocked( + wantedPackageName, APP_UID, UserHandle.USER_SYSTEM, APP_PID)); + } + + @Test + public void resolveValidReportedPackage_anotherPkgNameWithoutActAsPkgPermission_returnUidPkg() + throws PackageManager.NameNotFoundException { + final String wantedPackageName = PACKAGE_NAME2; + final int wantedUid = APP_UID + 1; + final String[] uidPackages = {PACKAGE_NAME}; + when(mMockPackageManager.getPackagesForUid(APP_UID)) + .thenReturn(uidPackages); + when(mMockPackageManager.getPackageUidAsUser(wantedPackageName, UserHandle.USER_SYSTEM)) + .thenReturn(wantedUid); + when(mMockAppWidgetManager.getHostedWidgetPackages(APP_UID)) + .thenReturn(new ArraySet<>()); + when(mMockContext.checkPermission( + eq(Manifest.permission.ACT_AS_PACKAGE_FOR_ACCESSIBILITY), anyInt(), eq(APP_UID))) + .thenReturn(PackageManager.PERMISSION_DENIED); + + assertEquals(PACKAGE_NAME, mA11ySecurityPolicy.resolveValidReportedPackageLocked( + wantedPackageName, APP_UID, UserHandle.USER_SYSTEM, APP_PID)); } @Test @@ -432,21 +479,59 @@ public class AccessibilitySecurityPolicyTest { UserHandle.USER_CURRENT_OR_SELF); } - @Test(expected = IllegalArgumentException.class) - public void resolveCallingUserId_callingParentNotCurrentUser_userIdIsInvalid_shouldException() { + @Test + public void resolveCallingUserId_anotherUserIdWithCrossUserPermission_returnUserId() { final AccessibilitySecurityPolicy spySecurityPolicy = Mockito.spy(mA11ySecurityPolicy); final int callingUserId = UserHandle.getUserId(Process.myUid()); final int callingParentId = 20; final int currentUserId = 30; - final int invalidUserId = 40; + final int wantedUserId = 40; when(mMockA11yUserManager.getCurrentUserIdLocked()) .thenReturn(currentUserId); doReturn(callingParentId).when(spySecurityPolicy).resolveProfileParentLocked( callingUserId); - when(mMockContext.checkCallingPermission(any())) + when(mMockContext.checkCallingPermission(Manifest.permission.INTERACT_ACROSS_USERS)) .thenReturn(PackageManager.PERMISSION_GRANTED); - spySecurityPolicy.resolveCallingUserIdEnforcingPermissionsLocked(invalidUserId); + assertEquals(wantedUserId, + spySecurityPolicy.resolveCallingUserIdEnforcingPermissionsLocked(wantedUserId)); + } + + @Test + public void resolveCallingUserId_anotherUserIdWithCrossUserFullPermission_returnUserId() { + final AccessibilitySecurityPolicy spySecurityPolicy = Mockito.spy(mA11ySecurityPolicy); + final int callingUserId = UserHandle.getUserId(Process.myUid()); + final int callingParentId = 20; + final int currentUserId = 30; + final int wantedUserId = 40; + when(mMockA11yUserManager.getCurrentUserIdLocked()) + .thenReturn(currentUserId); + doReturn(callingParentId).when(spySecurityPolicy).resolveProfileParentLocked( + callingUserId); + when(mMockContext.checkCallingPermission(Manifest.permission.INTERACT_ACROSS_USERS_FULL)) + .thenReturn(PackageManager.PERMISSION_GRANTED); + + assertEquals(wantedUserId, + spySecurityPolicy.resolveCallingUserIdEnforcingPermissionsLocked(wantedUserId)); + } + + @Test(expected = SecurityException.class) + public void resolveCallingUserId_anotherUserIdWithoutCrossUserPermission_shouldException() { + final AccessibilitySecurityPolicy spySecurityPolicy = Mockito.spy(mA11ySecurityPolicy); + final int callingUserId = UserHandle.getUserId(Process.myUid()); + final int callingParentId = 20; + final int currentUserId = 30; + final int wantedUserId = 40; + when(mMockA11yUserManager.getCurrentUserIdLocked()) + .thenReturn(currentUserId); + doReturn(callingParentId).when(spySecurityPolicy).resolveProfileParentLocked( + callingUserId); + when(mMockContext.checkCallingPermission(Manifest.permission.INTERACT_ACROSS_USERS)) + .thenReturn(PackageManager.PERMISSION_DENIED); + when(mMockContext.checkCallingPermission(Manifest.permission.INTERACT_ACROSS_USERS_FULL)) + .thenReturn(PackageManager.PERMISSION_DENIED); + + spySecurityPolicy.resolveCallingUserIdEnforcingPermissionsLocked(wantedUserId); } @Test diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityWindowManagerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityWindowManagerTest.java index 10a86f9ea5276..e4d51e4374a78 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityWindowManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityWindowManagerTest.java @@ -134,7 +134,7 @@ public class AccessibilityWindowManagerTest { when(mMockA11ySecurityPolicy.resolveCallingUserIdEnforcingPermissionsLocked( USER_SYSTEM_ID)).thenReturn(USER_SYSTEM_ID); when(mMockA11ySecurityPolicy.resolveValidReportedPackageLocked( - anyString(), anyInt(), anyInt())).thenReturn(PACKAGE_NAME); + anyString(), anyInt(), anyInt(), anyInt())).thenReturn(PACKAGE_NAME); mA11yWindowManager = new AccessibilityWindowManager(new Object(), mHandler, mMockWindowManagerInternal,