From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Pavel Grafov <pgrafov@google.com>
Date: Wed, 5 Apr 2023 15:15:41 +0000
Subject: [PATCH] Ensure policy has no absurdly long strings

The following APIs now enforce limits and throw IllegalArgumentException
when limits are violated:
* DPM.setTrustAgentConfiguration() limits agent packgage name,
  component name, and strings within configuration bundle.
* DPM.setPermittedAccessibilityServices() limits package names.
* DPM.setPermittedInputMethods() limits package names.
* DPM.setAccountManagementDisabled() limits account name.
* DPM.setLockTaskPackages() limits package names.
* DPM.setAffiliationIds() limits id.
* DPM.transferOwnership() limits strings inside the bundle.

Package names are limited at 223, because they become directory names
and it is a filesystem restriction, see FrameworkParsingPackageUtils.

All other strings are limited at 65535, because longer ones break binary
XML serializer.

The following APIs silently truncate strings that are long beyond reason:
* DPM.setShortSupportMessage() truncates message at 200.
* DPM.setLongSupportMessage() truncates message at 20000.
* DPM.setOrganizationName() truncates org name at 200.

Bug: 260729089
Test: atest com.android.server.devicepolicy
(cherry picked from https://googleplex-android-review.googlesource.com/q/commit:bb7e82ceaa6d16267e7b0e14563161b506d26be8)
Merged-In: Idcf54e408722f164d16bf2f24a00cd1f5b626d23
Change-Id: Idcf54e408722f164d16bf2f24a00cd1f5b626d23
---
 .../app/admin/DevicePolicyManager.java        |  3 +-
 .../DevicePolicyManagerService.java           | 91 ++++++++++++++++++-
 2 files changed, 90 insertions(+), 4 deletions(-)

diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 485ce78c3320..28b7ccb7b946 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -8100,7 +8100,8 @@ public class DevicePolicyManager {
 
     /**
      * Called by a device admin to set the long support message. This will be displayed to the user
-     * in the device administators settings screen.
+     * in the device administrators settings screen. If the message is longer than 20000 characters
+     * it may be truncated.
      * <p>
      * If the long support message needs to be localized, it is the responsibility of the
      * {@link DeviceAdminReceiver} to listen to the {@link Intent#ACTION_LOCALE_CHANGED} broadcast
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index d7539e11bea9..2fd54b4981af 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -250,6 +250,7 @@ import java.lang.reflect.Constructor;
 import java.nio.charset.StandardCharsets;
 import java.text.DateFormat;
 import java.time.LocalDate;
+import java.util.ArrayDeque;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
@@ -260,6 +261,7 @@ import java.util.List;
 import java.util.Map;
 import java.util.Map.Entry;
 import java.util.Objects;
+import java.util.Queue;
 import java.util.Set;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
@@ -325,6 +327,15 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
 
     private static final int REQUEST_EXPIRE_PASSWORD = 5571;
 
+    // Binary XML serializer doesn't support longer strings
+    private static final int MAX_POLICY_STRING_LENGTH = 65535;
+    // FrameworkParsingPackageUtils#MAX_FILE_NAME_SIZE, Android packages are used in dir names.
+    private static final int MAX_PACKAGE_NAME_LENGTH = 223;
+
+    private static final int MAX_LONG_SUPPORT_MESSAGE_LENGTH = 20000;
+    private static final int MAX_SHORT_SUPPORT_MESSAGE_LENGTH = 200;
+    private static final int MAX_ORG_NAME_LENGTH = 200;
+
     private static final long MS_PER_DAY = TimeUnit.DAYS.toMillis(1);
 
     private static final long EXPIRATION_GRACE_PERIOD_MS = 5 * MS_PER_DAY; // 5 days, in ms
@@ -8284,6 +8295,12 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
         }
         Preconditions.checkNotNull(admin, "admin is null");
         Preconditions.checkNotNull(agent, "agent is null");
+        enforceMaxPackageNameLength(agent.getPackageName());
+        final String agentAsString = agent.flattenToString();
+        enforceMaxStringLength(agentAsString, "agent name");
+        if (args != null) {
+            enforceMaxStringLength(args, "args");
+        }
         final int userHandle = UserHandle.getCallingUserId();
         synchronized (getLockObject()) {
             ActiveAdmin ap = getActiveAdminForCallerLocked(admin,
@@ -8486,6 +8503,10 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
         Preconditions.checkNotNull(who, "ComponentName is null");
 
         if (packageList != null) {
+            for (String pkg : (List<String>) packageList) {
+                enforceMaxPackageNameLength(pkg);
+            }
+
             int userId = UserHandle.getCallingUserId();
             List<AccessibilityServiceInfo> enabledServices = null;
             long id = mInjector.binderClearCallingIdentity();
@@ -8668,6 +8689,10 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
 
         final int callingUserId = mInjector.userHandleGetCallingUserId();
         if (packageList != null) {
+            for (String pkg : (List<String>) packageList) {
+                enforceMaxPackageNameLength(pkg);
+            }
+
             // InputMethodManager fetches input methods for current user.
             // So this can only be set when calling user is the current user
             // or parent is current user in case of managed profiles.
@@ -9608,6 +9633,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
             return;
         }
         Preconditions.checkNotNull(who, "ComponentName is null");
+        enforceMaxStringLength(accountType, "account type");
         synchronized (getLockObject()) {
             ActiveAdmin ap = getActiveAdminForCallerLocked(who,
                     DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
@@ -9871,6 +9897,9 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
             throws SecurityException {
         Preconditions.checkNotNull(who, "ComponentName is null");
         Preconditions.checkNotNull(packages, "packages is null");
+        for (String pkg : packages) {
+            enforceMaxPackageNameLength(pkg);
+        }
 
         synchronized (getLockObject()) {
             enforceCanCallLockTaskLocked(who);
@@ -11249,6 +11278,9 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
         if (!mHasFeature) {
             return;
         }
+
+        message = truncateIfLonger(message, MAX_LONG_SUPPORT_MESSAGE_LENGTH);
+
         Preconditions.checkNotNull(who, "ComponentName is null");
         final int userHandle = mInjector.userHandleGetCallingUserId();
         synchronized (getLockObject()) {
@@ -11280,6 +11312,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
             return;
         }
         Preconditions.checkNotNull(who, "ComponentName is null");
+        message = truncateIfLonger(message, MAX_SHORT_SUPPORT_MESSAGE_LENGTH);
+
         final int userHandle = mInjector.userHandleGetCallingUserId();
         synchronized (getLockObject()) {
             ActiveAdmin admin = getActiveAdminForUidLocked(who,
@@ -11408,6 +11442,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
         Preconditions.checkNotNull(who, "ComponentName is null");
         final int userHandle = mInjector.userHandleGetCallingUserId();
 
+        text = truncateIfLonger(text, MAX_ORG_NAME_LENGTH);
+
         synchronized (getLockObject()) {
             ActiveAdmin admin = getActiveAdminForCallerLocked(who,
                     DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
@@ -11572,9 +11608,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
             throw new IllegalArgumentException("ids must not be null");
         }
         for (String id : ids) {
-            if (TextUtils.isEmpty(id)) {
-                throw new IllegalArgumentException("ids must not contain empty string");
-            }
+            Preconditions.checkArgument(!TextUtils.isEmpty(id), "ids must not have empty string");
+            enforceMaxStringLength(id, "affiliation id");
         }
 
         final Set<String> affiliationIds = new ArraySet<>(ids);
@@ -12740,6 +12775,9 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
 
         Preconditions.checkNotNull(admin, "Admin cannot be null.");
         Preconditions.checkNotNull(target, "Target cannot be null.");
+        if (bundle != null) {
+            enforceMaxStringLength(bundle, "bundle");
+        }
 
         enforceProfileOrDeviceOwner(admin);
 
@@ -13194,4 +13232,51 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
     private static String getManagedProvisioningPackage(Context context) {
         return context.getResources().getString(R.string.config_managed_provisioning_package);
     }
+
+    /**
+     * Truncates char sequence to maximum length, nulls are ignored.
+     */
+    private static CharSequence truncateIfLonger(CharSequence input, int maxLength) {
+        return input == null || input.length() <= maxLength
+                ? input
+                : input.subSequence(0, maxLength);
+    }
+
+    /**
+     * Throw if string argument is too long to be serialized.
+     */
+    private static void enforceMaxStringLength(String str, String argName) {
+        Preconditions.checkArgument(
+                str.length() <= MAX_POLICY_STRING_LENGTH, argName + " loo long");
+    }
+
+    private static void enforceMaxPackageNameLength(String pkg) {
+        Preconditions.checkArgument(
+                pkg.length() <= MAX_PACKAGE_NAME_LENGTH, "Package name too long");
+    }
+
+    /**
+     * Throw if persistable bundle contains any string that we can't serialize.
+     */
+    private static void enforceMaxStringLength(PersistableBundle bundle, String argName) {
+        // Persistable bundles can have other persistable bundles as values, traverse with a queue.
+        Queue<PersistableBundle> queue = new ArrayDeque<>();
+        queue.add(bundle);
+        while (!queue.isEmpty()) {
+            PersistableBundle current = queue.remove();
+            for (String key : current.keySet()) {
+                enforceMaxStringLength(key, "key in " + argName);
+                Object value = current.get(key);
+                if (value instanceof String) {
+                    enforceMaxStringLength((String) value, "string value in " + argName);
+                } else if (value instanceof String[]) {
+                    for (String str : (String[]) value) {
+                        enforceMaxStringLength(str, "string value in " + argName);
+                    }
+                } else if (value instanceof PersistableBundle) {
+                    queue.add((PersistableBundle) value);
+                }
+            }
+        }
+    }
 }
