Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 8f3b3433 authored by Esteban Talavera's avatar Esteban Talavera Committed by Android (Google) Code Review
Browse files

Merge "Wipe device or profile if max failed attempt reached"

parents bd06bb65 b88f42b6
Loading
Loading
Loading
Loading
+91 −78
Original line number Diff line number Diff line
@@ -1620,6 +1620,11 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
            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) {
            return SystemProperties.getBoolean(key, def);
        }
@@ -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();

        if (wipeExtRequested) {
@@ -5147,46 +5152,69 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
            sm.wipeAdoptableDisks();
        }
        try {
            RecoverySystem.rebootWipeUserData(mContext, false /* shutdown */, reason, force);
            mInjector.recoverySystemRebootWipeUserData(
                    /*shutdown=*/ false, reason, /*force=*/ true);
        } catch (IOException | SecurityException 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
    public void wipeData(int flags) {
        if (!mHasFeature) {
            return;
        }
        final int userHandle = mInjector.userHandleGetCallingUserId();
        enforceFullCrossUsersPermission(userHandle);
        enforceFullCrossUsersPermission(mInjector.userHandleGetCallingUserId());

        final String source;
        final ActiveAdmin admin;
        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.
            final ActiveAdmin admin = getActiveAdminForCallerLocked(null,
                    DeviceAdminInfo.USES_POLICY_WIPE_DATA);
            source = admin.info.getComponent().flattenToShortString();
            admin = getActiveAdminForCallerLocked(null, DeviceAdminInfo.USES_POLICY_WIPE_DATA);
        }
        String reason = "DevicePolicyManager.wipeData() from "
                + 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();
        try {
            // First check whether the admin is allowed to wipe the device/user/profile.
            final String restriction;
                if (userHandle == UserHandle.USER_SYSTEM) {
            if (userId == UserHandle.USER_SYSTEM) {
                restriction = UserManager.DISALLOW_FACTORY_RESET;
                } else if (isManagedProfile(userHandle)) {
            } else if (isManagedProfile(userId)) {
                restriction = UserManager.DISALLOW_REMOVE_MANAGED_PROFILE;
            } else {
                restriction = UserManager.DISALLOW_REMOVE_USER;
            }
                if (isAdminAffectedByRestriction(
                        admin.info.getComponent(), restriction, userHandle)) {
            if (isAdminAffectedByRestriction(admin, restriction, userId)) {
                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 (!isDeviceOwner(admin.info.getComponent(), userHandle)) {
                if (!isDeviceOwner(admin, userId)) {
                    throw new SecurityException(
                            "Only device owner admins can set WIPE_RESET_PROTECTION_DATA");
                }
@@ -5197,44 +5225,14 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
                }
            }

            } finally {
                mInjector.binderRestoreCallingIdentity(ident);
            }
        }
        final boolean wipeExtRequested = (flags & WIPE_EXTERNAL_STORAGE) != 0;
        wipeDeviceNoLock(wipeExtRequested, userHandle,
                "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);
            // 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
            // removes that user (but still clears FRP...)
            if (userId == UserHandle.USER_SYSTEM) {
                forceWipeDeviceNoLock(/*wipeExtRequested=*/ (flags & WIPE_EXTERNAL_STORAGE) != 0,
                        reason);
            } else {
                try {
                    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
                }
                forceWipeUser(userId);
            }
        } finally {
            mInjector.binderRestoreCallingIdentity(ident);
@@ -5374,25 +5372,21 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
        mContext.enforceCallingOrSelfPermission(
                android.Manifest.permission.BIND_DEVICE_ADMIN, null);

        boolean wipeData = false;
        ActiveAdmin strictestAdmin = null;
        final long ident = mInjector.binderClearCallingIdentity();
        try {
            boolean wipeData = false;
            int identifier = 0;
            synchronized (this) {
                DevicePolicyData policy = getUserData(userHandle);
                policy.mFailedPasswordAttempts++;
                saveSettingsLocked(userHandle);
                if (mHasFeature) {
                    ActiveAdmin strictestAdmin = getAdminWithMinimumFailedPasswordsForWipeLocked(
                    strictestAdmin = getAdminWithMinimumFailedPasswordsForWipeLocked(
                            userHandle, /* parent */ false);
                    int max = strictestAdmin != null
                            ? strictestAdmin.maximumFailedPasswordsForWipe : 0;
                    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;
                        identifier = strictestAdmin.getUserHandle().getIdentifier();
                    }

                    sendAdminCommandForLockscreenPoliciesLocked(
@@ -5400,14 +5394,33 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
                            DeviceAdminInfo.USES_POLICY_WATCH_LOGIN, userHandle);
                }
            }
            if (wipeData) {
                // Call without holding lock.
                wipeDeviceNoLock(false, identifier, "reportFailedPasswordAttempt()", false);
            }
        } finally {
            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()) {
            SecurityLog.writeEvent(SecurityLog.TAG_KEYGUARD_DISMISS_AUTH_ATTEMPT, /*result*/ 0,
                    /*method strength*/ 1);
+7 −0
Original line number Diff line number Diff line
@@ -37,6 +37,7 @@ import android.view.IWindowManager;
import com.android.internal.widget.LockPatternUtils;

import java.io.File;
import java.io.IOException;
import java.util.Map;

/**
@@ -263,6 +264,12 @@ public class DevicePolicyManagerServiceTestable extends DevicePolicyManagerServi
            context.powerManager.reboot(reason);
        }

        @Override
        void recoverySystemRebootWipeUserData(boolean shutdown, String reason, boolean force)
                throws IOException {
            context.recoverySystem.rebootWipeUserData(shutdown, reason, force);
        }

        @Override
        boolean systemPropertiesGetBoolean(String key, boolean def) {
            return context.systemProperties.getBoolean(key, def);
+135 −0
Original line number Diff line number Diff line
@@ -85,6 +85,7 @@ import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.when;

/**
@@ -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 {
        final String permission = "some.permission";
        final String app1 = "com.example.app1";
+9 −0
Original line number Diff line number Diff line
@@ -55,6 +55,7 @@ import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

@@ -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 boolean getBoolean(String key, boolean def) {
            return false;
@@ -267,6 +274,7 @@ public class DpmMockContext extends MockContext {
    public final UserManagerForMock userManagerForMock;
    public final PowerManagerForMock powerManager;
    public final PowerManagerInternal powerManagerInternal;
    public final RecoverySystemForMock recoverySystem;
    public final NotificationManager notificationManager;
    public final IIpConnectivityMetrics iipConnectivityMetrics;
    public final IWindowManager iwindowManager;
@@ -312,6 +320,7 @@ public class DpmMockContext extends MockContext {
        packageManagerInternal = mock(PackageManagerInternal.class);
        powerManager = mock(PowerManagerForMock.class);
        powerManagerInternal = mock(PowerManagerInternal.class);
        recoverySystem = mock(RecoverySystemForMock.class);
        notificationManager = mock(NotificationManager.class);
        iipConnectivityMetrics = mock(IIpConnectivityMetrics.class);
        iwindowManager = mock(IWindowManager.class);