Merge "Prevent ACTION_FACTORY_RESET from resetting device in a non-system user" into sc-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
81980798fe
@@ -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;
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user