From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: Pavel Grafov 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 5e263b0d05b6..bff9bfdf185d 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -9075,7 +9075,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. *

* 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 b2e23da08e2d..682ea6edf726 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -278,6 +278,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; @@ -287,6 +288,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; +import java.util.Queue; import java.util.Set; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; @@ -351,6 +353,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 @@ -9042,6 +9053,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, @@ -9262,6 +9279,10 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { Preconditions.checkNotNull(who, "ComponentName is null"); if (packageList != null) { + for (String pkg : (List) packageList) { + enforceMaxPackageNameLength(pkg); + } + int userId = UserHandle.getCallingUserId(); List enabledServices = null; long id = mInjector.binderClearCallingIdentity(); @@ -9450,6 +9471,10 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } final int callingUserId = mInjector.userHandleGetCallingUserId(); if (packageList != null) { + for (String pkg : (List) packageList) { + enforceMaxPackageNameLength(pkg); + } + List enabledImes = InputMethodManagerInternal.get() .getEnabledInputMethodListAsUser(callingUserId); if (enabledImes != null) { @@ -10424,6 +10449,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); @@ -10709,6 +10735,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); @@ -12223,6 +12252,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, @@ -12256,6 +12287,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()) { @@ -12393,6 +12427,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); @@ -12604,9 +12640,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 affiliationIds = new ArraySet<>(ids); @@ -13728,6 +13763,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); @@ -14505,4 +14543,51 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { return DevicePolicyConstants.loadFromString( mInjector.settingsGlobalGetString(Global.DEVICE_POLICY_CONSTANTS)); } + + /** + * 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 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); + } + } + } + } }