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;
|
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;
|
||||||
|
|||||||
@@ -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));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user