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
Merged-In: I2e25a308bcba6ea0edee89c7a78465f766bdbeac
This commit is contained in:
Josh Hou
2023-01-09 18:18:11 +08:00
parent 521278a00d
commit 33ef83795e
2 changed files with 183 additions and 8 deletions

View File

@@ -19,6 +19,7 @@ package com.android.settings.localepicker;
import android.app.FragmentTransaction; import android.app.FragmentTransaction;
import android.app.LocaleManager; import android.app.LocaleManager;
import android.content.Context; import android.content.Context;
import android.content.pm.PackageManager;
import android.net.Uri; import android.net.Uri;
import android.os.Bundle; import android.os.Bundle;
import android.os.LocaleList; import android.os.LocaleList;
@@ -34,6 +35,7 @@ import com.android.internal.app.LocalePickerWithRegion;
import com.android.internal.app.LocaleStore; import com.android.internal.app.LocaleStore;
import com.android.settings.R; import com.android.settings.R;
import com.android.settings.applications.AppInfoBase; import com.android.settings.applications.AppInfoBase;
import com.android.settings.applications.AppLocaleUtil;
import com.android.settings.applications.appinfo.AppLocaleDetails; import com.android.settings.applications.appinfo.AppLocaleDetails;
import com.android.settings.core.SettingsBaseActivity; import com.android.settings.core.SettingsBaseActivity;
@@ -64,12 +66,17 @@ public class AppLocalePickerActivity extends SettingsBaseActivity
} }
mContextAsUser = this; mContextAsUser = this;
if (getIntent().hasExtra(AppInfoBase.ARG_PACKAGE_UID)) { if (getIntent().hasExtra(AppInfoBase.ARG_PACKAGE_UID)) {
int userId = getIntent().getIntExtra(AppInfoBase.ARG_PACKAGE_UID, -1); int uid = getIntent().getIntExtra(AppInfoBase.ARG_PACKAGE_UID, -1);
if (userId != -1) { if (uid != -1) {
UserHandle userHandle = UserHandle.getUserHandleForUid(userId); UserHandle userHandle = UserHandle.getUserHandleForUid(uid);
mContextAsUser = createContextAsUser(userHandle, 0); 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); setTitle(R.string.app_locale_picker_title);
getActionBar().setDisplayHomeAsUpEnabled(true); getActionBar().setDisplayHomeAsUpEnabled(true);
@@ -160,4 +167,10 @@ public class AppLocalePickerActivity extends SettingsBaseActivity
.replace(R.id.content_frame, mLocalePickerWithRegion) .replace(R.id.content_frame, mLocalePickerWithRegion)
.commit(); .commit();
} }
private boolean canDisplayLocaleUi() {
return AppLocaleUtil.canDisplayLocaleUi(mContextAsUser, mPackageName,
mContextAsUser.getPackageManager().queryIntentActivities(
AppLocaleUtil.LAUNCHER_ENTRY_INTENT, PackageManager.GET_META_DATA));
}
} }

View File

@@ -18,25 +18,35 @@ package com.android.settings.localepicker;
import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times; 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.annotation.Nullable;
import android.app.Activity; import android.app.Activity;
import android.app.ApplicationPackageManager; import android.app.ApplicationPackageManager;
import android.app.LocaleConfig;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.InstallSourceInfo; 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.net.Uri;
import android.os.LocaleList;
import android.os.Process; import android.os.Process;
import android.os.UserHandle; import android.os.UserHandle;
import android.telephony.TelephonyManager; import android.telephony.TelephonyManager;
import androidx.annotation.ArrayRes;
import com.android.internal.app.LocaleStore; import com.android.internal.app.LocaleStore;
import com.android.settings.applications.AppInfoBase; import com.android.settings.applications.AppInfoBase;
import java.util.Locale; import org.junit.After;
import org.junit.Before;
import org.junit.Rule; import org.junit.Rule;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
@@ -45,17 +55,26 @@ import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule; import org.mockito.junit.MockitoRule;
import org.robolectric.Robolectric; import org.robolectric.Robolectric;
import org.robolectric.RobolectricTestRunner; import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.Shadows; import org.robolectric.Shadows;
import org.robolectric.android.controller.ActivityController; import org.robolectric.android.controller.ActivityController;
import org.robolectric.annotation.Config; import org.robolectric.annotation.Config;
import org.robolectric.annotation.Implementation; import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements; import org.robolectric.annotation.Implements;
import org.robolectric.shadows.ShadowPackageManager;
import org.robolectric.shadows.ShadowTelephonyManager; import org.robolectric.shadows.ShadowTelephonyManager;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
@RunWith(RobolectricTestRunner.class) @RunWith(RobolectricTestRunner.class)
@Config( @Config(
shadows = { shadows = {
AppLocalePickerActivityTest.ShadowApplicationPackageManager.class, AppLocalePickerActivityTest.ShadowApplicationPackageManager.class,
AppLocalePickerActivityTest.ShadowResources.class,
AppLocalePickerActivityTest.ShadowUserHandle.class,
AppLocalePickerActivityTest.ShadowLocaleConfig.class,
}) })
public class AppLocalePickerActivityTest { public class AppLocalePickerActivityTest {
private static final String TEST_PACKAGE_NAME = "com.android.settings"; private static final String TEST_PACKAGE_NAME = "com.android.settings";
@@ -67,21 +86,99 @@ public class AppLocalePickerActivityTest {
@Rule @Rule
public MockitoRule rule = MockitoJUnit.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());
}
@After
public void tearDown() {
mPackageManager.removePackage(TEST_PACKAGE_NAME);
ShadowResources.setDisAllowPackage(false);
ShadowApplicationPackageManager.setNoLaunchEntry(false);
ShadowUserHandle.setUserId(0);
ShadowLocaleConfig.setStatus(LocaleConfig.STATUS_SUCCESS);
}
@Test @Test
public void launchAppLocalePickerActivity_hasPackageName_success() { public void launchAppLocalePickerActivity_hasPackageName_success() {
ActivityController<TestAppLocalePickerActivity> controller = ActivityController<TestAppLocalePickerActivity> controller =
initActivityController(true); initActivityController(true);
controller.create(); controller.create();
assertThat(controller.get().isFinishing()).isFalse(); assertThat(controller.get().isFinishing()).isFalse();
} }
@Test
public void launchAppLocalePickerActivity_appNoLocaleConfig_failed() {
ShadowLocaleConfig.setStatus(LocaleConfig.STATUS_NOT_SPECIFIED);
ActivityController<TestAppLocalePickerActivity> 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<TestAppLocalePickerActivity> controller =
initActivityController(true);
controller.create();
assertThat(controller.get().isFinishing()).isTrue();
}
@Test
public void launchAppLocalePickerActivity_appMatchDisallowedPackage_failed() {
ShadowResources.setDisAllowPackage(true);
ActivityController<TestAppLocalePickerActivity> controller =
initActivityController(true);
controller.create();
assertThat(controller.get().isFinishing()).isTrue();
}
@Test
public void launchAppLocalePickerActivity_appNoLaunchEntry_failed() {
ShadowApplicationPackageManager.setNoLaunchEntry(true);
ActivityController<TestAppLocalePickerActivity> controller =
initActivityController(true);
controller.create();
assertThat(controller.get().isFinishing()).isTrue();
}
@Test
public void launchAppLocalePickerActivity_modifyAppLocalesOfAnotherUser_failed() {
ShadowUserHandle.setUserId(10);
ActivityController<TestAppLocalePickerActivity> controller =
initActivityController(true);
controller.create();
assertThat(controller.get().isFinishing()).isTrue();
}
@Test @Test
public void launchAppLocalePickerActivity_intentWithoutPackageName_failed() { public void launchAppLocalePickerActivity_intentWithoutPackageName_failed() {
ActivityController<TestAppLocalePickerActivity> controller = ActivityController<TestAppLocalePickerActivity> controller =
initActivityController(false); initActivityController(false);
controller.create(); controller.create();
assertThat(controller.get().isFinishing()).isTrue(); assertThat(controller.get().isFinishing()).isTrue();
@@ -125,7 +222,7 @@ public class AppLocalePickerActivityTest {
if (hasPackageName) { if (hasPackageName) {
data.setData(TEST_PACKAGE_URI); data.setData(TEST_PACKAGE_URI);
} }
data.putExtra(AppInfoBase.ARG_PACKAGE_UID, UserHandle.getUserId(Process.myUid())); data.putExtra(AppInfoBase.ARG_PACKAGE_UID, Process.myUid());
ActivityController<TestAppLocalePickerActivity> activityController = ActivityController<TestAppLocalePickerActivity> activityController =
Robolectric.buildActivity(TestAppLocalePickerActivity.class, data); Robolectric.buildActivity(TestAppLocalePickerActivity.class, data);
Activity activity = activityController.get(); Activity activity = activityController.get();
@@ -149,10 +246,75 @@ public class AppLocalePickerActivityTest {
@Implements(ApplicationPackageManager.class) @Implements(ApplicationPackageManager.class)
public static class ShadowApplicationPackageManager extends public static class ShadowApplicationPackageManager extends
org.robolectric.shadows.ShadowApplicationPackageManager { org.robolectric.shadows.ShadowApplicationPackageManager {
private static boolean sNoLaunchEntry = false;
@Implementation @Implementation
protected Object getInstallSourceInfo(String packageName) { protected Object getInstallSourceInfo(String packageName) {
return new InstallSourceInfo("", null, null, ""); return new InstallSourceInfo("", null, null, "");
} }
@Implementation
protected List<ResolveInfo> 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;
}
}
@Implements(LocaleConfig.class)
public static class ShadowLocaleConfig {
private static int sStatus = 0;
@Implementation
public @Nullable LocaleList getSupportedLocales() {
return LocaleList.forLanguageTags("en-US");
}
private static void setStatus(@LocaleConfig.Status int status) {
sStatus = status;
}
@Implementation
public @LocaleConfig.Status int getStatus() {
return sStatus;
}
} }
} }