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/core/java/android/app/admin/DeviceAdminInfo.java b/core/java/android/app/admin/DeviceAdminInfo.java
index 5fbe5b3..cae3cf5 100644
--- a/core/java/android/app/admin/DeviceAdminInfo.java
+++ b/core/java/android/app/admin/DeviceAdminInfo.java
@@ -73,12 +73,10 @@
      * that the user can select, via {@link DevicePolicyManager#setPasswordQuality}
      * and {@link DevicePolicyManager#setPasswordMinimumLength}.
      *
-     * <p>To control this policy, the device admin must have a "limit-password"
-     * tag in the "uses-policies" section of its meta-data.
-     *
-     * <p>This policy is deprecated for use by a device admin.  In future releases, it will
-     * only be possible for a device owner or profile owner to enforce constraints on user
-     * passwords.
+     * <p>To control this policy, the device admin must be a device owner or profile owner,
+     * and must have a "limit-password" tag in the "uses-policies" section of its meta-data.
+     * If used by a device owner, the policy only affects the primary user and its profiles,
+     * but not any secondary users on the device.
      */
     public static final int USES_POLICY_LIMIT_PASSWORD = 0;
 
@@ -138,11 +136,10 @@
      * A type of policy that this device admin can use: force the user to
      * change their password after an administrator-defined time limit.
      *
-     * <p>To control this policy, the device admin must have an "expire-password"
-     * tag in the "uses-policies" section of its meta-data.
-     *
-     * <p>This policy is deprecated for use by a device admin.  In future releases, it will
-     * only be possible for a device owner or profile owner to enforce password expiry.
+     * <p>To control this policy, the device admin must be a device owner or profile owner,
+     * and must have an "expire-password" tag in the "uses-policies" section of its meta-data.
+     * If used by a device owner, the policy only affects the primary user and its profiles,
+     * but not any secondary users on the device.
      */
     public static final int USES_POLICY_EXPIRE_PASSWORD = 6;
 
@@ -157,23 +154,19 @@
     /**
      * A type of policy that this device admin can use: disables use of all device cameras.
      *
-     * <p>To control this policy, the device admin must have a "disable-camera"
-     * tag in the "uses-policies" section of its meta-data.
-     *
-     * <p>This policy is deprecated for use by a device admin.  In future releases, it will
-     * only be possible for a device owner or profile owner to disable use of the camera.
+     * <p>To control this policy, the device admin must be a device owner or profile owner,
+     * and must have a "disable-camera" tag in the "uses-policies" section of its meta-data.
+     * If used by a device owner, the policy affects all users on the device.
      */
     public static final int USES_POLICY_DISABLE_CAMERA = 8;
 
     /**
      * A type of policy that this device admin can use: disables use of keyguard features.
      *
-     * <p>To control this policy, the device admin must have a "disable-keyguard-features"
-     * tag in the "uses-policies" section of its meta-data.
-     *
-     * <p>This policy is deprecated for use by a device admin.  In future releases, it will
-     * only be possible for a device owner or profile owner to disable use of keyguard
-     * features.
+     * <p>To control this policy, the device admin must be a device owner or profile owner,
+     * and must have a "disable-keyguard-features" tag in the "uses-policies" section of its
+     * meta-data.  If used by a device owner, the policy only affects the primary user and
+     * its profiles, but not any secondary users on the device.
      */
     public static final int USES_POLICY_DISABLE_KEYGUARD_FEATURES = 9;
 
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() {