diff options
3 files changed, 243 insertions, 6 deletions
diff --git a/core/java/android/app/admin/DevicePolicyManagerInternal.java b/core/java/android/app/admin/DevicePolicyManagerInternal.java index 840f3a3c6bb8..3a61ca1fbf93 100644 --- a/core/java/android/app/admin/DevicePolicyManagerInternal.java +++ b/core/java/android/app/admin/DevicePolicyManagerInternal.java @@ -271,6 +271,31 @@ public abstract class DevicePolicyManagerInternal { public abstract void resetOp(int op, String packageName, @UserIdInt int userId); /** + * Checks if the calling process has been granted permission to apply a device policy on a + * specific user. + * + * The given permission will be checked along with its associated cross-user permission, if it + * exists and the target user is different to the calling user. + * + * @param permission The name of the permission being checked. + * @param targetUserId The userId of the user which the caller needs permission to act on. + * @throws SecurityException If the calling process has not been granted the permission. + */ + public abstract void enforcePermission(String permission, int targetUserId); + + /** + * Return whether the calling process has been granted permission to apply a device policy on + * a specific user. + * + * The given permission will be checked along with its associated cross-user + * permission, if it exists and the target user is different to the calling user. + * + * @param permission The name of the permission being checked. + * @param targetUserId The userId of the user which the caller needs permission to act on. + */ + public abstract boolean hasPermission(String permission, int targetUserId); + + /** * Returns whether new "turn off work" behavior is enabled via feature flag. */ public abstract boolean isKeepProfilesRunningEnabled(); diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index c42ddf81dfd4..92d6fb194557 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -18,6 +18,10 @@ package com.android.server.devicepolicy; import static android.Manifest.permission.BIND_DEVICE_ADMIN; import static android.Manifest.permission.MANAGE_CA_CERTIFICATES; +import static android.Manifest.permission.MANAGE_DEVICE_POLICY_ACROSS_USERS; +import static android.Manifest.permission.MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL; +import static android.Manifest.permission.MANAGE_DEVICE_POLICY_ACROSS_USERS_SECURITY_CRITICAL; +import static android.Manifest.permission.QUERY_ADMIN_POLICY; import static android.Manifest.permission.REQUEST_PASSWORD_COMPLEXITY; import static android.accessibilityservice.AccessibilityServiceInfo.FEEDBACK_ALL_MASK; import static android.app.ActivityManager.LOCK_TASK_MODE_NONE; @@ -137,6 +141,7 @@ import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_AWARE; import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_UNAWARE; import static android.content.pm.PackageManager.MATCH_UNINSTALLED_PACKAGES; +import static android.content.pm.PackageManager.PERMISSION_GRANTED; import static android.net.ConnectivityManager.PROFILE_NETWORK_PREFERENCE_DEFAULT; import static android.net.ConnectivityManager.PROFILE_NETWORK_PREFERENCE_ENTERPRISE; import static android.net.ConnectivityManager.PROFILE_NETWORK_PREFERENCE_ENTERPRISE_NO_FALLBACK; @@ -13393,6 +13398,16 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { broadcastIntentToDevicePolicyManagerRoleHolder(intent, parentHandle); } + @Override + public void enforcePermission(String permission, int targetUserId) { + DevicePolicyManagerService.this.enforcePermission(permission, targetUserId); + } + + @Override + public boolean hasPermission(String permission, int targetUserId) { + return DevicePolicyManagerService.this.hasPermission(permission, targetUserId); + } + private void broadcastIntentToCrossProfileManifestReceivers( Intent intent, UserHandle userHandle, boolean requiresPermission) { final int userId = userHandle.getIdentifier(); @@ -18137,14 +18152,25 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } } + private String getDevicePolicyManagementRoleHolderPackageName(Context context) { RoleManager roleManager = context.getSystemService(RoleManager.class); - List<String> roleHolders = - roleManager.getRoleHolders(RoleManager.ROLE_DEVICE_POLICY_MANAGEMENT); - if (roleHolders.isEmpty()) { - return null; - } - return roleHolders.get(0); + + // Calling identity needs to be cleared as this method is used in the permissions checks. + return mInjector.binderWithCleanCallingIdentity(() -> { + List<String> roleHolders = + roleManager.getRoleHolders(RoleManager.ROLE_DEVICE_POLICY_MANAGEMENT); + if (roleHolders.isEmpty()) { + return null; + } + return roleHolders.get(0); + }); + } + + private boolean isDevicePolicyManagementRoleHolder(CallerIdentity caller) { + String devicePolicyManagementRoleHolderPackageName = + getDevicePolicyManagementRoleHolderPackageName(mContext); + return caller.getPackageName().equals(devicePolicyManagementRoleHolderPackageName); } private void resetInteractAcrossProfilesAppOps() { @@ -19221,6 +19247,174 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { }); } + // DPC types + private static final int DEFAULT_DEVICE_OWNER = 0; + private static final int FINANCED_DEVICE_OWNER = 1; + private static final int PROFILE_OWNER_OF_ORGANIZATION_OWNED_DEVICE = 2; + private static final int PROFILE_OWNER_ON_USER_0 = 3; + private static final int PROFILE_OWNER = 4; + + // Permissions of existing DPC types. + private static final List<String> DEFAULT_DEVICE_OWNER_PERMISSIONS = List.of( + MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL, + MANAGE_DEVICE_POLICY_ACROSS_USERS, + MANAGE_DEVICE_POLICY_ACROSS_USERS_SECURITY_CRITICAL); + private static final List<String> FINANCED_DEVICE_OWNER_PERMISSIONS = List.of( + MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL, + MANAGE_DEVICE_POLICY_ACROSS_USERS, + MANAGE_DEVICE_POLICY_ACROSS_USERS_SECURITY_CRITICAL); + private static final List<String> PROFILE_OWNER_OF_ORGANIZATION_OWNED_DEVICE_PERMISSIONS = + List.of( + MANAGE_DEVICE_POLICY_ACROSS_USERS, + MANAGE_DEVICE_POLICY_ACROSS_USERS_SECURITY_CRITICAL); + private static final List<String> PROFILE_OWNER_ON_USER_0_PERMISSIONS = List.of(); + private static final List<String> PROFILE_OWNER_PERMISSIONS = List.of( + MANAGE_DEVICE_POLICY_ACROSS_USERS_SECURITY_CRITICAL); + + + private static final HashMap<Integer, List<String>> DPC_PERMISSIONS = new HashMap<>(); + { + DPC_PERMISSIONS.put(DEFAULT_DEVICE_OWNER, DEFAULT_DEVICE_OWNER_PERMISSIONS); + DPC_PERMISSIONS.put(FINANCED_DEVICE_OWNER, FINANCED_DEVICE_OWNER_PERMISSIONS); + DPC_PERMISSIONS.put(PROFILE_OWNER_OF_ORGANIZATION_OWNED_DEVICE, + PROFILE_OWNER_OF_ORGANIZATION_OWNED_DEVICE_PERMISSIONS); + DPC_PERMISSIONS.put(PROFILE_OWNER_ON_USER_0, PROFILE_OWNER_ON_USER_0_PERMISSIONS); + DPC_PERMISSIONS.put(PROFILE_OWNER, PROFILE_OWNER_PERMISSIONS); + } + + //TODO(b/254253251) Fill this map in as new permissions are added for policies. + private static final HashMap<String, Integer> ACTIVE_ADMIN_POLICIES = new HashMap<>(); + + private static final HashMap<String, String> CROSS_USER_PERMISSIONS = new HashMap<>(); + + /** + * Checks if the calling process has been granted permission to apply a device policy on a + * specific user. + * The given permission will be checked along with its associated cross-user permission if it + * exists and the target user is different to the calling user. + * + * @param permission The name of the permission being checked. + * @param targetUserId The userId of the user which the caller needs permission to act on. + * @throws SecurityException if the caller has not been granted the given permission, + * the associtated cross-user permission if the caller's user is different to the target user. + */ + private void enforcePermission(String permission, int targetUserId) + throws SecurityException { + if (!hasPermission(permission, targetUserId)) { + throw new SecurityException("Caller does not have the required permissions for " + + "this user. Permissions required: {" + + permission + + ", " + + CROSS_USER_PERMISSIONS.get(permission) + + "}"); + } + } + + /** + * Return whether the calling process has been granted permission to query a device policy on + * a specific user. + * + * @param permission The name of the permission being checked. + * @param targetUserId The userId of the user which the caller needs permission to act on. + * @throws SecurityException if the caller has not been granted the given permission, + * the associatated cross-user permission if the caller's user is different to the target user + * and if the user has not been granted {@link QUERY_ADMIN_POLICY}. + */ + private void enforceCanQuery(String permission, int targetUserId) throws SecurityException { + if (hasPermission(QUERY_ADMIN_POLICY)) { + return; + } + enforcePermission(permission, targetUserId); + } + + /** + * Return whether the calling process has been granted permission to apply a device policy on + * a specific user. + * + * @param permission The name of the permission being checked. + * @param targetUserId The userId of the user which the caller needs permission to act on. + */ + private boolean hasPermission(String permission, int targetUserId) { + boolean hasPermission = hasPermission(permission); + if (getCallerIdentity().getUserId() != targetUserId) { + hasPermission = hasPermission + && hasPermission(CROSS_USER_PERMISSIONS.get(permission)); + } + return hasPermission; + } + + /** + * Return whether the calling process has been granted the given permission. + * + * @param permission The name of the permission being checked. + */ + private boolean hasPermission(String permission) { + if (permission == null) { + return true; + } + + CallerIdentity caller = getCallerIdentity(); + + // Check if the caller holds the permission + if (mContext.checkCallingOrSelfPermission(permission) == PERMISSION_GRANTED) { + return true; + } + // Check the permissions of DPCs + if (isDefaultDeviceOwner(caller)) { + return DPC_PERMISSIONS.get(DEFAULT_DEVICE_OWNER).contains(permission); + } + if (isFinancedDeviceOwner(caller)) { + return DPC_PERMISSIONS.get(FINANCED_DEVICE_OWNER).contains(permission); + } + if (isProfileOwnerOfOrganizationOwnedDevice(caller)) { + return DPC_PERMISSIONS.get(PROFILE_OWNER_OF_ORGANIZATION_OWNED_DEVICE).contains( + permission); + } + if (isProfileOwnerOnUser0(caller)) { + return DPC_PERMISSIONS.get(PROFILE_OWNER_ON_USER_0).contains(permission); + } + if (isProfileOwner(caller)) { + return DPC_PERMISSIONS.get(PROFILE_OWNER).contains(permission); + } + // Check the permission for the role-holder + if (isDevicePolicyManagementRoleHolder(caller)) { + return anyDpcHasPermission(permission, mContext.getUserId()); + } + // Check if the caller is an active admin that uses a certain policy. + if (ACTIVE_ADMIN_POLICIES.containsKey(permission)) { + return getActiveAdminForCallerLocked( + null, ACTIVE_ADMIN_POLICIES.get(permission), false) != null; + } + + return false; + } + + /** + * Returns whether there is a DPC on the given user that has been granted the given permission. + * + * @param permission The name of the permission being checked. + * @param userId The id of the user to check. + */ + private boolean anyDpcHasPermission(String permission, int userId) { + if (mOwners.isDefaultDeviceOwnerUserId(userId)) { + return DPC_PERMISSIONS.get(DEFAULT_DEVICE_OWNER).contains(permission); + } + if (mOwners.isFinancedDeviceOwnerUserId(userId)) { + return DPC_PERMISSIONS.get(FINANCED_DEVICE_OWNER).contains(permission); + } + if (mOwners.isProfileOwnerOfOrganizationOwnedDevice(userId)) { + return DPC_PERMISSIONS.get(PROFILE_OWNER_OF_ORGANIZATION_OWNED_DEVICE).contains( + permission); + } + if (userId == 0 && mOwners.hasProfileOwner(0)) { + return DPC_PERMISSIONS.get(PROFILE_OWNER_ON_USER_0).contains(permission); + } + if (mOwners.hasProfileOwner(userId)) { + return DPC_PERMISSIONS.get(PROFILE_OWNER).contains(permission); + } + return false; + } + // TODO(b/260560985): properly gate coexistence changes private boolean isCoexistenceEnabled(CallerIdentity caller) { return isCoexistenceFlagEnabled() diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java b/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java index 6f172e4515fc..581a19913530 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java @@ -19,6 +19,7 @@ package com.android.server.devicepolicy; import static android.app.admin.DevicePolicyManager.DEPRECATE_USERMANAGERINTERNAL_DEVICEPOLICY_DEFAULT; import static android.app.admin.DevicePolicyManager.DEPRECATE_USERMANAGERINTERNAL_DEVICEPOLICY_FLAG; import static android.app.admin.DevicePolicyManager.DEVICE_OWNER_TYPE_DEFAULT; +import static android.app.admin.DevicePolicyManager.DEVICE_OWNER_TYPE_FINANCED; import static com.android.server.devicepolicy.DeviceStateCacheImpl.NO_DEVICE_OWNER; @@ -455,6 +456,23 @@ class Owners { } } + boolean isDefaultDeviceOwnerUserId(int userId) { + synchronized (mData) { + return mData.mDeviceOwner != null + && mData.mDeviceOwnerUserId == userId + && getDeviceOwnerType(getDeviceOwnerPackageName()) == DEVICE_OWNER_TYPE_DEFAULT; + } + } + + boolean isFinancedDeviceOwnerUserId(int userId) { + synchronized (mData) { + return mData.mDeviceOwner != null + && mData.mDeviceOwnerUserId == userId + && getDeviceOwnerType(getDeviceOwnerPackageName()) + == DEVICE_OWNER_TYPE_FINANCED; + } + } + boolean hasProfileOwner(int userId) { synchronized (mData) { return getProfileOwnerComponent(userId) != null; |