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

This commit is contained in:
Kholoud Mohamed
2021-06-14 10:24:48 +00:00
committed by Android (Google) Code Review
2 changed files with 149 additions and 3 deletions

View File

@@ -16,19 +16,29 @@
package com.android.server; 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.app.ProgressDialog;
import android.content.BroadcastReceiver; import android.content.BroadcastReceiver;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.os.AsyncTask; import android.os.AsyncTask;
import android.os.Binder;
import android.os.RecoverySystem; import android.os.RecoverySystem;
import android.os.RemoteException;
import android.os.UserHandle; import android.os.UserHandle;
import android.os.UserManager;
import android.os.storage.StorageManager; import android.os.storage.StorageManager;
import android.text.TextUtils; import android.text.TextUtils;
import android.util.Slog; import android.util.Slog;
import android.view.WindowManager; import android.view.WindowManager;
import com.android.internal.R; 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; import java.io.IOException;
@@ -71,6 +81,19 @@ public class MasterClearReceiver extends BroadcastReceiver {
final boolean forceWipe = intent.getBooleanExtra(Intent.EXTRA_FORCE_MASTER_CLEAR, false) final boolean forceWipe = intent.getBooleanExtra(Intent.EXTRA_FORCE_MASTER_CLEAR, false)
|| intent.getBooleanExtra(Intent.EXTRA_FORCE_FACTORY_RESET, 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 !!!"); Slog.w(TAG, "!!! FACTORY RESET !!!");
// The reboot call is blocking, so we need to do it on another thread. // The reboot call is blocking, so we need to do it on another thread.
Thread thr = new Thread("Reboot") { 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 class WipeDataTask extends AsyncTask<Void, Void, Void> {
private final Thread mChainedTask; private final Thread mChainedTask;
private final Context mContext; private final Context mContext;

View File

@@ -24,19 +24,25 @@ import static com.google.common.truth.Truth.assertWithMessage;
import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean; 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.eq;
import static org.mockito.ArgumentMatchers.same; import static org.mockito.ArgumentMatchers.same;
import static org.mockito.Mockito.never; 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.Context;
import android.content.ContextWrapper; import android.content.ContextWrapper;
import android.content.Intent; import android.content.Intent;
import android.os.Looper; import android.os.Looper;
import android.os.RecoverySystem; import android.os.RecoverySystem;
import android.os.UserHandle;
import android.os.UserManager;
import android.os.storage.StorageManager; import android.os.storage.StorageManager;
import android.platform.test.annotations.Presubmit; import android.platform.test.annotations.Presubmit;
import android.util.Log; import android.util.Log;
import android.view.WindowManager;
import androidx.test.InstrumentationRegistry; import androidx.test.InstrumentationRegistry;
@@ -68,7 +74,13 @@ public final class MasterClearReceiverTest {
@Override @Override
public Object getSystemService(String name) { public Object getSystemService(String name) {
Log.v(TAG, "getSystemService(): " + 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; private StorageManager mSm;
@Mock @Mock
private WindowManager mWm; private UserManager mUserManager;
@Before @Before
public void startSession() { public void startSession() {
mSession = mockitoSession() mSession = mockitoSession()
.initMocks(this) .initMocks(this)
.mockStatic(RecoverySystem.class) .mockStatic(RecoverySystem.class)
.mockStatic(UserManager.class)
.strictness(Strictness.LENIENT) .strictness(Strictness.LENIENT)
.startMocking(); .startMocking();
setPendingResultForUser(UserHandle.myUserId());
} }
@After @After
@@ -148,6 +162,32 @@ public final class MasterClearReceiverTest {
verifyWipeExternalData(); 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() { private void expectNoWipeExternalData() {
// This is a trick to simplify how the order of methods are called: as wipeAdoptableDisks() // 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(); }).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 { private void verifyRebootWipeUserData() throws Exception {
verifyRebootWipeUserData(/* shutdown= */ false, /* reason= */ null, /* force= */ false, verifyRebootWipeUserData(/* shutdown= */ false, /* reason= */ null, /* force= */ false,
/* wipeEuicc= */ false); /* wipeEuicc= */ false);
@@ -200,6 +252,11 @@ public final class MasterClearReceiverTest {
eq(force), eq(wipeEuicc))); eq(force), eq(wipeEuicc)));
} }
private void verifyNoRebootWipeUserData() {
verify(()-> RecoverySystem.rebootWipeUserData(
any(), anyBoolean(), anyString(), anyBoolean(), anyBoolean()), never());
}
private void verifyWipeExternalData() { private void verifyWipeExternalData() {
verify(mSm).wipeAdoptableDisks(); verify(mSm).wipeAdoptableDisks();
} }
@@ -207,4 +264,21 @@ public final class MasterClearReceiverTest {
private void verifyNoWipeExternalData() { private void verifyNoWipeExternalData() {
verify(mSm, never()).wipeAdoptableDisks(); 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));
}
} }