FRP bypass defense in the settings app
Over the last few years, there have been a number of Factory Reset Protection bypass bugs in the SUW flow. It's unlikely to defense all points from individual apps. Therefore, we decide to block some critical pages when user doesn't complete the SUW flow. Test: Can't open the certain pages in the suw flow. Bug: 258422561 Fix: 200746457 Bug: 202975040 Fix: 213091525 Fix: 213090835 Fix: 201561699 Fix: 213090827 Fix: 213090875 Change-Id: Ia18f367109df5af7da0a5acad7702898a459d32e Merged-In: Ia18f367109df5af7da0a5acad7702898a459d32e
This commit is contained in:
@@ -54,6 +54,7 @@ import com.android.settingslib.search.Indexable;
|
|||||||
import com.android.settingslib.widget.LayoutPreference;
|
import com.android.settingslib.widget.LayoutPreference;
|
||||||
|
|
||||||
import com.google.android.material.appbar.AppBarLayout;
|
import com.google.android.material.appbar.AppBarLayout;
|
||||||
|
import com.google.android.setupcompat.util.WizardManagerHelper;
|
||||||
|
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
@@ -63,7 +64,7 @@ import java.util.UUID;
|
|||||||
public abstract class SettingsPreferenceFragment extends InstrumentedPreferenceFragment
|
public abstract class SettingsPreferenceFragment extends InstrumentedPreferenceFragment
|
||||||
implements DialogCreatable, HelpResourceProvider, Indexable {
|
implements DialogCreatable, HelpResourceProvider, Indexable {
|
||||||
|
|
||||||
private static final String TAG = "SettingsPreference";
|
private static final String TAG = "SettingsPreferenceFragment";
|
||||||
|
|
||||||
private static final String SAVE_HIGHLIGHTED_KEY = "android:preference_highlighted";
|
private static final String SAVE_HIGHLIGHTED_KEY = "android:preference_highlighted";
|
||||||
|
|
||||||
@@ -122,6 +123,15 @@ public abstract class SettingsPreferenceFragment extends InstrumentedPreferenceF
|
|||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
public boolean mPreferenceHighlighted = false;
|
public boolean mPreferenceHighlighted = false;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAttach(Context context) {
|
||||||
|
if (shouldSkipForInitialSUW() && !WizardManagerHelper.isDeviceProvisioned(getContext())) {
|
||||||
|
Log.w(TAG, "Skip " + getClass().getSimpleName() + " before SUW completed.");
|
||||||
|
finish();
|
||||||
|
}
|
||||||
|
super.onAttach(context);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle icicle) {
|
public void onCreate(Bundle icicle) {
|
||||||
super.onCreate(icicle);
|
super.onCreate(icicle);
|
||||||
@@ -268,6 +278,16 @@ public abstract class SettingsPreferenceFragment extends InstrumentedPreferenceF
|
|||||||
|| (mAdapter.getPreferenceAdapterPosition(preference) != RecyclerView.NO_POSITION));
|
|| (mAdapter.getPreferenceAdapterPosition(preference) != RecyclerView.NO_POSITION));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether UI should be skipped in the initial SUW flow.
|
||||||
|
*
|
||||||
|
* @return {@code true} when UI should be skipped in the initial SUW flow.
|
||||||
|
* {@code false} when UI should not be skipped in the initial SUW flow.
|
||||||
|
*/
|
||||||
|
protected boolean shouldSkipForInitialSUW() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
protected void onDataSetChanged() {
|
protected void onDataSetChanged() {
|
||||||
highlightPreferenceIfNeeded();
|
highlightPreferenceIfNeeded();
|
||||||
updateEmptyView();
|
updateEmptyView();
|
||||||
|
@@ -84,6 +84,11 @@ public class AccountDashboardFragment extends DashboardFragment {
|
|||||||
return controllers;
|
return controllers;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean shouldSkipForInitialSUW() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
static void buildAutofillPreferenceControllers(
|
static void buildAutofillPreferenceControllers(
|
||||||
Context context, List<AbstractPreferenceController> controllers) {
|
Context context, List<AbstractPreferenceController> controllers) {
|
||||||
controllers.add(new DefaultAutofillPreferenceController(context));
|
controllers.add(new DefaultAutofillPreferenceController(context));
|
||||||
|
@@ -503,6 +503,11 @@ public class AppInfoDashboardFragment extends DashboardFragment
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean shouldSkipForInitialSUW() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
private void uninstallPkg(String packageName, boolean allUsers, boolean andDisable) {
|
private void uninstallPkg(String packageName, boolean allUsers, boolean andDisable) {
|
||||||
stopListeningToPackageRemove();
|
stopListeningToPackageRemove();
|
||||||
// Create new intent to launch Uninstaller activity
|
// Create new intent to launch Uninstaller activity
|
||||||
|
@@ -215,6 +215,11 @@ public class DevelopmentSettingsDashboardFragment extends RestrictedDashboardFra
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean shouldSkipForInitialSUW() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Long-pressing a developer options quick settings tile will by default (see
|
* Long-pressing a developer options quick settings tile will by default (see
|
||||||
* QS_TILE_PREFERENCES in the manifest) take you to the developer options page.
|
* QS_TILE_PREFERENCES in the manifest) take you to the developer options page.
|
||||||
|
@@ -64,6 +64,11 @@ public class ResetDashboardFragment extends DashboardFragment {
|
|||||||
use(EraseEuiccDataController.class).setFragment(this);
|
use(EraseEuiccDataController.class).setFragment(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean shouldSkipForInitialSUW() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
private static List<AbstractPreferenceController> buildPreferenceControllers(Context context,
|
private static List<AbstractPreferenceController> buildPreferenceControllers(Context context,
|
||||||
Lifecycle lifecycle) {
|
Lifecycle lifecycle) {
|
||||||
final List<AbstractPreferenceController> controllers = new ArrayList<>();
|
final List<AbstractPreferenceController> controllers = new ArrayList<>();
|
||||||
|
@@ -23,11 +23,13 @@ import static org.mockito.Mockito.doReturn;
|
|||||||
import static org.mockito.Mockito.mock;
|
import static org.mockito.Mockito.mock;
|
||||||
import static org.mockito.Mockito.never;
|
import static org.mockito.Mockito.never;
|
||||||
import static org.mockito.Mockito.spy;
|
import static org.mockito.Mockito.spy;
|
||||||
|
import static org.mockito.Mockito.times;
|
||||||
import static org.mockito.Mockito.verify;
|
import static org.mockito.Mockito.verify;
|
||||||
import static org.mockito.Mockito.when;
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
import android.provider.Settings;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.widget.FrameLayout;
|
import android.widget.FrameLayout;
|
||||||
|
|
||||||
@@ -41,6 +43,7 @@ import com.android.settings.testutils.FakeFeatureFactory;
|
|||||||
import com.android.settings.testutils.shadow.ShadowFragment;
|
import com.android.settings.testutils.shadow.ShadowFragment;
|
||||||
import com.android.settings.widget.WorkOnlyCategory;
|
import com.android.settings.widget.WorkOnlyCategory;
|
||||||
|
|
||||||
|
import org.junit.After;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
@@ -64,7 +67,9 @@ public class SettingsPreferenceFragmentTest {
|
|||||||
private PreferenceScreen mPreferenceScreen;
|
private PreferenceScreen mPreferenceScreen;
|
||||||
private Context mContext;
|
private Context mContext;
|
||||||
private TestFragment mFragment;
|
private TestFragment mFragment;
|
||||||
|
private TestFragment2 mFragment2;
|
||||||
private View mEmptyView;
|
private View mEmptyView;
|
||||||
|
private int mInitDeviceProvisionedValue;
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void setUp() {
|
public void setUp() {
|
||||||
@@ -72,13 +77,24 @@ public class SettingsPreferenceFragmentTest {
|
|||||||
FakeFeatureFactory.setupForTest();
|
FakeFeatureFactory.setupForTest();
|
||||||
mContext = RuntimeEnvironment.application;
|
mContext = RuntimeEnvironment.application;
|
||||||
mFragment = spy(new TestFragment());
|
mFragment = spy(new TestFragment());
|
||||||
|
mFragment2 = spy(new TestFragment2());
|
||||||
doReturn(mActivity).when(mFragment).getActivity();
|
doReturn(mActivity).when(mFragment).getActivity();
|
||||||
when(mFragment.getContext()).thenReturn(mContext);
|
when(mFragment.getContext()).thenReturn(mContext);
|
||||||
|
when(mFragment2.getContext()).thenReturn(mContext);
|
||||||
|
|
||||||
mEmptyView = new View(mContext);
|
mEmptyView = new View(mContext);
|
||||||
ReflectionHelpers.setField(mFragment, "mEmptyView", mEmptyView);
|
ReflectionHelpers.setField(mFragment, "mEmptyView", mEmptyView);
|
||||||
|
|
||||||
doReturn(ITEM_COUNT).when(mPreferenceScreen).getPreferenceCount();
|
doReturn(ITEM_COUNT).when(mPreferenceScreen).getPreferenceCount();
|
||||||
|
|
||||||
|
mInitDeviceProvisionedValue = Settings.Global.getInt(mContext.getContentResolver(),
|
||||||
|
Settings.Global.DEVICE_PROVISIONED, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@After
|
||||||
|
public void tearDown() {
|
||||||
|
Settings.Global.putInt(mContext.getContentResolver(),
|
||||||
|
Settings.Global.DEVICE_PROVISIONED, mInitDeviceProvisionedValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -210,8 +226,66 @@ public class SettingsPreferenceFragmentTest {
|
|||||||
assertThat(mFragment.mPinnedHeaderFrameLayout.getVisibility()).isEqualTo(View.INVISIBLE);
|
assertThat(mFragment.mPinnedHeaderFrameLayout.getVisibility()).isEqualTo(View.INVISIBLE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void onAttach_shouldNotSkipForSUWAndDeviceIsProvisioned_notCallFinish() {
|
||||||
|
Settings.Global.putInt(mContext.getContentResolver(),
|
||||||
|
Settings.Global.DEVICE_PROVISIONED, 1);
|
||||||
|
|
||||||
|
mFragment.onAttach(mContext);
|
||||||
|
|
||||||
|
verify(mFragment, never()).finish();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void onAttach_shouldNotSkipForSUWAndDeviceIsNotProvisioned_notCallFinish() {
|
||||||
|
Settings.Global.putInt(mContext.getContentResolver(),
|
||||||
|
Settings.Global.DEVICE_PROVISIONED, 0);
|
||||||
|
|
||||||
|
mFragment.onAttach(mContext);
|
||||||
|
|
||||||
|
verify(mFragment, never()).finish();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void onAttach_shouldSkipForSUWAndDeviceIsDeviceProvisioned_notCallFinish() {
|
||||||
|
Settings.Global.putInt(mContext.getContentResolver(),
|
||||||
|
Settings.Global.DEVICE_PROVISIONED, 1);
|
||||||
|
|
||||||
|
mFragment2.onAttach(mContext);
|
||||||
|
|
||||||
|
verify(mFragment2, never()).finish();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void onAttach_shouldSkipForSUWAndDeviceProvisioned_notCallFinish() {
|
||||||
|
Settings.Global.putInt(mContext.getContentResolver(),
|
||||||
|
Settings.Global.DEVICE_PROVISIONED, 0);
|
||||||
|
|
||||||
|
mFragment2.onAttach(mContext);
|
||||||
|
|
||||||
|
verify(mFragment2, times(1)).finish();
|
||||||
|
}
|
||||||
|
|
||||||
public static class TestFragment extends SettingsPreferenceFragment {
|
public static class TestFragment extends SettingsPreferenceFragment {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean shouldSkipForInitialSUW() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getMetricsCategory() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class TestFragment2 extends SettingsPreferenceFragment {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean shouldSkipForInitialSUW() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getMetricsCategory() {
|
public int getMetricsCategory() {
|
||||||
return 0;
|
return 0;
|
||||||
|
@@ -114,4 +114,9 @@ public class AccountDashboardFragmentTest {
|
|||||||
|
|
||||||
assertThat(indexRaws).isNotEmpty();
|
assertThat(indexRaws).isNotEmpty();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldSkipForInitialSUW_returnTrue() {
|
||||||
|
assertThat(mFragment.shouldSkipForInitialSUW()).isTrue();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -384,6 +384,11 @@ public final class AppInfoDashboardFragmentTest {
|
|||||||
.isTrue();
|
.isTrue();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldSkipForInitialSUW_returnTrue() {
|
||||||
|
assertThat(mFragment.shouldSkipForInitialSUW()).isTrue();
|
||||||
|
}
|
||||||
|
|
||||||
@Implements(AppUtils.class)
|
@Implements(AppUtils.class)
|
||||||
public static class ShadowAppUtils {
|
public static class ShadowAppUtils {
|
||||||
|
|
||||||
|
@@ -278,6 +278,11 @@ public class DevelopmentSettingsDashboardFragmentTest {
|
|||||||
verify(controller).onDisableLogPersistDialogRejected();
|
verify(controller).onDisableLogPersistDialogRejected();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldSkipForInitialSUW_returnTrue() {
|
||||||
|
assertThat(mDashboard.shouldSkipForInitialSUW()).isTrue();
|
||||||
|
}
|
||||||
|
|
||||||
@Implements(EnableDevelopmentSettingWarningDialog.class)
|
@Implements(EnableDevelopmentSettingWarningDialog.class)
|
||||||
public static class ShadowEnableDevelopmentSettingWarningDialog {
|
public static class ShadowEnableDevelopmentSettingWarningDialog {
|
||||||
|
|
||||||
|
@@ -0,0 +1,40 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2022 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.android.settings.system;
|
||||||
|
|
||||||
|
import static com.google.common.truth.Truth.assertThat;
|
||||||
|
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
import org.robolectric.RobolectricTestRunner;
|
||||||
|
|
||||||
|
@RunWith(RobolectricTestRunner.class)
|
||||||
|
public class ResetDashboardFragmentTest {
|
||||||
|
|
||||||
|
private ResetDashboardFragment mFragment;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setup() {
|
||||||
|
mFragment = new ResetDashboardFragment();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldSkipForInitialSUW_returnTrue() {
|
||||||
|
assertThat(mFragment.shouldSkipForInitialSUW()).isTrue();
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user