Loading services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +91 −78 Original line number Original line Diff line number Diff line Loading @@ -1620,6 +1620,11 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { mContext.getSystemService(PowerManager.class).reboot(reason); mContext.getSystemService(PowerManager.class).reboot(reason); } } void recoverySystemRebootWipeUserData(boolean shutdown, String reason, boolean force) throws IOException { RecoverySystem.rebootWipeUserData(mContext, shutdown, reason, force); } boolean systemPropertiesGetBoolean(String key, boolean def) { boolean systemPropertiesGetBoolean(String key, boolean def) { return SystemProperties.getBoolean(key, def); return SystemProperties.getBoolean(key, def); } } Loading Loading @@ -5138,7 +5143,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } } } } private void wipeDataNoLock(boolean wipeExtRequested, String reason, boolean force) { private void forceWipeDeviceNoLock(boolean wipeExtRequested, String reason) { wtfIfInLock(); wtfIfInLock(); if (wipeExtRequested) { if (wipeExtRequested) { Loading @@ -5147,46 +5152,69 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { sm.wipeAdoptableDisks(); sm.wipeAdoptableDisks(); } } try { try { RecoverySystem.rebootWipeUserData(mContext, false /* shutdown */, reason, force); mInjector.recoverySystemRebootWipeUserData( /*shutdown=*/ false, reason, /*force=*/ true); } catch (IOException | SecurityException e) { } catch (IOException | SecurityException e) { Slog.w(LOG_TAG, "Failed requesting data wipe", e); Slog.w(LOG_TAG, "Failed requesting data wipe", e); } } } } private void forceWipeUser(int userId) { try { IActivityManager am = mInjector.getIActivityManager(); if (am.getCurrentUser().id == userId) { am.switchUser(UserHandle.USER_SYSTEM); } boolean userRemoved = mUserManagerInternal.removeUserEvenWhenDisallowed(userId); if (!userRemoved) { Slog.w(LOG_TAG, "Couldn't remove user " + userId); } else if (isManagedProfile(userId)) { sendWipeProfileNotification(); } } catch (RemoteException re) { // Shouldn't happen } } @Override @Override public void wipeData(int flags) { public void wipeData(int flags) { if (!mHasFeature) { if (!mHasFeature) { return; return; } } final int userHandle = mInjector.userHandleGetCallingUserId(); enforceFullCrossUsersPermission(mInjector.userHandleGetCallingUserId()); enforceFullCrossUsersPermission(userHandle); final String source; final ActiveAdmin admin; synchronized (this) { synchronized (this) { // This API can only be called by an active device admin, admin = getActiveAdminForCallerLocked(null, DeviceAdminInfo.USES_POLICY_WIPE_DATA); // so try to retrieve it to check that the caller is one. } final ActiveAdmin admin = getActiveAdminForCallerLocked(null, String reason = "DevicePolicyManager.wipeData() from " DeviceAdminInfo.USES_POLICY_WIPE_DATA); + admin.info.getComponent().flattenToShortString(); source = admin.info.getComponent().flattenToShortString(); wipeDataNoLock( admin.info.getComponent(), flags, reason, admin.getUserHandle().getIdentifier()); } private void wipeDataNoLock(ComponentName admin, int flags, String reason, int userId) { wtfIfInLock(); long ident = mInjector.binderClearCallingIdentity(); long ident = mInjector.binderClearCallingIdentity(); try { try { // First check whether the admin is allowed to wipe the device/user/profile. final String restriction; final String restriction; if (userHandle == UserHandle.USER_SYSTEM) { if (userId == UserHandle.USER_SYSTEM) { restriction = UserManager.DISALLOW_FACTORY_RESET; restriction = UserManager.DISALLOW_FACTORY_RESET; } else if (isManagedProfile(userHandle)) { } else if (isManagedProfile(userId)) { restriction = UserManager.DISALLOW_REMOVE_MANAGED_PROFILE; restriction = UserManager.DISALLOW_REMOVE_MANAGED_PROFILE; } else { } else { restriction = UserManager.DISALLOW_REMOVE_USER; restriction = UserManager.DISALLOW_REMOVE_USER; } } if (isAdminAffectedByRestriction( if (isAdminAffectedByRestriction(admin, restriction, userId)) { admin.info.getComponent(), restriction, userHandle)) { throw new SecurityException("Cannot wipe data. " + restriction throw new SecurityException("Cannot wipe data. " + restriction + " restriction is set for user " + userHandle); + " restriction is set for user " + userId); } } if ((flags & WIPE_RESET_PROTECTION_DATA) != 0) { if ((flags & WIPE_RESET_PROTECTION_DATA) != 0) { if (!isDeviceOwner(admin.info.getComponent(), userHandle)) { if (!isDeviceOwner(admin, userId)) { throw new SecurityException( throw new SecurityException( "Only device owner admins can set WIPE_RESET_PROTECTION_DATA"); "Only device owner admins can set WIPE_RESET_PROTECTION_DATA"); } } Loading @@ -5197,44 +5225,14 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } } } } } finally { // TODO If split user is enabled and the device owner is set in the primary user mInjector.binderRestoreCallingIdentity(ident); // (rather than system), we should probably trigger factory reset. Current code just } // removes that user (but still clears FRP...) } if (userId == UserHandle.USER_SYSTEM) { final boolean wipeExtRequested = (flags & WIPE_EXTERNAL_STORAGE) != 0; forceWipeDeviceNoLock(/*wipeExtRequested=*/ (flags & WIPE_EXTERNAL_STORAGE) != 0, wipeDeviceNoLock(wipeExtRequested, userHandle, reason); "DevicePolicyManager.wipeData() from " + source, /*force=*/ true); } private void wipeDeviceNoLock( boolean wipeExtRequested, final int userHandle, String reason, boolean force) { wtfIfInLock(); long ident = mInjector.binderClearCallingIdentity(); try { // TODO If split user is enabled and the device owner is set in the primary user (rather // than system), we should probably trigger factory reset. Current code just remove // that user (but still clears FRP...) if (userHandle == UserHandle.USER_SYSTEM) { wipeDataNoLock(wipeExtRequested, reason, force); } else { } else { try { forceWipeUser(userId); IActivityManager am = mInjector.getIActivityManager(); if (am.getCurrentUser().id == userHandle) { am.switchUser(UserHandle.USER_SYSTEM); } boolean userRemoved = force ? mUserManagerInternal.removeUserEvenWhenDisallowed(userHandle) : mUserManager.removeUser(userHandle); if (!userRemoved) { Slog.w(LOG_TAG, "Couldn't remove user " + userHandle); } else if (isManagedProfile(userHandle)) { sendWipeProfileNotification(); } } catch (RemoteException re) { // Shouldn't happen } } } } finally { } finally { mInjector.binderRestoreCallingIdentity(ident); mInjector.binderRestoreCallingIdentity(ident); Loading Loading @@ -5374,25 +5372,21 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { mContext.enforceCallingOrSelfPermission( mContext.enforceCallingOrSelfPermission( android.Manifest.permission.BIND_DEVICE_ADMIN, null); android.Manifest.permission.BIND_DEVICE_ADMIN, null); boolean wipeData = false; ActiveAdmin strictestAdmin = null; final long ident = mInjector.binderClearCallingIdentity(); final long ident = mInjector.binderClearCallingIdentity(); try { try { boolean wipeData = false; int identifier = 0; synchronized (this) { synchronized (this) { DevicePolicyData policy = getUserData(userHandle); DevicePolicyData policy = getUserData(userHandle); policy.mFailedPasswordAttempts++; policy.mFailedPasswordAttempts++; saveSettingsLocked(userHandle); saveSettingsLocked(userHandle); if (mHasFeature) { if (mHasFeature) { ActiveAdmin strictestAdmin = getAdminWithMinimumFailedPasswordsForWipeLocked( strictestAdmin = getAdminWithMinimumFailedPasswordsForWipeLocked( userHandle, /* parent */ false); userHandle, /* parent */ false); int max = strictestAdmin != null int max = strictestAdmin != null ? strictestAdmin.maximumFailedPasswordsForWipe : 0; ? strictestAdmin.maximumFailedPasswordsForWipe : 0; if (max > 0 && policy.mFailedPasswordAttempts >= max) { if (max > 0 && policy.mFailedPasswordAttempts >= max) { // Wipe the user/profile associated with the policy that was violated. This // is not necessarily calling user: if the policy that fired was from a // managed profile rather than the main user profile, we wipe former only. wipeData = true; wipeData = true; identifier = strictestAdmin.getUserHandle().getIdentifier(); } } sendAdminCommandForLockscreenPoliciesLocked( sendAdminCommandForLockscreenPoliciesLocked( Loading @@ -5400,14 +5394,33 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { DeviceAdminInfo.USES_POLICY_WATCH_LOGIN, userHandle); DeviceAdminInfo.USES_POLICY_WATCH_LOGIN, userHandle); } } } } if (wipeData) { // Call without holding lock. wipeDeviceNoLock(false, identifier, "reportFailedPasswordAttempt()", false); } } finally { } finally { mInjector.binderRestoreCallingIdentity(ident); mInjector.binderRestoreCallingIdentity(ident); } } if (wipeData && strictestAdmin != null) { final int userId = strictestAdmin.getUserHandle().getIdentifier(); Slog.i(LOG_TAG, "Max failed password attempts policy reached for admin: " + strictestAdmin.info.getComponent().flattenToShortString() + ". Calling wipeData for user " + userId); // Attempt to wipe the device/user/profile associated with the admin, as if the // admin had called wipeData(). That way we can check whether the admin is actually // allowed to wipe the device (e.g. a regular device admin shouldn't be able to wipe the // device if the device owner has set DISALLOW_FACTORY_RESET, but the DO should be // able to do so). // IMPORTANT: Call without holding the lock to prevent deadlock. try { wipeDataNoLock(strictestAdmin.info.getComponent(), /*flags=*/ 0, /*reason=*/ "reportFailedPasswordAttempt()", userId); } catch (SecurityException e) { Slog.w(LOG_TAG, "Failed to wipe user " + userId + " after max failed password attempts reached.", e); } } if (mInjector.securityLogIsLoggingEnabled()) { if (mInjector.securityLogIsLoggingEnabled()) { SecurityLog.writeEvent(SecurityLog.TAG_KEYGUARD_DISMISS_AUTH_ATTEMPT, /*result*/ 0, SecurityLog.writeEvent(SecurityLog.TAG_KEYGUARD_DISMISS_AUTH_ATTEMPT, /*result*/ 0, /*method strength*/ 1); /*method strength*/ 1); Loading services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java +7 −0 Original line number Original line Diff line number Diff line Loading @@ -37,6 +37,7 @@ import android.view.IWindowManager; import com.android.internal.widget.LockPatternUtils; import com.android.internal.widget.LockPatternUtils; import java.io.File; import java.io.File; import java.io.IOException; import java.util.Map; import java.util.Map; /** /** Loading Loading @@ -263,6 +264,12 @@ public class DevicePolicyManagerServiceTestable extends DevicePolicyManagerServi context.powerManager.reboot(reason); context.powerManager.reboot(reason); } } @Override void recoverySystemRebootWipeUserData(boolean shutdown, String reason, boolean force) throws IOException { context.recoverySystem.rebootWipeUserData(shutdown, reason, force); } @Override @Override boolean systemPropertiesGetBoolean(String key, boolean def) { boolean systemPropertiesGetBoolean(String key, boolean def) { return context.systemProperties.getBoolean(key, def); return context.systemProperties.getBoolean(key, def); Loading services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java +135 −0 Original line number Original line Diff line number Diff line Loading @@ -85,6 +85,7 @@ import static org.mockito.Mockito.never; import static org.mockito.Mockito.reset; import static org.mockito.Mockito.reset; import static org.mockito.Mockito.times; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyZeroInteractions; import static org.mockito.Mockito.when; import static org.mockito.Mockito.when; /** /** Loading Loading @@ -3344,6 +3345,140 @@ public class DevicePolicyManagerTest extends DpmTestBase { } } } } public void testWipeDataDeviceOwner() throws Exception { setDeviceOwner(); when(mContext.userManager.getUserRestrictionSource( UserManager.DISALLOW_FACTORY_RESET, UserHandle.SYSTEM)) .thenReturn(UserManager.RESTRICTION_SOURCE_DEVICE_OWNER); dpm.wipeData(0); verify(mContext.recoverySystem).rebootWipeUserData( /*shutdown=*/ eq(false), anyString(), /*force=*/ eq(true)); } public void testWipeDataDeviceOwnerDisallowed() throws Exception { setDeviceOwner(); when(mContext.userManager.getUserRestrictionSource( UserManager.DISALLOW_FACTORY_RESET, UserHandle.SYSTEM)) .thenReturn(UserManager.RESTRICTION_SOURCE_SYSTEM); try { // The DO is not allowed to wipe the device if the user restriction was set // by the system dpm.wipeData(0); fail("SecurityException not thrown"); } catch (SecurityException expected) { } } public void testMaximumFailedPasswordAttemptsReachedManagedProfile() throws Exception { final int MANAGED_PROFILE_USER_ID = 15; final int MANAGED_PROFILE_ADMIN_UID = UserHandle.getUid(MANAGED_PROFILE_USER_ID, 19436); addManagedProfile(admin1, MANAGED_PROFILE_ADMIN_UID, admin1); // Even if the caller is the managed profile, the current user is the user 0 when(mContext.iactivityManager.getCurrentUser()) .thenReturn(new UserInfo(UserHandle.USER_SYSTEM, "user system", 0)); when(mContext.userManager.getUserRestrictionSource( UserManager.DISALLOW_REMOVE_MANAGED_PROFILE, UserHandle.of(MANAGED_PROFILE_USER_ID))) .thenReturn(UserManager.RESTRICTION_SOURCE_PROFILE_OWNER); mContext.binder.callingUid = MANAGED_PROFILE_ADMIN_UID; dpm.setMaximumFailedPasswordsForWipe(admin1, 3); mContext.binder.callingUid = DpmMockContext.SYSTEM_UID; mContext.callerPermissions.add(permission.BIND_DEVICE_ADMIN); // Failed password attempts on the parent user are taken into account, as there isn't a // separate work challenge. dpm.reportFailedPasswordAttempt(UserHandle.USER_SYSTEM); dpm.reportFailedPasswordAttempt(UserHandle.USER_SYSTEM); dpm.reportFailedPasswordAttempt(UserHandle.USER_SYSTEM); // The profile should be wiped even if DISALLOW_REMOVE_MANAGED_PROFILE is enabled, because // both the user restriction and the policy were set by the PO. verify(mContext.userManagerInternal).removeUserEvenWhenDisallowed( MANAGED_PROFILE_USER_ID); verifyZeroInteractions(mContext.recoverySystem); } public void testMaximumFailedPasswordAttemptsReachedManagedProfileDisallowed() throws Exception { final int MANAGED_PROFILE_USER_ID = 15; final int MANAGED_PROFILE_ADMIN_UID = UserHandle.getUid(MANAGED_PROFILE_USER_ID, 19436); addManagedProfile(admin1, MANAGED_PROFILE_ADMIN_UID, admin1); // Even if the caller is the managed profile, the current user is the user 0 when(mContext.iactivityManager.getCurrentUser()) .thenReturn(new UserInfo(UserHandle.USER_SYSTEM, "user system", 0)); when(mContext.userManager.getUserRestrictionSource( UserManager.DISALLOW_REMOVE_MANAGED_PROFILE, UserHandle.of(MANAGED_PROFILE_USER_ID))) .thenReturn(UserManager.RESTRICTION_SOURCE_SYSTEM); mContext.binder.callingUid = MANAGED_PROFILE_ADMIN_UID; dpm.setMaximumFailedPasswordsForWipe(admin1, 3); mContext.binder.callingUid = DpmMockContext.SYSTEM_UID; mContext.callerPermissions.add(permission.BIND_DEVICE_ADMIN); // Failed password attempts on the parent user are taken into account, as there isn't a // separate work challenge. dpm.reportFailedPasswordAttempt(UserHandle.USER_SYSTEM); dpm.reportFailedPasswordAttempt(UserHandle.USER_SYSTEM); dpm.reportFailedPasswordAttempt(UserHandle.USER_SYSTEM); // DISALLOW_REMOVE_MANAGED_PROFILE was set by the system, not the PO, so the profile is // not wiped. verify(mContext.userManagerInternal, never()) .removeUserEvenWhenDisallowed(anyInt()); verifyZeroInteractions(mContext.recoverySystem); } public void testMaximumFailedPasswordAttemptsReachedDeviceOwner() throws Exception { setDeviceOwner(); when(mContext.userManager.getUserRestrictionSource( UserManager.DISALLOW_FACTORY_RESET, UserHandle.SYSTEM)) .thenReturn(UserManager.RESTRICTION_SOURCE_DEVICE_OWNER); dpm.setMaximumFailedPasswordsForWipe(admin1, 3); mContext.binder.callingUid = DpmMockContext.SYSTEM_UID; mContext.callerPermissions.add(permission.BIND_DEVICE_ADMIN); dpm.reportFailedPasswordAttempt(UserHandle.USER_SYSTEM); dpm.reportFailedPasswordAttempt(UserHandle.USER_SYSTEM); dpm.reportFailedPasswordAttempt(UserHandle.USER_SYSTEM); // The device should be wiped even if DISALLOW_FACTORY_RESET is enabled, because both the // user restriction and the policy were set by the DO. verify(mContext.recoverySystem).rebootWipeUserData( /*shutdown=*/ eq(false), anyString(), /*force=*/ eq(true)); } public void testMaximumFailedPasswordAttemptsReachedDeviceOwnerDisallowed() throws Exception { setDeviceOwner(); when(mContext.userManager.getUserRestrictionSource( UserManager.DISALLOW_FACTORY_RESET, UserHandle.SYSTEM)) .thenReturn(UserManager.RESTRICTION_SOURCE_SYSTEM); dpm.setMaximumFailedPasswordsForWipe(admin1, 3); mContext.binder.callingUid = DpmMockContext.SYSTEM_UID; mContext.callerPermissions.add(permission.BIND_DEVICE_ADMIN); dpm.reportFailedPasswordAttempt(UserHandle.USER_SYSTEM); dpm.reportFailedPasswordAttempt(UserHandle.USER_SYSTEM); dpm.reportFailedPasswordAttempt(UserHandle.USER_SYSTEM); // DISALLOW_FACTORY_RESET was set by the system, not the DO, so the device is not wiped. verifyZeroInteractions(mContext.recoverySystem); verify(mContext.userManagerInternal, never()) .removeUserEvenWhenDisallowed(anyInt()); } public void testGetPermissionGrantState() throws Exception { public void testGetPermissionGrantState() throws Exception { final String permission = "some.permission"; final String permission = "some.permission"; final String app1 = "com.example.app1"; final String app1 = "com.example.app1"; Loading services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java +9 −0 Original line number Original line Diff line number Diff line Loading @@ -55,6 +55,7 @@ import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; import org.mockito.stubbing.Answer; import java.io.File; import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.ArrayList; import java.util.List; import java.util.List; Loading Loading @@ -158,6 +159,12 @@ public class DpmMockContext extends MockContext { } } } } public static class RecoverySystemForMock { public void rebootWipeUserData( boolean shutdown, String reason, boolean force) throws IOException { } } public static class SystemPropertiesForMock { public static class SystemPropertiesForMock { public boolean getBoolean(String key, boolean def) { public boolean getBoolean(String key, boolean def) { return false; return false; Loading Loading @@ -267,6 +274,7 @@ public class DpmMockContext extends MockContext { public final UserManagerForMock userManagerForMock; public final UserManagerForMock userManagerForMock; public final PowerManagerForMock powerManager; public final PowerManagerForMock powerManager; public final PowerManagerInternal powerManagerInternal; public final PowerManagerInternal powerManagerInternal; public final RecoverySystemForMock recoverySystem; public final NotificationManager notificationManager; public final NotificationManager notificationManager; public final IIpConnectivityMetrics iipConnectivityMetrics; public final IIpConnectivityMetrics iipConnectivityMetrics; public final IWindowManager iwindowManager; public final IWindowManager iwindowManager; Loading Loading @@ -312,6 +320,7 @@ public class DpmMockContext extends MockContext { packageManagerInternal = mock(PackageManagerInternal.class); packageManagerInternal = mock(PackageManagerInternal.class); powerManager = mock(PowerManagerForMock.class); powerManager = mock(PowerManagerForMock.class); powerManagerInternal = mock(PowerManagerInternal.class); powerManagerInternal = mock(PowerManagerInternal.class); recoverySystem = mock(RecoverySystemForMock.class); notificationManager = mock(NotificationManager.class); notificationManager = mock(NotificationManager.class); iipConnectivityMetrics = mock(IIpConnectivityMetrics.class); iipConnectivityMetrics = mock(IIpConnectivityMetrics.class); iwindowManager = mock(IWindowManager.class); iwindowManager = mock(IWindowManager.class); Loading Loading
services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +91 −78 Original line number Original line Diff line number Diff line Loading @@ -1620,6 +1620,11 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { mContext.getSystemService(PowerManager.class).reboot(reason); mContext.getSystemService(PowerManager.class).reboot(reason); } } void recoverySystemRebootWipeUserData(boolean shutdown, String reason, boolean force) throws IOException { RecoverySystem.rebootWipeUserData(mContext, shutdown, reason, force); } boolean systemPropertiesGetBoolean(String key, boolean def) { boolean systemPropertiesGetBoolean(String key, boolean def) { return SystemProperties.getBoolean(key, def); return SystemProperties.getBoolean(key, def); } } Loading Loading @@ -5138,7 +5143,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } } } } private void wipeDataNoLock(boolean wipeExtRequested, String reason, boolean force) { private void forceWipeDeviceNoLock(boolean wipeExtRequested, String reason) { wtfIfInLock(); wtfIfInLock(); if (wipeExtRequested) { if (wipeExtRequested) { Loading @@ -5147,46 +5152,69 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { sm.wipeAdoptableDisks(); sm.wipeAdoptableDisks(); } } try { try { RecoverySystem.rebootWipeUserData(mContext, false /* shutdown */, reason, force); mInjector.recoverySystemRebootWipeUserData( /*shutdown=*/ false, reason, /*force=*/ true); } catch (IOException | SecurityException e) { } catch (IOException | SecurityException e) { Slog.w(LOG_TAG, "Failed requesting data wipe", e); Slog.w(LOG_TAG, "Failed requesting data wipe", e); } } } } private void forceWipeUser(int userId) { try { IActivityManager am = mInjector.getIActivityManager(); if (am.getCurrentUser().id == userId) { am.switchUser(UserHandle.USER_SYSTEM); } boolean userRemoved = mUserManagerInternal.removeUserEvenWhenDisallowed(userId); if (!userRemoved) { Slog.w(LOG_TAG, "Couldn't remove user " + userId); } else if (isManagedProfile(userId)) { sendWipeProfileNotification(); } } catch (RemoteException re) { // Shouldn't happen } } @Override @Override public void wipeData(int flags) { public void wipeData(int flags) { if (!mHasFeature) { if (!mHasFeature) { return; return; } } final int userHandle = mInjector.userHandleGetCallingUserId(); enforceFullCrossUsersPermission(mInjector.userHandleGetCallingUserId()); enforceFullCrossUsersPermission(userHandle); final String source; final ActiveAdmin admin; synchronized (this) { synchronized (this) { // This API can only be called by an active device admin, admin = getActiveAdminForCallerLocked(null, DeviceAdminInfo.USES_POLICY_WIPE_DATA); // so try to retrieve it to check that the caller is one. } final ActiveAdmin admin = getActiveAdminForCallerLocked(null, String reason = "DevicePolicyManager.wipeData() from " DeviceAdminInfo.USES_POLICY_WIPE_DATA); + admin.info.getComponent().flattenToShortString(); source = admin.info.getComponent().flattenToShortString(); wipeDataNoLock( admin.info.getComponent(), flags, reason, admin.getUserHandle().getIdentifier()); } private void wipeDataNoLock(ComponentName admin, int flags, String reason, int userId) { wtfIfInLock(); long ident = mInjector.binderClearCallingIdentity(); long ident = mInjector.binderClearCallingIdentity(); try { try { // First check whether the admin is allowed to wipe the device/user/profile. final String restriction; final String restriction; if (userHandle == UserHandle.USER_SYSTEM) { if (userId == UserHandle.USER_SYSTEM) { restriction = UserManager.DISALLOW_FACTORY_RESET; restriction = UserManager.DISALLOW_FACTORY_RESET; } else if (isManagedProfile(userHandle)) { } else if (isManagedProfile(userId)) { restriction = UserManager.DISALLOW_REMOVE_MANAGED_PROFILE; restriction = UserManager.DISALLOW_REMOVE_MANAGED_PROFILE; } else { } else { restriction = UserManager.DISALLOW_REMOVE_USER; restriction = UserManager.DISALLOW_REMOVE_USER; } } if (isAdminAffectedByRestriction( if (isAdminAffectedByRestriction(admin, restriction, userId)) { admin.info.getComponent(), restriction, userHandle)) { throw new SecurityException("Cannot wipe data. " + restriction throw new SecurityException("Cannot wipe data. " + restriction + " restriction is set for user " + userHandle); + " restriction is set for user " + userId); } } if ((flags & WIPE_RESET_PROTECTION_DATA) != 0) { if ((flags & WIPE_RESET_PROTECTION_DATA) != 0) { if (!isDeviceOwner(admin.info.getComponent(), userHandle)) { if (!isDeviceOwner(admin, userId)) { throw new SecurityException( throw new SecurityException( "Only device owner admins can set WIPE_RESET_PROTECTION_DATA"); "Only device owner admins can set WIPE_RESET_PROTECTION_DATA"); } } Loading @@ -5197,44 +5225,14 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } } } } } finally { // TODO If split user is enabled and the device owner is set in the primary user mInjector.binderRestoreCallingIdentity(ident); // (rather than system), we should probably trigger factory reset. Current code just } // removes that user (but still clears FRP...) } if (userId == UserHandle.USER_SYSTEM) { final boolean wipeExtRequested = (flags & WIPE_EXTERNAL_STORAGE) != 0; forceWipeDeviceNoLock(/*wipeExtRequested=*/ (flags & WIPE_EXTERNAL_STORAGE) != 0, wipeDeviceNoLock(wipeExtRequested, userHandle, reason); "DevicePolicyManager.wipeData() from " + source, /*force=*/ true); } private void wipeDeviceNoLock( boolean wipeExtRequested, final int userHandle, String reason, boolean force) { wtfIfInLock(); long ident = mInjector.binderClearCallingIdentity(); try { // TODO If split user is enabled and the device owner is set in the primary user (rather // than system), we should probably trigger factory reset. Current code just remove // that user (but still clears FRP...) if (userHandle == UserHandle.USER_SYSTEM) { wipeDataNoLock(wipeExtRequested, reason, force); } else { } else { try { forceWipeUser(userId); IActivityManager am = mInjector.getIActivityManager(); if (am.getCurrentUser().id == userHandle) { am.switchUser(UserHandle.USER_SYSTEM); } boolean userRemoved = force ? mUserManagerInternal.removeUserEvenWhenDisallowed(userHandle) : mUserManager.removeUser(userHandle); if (!userRemoved) { Slog.w(LOG_TAG, "Couldn't remove user " + userHandle); } else if (isManagedProfile(userHandle)) { sendWipeProfileNotification(); } } catch (RemoteException re) { // Shouldn't happen } } } } finally { } finally { mInjector.binderRestoreCallingIdentity(ident); mInjector.binderRestoreCallingIdentity(ident); Loading Loading @@ -5374,25 +5372,21 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { mContext.enforceCallingOrSelfPermission( mContext.enforceCallingOrSelfPermission( android.Manifest.permission.BIND_DEVICE_ADMIN, null); android.Manifest.permission.BIND_DEVICE_ADMIN, null); boolean wipeData = false; ActiveAdmin strictestAdmin = null; final long ident = mInjector.binderClearCallingIdentity(); final long ident = mInjector.binderClearCallingIdentity(); try { try { boolean wipeData = false; int identifier = 0; synchronized (this) { synchronized (this) { DevicePolicyData policy = getUserData(userHandle); DevicePolicyData policy = getUserData(userHandle); policy.mFailedPasswordAttempts++; policy.mFailedPasswordAttempts++; saveSettingsLocked(userHandle); saveSettingsLocked(userHandle); if (mHasFeature) { if (mHasFeature) { ActiveAdmin strictestAdmin = getAdminWithMinimumFailedPasswordsForWipeLocked( strictestAdmin = getAdminWithMinimumFailedPasswordsForWipeLocked( userHandle, /* parent */ false); userHandle, /* parent */ false); int max = strictestAdmin != null int max = strictestAdmin != null ? strictestAdmin.maximumFailedPasswordsForWipe : 0; ? strictestAdmin.maximumFailedPasswordsForWipe : 0; if (max > 0 && policy.mFailedPasswordAttempts >= max) { if (max > 0 && policy.mFailedPasswordAttempts >= max) { // Wipe the user/profile associated with the policy that was violated. This // is not necessarily calling user: if the policy that fired was from a // managed profile rather than the main user profile, we wipe former only. wipeData = true; wipeData = true; identifier = strictestAdmin.getUserHandle().getIdentifier(); } } sendAdminCommandForLockscreenPoliciesLocked( sendAdminCommandForLockscreenPoliciesLocked( Loading @@ -5400,14 +5394,33 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { DeviceAdminInfo.USES_POLICY_WATCH_LOGIN, userHandle); DeviceAdminInfo.USES_POLICY_WATCH_LOGIN, userHandle); } } } } if (wipeData) { // Call without holding lock. wipeDeviceNoLock(false, identifier, "reportFailedPasswordAttempt()", false); } } finally { } finally { mInjector.binderRestoreCallingIdentity(ident); mInjector.binderRestoreCallingIdentity(ident); } } if (wipeData && strictestAdmin != null) { final int userId = strictestAdmin.getUserHandle().getIdentifier(); Slog.i(LOG_TAG, "Max failed password attempts policy reached for admin: " + strictestAdmin.info.getComponent().flattenToShortString() + ". Calling wipeData for user " + userId); // Attempt to wipe the device/user/profile associated with the admin, as if the // admin had called wipeData(). That way we can check whether the admin is actually // allowed to wipe the device (e.g. a regular device admin shouldn't be able to wipe the // device if the device owner has set DISALLOW_FACTORY_RESET, but the DO should be // able to do so). // IMPORTANT: Call without holding the lock to prevent deadlock. try { wipeDataNoLock(strictestAdmin.info.getComponent(), /*flags=*/ 0, /*reason=*/ "reportFailedPasswordAttempt()", userId); } catch (SecurityException e) { Slog.w(LOG_TAG, "Failed to wipe user " + userId + " after max failed password attempts reached.", e); } } if (mInjector.securityLogIsLoggingEnabled()) { if (mInjector.securityLogIsLoggingEnabled()) { SecurityLog.writeEvent(SecurityLog.TAG_KEYGUARD_DISMISS_AUTH_ATTEMPT, /*result*/ 0, SecurityLog.writeEvent(SecurityLog.TAG_KEYGUARD_DISMISS_AUTH_ATTEMPT, /*result*/ 0, /*method strength*/ 1); /*method strength*/ 1); Loading
services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java +7 −0 Original line number Original line Diff line number Diff line Loading @@ -37,6 +37,7 @@ import android.view.IWindowManager; import com.android.internal.widget.LockPatternUtils; import com.android.internal.widget.LockPatternUtils; import java.io.File; import java.io.File; import java.io.IOException; import java.util.Map; import java.util.Map; /** /** Loading Loading @@ -263,6 +264,12 @@ public class DevicePolicyManagerServiceTestable extends DevicePolicyManagerServi context.powerManager.reboot(reason); context.powerManager.reboot(reason); } } @Override void recoverySystemRebootWipeUserData(boolean shutdown, String reason, boolean force) throws IOException { context.recoverySystem.rebootWipeUserData(shutdown, reason, force); } @Override @Override boolean systemPropertiesGetBoolean(String key, boolean def) { boolean systemPropertiesGetBoolean(String key, boolean def) { return context.systemProperties.getBoolean(key, def); return context.systemProperties.getBoolean(key, def); Loading
services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java +135 −0 Original line number Original line Diff line number Diff line Loading @@ -85,6 +85,7 @@ import static org.mockito.Mockito.never; import static org.mockito.Mockito.reset; import static org.mockito.Mockito.reset; import static org.mockito.Mockito.times; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyZeroInteractions; import static org.mockito.Mockito.when; import static org.mockito.Mockito.when; /** /** Loading Loading @@ -3344,6 +3345,140 @@ public class DevicePolicyManagerTest extends DpmTestBase { } } } } public void testWipeDataDeviceOwner() throws Exception { setDeviceOwner(); when(mContext.userManager.getUserRestrictionSource( UserManager.DISALLOW_FACTORY_RESET, UserHandle.SYSTEM)) .thenReturn(UserManager.RESTRICTION_SOURCE_DEVICE_OWNER); dpm.wipeData(0); verify(mContext.recoverySystem).rebootWipeUserData( /*shutdown=*/ eq(false), anyString(), /*force=*/ eq(true)); } public void testWipeDataDeviceOwnerDisallowed() throws Exception { setDeviceOwner(); when(mContext.userManager.getUserRestrictionSource( UserManager.DISALLOW_FACTORY_RESET, UserHandle.SYSTEM)) .thenReturn(UserManager.RESTRICTION_SOURCE_SYSTEM); try { // The DO is not allowed to wipe the device if the user restriction was set // by the system dpm.wipeData(0); fail("SecurityException not thrown"); } catch (SecurityException expected) { } } public void testMaximumFailedPasswordAttemptsReachedManagedProfile() throws Exception { final int MANAGED_PROFILE_USER_ID = 15; final int MANAGED_PROFILE_ADMIN_UID = UserHandle.getUid(MANAGED_PROFILE_USER_ID, 19436); addManagedProfile(admin1, MANAGED_PROFILE_ADMIN_UID, admin1); // Even if the caller is the managed profile, the current user is the user 0 when(mContext.iactivityManager.getCurrentUser()) .thenReturn(new UserInfo(UserHandle.USER_SYSTEM, "user system", 0)); when(mContext.userManager.getUserRestrictionSource( UserManager.DISALLOW_REMOVE_MANAGED_PROFILE, UserHandle.of(MANAGED_PROFILE_USER_ID))) .thenReturn(UserManager.RESTRICTION_SOURCE_PROFILE_OWNER); mContext.binder.callingUid = MANAGED_PROFILE_ADMIN_UID; dpm.setMaximumFailedPasswordsForWipe(admin1, 3); mContext.binder.callingUid = DpmMockContext.SYSTEM_UID; mContext.callerPermissions.add(permission.BIND_DEVICE_ADMIN); // Failed password attempts on the parent user are taken into account, as there isn't a // separate work challenge. dpm.reportFailedPasswordAttempt(UserHandle.USER_SYSTEM); dpm.reportFailedPasswordAttempt(UserHandle.USER_SYSTEM); dpm.reportFailedPasswordAttempt(UserHandle.USER_SYSTEM); // The profile should be wiped even if DISALLOW_REMOVE_MANAGED_PROFILE is enabled, because // both the user restriction and the policy were set by the PO. verify(mContext.userManagerInternal).removeUserEvenWhenDisallowed( MANAGED_PROFILE_USER_ID); verifyZeroInteractions(mContext.recoverySystem); } public void testMaximumFailedPasswordAttemptsReachedManagedProfileDisallowed() throws Exception { final int MANAGED_PROFILE_USER_ID = 15; final int MANAGED_PROFILE_ADMIN_UID = UserHandle.getUid(MANAGED_PROFILE_USER_ID, 19436); addManagedProfile(admin1, MANAGED_PROFILE_ADMIN_UID, admin1); // Even if the caller is the managed profile, the current user is the user 0 when(mContext.iactivityManager.getCurrentUser()) .thenReturn(new UserInfo(UserHandle.USER_SYSTEM, "user system", 0)); when(mContext.userManager.getUserRestrictionSource( UserManager.DISALLOW_REMOVE_MANAGED_PROFILE, UserHandle.of(MANAGED_PROFILE_USER_ID))) .thenReturn(UserManager.RESTRICTION_SOURCE_SYSTEM); mContext.binder.callingUid = MANAGED_PROFILE_ADMIN_UID; dpm.setMaximumFailedPasswordsForWipe(admin1, 3); mContext.binder.callingUid = DpmMockContext.SYSTEM_UID; mContext.callerPermissions.add(permission.BIND_DEVICE_ADMIN); // Failed password attempts on the parent user are taken into account, as there isn't a // separate work challenge. dpm.reportFailedPasswordAttempt(UserHandle.USER_SYSTEM); dpm.reportFailedPasswordAttempt(UserHandle.USER_SYSTEM); dpm.reportFailedPasswordAttempt(UserHandle.USER_SYSTEM); // DISALLOW_REMOVE_MANAGED_PROFILE was set by the system, not the PO, so the profile is // not wiped. verify(mContext.userManagerInternal, never()) .removeUserEvenWhenDisallowed(anyInt()); verifyZeroInteractions(mContext.recoverySystem); } public void testMaximumFailedPasswordAttemptsReachedDeviceOwner() throws Exception { setDeviceOwner(); when(mContext.userManager.getUserRestrictionSource( UserManager.DISALLOW_FACTORY_RESET, UserHandle.SYSTEM)) .thenReturn(UserManager.RESTRICTION_SOURCE_DEVICE_OWNER); dpm.setMaximumFailedPasswordsForWipe(admin1, 3); mContext.binder.callingUid = DpmMockContext.SYSTEM_UID; mContext.callerPermissions.add(permission.BIND_DEVICE_ADMIN); dpm.reportFailedPasswordAttempt(UserHandle.USER_SYSTEM); dpm.reportFailedPasswordAttempt(UserHandle.USER_SYSTEM); dpm.reportFailedPasswordAttempt(UserHandle.USER_SYSTEM); // The device should be wiped even if DISALLOW_FACTORY_RESET is enabled, because both the // user restriction and the policy were set by the DO. verify(mContext.recoverySystem).rebootWipeUserData( /*shutdown=*/ eq(false), anyString(), /*force=*/ eq(true)); } public void testMaximumFailedPasswordAttemptsReachedDeviceOwnerDisallowed() throws Exception { setDeviceOwner(); when(mContext.userManager.getUserRestrictionSource( UserManager.DISALLOW_FACTORY_RESET, UserHandle.SYSTEM)) .thenReturn(UserManager.RESTRICTION_SOURCE_SYSTEM); dpm.setMaximumFailedPasswordsForWipe(admin1, 3); mContext.binder.callingUid = DpmMockContext.SYSTEM_UID; mContext.callerPermissions.add(permission.BIND_DEVICE_ADMIN); dpm.reportFailedPasswordAttempt(UserHandle.USER_SYSTEM); dpm.reportFailedPasswordAttempt(UserHandle.USER_SYSTEM); dpm.reportFailedPasswordAttempt(UserHandle.USER_SYSTEM); // DISALLOW_FACTORY_RESET was set by the system, not the DO, so the device is not wiped. verifyZeroInteractions(mContext.recoverySystem); verify(mContext.userManagerInternal, never()) .removeUserEvenWhenDisallowed(anyInt()); } public void testGetPermissionGrantState() throws Exception { public void testGetPermissionGrantState() throws Exception { final String permission = "some.permission"; final String permission = "some.permission"; final String app1 = "com.example.app1"; final String app1 = "com.example.app1"; Loading
services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java +9 −0 Original line number Original line Diff line number Diff line Loading @@ -55,6 +55,7 @@ import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; import org.mockito.stubbing.Answer; import java.io.File; import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.ArrayList; import java.util.List; import java.util.List; Loading Loading @@ -158,6 +159,12 @@ public class DpmMockContext extends MockContext { } } } } public static class RecoverySystemForMock { public void rebootWipeUserData( boolean shutdown, String reason, boolean force) throws IOException { } } public static class SystemPropertiesForMock { public static class SystemPropertiesForMock { public boolean getBoolean(String key, boolean def) { public boolean getBoolean(String key, boolean def) { return false; return false; Loading Loading @@ -267,6 +274,7 @@ public class DpmMockContext extends MockContext { public final UserManagerForMock userManagerForMock; public final UserManagerForMock userManagerForMock; public final PowerManagerForMock powerManager; public final PowerManagerForMock powerManager; public final PowerManagerInternal powerManagerInternal; public final PowerManagerInternal powerManagerInternal; public final RecoverySystemForMock recoverySystem; public final NotificationManager notificationManager; public final NotificationManager notificationManager; public final IIpConnectivityMetrics iipConnectivityMetrics; public final IIpConnectivityMetrics iipConnectivityMetrics; public final IWindowManager iwindowManager; public final IWindowManager iwindowManager; Loading Loading @@ -312,6 +320,7 @@ public class DpmMockContext extends MockContext { packageManagerInternal = mock(PackageManagerInternal.class); packageManagerInternal = mock(PackageManagerInternal.class); powerManager = mock(PowerManagerForMock.class); powerManager = mock(PowerManagerForMock.class); powerManagerInternal = mock(PowerManagerInternal.class); powerManagerInternal = mock(PowerManagerInternal.class); recoverySystem = mock(RecoverySystemForMock.class); notificationManager = mock(NotificationManager.class); notificationManager = mock(NotificationManager.class); iipConnectivityMetrics = mock(IIpConnectivityMetrics.class); iipConnectivityMetrics = mock(IIpConnectivityMetrics.class); iwindowManager = mock(IWindowManager.class); iwindowManager = mock(IWindowManager.class); Loading