Block Policies From Device Admin Targetting Q
If a device admin app targets Android Q or above, and it is not a device
owner or profile owner, throw a SecurityException if it attempts to
control the following policies:
- DeviceAdminInfo.USES_POLICY_DISABLE_CAMERA
- DeviceAdminInfo.USES_POLICY_DISABLE_KEYGUARD_FEATURES
- DeviceAdminInfo.USES_POLICY_EXPIRE_PASSWORD
- DeviceAdminInfo.USES_POLICY_LIMIT_PASSWORD
The set of policies available to a device admin targetting Android P or below is unchanged.
Bug: 111546201
Test: com.android.server.devicepolicy.DevicePolicyManagerTest
Test: com.android.cts.devicepolicy.DeviceAdminHostSideTestApi24
Test: com.android.cts.devicepolicy.DeviceAdminHostSideTestApi29
Test: com.android.cts.devicepolicy.ManagedProfileTest
Change-Id: Idcd0b4b91ad2fa363535c718928d382c7da054d4
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 81ac6a4..1ea1a9e 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -377,6 +377,7 @@
private static final Set<String> GLOBAL_SETTINGS_WHITELIST;
private static final Set<String> GLOBAL_SETTINGS_DEPRECATED;
private static final Set<String> SYSTEM_SETTINGS_WHITELIST;
+ private static final Set<Integer> DA_DISALLOWED_POLICIES;
static {
SECURE_SETTINGS_WHITELIST = new ArraySet<>();
SECURE_SETTINGS_WHITELIST.add(Settings.Secure.DEFAULT_INPUT_METHOD);
@@ -408,6 +409,12 @@
SYSTEM_SETTINGS_WHITELIST.add(Settings.System.SCREEN_BRIGHTNESS);
SYSTEM_SETTINGS_WHITELIST.add(Settings.System.SCREEN_BRIGHTNESS_MODE);
SYSTEM_SETTINGS_WHITELIST.add(Settings.System.SCREEN_OFF_TIMEOUT);
+
+ DA_DISALLOWED_POLICIES = new ArraySet<>();
+ DA_DISALLOWED_POLICIES.add(DeviceAdminInfo.USES_POLICY_DISABLE_CAMERA);
+ DA_DISALLOWED_POLICIES.add(DeviceAdminInfo.USES_POLICY_DISABLE_KEYGUARD_FEATURES);
+ DA_DISALLOWED_POLICIES.add(DeviceAdminInfo.USES_POLICY_EXPIRE_PASSWORD);
+ DA_DISALLOWED_POLICIES.add(DeviceAdminInfo.USES_POLICY_LIMIT_PASSWORD);
}
/**
@@ -2607,6 +2614,9 @@
final int userId = UserHandle.getUserId(callingUid);
final DevicePolicyData policy = getUserData(userId);
ActiveAdmin admin = policy.mAdminMap.get(who);
+ final boolean isDeviceOwner = isDeviceOwner(admin.info.getComponent(), userId);
+ final boolean isProfileOwner = isProfileOwner(admin.info.getComponent(), userId);
+
if (reqPolicy == DeviceAdminInfo.USES_POLICY_DEVICE_OWNER) {
throw new SecurityException("Admin " + admin.info.getComponent()
+ " does not own the device");
@@ -2615,6 +2625,11 @@
throw new SecurityException("Admin " + admin.info.getComponent()
+ " does not own the profile");
}
+ if (DA_DISALLOWED_POLICIES.contains(reqPolicy) && !isDeviceOwner && !isProfileOwner) {
+ throw new SecurityException("Admin " + admin.info.getComponent()
+ + " is not a device owner or profile owner, so may not use policy: "
+ + admin.info.getTagForPolicy(reqPolicy));
+ }
throw new SecurityException("Admin " + admin.info.getComponent()
+ " did not specify uses-policy for: "
+ admin.info.getTagForPolicy(reqPolicy));
@@ -2694,7 +2709,10 @@
// DO always has the PO power.
return ownsDevice || ownsProfile;
} else {
- return admin.info.usesPolicy(reqPolicy);
+ boolean allowedToUsePolicy = ownsDevice || ownsProfile
+ || !DA_DISALLOWED_POLICIES.contains(reqPolicy)
+ || getTargetSdk(admin.info.getPackageName(), userId) < Build.VERSION_CODES.Q;
+ return allowedToUsePolicy && admin.info.usesPolicy(reqPolicy);
}
}
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
index a23636c..5da2a85 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -1713,24 +1713,35 @@
UserManager.DISALLOW_ADD_USER),
eq(true), eq(CAMERA_DISABLED_GLOBALLY));
reset(getServices().userManagerInternal);
+ }
- // Set up another DA and let it disable camera. Now DISALLOW_CAMERA will only be applied
- // locally.
- dpm.setCameraDisabled(admin1, false);
- reset(getServices().userManagerInternal);
+ public void testDaDisallowedPolicies_SecurityException() throws Exception {
+ mContext.callerPermissions.add(permission.MANAGE_DEVICE_ADMINS);
+ mContext.callerPermissions.add(permission.INTERACT_ACROSS_USERS_FULL);
- setUpPackageManagerForAdmin(admin2, DpmMockContext.CALLER_SYSTEM_USER_UID);
- dpm.setActiveAdmin(admin2, /* replace =*/ false, UserHandle.USER_SYSTEM);
- dpm.setCameraDisabled(admin2, true);
+ setUpPackageManagerForAdmin(admin1, DpmMockContext.CALLER_SYSTEM_USER_UID);
+ dpm.setActiveAdmin(admin1, /* replace =*/ false, UserHandle.USER_SYSTEM);
- verify(getServices().userManagerInternal).setDevicePolicyUserRestrictions(
- eq(UserHandle.USER_SYSTEM),
- // DISALLOW_CAMERA will be applied to both local and global. <- TODO: fix this
- MockUtils.checkUserRestrictions(UserManager.DISALLOW_FUN,
- UserManager.DISALLOW_ADD_USER),
- eq(true), eq(CAMERA_DISABLED_LOCALLY));
- reset(getServices().userManagerInternal);
- // TODO Make sure restrictions are written to the file.
+ boolean originalCameraDisabled = dpm.getCameraDisabled(admin1);
+ assertExpectException(SecurityException.class, /* messageRegex= */ null,
+ () -> dpm.setCameraDisabled(admin1, true));
+ assertEquals(originalCameraDisabled, dpm.getCameraDisabled(admin1));
+
+ int originalKeyguardDisabledFeatures = dpm.getKeyguardDisabledFeatures(admin1);
+ assertExpectException(SecurityException.class, /* messageRegex= */ null,
+ () -> dpm.setKeyguardDisabledFeatures(admin1,
+ DevicePolicyManager.KEYGUARD_DISABLE_FEATURES_ALL));
+ assertEquals(originalKeyguardDisabledFeatures, dpm.getKeyguardDisabledFeatures(admin1));
+
+ long originalPasswordExpirationTimeout = dpm.getPasswordExpirationTimeout(admin1);
+ assertExpectException(SecurityException.class, /* messageRegex= */ null,
+ () -> dpm.setPasswordExpirationTimeout(admin1, 1234));
+ assertEquals(originalPasswordExpirationTimeout, dpm.getPasswordExpirationTimeout(admin1));
+
+ int originalPasswordQuality = dpm.getPasswordQuality(admin1);
+ assertExpectException(SecurityException.class, /* messageRegex= */ null,
+ () -> dpm.setPasswordQuality(admin1, DevicePolicyManager.PASSWORD_QUALITY_NUMERIC));
+ assertEquals(originalPasswordQuality, dpm.getPasswordQuality(admin1));
}
public void testSetUserRestriction_asPo() {