From 80462c370b26dcaca2ed5574878f234058e49616 Mon Sep 17 00:00:00 2001 From: Chaohui Wang Date: Sat, 29 Apr 2023 23:18:44 +0800 Subject: [PATCH] FRP bypass defense in the Settings App for SPA 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. Fix: 280154358 Test: Unit test Change-Id: I06e73386711d5ad13c89d033cf0fe3164781c0ef --- src/com/android/settings/spa/SpaActivity.kt | 20 +++++++ .../android/settings/spa/SpaActivityTest.kt | 56 ++++++++++++++++--- 2 files changed, 67 insertions(+), 9 deletions(-) diff --git a/src/com/android/settings/spa/SpaActivity.kt b/src/com/android/settings/spa/SpaActivity.kt index 27f7241b257..2b52b21af0c 100644 --- a/src/com/android/settings/spa/SpaActivity.kt +++ b/src/com/android/settings/spa/SpaActivity.kt @@ -22,14 +22,34 @@ import android.content.Intent import android.os.RemoteException import android.os.UserHandle import android.util.Log +import androidx.annotation.VisibleForTesting +import com.android.settings.spa.app.appinfo.AppInfoSettingsProvider import com.android.settingslib.spa.framework.BrowseActivity +import com.android.settingslib.spa.framework.common.SettingsPage import com.android.settingslib.spa.framework.util.SESSION_BROWSE import com.android.settingslib.spa.framework.util.SESSION_EXTERNAL import com.android.settingslib.spa.framework.util.appendSpaParams +import com.google.android.setupcompat.util.WizardManagerHelper class SpaActivity : BrowseActivity() { + override fun isPageEnabled(page: SettingsPage) = + super.isPageEnabled(page) && !isSuwAndPageBlocked(page.sppName) + companion object { private const val TAG = "SpaActivity" + + /** The pages that blocked from SUW. */ + private val SuwBlockedPages = setOf(AppInfoSettingsProvider.name) + + @VisibleForTesting + fun Context.isSuwAndPageBlocked(name: String): Boolean = + if (name in SuwBlockedPages && !WizardManagerHelper.isDeviceProvisioned(this)) { + Log.w(TAG, "$name blocked before SUW completed."); + true + } else { + false + } + @JvmStatic fun Context.startSpaActivity(destination: String) { val intent = Intent(this, SpaActivity::class.java) diff --git a/tests/spa_unit/src/com/android/settings/spa/SpaActivityTest.kt b/tests/spa_unit/src/com/android/settings/spa/SpaActivityTest.kt index 46b956e6ccc..1b2a7b1ee90 100644 --- a/tests/spa_unit/src/com/android/settings/spa/SpaActivityTest.kt +++ b/tests/spa_unit/src/com/android/settings/spa/SpaActivityTest.kt @@ -21,33 +21,71 @@ import android.content.Intent import android.net.Uri import android.os.UserHandle import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.android.dx.mockito.inline.extended.ExtendedMockito +import com.android.settings.spa.SpaActivity.Companion.isSuwAndPageBlocked import com.android.settings.spa.SpaActivity.Companion.startSpaActivity import com.android.settings.spa.SpaActivity.Companion.startSpaActivityForApp +import com.android.settings.spa.app.AllAppListPageProvider +import com.android.settings.spa.app.appinfo.AppInfoSettingsProvider import com.android.settingslib.spa.framework.util.KEY_DESTINATION +import com.google.android.setupcompat.util.WizardManagerHelper import com.google.common.truth.Truth.assertThat +import org.junit.After import org.junit.Before -import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith -import org.mockito.Answers import org.mockito.ArgumentCaptor import org.mockito.Mock import org.mockito.Mockito.verify -import org.mockito.Mockito.`when` -import org.mockito.junit.MockitoJUnit -import org.mockito.junit.MockitoRule +import org.mockito.MockitoSession +import org.mockito.quality.Strictness +import org.mockito.Mockito.`when` as whenever @RunWith(AndroidJUnit4::class) class SpaActivityTest { - @get:Rule - val mockito: MockitoRule = MockitoJUnit.rule() + private lateinit var mockSession: MockitoSession - @Mock(answer = Answers.RETURNS_DEEP_STUBS) + @Mock private lateinit var context: Context @Before fun setUp() { - `when`(context.applicationContext.packageName).thenReturn("com.android.settings") + mockSession = ExtendedMockito.mockitoSession() + .initMocks(this) + .mockStatic(WizardManagerHelper::class.java) + .strictness(Strictness.LENIENT) + .startMocking() + whenever(context.applicationContext).thenReturn(context) + } + + @After + fun tearDown() { + mockSession.finishMocking() + } + + @Test + fun isSuwAndPageBlocked_regularPage_notBlocked() { + val isBlocked = context.isSuwAndPageBlocked(AllAppListPageProvider.name) + + assertThat(isBlocked).isFalse() + } + + @Test + fun isSuwAndPageBlocked_blocklistedPageInSuw_blocked() { + whenever(WizardManagerHelper.isDeviceProvisioned(context)).thenReturn(false) + + val isBlocked = context.isSuwAndPageBlocked(AppInfoSettingsProvider.name) + + assertThat(isBlocked).isTrue() + } + + @Test + fun isSuwAndPageBlocked_blocklistedPageNotInSuw_notBlocked() { + whenever(WizardManagerHelper.isDeviceProvisioned(context)).thenReturn(true) + + val isBlocked = context.isSuwAndPageBlocked(AppInfoSettingsProvider.name) + + assertThat(isBlocked).isFalse() } @Test