diff options
12 files changed, 436 insertions, 40 deletions
diff --git a/api/system-current.txt b/api/system-current.txt index fa49f07abf52..e8465fade67f 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -74,6 +74,7 @@ package android { field public static final java.lang.String GET_PROCESS_STATE_AND_OOM_SCORE = "android.permission.GET_PROCESS_STATE_AND_OOM_SCORE"; field public static final java.lang.String GET_TOP_ACTIVITY_INFO = "android.permission.GET_TOP_ACTIVITY_INFO"; field public static final java.lang.String GLOBAL_SEARCH = "android.permission.GLOBAL_SEARCH"; + field public static final java.lang.String GRANT_PROFILE_OWNER_DEVICE_IDS_ACCESS = "android.permission.GRANT_PROFILE_OWNER_DEVICE_IDS_ACCESS"; field public static final java.lang.String GRANT_RUNTIME_PERMISSIONS = "android.permission.GRANT_RUNTIME_PERMISSIONS"; field public static final java.lang.String HARDWARE_TEST = "android.permission.HARDWARE_TEST"; field public static final java.lang.String HDMI_CEC = "android.permission.HDMI_CEC"; @@ -581,6 +582,7 @@ package android.app.admin { method public boolean packageHasActiveAdmins(java.lang.String); method public deprecated boolean setActiveProfileOwner(android.content.ComponentName, java.lang.String) throws java.lang.IllegalArgumentException; method public void setDeviceProvisioningConfigApplied(); + method public void setProfileOwnerCanAccessDeviceIdsForUser(android.content.ComponentName, android.os.UserHandle); field public static final java.lang.String ACCOUNT_FEATURE_DEVICE_OR_PROFILE_OWNER_ALLOWED = "android.account.DEVICE_OR_PROFILE_OWNER_ALLOWED"; field public static final java.lang.String ACCOUNT_FEATURE_DEVICE_OR_PROFILE_OWNER_DISALLOWED = "android.account.DEVICE_OR_PROFILE_OWNER_DISALLOWED"; field public static final java.lang.String ACTION_PROVISION_FINALIZATION = "android.app.action.PROVISION_FINALIZATION"; diff --git a/cmds/dpm/src/com/android/commands/dpm/Dpm.java b/cmds/dpm/src/com/android/commands/dpm/Dpm.java index 376b13cd371e..6c6797a328c9 100644 --- a/cmds/dpm/src/com/android/commands/dpm/Dpm.java +++ b/cmds/dpm/src/com/android/commands/dpm/Dpm.java @@ -48,6 +48,8 @@ public final class Dpm extends BaseCommand { private static final String COMMAND_CLEAR_FREEZE_PERIOD_RECORD = "clear-freeze-period-record"; private static final String COMMAND_FORCE_NETWORK_LOGS = "force-network-logs"; private static final String COMMAND_FORCE_SECURITY_LOGS = "force-security-logs"; + private static final String COMMAND_GRANT_PO_DEVICE_ID_ACCESS = + "grant-profile-owner-device-ids-access"; private IDevicePolicyManager mDevicePolicyManager; private int mUserId = UserHandle.USER_SYSTEM; @@ -89,7 +91,10 @@ public final class Dpm extends BaseCommand { "the DPC and triggers DeviceAdminReceiver.onNetworkLogsAvailable() if needed.\n" + "\n" + "dpm " + COMMAND_FORCE_SECURITY_LOGS + ": makes all security logs available to " + - "the DPC and triggers DeviceAdminReceiver.onSecurityLogsAvailable() if needed."); + "the DPC and triggers DeviceAdminReceiver.onSecurityLogsAvailable() if needed." + + "\n" + + "usage: dpm " + COMMAND_GRANT_PO_DEVICE_ID_ACCESS + ": " + + "[ --user <USER_ID> | current ] <COMPONENT>\n"); } @Override @@ -124,6 +129,9 @@ public final class Dpm extends BaseCommand { case COMMAND_FORCE_SECURITY_LOGS: runForceSecurityLogs(); break; + case COMMAND_GRANT_PO_DEVICE_ID_ACCESS: + runGrantProfileOwnerDeviceIdsAccess(); + break; default: throw new IllegalArgumentException ("unknown command '" + command + "'"); } @@ -242,6 +250,13 @@ public final class Dpm extends BaseCommand { System.out.println("Success"); } + + private void runGrantProfileOwnerDeviceIdsAccess() throws RemoteException { + parseArgs(/*canHaveName=*/ false); + mDevicePolicyManager.grantDeviceIdsAccessToProfileOwner(mComponent, mUserId); + System.out.println("Success"); + } + private ComponentName parseComponentName(String component) { ComponentName cn = ComponentName.unflattenFromString(component); if (cn == null) { diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index f129a717f2cd..24ee7f757f55 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -9792,7 +9792,6 @@ public class DevicePolicyManager { } } - /** * Sets the global Private DNS mode and host to be used. * May only be called by the device owner. @@ -9867,4 +9866,31 @@ public class DevicePolicyManager { throw re.rethrowFromSystemServer(); } } + + /** + * Grants the profile owner of the given user access to device identifiers (such as + * serial number, IMEI and MEID). + * + * <p>This lets the profile owner request inclusion of device identifiers when calling + * {@link generateKeyPair}. + * + * <p>This grant is necessary to guarantee that profile owners can access device identifiers. + * + * <p>Privileged system API - meant to be called by the system, particularly the managed + * provisioning app, when a work profile is set up. + * + * @hide + */ + @SystemApi + public void setProfileOwnerCanAccessDeviceIdsForUser( + @NonNull ComponentName who, @NonNull UserHandle userHandle) { + if (mService == null) { + return; + } + try { + mService.grantDeviceIdsAccessToProfileOwner(who, userHandle.getIdentifier()); + } catch (RemoteException re) { + throw re.rethrowFromSystemServer(); + } + } } diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl index ce1f4ef9e2e4..918c1278a2fe 100644 --- a/core/java/android/app/admin/IDevicePolicyManager.aidl +++ b/core/java/android/app/admin/IDevicePolicyManager.aidl @@ -417,4 +417,6 @@ interface IDevicePolicyManager { void setGlobalPrivateDns(in ComponentName admin, int mode, in String privateDnsHost); int getGlobalPrivateDnsMode(in ComponentName admin); String getGlobalPrivateDnsHost(in ComponentName admin); + + void grantDeviceIdsAccessToProfileOwner(in ComponentName who, int userId); } diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 297687933018..5360e8a5dbc5 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -4224,7 +4224,13 @@ <!-- @SystemApi Allows modifying accessibility state. @hide --> <permission android:name="android.permission.MANAGE_ACCESSIBILITY" - android:protectionLevel="signature|setup" /> + android:protectionLevel="signature|setup" /> + + <!-- @SystemApi Allows an app to grant a profile owner access to device identifiers. + <p>Not for use by third-party applications. + @hide --> + <permission android:name="android.permission.GRANT_PROFILE_OWNER_DEVICE_IDS_ACCESS" + android:protectionLevel="signature" /> <application android:process="system" android:persistent="true" diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml index 84cb5f80ece3..495e51ae573f 100644 --- a/data/etc/privapp-permissions-platform.xml +++ b/data/etc/privapp-permissions-platform.xml @@ -91,6 +91,7 @@ applications that come with the platform <permission name="android.permission.CONNECTIVITY_INTERNAL"/> <permission name="android.permission.CRYPT_KEEPER"/> <permission name="android.permission.DELETE_PACKAGES"/> + <permission name="android.permission.GRANT_PROFILE_OWNER_DEVICE_IDS_ACCESS"/> <permission name="android.permission.INSTALL_PACKAGES"/> <permission name="android.permission.INTERACT_ACROSS_USERS"/> <permission name="android.permission.MANAGE_DEVICE_ADMINS"/> diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java b/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java index 2dbbf55a9347..5926bddbf847 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java @@ -88,4 +88,7 @@ abstract class BaseIDevicePolicyManager extends IDevicePolicyManager.Stub { public String getGlobalPrivateDnsHost(ComponentName who) { return null; } + + @Override + public void grantDeviceIdsAccessToProfileOwner(ComponentName who, int userId) { } } diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index a7542d70c0e0..bbbc40cedd3e 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -55,16 +55,15 @@ import static android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_NOTIFICATI import static android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_OVERVIEW; import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_COMPLEX; import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED; -import static android.app.admin.DevicePolicyManager.PRIVATE_DNS_MODE_UNKNOWN; import static android.app.admin.DevicePolicyManager.PRIVATE_DNS_MODE_OFF; import static android.app.admin.DevicePolicyManager.PRIVATE_DNS_MODE_OPPORTUNISTIC; import static android.app.admin.DevicePolicyManager.PRIVATE_DNS_MODE_PROVIDER_HOSTNAME; +import static android.app.admin.DevicePolicyManager.PRIVATE_DNS_MODE_UNKNOWN; import static android.app.admin.DevicePolicyManager.PROFILE_KEYGUARD_FEATURES_AFFECT_OWNER; import static android.app.admin.DevicePolicyManager.WIPE_EUICC; import static android.app.admin.DevicePolicyManager.WIPE_EXTERNAL_STORAGE; import static android.app.admin.DevicePolicyManager.WIPE_RESET_PROTECTION_DATA; import static android.content.pm.PackageManager.MATCH_UNINSTALLED_PACKAGES; - import static android.provider.Settings.Global.PRIVATE_DNS_MODE; import static android.provider.Settings.Global.PRIVATE_DNS_SPECIFIER; import static android.provider.Telephony.Carriers.DPC_URI; @@ -75,11 +74,8 @@ import static com.android.internal.logging.nano.MetricsProto.MetricsEvent .PROVISIONING_ENTRY_POINT_ADB; import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker .STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW; - import static com.android.server.devicepolicy.TransferOwnershipMetadataManager.ADMIN_TYPE_DEVICE_OWNER; import static com.android.server.devicepolicy.TransferOwnershipMetadataManager.ADMIN_TYPE_PROFILE_OWNER; - - import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME; import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT; @@ -228,19 +224,19 @@ import com.android.internal.util.FastXmlSerializer; import com.android.internal.util.FunctionalUtils.ThrowingRunnable; import com.android.internal.util.JournaledFile; import com.android.internal.util.Preconditions; +import com.android.internal.util.StatLogger; import com.android.internal.util.XmlUtils; import com.android.internal.widget.LockPatternUtils; import com.android.server.LocalServices; import com.android.server.LockGuard; -import com.android.internal.util.StatLogger; import com.android.server.SystemServerInitThreadPool; import com.android.server.SystemService; import com.android.server.devicepolicy.DevicePolicyManagerService.ActiveAdmin.TrustAgentInfo; import com.android.server.net.NetworkPolicyManagerInternal; import com.android.server.pm.UserRestrictionsUtils; import com.android.server.storage.DeviceStorageMonitorInternal; - import com.android.server.uri.UriGrantsManagerInternal; + import com.google.android.collect.Sets; import org.xmlpull.v1.XmlPullParser; @@ -267,7 +263,6 @@ import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; -import java.util.Objects; import java.util.Set; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; @@ -2701,7 +2696,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { final DevicePolicyData policy = getUserData(userId); ActiveAdmin admin = policy.mAdminMap.get(who); if (admin == null) { - throw new SecurityException("No active admin " + who); + throw new SecurityException("No active admin " + who + " for UID " + uid); } if (admin.getUid() != uid) { throw new SecurityException("Admin " + who + " is not owned by uid " + uid); @@ -2709,6 +2704,16 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { return admin; } + /** + * Returns the active admin for the user of the caller as denoted by uid, which implements + * the {@code reqPolicy}. + * + * The {@code who} parameter is used as a hint: + * If provided, it must be the component name of the active admin for that user and the caller + * uid must match the uid of the admin. + * If not provided, iterate over all of the active admins in the DevicePolicyData for that user + * and return the one with the uid specified as parameter, and has the policy specified. + */ private ActiveAdmin getActiveAdminWithPolicyForUidLocked(ComponentName who, int reqPolicy, int uid) { ensureLocked(); @@ -5435,23 +5440,54 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { return false; } - private void enforceIsDeviceOwnerOrCertInstallerOfDeviceOwner( + /** + * Enforce one the following conditions are met: + * (1) The device has a Device Owner, and one of the following holds: + * (1.1) The caller is the Device Owner + * (1.2) The caller is another app in the same user as the device owner, AND + * The caller is the delegated certificate installer. + * (2) The user has a profile owner, AND: + * (2.1) The profile owner has been granted access to Device IDs and one of the following + * holds: + * (2.1.1) The caller is the profile owner. + * (2.1.2) The caller is from another app in the same user as the profile owner, AND + * (2.1.2.1) The caller is the delegated cert installer. + * + * For the device owner case, simply check that the caller is the device owner or the + * delegated certificate installer. + * + * For the profile owner case, first check that the caller is the profile owner or can + * manage the DELEGATION_CERT_INSTALL scope. + * If that check succeeds, ensure the profile owner was granted access to device + * identifiers. The grant is transitive: The delegated cert installer is implicitly allowed + * access to device identifiers in this case as part of the delegation. + */ + @VisibleForTesting + public void enforceCallerCanRequestDeviceIdAttestation( ComponentName who, String callerPackage, int callerUid) throws SecurityException { - if (who == null) { - if (!mOwners.hasDeviceOwner()) { - throw new SecurityException("Not in Device Owner mode."); - } - if (UserHandle.getUserId(callerUid) != mOwners.getDeviceOwnerUserId()) { - throw new SecurityException("Caller not from device owner user"); - } - if (!isCallerDelegate(callerPackage, DELEGATION_CERT_INSTALL)) { - throw new SecurityException("Caller with uid " + mInjector.binderGetCallingUid() + - "has no permission to generate keys."); + final int userId = UserHandle.getUserId(callerUid); + + /** + * First check if there's a profile owner because the device could be in COMP mode (where + * there's a device owner and profile owner on the same device). + * If the caller is from the work profile, then it must be the PO or the delegate, and + * it must have the right permission to access device identifiers. + */ + if (hasProfileOwner(userId)) { + // Make sure that the caller is the profile owner or delegate. + enforceCanManageScope(who, callerPackage, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER, + DELEGATION_CERT_INSTALL); + // Verify that the profile owner was granted access to Device IDs. + if (canProfileOwnerAccessDeviceIds(userId)) { + return; } - } else { - // Caller provided - check it is the device owner. - enforceDeviceOwner(who); + throw new SecurityException( + "Profile Owner is not allowed to access Device IDs."); } + + // If not, fall back to the device owner check. + enforceCanManageScope(who, callerPackage, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER, + DELEGATION_CERT_INSTALL); } @VisibleForTesting @@ -5499,7 +5535,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { final int callingUid = mInjector.binderGetCallingUid(); if (deviceIdAttestationRequired && attestationUtilsFlags.length > 0) { - enforceIsDeviceOwnerOrCertInstallerOfDeviceOwner(who, callerPackage, callingUid); + enforceCallerCanRequestDeviceIdAttestation(who, callerPackage, callingUid); } else { enforceCanManageScope(who, callerPackage, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER, DELEGATION_CERT_INSTALL); @@ -7365,6 +7401,18 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { return who != null && who.equals(profileOwner); } + private boolean hasProfileOwner(int userId) { + synchronized (getLockObject()) { + return mOwners.hasProfileOwner(userId); + } + } + + private boolean canProfileOwnerAccessDeviceIds(int userId) { + synchronized (getLockObject()) { + return mOwners.canProfileOwnerAccessDeviceIds(userId); + } + } + @Override public ComponentName getDeviceOwnerComponent(boolean callingUserOnly) { if (!mHasFeature) { @@ -11583,6 +11631,53 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { return false; } + private boolean hasGrantProfileOwnerDevcieIdAccessPermission() { + return mContext.checkCallingPermission( + android.Manifest.permission.GRANT_PROFILE_OWNER_DEVICE_IDS_ACCESS) + == PackageManager.PERMISSION_GRANTED; + } + + @Override + public void grantDeviceIdsAccessToProfileOwner(ComponentName who, int userId) { + // As the caller is the system, it must specify the component name of the profile owner + // as a sanity / safety check. + Preconditions.checkNotNull(who); + + if (!mHasFeature) { + return; + } + + // Only privileged system apps can grant the Profile Owner access to Device IDs. + if (!(isCallerWithSystemUid() || isAdb() + || hasGrantProfileOwnerDevcieIdAccessPermission())) { + throw new SecurityException( + "Only the system can grant Device IDs access for a profile owner."); + } + + if (isAdb() && hasIncompatibleAccountsOrNonAdbNoLock(userId, who)) { + throw new SecurityException( + "Can only be called from ADB if the device has no accounts."); + } + + // Grant access under lock. + synchronized (getLockObject()) { + // Sanity check: Make sure that the user has a profile owner and that the specified + // component is the profile owner of that user. + if (!isProfileOwner(who, userId)) { + throw new IllegalArgumentException(String.format( + "Component %s is not a Profile Owner of user %d", + who.flattenToString(), userId)); + } + + Slog.i(LOG_TAG, String.format("Granting Device ID access to %s, for user %d", + who.flattenToString(), userId)); + + // setProfileOwnerCanAccessDeviceIds will trigger writing of the profile owner + // data, no need to do it manually. + mOwners.setProfileOwnerCanAccessDeviceIds(userId); + } + } + private void pushMeteredDisabledPackagesLocked(int userId) { mInjector.getNetworkPolicyManagerInternal().setMeteredRestrictedPackages( getMeteredDisabledPackagesLocked(userId), userId); diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java b/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java index 632f0aad8880..a2bec7917e6f 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java @@ -42,6 +42,8 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.FastXmlSerializer; import com.android.server.LocalServices; +import libcore.io.IoUtils; + import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; import org.xmlpull.v1.XmlSerializer; @@ -58,8 +60,6 @@ import java.util.Map; import java.util.Objects; import java.util.Set; -import libcore.io.IoUtils; - /** * Stores and restores state for the Device and Profile owners and related device-wide information. * By definition there can be only one device owner, but there may be a profile owner for each user. @@ -99,6 +99,7 @@ class Owners { private static final String ATTR_USER_RESTRICTIONS_MIGRATED = "userRestrictionsMigrated"; private static final String ATTR_FREEZE_RECORD_START = "start"; private static final String ATTR_FREEZE_RECORD_END = "end"; + private static final String ATTR_CAN_ACCESS_DEVICE_IDS = "canAccessDeviceIds"; private final UserManager mUserManager; private final UserManagerInternal mUserManagerInternal; @@ -264,8 +265,12 @@ class Owners { void setDeviceOwnerWithRestrictionsMigrated(ComponentName admin, String ownerName, int userId, boolean userRestrictionsMigrated) { synchronized (mLock) { + // A device owner is allowed to access device identifiers. Even though this flag + // is not currently checked for device owner, it is set to true here so that it is + // semantically compatible with the meaning of this flag. mDeviceOwner = new OwnerInfo(ownerName, admin, userRestrictionsMigrated, - /* remoteBugreportUri =*/ null, /* remoteBugreportHash =*/ null); + /* remoteBugreportUri =*/ null, /* remoteBugreportHash =*/ + null, /* canAccessDeviceIds =*/true); mDeviceOwnerUserId = userId; mUserManagerInternal.setDeviceManaged(true); @@ -290,7 +295,7 @@ class Owners { // For a newly set PO, there's no need for migration. mProfileOwners.put(userId, new OwnerInfo(ownerName, admin, /* userRestrictionsMigrated =*/ true, /* remoteBugreportUri =*/ null, - /* remoteBugreportHash =*/ null)); + /* remoteBugreportHash =*/ null, /* canAccessDeviceIds =*/ false)); mUserManagerInternal.setUserManaged(userId, true); pushToPackageManagerLocked(); pushToAppOpsLocked(); @@ -311,7 +316,8 @@ class Owners { final OwnerInfo ownerInfo = mProfileOwners.get(userId); final OwnerInfo newOwnerInfo = new OwnerInfo(target.getPackageName(), target, ownerInfo.userRestrictionsMigrated, ownerInfo.remoteBugreportUri, - ownerInfo.remoteBugreportHash); + ownerInfo.remoteBugreportHash, /* canAccessDeviceIds =*/ + ownerInfo.canAccessDeviceIds); mProfileOwners.put(userId, newOwnerInfo); pushToPackageManagerLocked(); pushToAppOpsLocked(); @@ -324,7 +330,8 @@ class Owners { // See DevicePolicyManagerService#getDeviceOwnerName mDeviceOwner = new OwnerInfo(null, target, mDeviceOwner.userRestrictionsMigrated, mDeviceOwner.remoteBugreportUri, - mDeviceOwner.remoteBugreportHash); + mDeviceOwner.remoteBugreportHash, /* canAccessDeviceIds =*/ + mDeviceOwner.canAccessDeviceIds); pushToPackageManagerLocked(); pushToAppOpsLocked(); } @@ -351,6 +358,17 @@ class Owners { } } + /** + * Returns true if {@code userId} has a profile owner and that profile owner was granted + * the ability to access device identifiers. + */ + boolean canProfileOwnerAccessDeviceIds(int userId) { + synchronized (mLock) { + OwnerInfo profileOwner = mProfileOwners.get(userId); + return profileOwner != null ? profileOwner.canAccessDeviceIds : false; + } + } + Set<Integer> getProfileOwnerKeys() { synchronized (mLock) { return mProfileOwners.keySet(); @@ -486,6 +504,20 @@ class Owners { } } + /** Sets the grant to access device IDs, and also writes to file. */ + void setProfileOwnerCanAccessDeviceIds(int userId) { + synchronized (mLock) { + OwnerInfo profileOwner = mProfileOwners.get(userId); + if (profileOwner != null) { + profileOwner.canAccessDeviceIds = true; + } else { + Slog.e(TAG, String.format( + "Cannot grant Device IDs access for user %d, no profile owner.", userId)); + } + writeProfileOwner(userId); + } + } + private boolean readLegacyOwnerFileLocked(File file) { if (!file.exists()) { // Already migrated or the device has no owners. @@ -507,7 +539,7 @@ class Owners { String packageName = parser.getAttributeValue(null, ATTR_PACKAGE); mDeviceOwner = new OwnerInfo(name, packageName, /* userRestrictionsMigrated =*/ false, /* remoteBugreportUri =*/ null, - /* remoteBugreportHash =*/ null); + /* remoteBugreportHash =*/ null, /* canAccessDeviceIds =*/ true); mDeviceOwnerUserId = UserHandle.USER_SYSTEM; } else if (tag.equals(TAG_DEVICE_INITIALIZER)) { // Deprecated tag @@ -523,7 +555,8 @@ class Owners { profileOwnerComponentStr); if (admin != null) { profileOwnerInfo = new OwnerInfo(profileOwnerName, admin, - /* userRestrictionsMigrated =*/ false, null, null); + /* userRestrictionsMigrated =*/ false, null, + null, /* canAccessDeviceIds =*/ false); } else { // This shouldn't happen but switch from package name -> component name // might have written bad device owner files. b/17652534 @@ -534,7 +567,8 @@ class Owners { if (profileOwnerInfo == null) { profileOwnerInfo = new OwnerInfo(profileOwnerName, profileOwnerPackageName, /* userRestrictionsMigrated =*/ false, - /* remoteBugreportUri =*/ null, /* remoteBugreportHash =*/ null); + /* remoteBugreportUri =*/ null, /* remoteBugreportHash =*/ + null, /* canAccessDeviceIds =*/ false); } mProfileOwners.put(userId, profileOwnerInfo); } else if (TAG_SYSTEM_UPDATE_POLICY.equals(tag)) { @@ -894,25 +928,28 @@ class Owners { public boolean userRestrictionsMigrated; public String remoteBugreportUri; public String remoteBugreportHash; + public boolean canAccessDeviceIds; public OwnerInfo(String name, String packageName, boolean userRestrictionsMigrated, - String remoteBugreportUri, String remoteBugreportHash) { + String remoteBugreportUri, String remoteBugreportHash, boolean canAccessDeviceIds) { this.name = name; this.packageName = packageName; this.admin = new ComponentName(packageName, ""); this.userRestrictionsMigrated = userRestrictionsMigrated; this.remoteBugreportUri = remoteBugreportUri; this.remoteBugreportHash = remoteBugreportHash; + this.canAccessDeviceIds = canAccessDeviceIds; } public OwnerInfo(String name, ComponentName admin, boolean userRestrictionsMigrated, - String remoteBugreportUri, String remoteBugreportHash) { + String remoteBugreportUri, String remoteBugreportHash, boolean canAccessDeviceIds) { this.name = name; this.admin = admin; this.packageName = admin.getPackageName(); this.userRestrictionsMigrated = userRestrictionsMigrated; this.remoteBugreportUri = remoteBugreportUri; this.remoteBugreportHash = remoteBugreportHash; + this.canAccessDeviceIds = canAccessDeviceIds; } public void writeToXml(XmlSerializer out, String tag) throws IOException { @@ -932,6 +969,8 @@ class Owners { if (remoteBugreportHash != null) { out.attribute(null, ATTR_REMOTE_BUGREPORT_HASH, remoteBugreportHash); } + out.attribute(null, ATTR_CAN_ACCESS_DEVICE_IDS, + String.valueOf(canAccessDeviceIds)); out.endTag(null, tag); } @@ -948,13 +987,17 @@ class Owners { ATTR_REMOTE_BUGREPORT_URI); final String remoteBugreportHash = parser.getAttributeValue(null, ATTR_REMOTE_BUGREPORT_HASH); + final String canAccessDeviceIdsStr = + parser.getAttributeValue(null, ATTR_CAN_ACCESS_DEVICE_IDS); + final boolean canAccessDeviceIds = + ("true".equals(canAccessDeviceIdsStr)); // Has component name? If so, return [name, component] if (componentName != null) { final ComponentName admin = ComponentName.unflattenFromString(componentName); if (admin != null) { return new OwnerInfo(name, admin, userRestrictionsMigrated, - remoteBugreportUri, remoteBugreportHash); + remoteBugreportUri, remoteBugreportHash, canAccessDeviceIds); } else { // This shouldn't happen but switch from package name -> component name // might have written bad device owner files. b/17652534 @@ -965,13 +1008,14 @@ class Owners { // Else, build with [name, package] return new OwnerInfo(name, packageName, userRestrictionsMigrated, remoteBugreportUri, - remoteBugreportHash); + remoteBugreportHash, canAccessDeviceIds); } public void dump(String prefix, PrintWriter pw) { pw.println(prefix + "admin=" + admin); pw.println(prefix + "name=" + name); pw.println(prefix + "package=" + packageName); + pw.println(prefix + "canAccessDeviceIds=" + canAccessDeviceIds); } } 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 16b127c714cc..5dc6d8373f27 100644 --- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java @@ -4973,6 +4973,176 @@ public class DevicePolicyManagerTest extends DpmTestBase { assertProfileOwnershipRevertedWithFakeTransferMetadata(); } + public void testGrantDeviceIdsAccess_notToProfileOwner() throws Exception { + setupProfileOwner(); + configureContextForAccess(mContext, false); + + assertExpectException(SecurityException.class, /* messageRegex= */ null, + () -> dpm.setProfileOwnerCanAccessDeviceIdsForUser(admin2, + UserHandle.of(DpmMockContext.CALLER_UID))); + } + + public void testGrantDeviceIdsAccess_notByAuthorizedCaller() throws Exception { + setupProfileOwner(); + configureContextForAccess(mContext, false); + + assertExpectException(SecurityException.class, /* messageRegex= */ null, + () -> dpm.setProfileOwnerCanAccessDeviceIdsForUser(admin1, + UserHandle.of(DpmMockContext.CALLER_UID))); + } + + public void testGrantDeviceIdsAccess_byAuthorizedSystemCaller() throws Exception { + setupProfileOwner(); + + // This method will throw if the system context could not call + // setProfileOwnerCanAccessDeviceIds successfully. + configureProfileOwnerForDeviceIdAccess(admin1, DpmMockContext.CALLER_USER_HANDLE); + } + + private static void configureContextForAccess(DpmMockContext context, boolean granted) { + when(context.spiedContext.checkCallingPermission( + android.Manifest.permission.GRANT_PROFILE_OWNER_DEVICE_IDS_ACCESS)) + .thenReturn(granted ? PackageManager.PERMISSION_GRANTED + : PackageManager.PERMISSION_DENIED); + } + + public void testGrantDeviceIdsAccess_byAuthorizedManagedProvisioning() throws Exception { + setupProfileOwner(); + + final long ident = mServiceContext.binder.clearCallingIdentity(); + configureContextForAccess(mServiceContext, true); + + mServiceContext.binder.callingUid = + UserHandle.getUid(DpmMockContext.CALLER_USER_HANDLE, + DpmMockContext.CALLER_MANAGED_PROVISIONING_UID); + try { + runAsCaller(mServiceContext, dpms, dpm -> { + dpm.setProfileOwnerCanAccessDeviceIdsForUser(admin1, + UserHandle.of(DpmMockContext.CALLER_USER_HANDLE)); + }); + } finally { + mServiceContext.binder.restoreCallingIdentity(ident); + } + } + + public void testEnforceCallerCanRequestDeviceIdAttestation_deviceOwnerCaller() + throws Exception { + mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID; + setupDeviceOwner(); + configureContextForAccess(mContext, false); + + // Device owner should be allowed to request Device ID attestation. + dpms.enforceCallerCanRequestDeviceIdAttestation(admin1, admin1.getPackageName(), + DpmMockContext.CALLER_SYSTEM_USER_UID); + + // Another package must not be allowed to request Device ID attestation. + assertExpectException(SecurityException.class, null, + () -> dpms.enforceCallerCanRequestDeviceIdAttestation(null, + admin2.getPackageName(), DpmMockContext.CALLER_UID)); + // Another component that is not the admin must not be allowed to request Device ID + // attestation. + assertExpectException(SecurityException.class, null, + () -> dpms.enforceCallerCanRequestDeviceIdAttestation(admin2, + admin1.getPackageName(), DpmMockContext.CALLER_UID)); + } + + public void testEnforceCallerCanRequestDeviceIdAttestation_profileOwnerCaller() + throws Exception { + configureContextForAccess(mContext, false); + + // Make sure a security exception is thrown if the device has no profile owner. + assertExpectException(SecurityException.class, null, + () -> dpms.enforceCallerCanRequestDeviceIdAttestation(admin1, + admin1.getPackageName(), DpmMockContext.CALLER_SYSTEM_USER_UID)); + + setupProfileOwner(); + configureProfileOwnerForDeviceIdAccess(admin1, DpmMockContext.CALLER_USER_HANDLE); + + // The profile owner is allowed to request Device ID attestation. + mServiceContext.binder.callingUid = DpmMockContext.CALLER_UID; + dpms.enforceCallerCanRequestDeviceIdAttestation(admin1, admin1.getPackageName(), + DpmMockContext.CALLER_UID); + // But not another package. + assertExpectException(SecurityException.class, null, + () -> dpms.enforceCallerCanRequestDeviceIdAttestation(null, + admin2.getPackageName(), DpmMockContext.CALLER_UID)); + // Or another component which is not the admin. + assertExpectException(SecurityException.class, null, + () -> dpms.enforceCallerCanRequestDeviceIdAttestation(admin2, + admin2.getPackageName(), DpmMockContext.CALLER_UID)); + } + + public void runAsDelegatedCertInstaller(DpmRunnable action) throws Exception { + final long ident = mServiceContext.binder.clearCallingIdentity(); + + mServiceContext.binder.callingUid = UserHandle.getUid(DpmMockContext.CALLER_USER_HANDLE, + DpmMockContext.DELEGATE_CERT_INSTALLER_UID); + try { + runAsCaller(mServiceContext, dpms, action); + } finally { + mServiceContext.binder.restoreCallingIdentity(ident); + } + } + + public void testEnforceCallerCanRequestDeviceIdAttestation_delegateCaller() throws Exception { + setupProfileOwner(); + markDelegatedCertInstallerAsInstalled(); + + // Configure a delegated cert installer. + runAsCaller(mServiceContext, dpms, + dpm -> dpm.setDelegatedScopes(admin1, DpmMockContext.DELEGATE_PACKAGE_NAME, + Arrays.asList(DELEGATION_CERT_INSTALL))); + + configureProfileOwnerForDeviceIdAccess(admin1, DpmMockContext.CALLER_USER_HANDLE); + + // Make sure that the profile owner can still request Device ID attestation. + mServiceContext.binder.callingUid = DpmMockContext.CALLER_UID; + dpms.enforceCallerCanRequestDeviceIdAttestation(admin1, admin1.getPackageName(), + DpmMockContext.CALLER_UID); + + runAsDelegatedCertInstaller(dpm -> { + dpms.enforceCallerCanRequestDeviceIdAttestation(null, + DpmMockContext.DELEGATE_PACKAGE_NAME, + UserHandle.getUid(DpmMockContext.CALLER_USER_HANDLE, + DpmMockContext.DELEGATE_CERT_INSTALLER_UID)); + }); + } + + public void testEnforceCallerCanRequestDeviceIdAttestation_delegateCallerWithoutPermissions() + throws Exception { + setupProfileOwner(); + markDelegatedCertInstallerAsInstalled(); + + // Configure a delegated cert installer. + runAsCaller(mServiceContext, dpms, + dpm -> dpm.setDelegatedScopes(admin1, DpmMockContext.DELEGATE_PACKAGE_NAME, + Arrays.asList(DELEGATION_CERT_INSTALL))); + + + assertExpectException(SecurityException.class, null, + () -> dpms.enforceCallerCanRequestDeviceIdAttestation(admin1, + admin1.getPackageName(), + DpmMockContext.CALLER_UID)); + + runAsDelegatedCertInstaller(dpm -> { + assertExpectException(SecurityException.class, /* messageRegex= */ null, + () -> dpms.enforceCallerCanRequestDeviceIdAttestation(null, + DpmMockContext.DELEGATE_PACKAGE_NAME, + UserHandle.getUid(DpmMockContext.CALLER_USER_HANDLE, + DpmMockContext.DELEGATE_CERT_INSTALLER_UID))); + }); + } + + private void configureProfileOwnerForDeviceIdAccess(ComponentName who, int userId) { + final long ident = mServiceContext.binder.clearCallingIdentity(); + mServiceContext.binder.callingUid = + UserHandle.getUid(DpmMockContext.CALLER_USER_HANDLE, DpmMockContext.SYSTEM_UID); + runAsCaller(mServiceContext, dpms, dpm -> { + dpm.setProfileOwnerCanAccessDeviceIdsForUser(who, UserHandle.of(userId)); + }); + mServiceContext.binder.restoreCallingIdentity(ident); + } + // admin1 is the outgoing DPC, adminAnotherPakcage is the incoming one. private void assertDeviceOwnershipRevertedWithFakeTransferMetadata() throws Exception { writeFakeTransferMetadataFile(UserHandle.USER_SYSTEM, diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java index be00bb662d17..e411fb5d893f 100644 --- a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java +++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java @@ -59,6 +59,12 @@ public class DpmMockContext extends MockContext { public static final int CALLER_UID = UserHandle.getUid(CALLER_USER_HANDLE, 20123); /** + * UID corresponding to {@link #CALLER_USER_HANDLE}. + */ + public static final int CALLER_MANAGED_PROVISIONING_UID = UserHandle.getUid(CALLER_USER_HANDLE, + 20125); + + /** * UID used when a caller is on the system user. */ public static final int CALLER_SYSTEM_USER_UID = 20321; @@ -81,6 +87,10 @@ public class DpmMockContext extends MockContext { public static final String ANOTHER_PACKAGE_NAME = "com.another.package.name"; public static final int ANOTHER_UID = UserHandle.getUid(UserHandle.USER_SYSTEM, 18434); + public static final String DELEGATE_PACKAGE_NAME = "com.delegate.package.name"; + public static final int DELEGATE_CERT_INSTALLER_UID = UserHandle.getUid(UserHandle.USER_SYSTEM, + 18437); + private final MockSystemServices mMockSystemServices; public static class MockBinder { @@ -427,4 +437,9 @@ public class DpmMockContext extends MockContext { public int getUserId() { return UserHandle.getUserId(binder.getCallingUid()); } + + @Override + public int checkCallingPermission(String permission) { + return spiedContext.checkCallingPermission(permission); + } } diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmTestBase.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmTestBase.java index 0c8a7879ef5e..a34c2ff8ce07 100644 --- a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmTestBase.java +++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmTestBase.java @@ -50,6 +50,7 @@ public abstract class DpmTestBase extends AndroidTestCase { public ComponentName admin3; public ComponentName adminAnotherPackage; public ComponentName adminNoPerm; + public ComponentName delegateCertInstaller; @Override protected void setUp() throws Exception { @@ -66,6 +67,8 @@ public abstract class DpmTestBase extends AndroidTestCase { adminAnotherPackage = new ComponentName(DpmMockContext.ANOTHER_PACKAGE_NAME, "whatever.random.class"); adminNoPerm = new ComponentName(mRealTestContext, DummyDeviceAdmins.AdminNoPerm.class); + delegateCertInstaller = new ComponentName(DpmMockContext.DELEGATE_PACKAGE_NAME, + "some.random.class"); mockSystemPropertiesToReturnDefault(); } @@ -130,6 +133,20 @@ public abstract class DpmTestBase extends AndroidTestCase { eq(userId)); } + protected void markDelegatedCertInstallerAsInstalled() throws Exception { + final ApplicationInfo ai = new ApplicationInfo(); + ai.enabledSetting = PackageManager.COMPONENT_ENABLED_STATE_ENABLED; + ai.flags = ApplicationInfo.FLAG_HAS_CODE; + // Mark the package as installed on the work profile. + ai.uid = UserHandle.getUid(DpmMockContext.CALLER_USER_HANDLE, + DpmMockContext.DELEGATE_CERT_INSTALLER_UID); + ai.packageName = delegateCertInstaller.getPackageName(); + ai.name = delegateCertInstaller.getClassName(); + + markPackageAsInstalled(delegateCertInstaller.getPackageName(), ai, + DpmMockContext.CALLER_USER_HANDLE); + } + protected void setUpPackageManagerForAdmin(ComponentName admin, int packageUid) throws Exception { setUpPackageManagerForAdmin(admin, packageUid, |