Merge "Disallow DA to reset password, also fix all DO checks"
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 8bf8dcb..937caf3 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -1662,7 +1662,16 @@
* Force a new device unlock password (the password needed to access the
* entire device, not for individual accounts) on the user. This takes
* effect immediately.
- * The given password must be sufficient for the
+ *
+ * <p>Calling this from a managed profile that shares the password with the owner profile
+ * will throw a security exception.
+ *
+ * <p><em>Note: This API has been limited as of {@link android.os.Build.VERSION_CODES#N} for
+ * device admins that are not device owner and not profile owner.
+ * The password can now only be changed if there is currently no password set. Device owner
+ * and profile owner can still do this.</em>
+ *
+ * <p>The given password must be sufficient for the
* current password quality and length constraints as returned by
* {@link #getPasswordQuality(ComponentName)} and
* {@link #getPasswordMinimumLength(ComponentName)}; if it does not meet
@@ -1672,19 +1681,20 @@
* the currently active quality will be increased to match.
*
* <p>Calling with a null or empty password will clear any existing PIN,
- * pattern or password if the current password constraints allow it.
+ * pattern or password if the current password constraints allow it. <em>Note: This will not
+ * work in {@link android.os.Build.VERSION_CODES#N} and later for device admins that are not
+ * device owner and not profile owner. Once set, the password cannot be changed to null or
+ * empty, except by device owner or profile owner.</em>
*
* <p>The calling device admin must have requested
* {@link DeviceAdminInfo#USES_POLICY_RESET_PASSWORD} to be able to call
* this method; if it has not, a security exception will be thrown.
*
- * <p>Calling this from a managed profile will throw a security exception.
- *
* @param password The new password for the user. Null or empty clears the password.
* @param flags May be 0 or combination of {@link #RESET_PASSWORD_REQUIRE_ENTRY} and
* {@link #RESET_PASSWORD_DO_NOT_ASK_CREDENTIALS_ON_BOOT}.
* @return Returns true if the password was applied, or false if it is
- * not acceptable for the current constraints.
+ * not acceptable for the current constraints or if the user has not been decrypted yet.
*/
public boolean resetPassword(String password, int flags) {
if (mService != null) {
@@ -1792,7 +1802,7 @@
public void wipeData(int flags) {
if (mService != null) {
try {
- mService.wipeData(flags, myUserId());
+ mService.wipeData(flags);
} catch (RemoteException e) {
Log.w(TAG, "Failed talking with device policy service", e);
}
@@ -2668,14 +2678,14 @@
* does *not* check weather the device owner is actually running on the current user.
*/
public boolean isDeviceOwnerApp(String packageName) {
- if (mService != null) {
- try {
- return mService.isDeviceOwnerPackage(packageName);
- } catch (RemoteException e) {
- Log.w(TAG, "Failed talking with device policy service", e);
- }
+ if (packageName == null) {
+ return false;
}
- return false;
+ final ComponentName deviceOwner = getDeviceOwnerComponent();
+ if (deviceOwner == null) {
+ return false;
+ }
+ return packageName.equals(deviceOwner.getPackageName());
}
/**
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index e7e1833..7601cf2 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -81,7 +81,7 @@
void lockNow();
- void wipeData(int flags, int userHandle);
+ void wipeData(int flags);
ComponentName setGlobalProxy(in ComponentName admin, String proxySpec, String exclusionList);
ComponentName getGlobalProxyAdmin(int userHandle);
@@ -114,7 +114,6 @@
void reportSuccessfulPasswordAttempt(int userHandle);
boolean setDeviceOwner(in ComponentName who, String ownerName, int userId);
- boolean isDeviceOwnerPackage(String packageName);
ComponentName getDeviceOwner();
String getDeviceOwnerName();
void clearDeviceOwner(String packageName);
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 64628aa..bb805c1 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -12770,8 +12770,14 @@
ServiceManager.getService(Context.DEVICE_POLICY_SERVICE));
try {
if (dpm != null) {
+ final ComponentName deviceOwnerComponentName = dpm.getDeviceOwner();
+ final String deviceOwnerPackageName = deviceOwnerComponentName == null ? null
+ : deviceOwnerComponentName.getPackageName();
// Does the package contains the device owner?
- if (dpm.isDeviceOwnerPackage(packageName)) {
+ // TODO Do we have to do it even if userId != UserHandle.USER_ALL? Otherwise,
+ // this check is probably not needed, since DO should be registered as a device
+ // admin on some user too. (Original bug for this: b/17657954)
+ if (packageName.equals(deviceOwnerPackageName)) {
return true;
}
// Does it contain a device admin for any user?
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 6c2bd00..d55fa4a 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -1612,25 +1612,17 @@
@VisibleForTesting
boolean isActiveAdminWithPolicyForUserLocked(ActiveAdmin admin, int reqPolicy,
int userId) {
- boolean ownsDevice = isDeviceOwner(admin.info.getComponent());
- boolean ownsProfile = (getProfileOwner(userId) != null
- && getProfileOwner(userId).getPackageName()
- .equals(admin.info.getPackageName()));
+ final boolean ownsDevice = isDeviceOwner(admin.info.getComponent(), userId);
+ final boolean ownsProfile = isProfileOwner(admin.info.getComponent(), userId);
if (reqPolicy == DeviceAdminInfo.USES_POLICY_DEVICE_OWNER) {
- if ((userId == UserHandle.USER_SYSTEM && ownsDevice) || (ownsDevice && ownsProfile)) {
- return true;
- }
+ return ownsDevice;
} else if (reqPolicy == DeviceAdminInfo.USES_POLICY_PROFILE_OWNER) {
- if ((userId == UserHandle.USER_SYSTEM && ownsDevice) || ownsProfile) {
- return true;
- }
+ // DO always has the PO power.
+ return ownsDevice || ownsProfile;
} else {
- if (admin.info.usesPolicy(reqPolicy)) {
- return true;
- }
+ return admin.info.usesPolicy(reqPolicy);
}
- return false;
}
void sendAdminCommandLocked(ActiveAdmin admin, String action) {
@@ -2441,8 +2433,9 @@
return;
}
if (admin.getUid() != mInjector.binderGetCallingUid()) {
- // Active device owners must remain active admins.
- if (isDeviceOwner(adminReceiver)) {
+ // Active device/profile owners must remain active admins.
+ if (isDeviceOwner(adminReceiver, userHandle)
+ || isProfileOwner(adminReceiver, userHandle)) {
return;
}
mContext.enforceCallingOrSelfPermission(
@@ -3187,12 +3180,21 @@
}
@Override
- public boolean resetPassword(String passwordOrNull, int flags) {
+ public boolean resetPassword(String passwordOrNull, int flags) throws RemoteException {
if (!mHasFeature) {
return false;
}
final int userHandle = UserHandle.getCallingUserId();
- enforceNotManagedProfile(userHandle, "reset the password");
+
+ long ident = mInjector.binderClearCallingIdentity();
+ try {
+ if (mUserManager.getCredentialOwnerProfile(userHandle) != userHandle) {
+ throw new SecurityException("You can not change password for this profile because"
+ + " it shares the password with the owner profile");
+ }
+ } finally {
+ mInjector.binderRestoreCallingIdentity(ident);
+ }
String password = passwordOrNull != null ? passwordOrNull : "";
@@ -3200,8 +3202,35 @@
synchronized (this) {
// This api can only be called by an active device admin,
// so try to retrieve it to check that the caller is one.
- getActiveAdminForCallerLocked(null,
+ final ActiveAdmin admin = getActiveAdminForCallerLocked(null,
DeviceAdminInfo.USES_POLICY_RESET_PASSWORD);
+ final ComponentName adminComponent = admin.info.getComponent();
+
+ // As of N, only profile owners and device owners can reset the password.
+ if (!(isProfileOwner(adminComponent, userHandle)
+ || isDeviceOwner(adminComponent, userHandle))) {
+ final boolean preN = getTargetSdk(admin.info.getPackageName(), userHandle)
+ < android.os.Build.VERSION_CODES.N;
+ // As of N, password resetting to empty/null is not allowed anymore.
+ // TODO Should we allow DO/PO to set an empty password?
+ if (TextUtils.isEmpty(password)) {
+ if (!preN) {
+ throw new SecurityException("Cannot call with null password");
+ } else {
+ Slog.e(LOG_TAG, "Cannot call with null password");
+ return false;
+ }
+ }
+ // As of N, password cannot be changed by the admin if it is already set.
+ if (isLockScreenSecureUnchecked(userHandle)) {
+ if (!preN) {
+ throw new SecurityException("Admin cannot change current password");
+ } else {
+ Slog.e(LOG_TAG, "Admin cannot change current password");
+ return false;
+ }
+ }
+ }
quality = getPasswordQuality(null, userHandle);
if (quality != DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED) {
int realQuality = LockPatternUtils.computePasswordQuality(password);
@@ -3303,9 +3332,9 @@
// Don't do this with the lock held, because it is going to call
// back in to the service.
- long ident = mInjector.binderClearCallingIdentity();
+ ident = mInjector.binderClearCallingIdentity();
try {
- LockPatternUtils utils = new LockPatternUtils(mContext);
+ LockPatternUtils utils = mInjector.newLockPatternUtils();
if (!TextUtils.isEmpty(password)) {
utils.saveLockPassword(password, null, quality, userHandle);
} else {
@@ -3330,6 +3359,15 @@
return true;
}
+ private boolean isLockScreenSecureUnchecked(int userId) {
+ long ident = mInjector.binderClearCallingIdentity();
+ try {
+ return mInjector.newLockPatternUtils().isSecure(userId);
+ } finally {
+ mInjector.binderRestoreCallingIdentity(ident);
+ }
+ }
+
private void setDoNotAskCredentialsOnBoot() {
synchronized (this) {
DevicePolicyData policyData = getUserData(UserHandle.USER_SYSTEM);
@@ -3685,10 +3723,11 @@
}
@Override
- public void wipeData(int flags, final int userHandle) {
+ public void wipeData(int flags) {
if (!mHasFeature) {
return;
}
+ final int userHandle = mInjector.userHandleGetCallingUserId();
enforceCrossUserPermission(userHandle);
synchronized (this) {
// This API can only be called by an active device admin,
@@ -3701,8 +3740,7 @@
long ident = mInjector.binderClearCallingIdentity();
try {
if ((flags & WIPE_RESET_PROTECTION_DATA) != 0) {
- if (userHandle != UserHandle.USER_SYSTEM
- || !isDeviceOwner(admin.info.getComponent())) {
+ if (!isDeviceOwner(admin.info.getComponent(), userHandle)) {
throw new SecurityException(
"Only device owner admins can set WIPE_RESET_PROTECTION_DATA");
}
@@ -4325,7 +4363,7 @@
return;
}
Preconditions.checkNotNull(who, "ComponentName is null");
- final int userHandle = UserHandle.getCallingUserId();
+ final int userHandle = mInjector.userHandleGetCallingUserId();
synchronized (this) {
ActiveAdmin ap = getActiveAdminForCallerLocked(who,
DeviceAdminInfo.USES_POLICY_DISABLE_CAMERA);
@@ -4337,7 +4375,7 @@
// Tell the user manager that the restrictions have changed.
synchronized (mUserManagerInternal.getUserRestrictionsLock()) {
synchronized (this) {
- if (isDeviceOwner(who)) {
+ if (isDeviceOwner(who, userHandle)) {
mUserManagerInternal.updateEffectiveUserRestrictionsForAllUsersLR();
} else {
mUserManagerInternal.updateEffectiveUserRestrictionsLR(userHandle);
@@ -4499,24 +4537,17 @@
}
}
- public boolean isDeviceOwner(ComponentName who) {
- if (!mHasFeature) {
- return false;
- }
+ public boolean isDeviceOwner(ComponentName who, int userId) {
synchronized (this) {
- return mOwners.hasDeviceOwner() && mOwners.getDeviceOwnerComponent().equals(who);
+ return mOwners.hasDeviceOwner()
+ && mOwners.getDeviceOwnerUserId() == userId
+ && mOwners.getDeviceOwnerComponent().equals(who);
}
}
- @Override
- public boolean isDeviceOwnerPackage(String packageName) {
- if (!mHasFeature) {
- return false;
- }
- synchronized (this) {
- return mOwners.hasDeviceOwner()
- && mOwners.getDeviceOwnerComponent().getPackageName().equals(packageName);
- }
+ public boolean isProfileOwner(ComponentName who, int userId) {
+ final ComponentName profileOwner = getProfileOwner(userId);
+ return who != null && who.equals(profileOwner);
}
@Override
@@ -5637,9 +5668,8 @@
ActiveAdmin activeAdmin =
getActiveAdminForCallerLocked(who,
DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
- final boolean isDeviceOwner = isDeviceOwner(who);
- if (!isDeviceOwner && userHandle != UserHandle.USER_SYSTEM
- && DEVICE_OWNER_USER_RESTRICTIONS.contains(key)) {
+ final boolean isDeviceOwner = isDeviceOwner(who, userHandle);
+ if (!isDeviceOwner && DEVICE_OWNER_USER_RESTRICTIONS.contains(key)) {
throw new SecurityException(
"Profile owners cannot set user restriction " + key);
}
@@ -6132,9 +6162,8 @@
Bundle adminExtras = new Bundle();
adminExtras.putString(DeviceAdminReceiver.EXTRA_LOCK_TASK_PACKAGE, pkg);
for (ActiveAdmin admin : policy.mAdminList) {
- boolean ownsDevice = isDeviceOwner(admin.info.getComponent());
- boolean ownsProfile = (getProfileOwner(userHandle) != null
- && getProfileOwner(userHandle).equals(admin.info.getPackageName()));
+ final boolean ownsDevice = isDeviceOwner(admin.info.getComponent(), userHandle);
+ final boolean ownsProfile = isProfileOwner(admin.info.getComponent(), userHandle);
if (ownsDevice || ownsProfile) {
if (isEnabled) {
sendAdminCommandLocked(admin, DeviceAdminReceiver.ACTION_LOCK_TASK_ENTERING,
@@ -6186,13 +6215,12 @@
@Override
public void setSecureSetting(ComponentName who, String setting, String value) {
Preconditions.checkNotNull(who, "ComponentName is null");
- int callingUserId = UserHandle.getCallingUserId();
- final ContentResolver contentResolver = mContext.getContentResolver();
+ int callingUserId = mInjector.userHandleGetCallingUserId();
synchronized (this) {
getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
- if (isDeviceOwner(who)) {
+ if (isDeviceOwner(who, mInjector.userHandleGetCallingUserId())) {
if (!SECURE_SETTINGS_DEVICEOWNER_WHITELIST.contains(setting)) {
throw new SecurityException(String.format(
"Permission denial: Device owners cannot update %1$s", setting));
@@ -6504,13 +6532,26 @@
* @param callerUid UID of the caller.
* @return true if the caller is the device owner app
*/
- private boolean isCallerDeviceOwner(int callerUid) {
- String[] pkgs = mContext.getPackageManager().getPackagesForUid(callerUid);
- for (String pkg : pkgs) {
- if (isDeviceOwnerPackage(pkg)) {
- return true;
+ @VisibleForTesting
+ boolean isCallerDeviceOwner(int callerUid) {
+ synchronized (this) {
+ if (!mOwners.hasDeviceOwner()) {
+ return false;
+ }
+ if (UserHandle.getUserId(callerUid) != mOwners.getDeviceOwnerUserId()) {
+ return false;
+ }
+ final String deviceOwnerPackageName = mOwners.getDeviceOwnerComponent()
+ .getPackageName();
+ final String[] pkgs = mContext.getPackageManager().getPackagesForUid(callerUid);
+
+ for (String pkg : pkgs) {
+ if (deviceOwnerPackageName.equals(pkg)) {
+ return true;
+ }
}
}
+
return false;
}
@@ -6590,10 +6631,8 @@
getActiveAdminForCallerLocked(admin, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
long ident = mInjector.binderClearCallingIdentity();
try {
- final ApplicationInfo ai = mIPackageManager
- .getApplicationInfo(packageName, 0, user.getIdentifier());
- final int targetSdkVersion = ai == null ? 0 : ai.targetSdkVersion;
- if (targetSdkVersion < android.os.Build.VERSION_CODES.M) {
+ if (getTargetSdk(packageName, user.getIdentifier())
+ < android.os.Build.VERSION_CODES.M) {
return false;
}
final PackageManager packageManager = mContext.getPackageManager();
@@ -6708,4 +6747,15 @@
}
throw new IllegalArgumentException("Unknown provisioning action " + action);
}
+
+ /**
+ * Returns the target sdk version number that the given packageName was built for
+ * in the given user.
+ */
+ private int getTargetSdk(String packageName, int userId) throws RemoteException {
+ final ApplicationInfo ai = mIPackageManager
+ .getApplicationInfo(packageName, 0, userId);
+ final int targetSdkVersion = ai == null ? 0 : ai.targetSdkVersion;
+ return targetSdkVersion;
+ }
}