From ed9cbdea18c532a1264bfe7e4043496059e1e5fd Mon Sep 17 00:00:00 2001 From: Josh Hou Date: Mon, 9 Jan 2023 18:18:11 +0800 Subject: [PATCH] Fix the security vulnerability issue in AppLocalePickerActivity Examine whether the packages is allowed to display app locales list when creating the AppLocalePickerActivity, and examine whether the target user is the same as the calling user. Bug: 257954050 Test: Follows the test step listed in b/257954050#comment14 Change-Id: I2e25a308bcba6ea0edee89c7a78465f766bdbeac --- .../localepicker/AppLocalePickerActivity.java | 28 ++- .../AppLocalePickerActivityTest.java | 160 +++++++++++++++++- 2 files changed, 180 insertions(+), 8 deletions(-) diff --git a/src/com/android/settings/localepicker/AppLocalePickerActivity.java b/src/com/android/settings/localepicker/AppLocalePickerActivity.java index 2629b6ca639..e44242c2247 100644 --- a/src/com/android/settings/localepicker/AppLocalePickerActivity.java +++ b/src/com/android/settings/localepicker/AppLocalePickerActivity.java @@ -19,6 +19,7 @@ package com.android.settings.localepicker; import android.app.FragmentTransaction; import android.app.LocaleManager; import android.content.Context; +import android.content.pm.PackageManager; import android.net.Uri; import android.os.Bundle; import android.os.LocaleList; @@ -34,6 +35,7 @@ import com.android.internal.app.LocalePickerWithRegion; import com.android.internal.app.LocaleStore; import com.android.settings.R; import com.android.settings.applications.AppInfoBase; +import com.android.settings.applications.AppLocaleUtil; import com.android.settings.applications.appinfo.AppLocaleDetails; import com.android.settings.core.SettingsBaseActivity; @@ -64,12 +66,18 @@ public class AppLocalePickerActivity extends SettingsBaseActivity } mContextAsUser = this; if (getIntent().hasExtra(AppInfoBase.ARG_PACKAGE_UID)) { - int userId = getIntent().getIntExtra(AppInfoBase.ARG_PACKAGE_UID, -1); - if (userId != -1) { - UserHandle userHandle = UserHandle.getUserHandleForUid(userId); + int uid = getIntent().getIntExtra(AppInfoBase.ARG_PACKAGE_UID, -1); + + if (uid != -1) { + UserHandle userHandle = UserHandle.getUserHandleForUid(uid); mContextAsUser = createContextAsUser(userHandle, 0); } } + if (!canDisplayLocaleUi() || mContextAsUser.getUserId() != UserHandle.myUserId()) { + Log.w(TAG, "Not allow to display Locale Settings UI."); + finish(); + return; + } setTitle(R.string.app_locale_picker_title); getActionBar().setDisplayHomeAsUpEnabled(true); @@ -160,4 +168,18 @@ public class AppLocalePickerActivity extends SettingsBaseActivity .replace(R.id.content_frame, mLocalePickerWithRegion) .commit(); } + + private boolean canDisplayLocaleUi() { + try { + PackageManager packageManager = mContextAsUser.getPackageManager(); + return AppLocaleUtil.canDisplayLocaleUi(mContextAsUser, + packageManager.getApplicationInfo(mPackageName, 0), + packageManager.queryIntentActivities(AppLocaleUtil.LAUNCHER_ENTRY_INTENT, + PackageManager.GET_META_DATA)); + } catch (PackageManager.NameNotFoundException e) { + Log.e(TAG, "Unable to find info for package: " + mPackageName); + } + + return false; + } } \ No newline at end of file diff --git a/tests/robotests/src/com/android/settings/localepicker/AppLocalePickerActivityTest.java b/tests/robotests/src/com/android/settings/localepicker/AppLocalePickerActivityTest.java index 332a39b3436..b462d336d30 100644 --- a/tests/robotests/src/com/android/settings/localepicker/AppLocalePickerActivityTest.java +++ b/tests/robotests/src/com/android/settings/localepicker/AppLocalePickerActivityTest.java @@ -18,25 +18,36 @@ package com.android.settings.localepicker; import static com.google.common.truth.Truth.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.app.Activity; import android.app.ApplicationPackageManager; +import android.app.LocaleConfig; import android.content.Context; import android.content.Intent; +import android.content.pm.ApplicationInfo; import android.content.pm.InstallSourceInfo; +import android.content.pm.PackageInfo; +import android.content.pm.ResolveInfo; +import android.content.res.Resources; import android.net.Uri; +import android.os.LocaleList; import android.os.Process; import android.os.UserHandle; import android.telephony.TelephonyManager; +import androidx.annotation.ArrayRes; + import com.android.internal.app.LocaleStore; import com.android.settings.applications.AppInfoBase; +import com.android.settings.applications.AppLocaleUtil; -import java.util.Locale; - +import org.junit.After; +import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; @@ -45,17 +56,26 @@ import org.mockito.junit.MockitoJUnit; import org.mockito.junit.MockitoRule; import org.robolectric.Robolectric; import org.robolectric.RobolectricTestRunner; +import org.robolectric.RuntimeEnvironment; import org.robolectric.Shadows; import org.robolectric.android.controller.ActivityController; import org.robolectric.annotation.Config; import org.robolectric.annotation.Implementation; import org.robolectric.annotation.Implements; +import org.robolectric.shadows.ShadowPackageManager; import org.robolectric.shadows.ShadowTelephonyManager; +import org.robolectric.util.ReflectionHelpers; + +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; @RunWith(RobolectricTestRunner.class) @Config( shadows = { AppLocalePickerActivityTest.ShadowApplicationPackageManager.class, + AppLocalePickerActivityTest.ShadowResources.class, + AppLocalePickerActivityTest.ShadowUserHandle.class, }) public class AppLocalePickerActivityTest { private static final String TEST_PACKAGE_NAME = "com.android.settings"; @@ -63,25 +83,109 @@ public class AppLocalePickerActivityTest { @Mock LocaleStore.LocaleInfo mLocaleInfo; + @Mock + private LocaleConfig mLocaleConfig; @Rule public MockitoRule rule = MockitoJUnit.rule(); + private Context mContext; + private ShadowPackageManager mPackageManager; + + @Before + public void setUp() { + mContext = spy(RuntimeEnvironment.application); + mPackageManager = Shadows.shadowOf(mContext.getPackageManager()); + mLocaleConfig = mock(LocaleConfig.class); + when(mLocaleConfig.getStatus()).thenReturn(LocaleConfig.STATUS_SUCCESS); + when(mLocaleConfig.getSupportedLocales()).thenReturn(LocaleList.forLanguageTags("en-US")); + ReflectionHelpers.setStaticField(AppLocaleUtil.class, "sLocaleConfig", mLocaleConfig); + } + + @After + public void tearDown() { + mPackageManager.removePackage(TEST_PACKAGE_NAME); + ReflectionHelpers.setStaticField(AppLocaleUtil.class, "sLocaleConfig", null); + ShadowResources.setDisAllowPackage(false); + ShadowApplicationPackageManager.setNoLaunchEntry(false); + ShadowUserHandle.setUserId(0); + } + @Test public void launchAppLocalePickerActivity_hasPackageName_success() { ActivityController controller = initActivityController(true); - controller.create(); assertThat(controller.get().isFinishing()).isFalse(); } + @Test + public void launchAppLocalePickerActivity_appNoLocaleConfig_failed() { + when(mLocaleConfig.getStatus()).thenReturn(LocaleConfig.STATUS_NOT_SPECIFIED); + + ActivityController controller = + initActivityController(true); + controller.create(); + + assertThat(controller.get().isFinishing()).isTrue(); + } + + @Test + public void launchAppLocalePickerActivity_appSignPlatformKey_failed() { + final ApplicationInfo applicationInfo = new ApplicationInfo(); + applicationInfo.privateFlags |= ApplicationInfo.PRIVATE_FLAG_SIGNED_WITH_PLATFORM_KEY; + applicationInfo.packageName = TEST_PACKAGE_NAME; + + final PackageInfo packageInfo = new PackageInfo(); + packageInfo.packageName = TEST_PACKAGE_NAME; + packageInfo.applicationInfo = applicationInfo; + mPackageManager.installPackage(packageInfo); + + ActivityController controller = + initActivityController(true); + controller.create(); + + assertThat(controller.get().isFinishing()).isTrue(); + } + + @Test + public void launchAppLocalePickerActivity_appMatchDisallowedPackage_failed() { + ShadowResources.setDisAllowPackage(true); + + ActivityController controller = + initActivityController(true); + controller.create(); + + assertThat(controller.get().isFinishing()).isTrue(); + } + + @Test + public void launchAppLocalePickerActivity_appNoLaunchEntry_failed() { + ShadowApplicationPackageManager.setNoLaunchEntry(true); + + ActivityController controller = + initActivityController(true); + controller.create(); + + assertThat(controller.get().isFinishing()).isTrue(); + } + + @Test + public void launchAppLocalePickerActivity_modifyAppLocalesOfAnotherUser_failed() { + ShadowUserHandle.setUserId(10); + + ActivityController controller = + initActivityController(true); + controller.create(); + + assertThat(controller.get().isFinishing()).isTrue(); + } + @Test public void launchAppLocalePickerActivity_intentWithoutPackageName_failed() { ActivityController controller = initActivityController(false); - controller.create(); assertThat(controller.get().isFinishing()).isTrue(); @@ -125,7 +229,7 @@ public class AppLocalePickerActivityTest { if (hasPackageName) { data.setData(TEST_PACKAGE_URI); } - data.putExtra(AppInfoBase.ARG_PACKAGE_UID, UserHandle.getUserId(Process.myUid())); + data.putExtra(AppInfoBase.ARG_PACKAGE_UID, Process.myUid()); ActivityController activityController = Robolectric.buildActivity(TestAppLocalePickerActivity.class, data); Activity activity = activityController.get(); @@ -149,10 +253,56 @@ public class AppLocalePickerActivityTest { @Implements(ApplicationPackageManager.class) public static class ShadowApplicationPackageManager extends org.robolectric.shadows.ShadowApplicationPackageManager { + private static boolean sNoLaunchEntry = false; @Implementation protected Object getInstallSourceInfo(String packageName) { return new InstallSourceInfo("", null, null, ""); } + + @Implementation + protected List queryIntentActivities(Intent intent, int flags) { + if (sNoLaunchEntry) { + return new ArrayList(); + } else { + return super.queryIntentActivities(intent, flags); + } + } + + private static void setNoLaunchEntry(boolean noLaunchEntry) { + sNoLaunchEntry = noLaunchEntry; + } + } + + @Implements(Resources.class) + public static class ShadowResources extends + org.robolectric.shadows.ShadowResources { + private static boolean sDisAllowPackage = false; + + @Implementation + public String[] getStringArray(@ArrayRes int id) { + if (sDisAllowPackage) { + return new String[]{TEST_PACKAGE_NAME}; + } else { + return new String[0]; + } + } + + private static void setDisAllowPackage(boolean disAllowPackage) { + sDisAllowPackage = disAllowPackage; + } + } + + @Implements(UserHandle.class) + public static class ShadowUserHandle { + private static int sUserId = 0; + private static void setUserId(int userId) { + sUserId = userId; + } + + @Implementation + public static int getUserId(int userId) { + return sUserId; + } } }