Skip to content

Commit ff5bfb4

Browse files
Tsung-Mao FangArcWangInGoogle
Tsung-Mao Fang
authored andcommitted
[DO NOT MERGE] 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
1 parent 3cc8db3 commit ff5bfb4

10 files changed

+171
-1
lines changed

src/com/android/settings/SettingsPreferenceFragment.java

+22-1
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,8 @@
5555
import com.android.settingslib.search.Indexable;
5656
import com.android.settingslib.widget.LayoutPreference;
5757

58+
import com.google.android.setupcompat.util.WizardManagerHelper;
59+
5860
import java.util.UUID;
5961

6062
/**
@@ -63,7 +65,7 @@
6365
public abstract class SettingsPreferenceFragment extends InstrumentedPreferenceFragment
6466
implements DialogCreatable, HelpResourceProvider, Indexable {
6567

66-
private static final String TAG = "SettingsPreference";
68+
private static final String TAG = "SettingsPreferenceFragment";
6769

6870
private static final String SAVE_HIGHLIGHTED_KEY = "android:preference_highlighted";
6971

@@ -123,6 +125,15 @@ public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) {
123125
@VisibleForTesting
124126
public boolean mPreferenceHighlighted = false;
125127

128+
@Override
129+
public void onAttach(Context context) {
130+
if (shouldSkipForInitialSUW() && !WizardManagerHelper.isDeviceProvisioned(getContext())) {
131+
Log.w(TAG, "Skip " + getClass().getSimpleName() + " before SUW completed.");
132+
finish();
133+
}
134+
super.onAttach(context);
135+
}
136+
126137
@Override
127138
public void onCreate(Bundle icicle) {
128139
super.onCreate(icicle);
@@ -270,6 +281,16 @@ protected boolean isPreferenceExpanded(Preference preference) {
270281
|| (mAdapter.getPreferenceAdapterPosition(preference) != RecyclerView.NO_POSITION));
271282
}
272283

284+
/**
285+
* Whether UI should be skipped in the initial SUW flow.
286+
*
287+
* @return {@code true} when UI should be skipped in the initial SUW flow.
288+
* {@code false} when UI should not be skipped in the initial SUW flow.
289+
*/
290+
protected boolean shouldSkipForInitialSUW() {
291+
return false;
292+
}
293+
273294
protected void onDataSetChanged() {
274295
highlightPreferenceIfNeeded();
275296
updateEmptyView();

src/com/android/settings/accounts/AccountDashboardFragment.java

+5
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,11 @@ protected List<AbstractPreferenceController> createPreferenceControllers(Context
7272
return buildPreferenceControllers(context, this /* parent */, authorities);
7373
}
7474

75+
@Override
76+
protected boolean shouldSkipForInitialSUW() {
77+
return true;
78+
}
79+
7580
private static List<AbstractPreferenceController> buildPreferenceControllers(Context context,
7681
SettingsPreferenceFragment parent, String[] authorities) {
7782
final List<AbstractPreferenceController> controllers = new ArrayList<>();

src/com/android/settings/applications/appinfo/AppInfoDashboardFragment.java

+5
Original file line numberDiff line numberDiff line change
@@ -491,6 +491,11 @@ boolean refreshUi() {
491491
return true;
492492
}
493493

494+
@Override
495+
protected boolean shouldSkipForInitialSUW() {
496+
return true;
497+
}
498+
494499
private void uninstallPkg(String packageName, boolean allUsers, boolean andDisable) {
495500
stopListeningToPackageRemove();
496501
// Create new intent to launch Uninstaller activity

src/com/android/settings/development/DevelopmentSettingsDashboardFragment.java

+5
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,11 @@ public void onActivityCreated(Bundle icicle) {
204204
}
205205
}
206206

207+
@Override
208+
protected boolean shouldSkipForInitialSUW() {
209+
return true;
210+
}
211+
207212
@Override
208213
public View onCreateView(LayoutInflater inflater, ViewGroup container,
209214
Bundle savedInstanceState) {

src/com/android/settings/system/ResetDashboardFragment.java

+5
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,11 @@ protected List<AbstractPreferenceController> createPreferenceControllers(Context
5757
return buildPreferenceControllers(context, getSettingsLifecycle());
5858
}
5959

60+
@Override
61+
protected boolean shouldSkipForInitialSUW() {
62+
return true;
63+
}
64+
6065
private static List<AbstractPreferenceController> buildPreferenceControllers(Context context,
6166
Lifecycle lifecycle) {
6267
final List<AbstractPreferenceController> controllers = new ArrayList<>();

tests/robotests/src/com/android/settings/SettingsPreferenceFragmentTest.java

+74
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,13 @@
2323
import static org.mockito.Mockito.mock;
2424
import static org.mockito.Mockito.never;
2525
import static org.mockito.Mockito.spy;
26+
import static org.mockito.Mockito.times;
2627
import static org.mockito.Mockito.verify;
2728
import static org.mockito.Mockito.when;
2829

2930
import android.content.Context;
3031
import android.os.Bundle;
32+
import android.provider.Settings;
3133
import android.view.View;
3234
import android.widget.FrameLayout;
3335

@@ -41,6 +43,7 @@
4143
import com.android.settings.testutils.shadow.ShadowFragment;
4244
import com.android.settings.widget.WorkOnlyCategory;
4345

46+
import org.junit.After;
4447
import org.junit.Before;
4548
import org.junit.Test;
4649
import org.junit.runner.RunWith;
@@ -64,21 +67,34 @@ public class SettingsPreferenceFragmentTest {
6467
private PreferenceScreen mPreferenceScreen;
6568
private Context mContext;
6669
private TestFragment mFragment;
70+
private TestFragment2 mFragment2;
6771
private View mEmptyView;
72+
private int mInitDeviceProvisionedValue;
6873

6974
@Before
7075
public void setUp() {
7176
MockitoAnnotations.initMocks(this);
7277
FakeFeatureFactory.setupForTest();
7378
mContext = RuntimeEnvironment.application;
7479
mFragment = spy(new TestFragment());
80+
mFragment2 = spy(new TestFragment2());
7581
doReturn(mActivity).when(mFragment).getActivity();
7682
when(mFragment.getContext()).thenReturn(mContext);
83+
when(mFragment2.getContext()).thenReturn(mContext);
7784

7885
mEmptyView = new View(mContext);
7986
ReflectionHelpers.setField(mFragment, "mEmptyView", mEmptyView);
8087

8188
doReturn(ITEM_COUNT).when(mPreferenceScreen).getPreferenceCount();
89+
90+
mInitDeviceProvisionedValue = Settings.Global.getInt(mContext.getContentResolver(),
91+
Settings.Global.DEVICE_PROVISIONED, 0);
92+
}
93+
94+
@After
95+
public void tearDown() {
96+
Settings.Global.putInt(mContext.getContentResolver(),
97+
Settings.Global.DEVICE_PROVISIONED, mInitDeviceProvisionedValue);
8298
}
8399

84100
@Test
@@ -210,8 +226,66 @@ public void hidePinnedHeader_shouldBeInvisible() {
210226
assertThat(mFragment.mPinnedHeaderFrameLayout.getVisibility()).isEqualTo(View.INVISIBLE);
211227
}
212228

229+
@Test
230+
public void onAttach_shouldNotSkipForSUWAndDeviceIsProvisioned_notCallFinish() {
231+
Settings.Global.putInt(mContext.getContentResolver(),
232+
Settings.Global.DEVICE_PROVISIONED, 1);
233+
234+
mFragment.onAttach(mContext);
235+
236+
verify(mFragment, never()).finish();
237+
}
238+
239+
@Test
240+
public void onAttach_shouldNotSkipForSUWAndDeviceIsNotProvisioned_notCallFinish() {
241+
Settings.Global.putInt(mContext.getContentResolver(),
242+
Settings.Global.DEVICE_PROVISIONED, 0);
243+
244+
mFragment.onAttach(mContext);
245+
246+
verify(mFragment, never()).finish();
247+
}
248+
249+
@Test
250+
public void onAttach_shouldSkipForSUWAndDeviceIsDeviceProvisioned_notCallFinish() {
251+
Settings.Global.putInt(mContext.getContentResolver(),
252+
Settings.Global.DEVICE_PROVISIONED, 1);
253+
254+
mFragment2.onAttach(mContext);
255+
256+
verify(mFragment2, never()).finish();
257+
}
258+
259+
@Test
260+
public void onAttach_shouldSkipForSUWAndDeviceProvisioned_notCallFinish() {
261+
Settings.Global.putInt(mContext.getContentResolver(),
262+
Settings.Global.DEVICE_PROVISIONED, 0);
263+
264+
mFragment2.onAttach(mContext);
265+
266+
verify(mFragment2, times(1)).finish();
267+
}
268+
213269
public static class TestFragment extends SettingsPreferenceFragment {
214270

271+
@Override
272+
protected boolean shouldSkipForInitialSUW() {
273+
return false;
274+
}
275+
276+
@Override
277+
public int getMetricsCategory() {
278+
return 0;
279+
}
280+
}
281+
282+
public static class TestFragment2 extends SettingsPreferenceFragment {
283+
284+
@Override
285+
protected boolean shouldSkipForInitialSUW() {
286+
return true;
287+
}
288+
215289
@Override
216290
public int getMetricsCategory() {
217291
return 0;

tests/robotests/src/com/android/settings/accounts/AccountDashboardFragmentTest.java

+5
Original file line numberDiff line numberDiff line change
@@ -114,4 +114,9 @@ public void searchIndexProvider_hasAccounts_shouldIndex() {
114114

115115
assertThat(indexRaws).isNotEmpty();
116116
}
117+
118+
@Test
119+
public void shouldSkipForInitialSUW_returnTrue() {
120+
assertThat(mFragment.shouldSkipForInitialSUW()).isTrue();
121+
}
117122
}

tests/robotests/src/com/android/settings/applications/appinfo/AppInfoDashboardFragmentTest.java

+5
Original file line numberDiff line numberDiff line change
@@ -384,6 +384,11 @@ public void startAppInfoFragment_includesNewAndOldArgs() {
384384
.isTrue();
385385
}
386386

387+
@Test
388+
public void shouldSkipForInitialSUW_returnTrue() {
389+
assertThat(mFragment.shouldSkipForInitialSUW()).isTrue();
390+
}
391+
387392
@Implements(AppUtils.class)
388393
public static class ShadowAppUtils {
389394

tests/robotests/src/com/android/settings/development/DevelopmentSettingsDashboardFragmentTest.java

+5
Original file line numberDiff line numberDiff line change
@@ -276,6 +276,11 @@ public void onDisableLogPersistDialogRejected_shouldCallControllerDialogRejected
276276
verify(controller).onDisableLogPersistDialogRejected();
277277
}
278278

279+
@Test
280+
public void shouldSkipForInitialSUW_returnTrue() {
281+
assertThat(mDashboard.shouldSkipForInitialSUW()).isTrue();
282+
}
283+
279284
@Implements(EnableDevelopmentSettingWarningDialog.class)
280285
public static class ShadowEnableDevelopmentSettingWarningDialog {
281286

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
/*
2+
* Copyright (C) 2022 The Android Open Source Project
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.android.settings.system;
18+
19+
import static com.google.common.truth.Truth.assertThat;
20+
21+
import org.junit.Before;
22+
import org.junit.Test;
23+
import org.junit.runner.RunWith;
24+
import org.robolectric.RobolectricTestRunner;
25+
26+
@RunWith(RobolectricTestRunner.class)
27+
public class ResetDashboardFragmentTest {
28+
29+
private ResetDashboardFragment mFragment;
30+
31+
@Before
32+
public void setup() {
33+
mFragment = new ResetDashboardFragment();
34+
}
35+
36+
@Test
37+
public void shouldSkipForInitialSUW_returnTrue() {
38+
assertThat(mFragment.shouldSkipForInitialSUW()).isTrue();
39+
}
40+
}

0 commit comments

Comments
 (0)