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

Commit ddb4f627 authored by Kholoud Mohamed's avatar Kholoud Mohamed Committed by Automerger Merge Worker
Browse files

Merge "Prevent ACTION_FACTORY_RESET from resetting device in a non-system...

Merge "Prevent ACTION_FACTORY_RESET from resetting device in a non-system user" into sc-dev am: 81980798

Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/14717629

Change-Id: I4d5f4a02ea0859ea210fe1425fbc7403f2b32b28
parents 013e3748 81980798
Loading
Loading
Loading
Loading
+72 −0
Original line number Diff line number Diff line
@@ -16,19 +16,29 @@

package com.android.server;

import android.annotation.UserIdInt;
import android.app.ActivityManager;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.ProgressDialog;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.os.AsyncTask;
import android.os.Binder;
import android.os.RecoverySystem;
import android.os.RemoteException;
import android.os.UserHandle;
import android.os.UserManager;
import android.os.storage.StorageManager;
import android.text.TextUtils;
import android.util.Slog;
import android.view.WindowManager;

import com.android.internal.R;
import com.android.internal.messages.nano.SystemMessageProto;
import com.android.internal.notification.SystemNotificationChannels;
import com.android.server.utils.Slogf;

import java.io.IOException;

@@ -71,6 +81,19 @@ public class MasterClearReceiver extends BroadcastReceiver {
        final boolean forceWipe = intent.getBooleanExtra(Intent.EXTRA_FORCE_MASTER_CLEAR, false)
                || intent.getBooleanExtra(Intent.EXTRA_FORCE_FACTORY_RESET, false);

        // TODO(b/189938391): properly handle factory reset on headless system user mode.
        final int sendingUserId = getSendingUserId();
        if (sendingUserId != UserHandle.USER_SYSTEM && !UserManager.isHeadlessSystemUserMode()) {
            Slogf.w(
                    TAG,
                    "ACTION_FACTORY_RESET received on a non-system user %d, WIPING THE USER!!",
                    sendingUserId);
            if (!Binder.withCleanCallingIdentity(() -> wipeUser(context, sendingUserId, reason))) {
                Slogf.e(TAG, "Failed to wipe user %d", sendingUserId);
            }
            return;
        }

        Slog.w(TAG, "!!! FACTORY RESET !!!");
        // The reboot call is blocking, so we need to do it on another thread.
        Thread thr = new Thread("Reboot") {
@@ -101,6 +124,55 @@ public class MasterClearReceiver extends BroadcastReceiver {
        }
    }

    private boolean wipeUser(Context context, @UserIdInt int userId, String wipeReason) {
        final UserManager userManager = context.getSystemService(UserManager.class);
        final int result = userManager.removeUserOrSetEphemeral(
                userId, /* evenWhenDisallowed= */ false);
        if (result == UserManager.REMOVE_RESULT_ERROR) {
            Slogf.e(TAG, "Can't remove user %d", userId);
            return false;
        }
        if (getCurrentForegroundUserId() == userId) {
            try {
                if (!ActivityManager.getService().switchUser(UserHandle.USER_SYSTEM)) {
                    Slogf.w(TAG, "Can't switch from current user %d, user will get removed when "
                                    + "it is stopped.", userId);

                }
            } catch (RemoteException e) {
                Slogf.w(TAG, "Can't switch from current user %d, user will get removed when "
                        + "it is stopped.", userId);
            }
        }
        if (userManager.isManagedProfile(userId)) {
            sendWipeProfileNotification(context, wipeReason);
        }
        return true;
    }

    // This method is copied from DevicePolicyManagedService.
    private void sendWipeProfileNotification(Context context, String wipeReason) {
        final Notification notification =
                new Notification.Builder(context, SystemNotificationChannels.DEVICE_ADMIN)
                        .setSmallIcon(android.R.drawable.stat_sys_warning)
                        .setContentTitle(context.getString(R.string.work_profile_deleted))
                        .setContentText(wipeReason)
                        .setColor(context.getColor(R.color.system_notification_accent_color))
                        .setStyle(new Notification.BigTextStyle().bigText(wipeReason))
                        .build();
        context.getSystemService(NotificationManager.class).notify(
                SystemMessageProto.SystemMessage.NOTE_PROFILE_WIPED, notification);
    }

    private @UserIdInt int getCurrentForegroundUserId() {
        try {
            return ActivityManager.getCurrentUser();
        } catch (Exception e) {
            Slogf.e(TAG, "Can't get current user", e);
        }
        return UserHandle.USER_NULL;
    }

    private class WipeDataTask extends AsyncTask<Void, Void, Void> {
        private final Thread mChainedTask;
        private final Context mContext;
+77 −3
Original line number Diff line number Diff line
@@ -24,19 +24,25 @@ import static com.google.common.truth.Truth.assertWithMessage;

import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.same;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.when;

import android.app.Activity;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.ContextWrapper;
import android.content.Intent;
import android.os.Looper;
import android.os.RecoverySystem;
import android.os.UserHandle;
import android.os.UserManager;
import android.os.storage.StorageManager;
import android.platform.test.annotations.Presubmit;
import android.util.Log;
import android.view.WindowManager;

import androidx.test.InstrumentationRegistry;

@@ -68,7 +74,13 @@ public final class MasterClearReceiverTest {
        @Override
        public Object getSystemService(String name) {
            Log.v(TAG, "getSystemService(): " + name);
            return name.equals(Context.STORAGE_SERVICE) ? mSm : super.getSystemService(name);
            if (name.equals(Context.STORAGE_SERVICE)) {
                return mSm;
            }
            if (name.equals(Context.USER_SERVICE)) {
                return mUserManager;
            }
            return super.getSystemService(name);
        }
    };

@@ -85,15 +97,17 @@ public final class MasterClearReceiverTest {
    private StorageManager mSm;

    @Mock
    private WindowManager mWm;
    private UserManager mUserManager;

    @Before
    public void startSession() {
        mSession = mockitoSession()
                .initMocks(this)
                .mockStatic(RecoverySystem.class)
                .mockStatic(UserManager.class)
                .strictness(Strictness.LENIENT)
                .startMocking();
        setPendingResultForUser(UserHandle.myUserId());
    }

    @After
@@ -148,6 +162,32 @@ public final class MasterClearReceiverTest {
        verifyWipeExternalData();
    }

    @Test
    public void testNonSystemUser() throws Exception {
        expectWipeNonSystemUser();

        Intent intent = new Intent(Intent.ACTION_FACTORY_RESET);
        setPendingResultForUser(/* userId= */ 10);
        mReceiver.onReceive(mContext, intent);

        verifyNoRebootWipeUserData();
        verifyNoWipeExternalData();
        verifyWipeNonSystemUser();
    }

    @Test
    public void testHeadlessSystemUser() throws Exception {
        expectNoWipeExternalData();
        expectRebootWipeUserData();
        expectHeadlessSystemUserMode();

        Intent intent = new Intent(Intent.ACTION_FACTORY_RESET);
        setPendingResultForUser(/* userId= */ 10);
        mReceiver.onReceive(mContext, intent);

        verifyRebootWipeUserData();
        verifyNoWipeExternalData();
    }

    private void expectNoWipeExternalData() {
        // This is a trick to simplify how the order of methods are called: as wipeAdoptableDisks()
@@ -185,6 +225,18 @@ public final class MasterClearReceiverTest {
        }).when(mSm).wipeAdoptableDisks();
    }

    private void expectWipeNonSystemUser() {
        when(mUserManager.removeUserOrSetEphemeral(anyInt(), anyBoolean()))
                .thenReturn(UserManager.REMOVE_RESULT_REMOVED);
    }

    private void expectHeadlessSystemUserMode() {
        doAnswer((inv) -> {
            Log.i(TAG, inv.toString());
            return true;
        }).when(() -> UserManager.isHeadlessSystemUserMode());
    }

    private void verifyRebootWipeUserData() throws Exception  {
        verifyRebootWipeUserData(/* shutdown= */ false, /* reason= */ null, /* force= */ false,
                /* wipeEuicc= */ false);
@@ -200,6 +252,11 @@ public final class MasterClearReceiverTest {
                eq(force), eq(wipeEuicc)));
    }

    private void verifyNoRebootWipeUserData() {
        verify(()-> RecoverySystem.rebootWipeUserData(
                any(), anyBoolean(), anyString(), anyBoolean(), anyBoolean()), never());
    }

    private void verifyWipeExternalData() {
        verify(mSm).wipeAdoptableDisks();
    }
@@ -207,4 +264,21 @@ public final class MasterClearReceiverTest {
    private void verifyNoWipeExternalData() {
        verify(mSm, never()).wipeAdoptableDisks();
    }

    private void verifyWipeNonSystemUser() {
        verify(mUserManager).removeUserOrSetEphemeral(anyInt(), anyBoolean());
    }

    private void setPendingResultForUser(int userId) {
        mReceiver.setPendingResult(new BroadcastReceiver.PendingResult(
                Activity.RESULT_OK,
                "resultData",
                /* resultExtras= */ null,
                BroadcastReceiver.PendingResult.TYPE_UNREGISTERED,
                /* ordered= */ true,
                /* sticky= */ false,
                /* token= */ null,
                userId,
                /* flags= */ 0));
    }
}