diff options
15 files changed, 1392 insertions, 48 deletions
diff --git a/apct-tests/perftests/multiuser/AndroidManifest.xml b/apct-tests/perftests/multiuser/AndroidManifest.xml index b2a9524d29c4..893c8ca9328b 100644 --- a/apct-tests/perftests/multiuser/AndroidManifest.xml +++ b/apct-tests/perftests/multiuser/AndroidManifest.xml @@ -22,6 +22,7 @@ <uses-permission android:name="android.permission.INSTALL_PACKAGES" /> <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS" /> <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" /> + <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" /> <application> <uses-library android:name="android.test.runner" /> diff --git a/apct-tests/perftests/multiuser/src/android/multiuser/UserLifecycleTests.java b/apct-tests/perftests/multiuser/src/android/multiuser/UserLifecycleTests.java index 32107b4e789e..e74e4a958eb9 100644 --- a/apct-tests/perftests/multiuser/src/android/multiuser/UserLifecycleTests.java +++ b/apct-tests/perftests/multiuser/src/android/multiuser/UserLifecycleTests.java @@ -38,8 +38,10 @@ import android.os.Bundle; import android.os.IBinder; import android.os.IProgressListener; import android.os.RemoteException; +import android.os.SystemProperties; import android.os.UserHandle; import android.os.UserManager; +import android.perftests.utils.ShellHelper; import android.util.Log; import android.view.WindowManagerGlobal; @@ -85,6 +87,14 @@ public class UserLifecycleTests { private static final String DUMMY_PACKAGE_NAME = "perftests.multiuser.apps.dummyapp"; + // Copy of UserSystemPackageInstaller whitelist mode constants. + private static final String PACKAGE_WHITELIST_MODE_PROP = + "persist.debug.user.package_whitelist_mode"; + private static final int USER_TYPE_PACKAGE_WHITELIST_MODE_DISABLE = 0; + private static final int USER_TYPE_PACKAGE_WHITELIST_MODE_ENFORCE = 0b001; + private static final int USER_TYPE_PACKAGE_WHITELIST_MODE_IMPLICIT_WHITELIST = 0b100; + private static final int USER_TYPE_PACKAGE_WHITELIST_MODE_DEVICE_DEFAULT = -1; + private UserManager mUm; private ActivityManager mAm; private IActivityManager mIam; @@ -442,6 +452,55 @@ public class UserLifecycleTests { } } + // TODO: This is just a POC. Do this properly and add more. + /** Tests starting (unlocking) a newly-created profile using the user-type-pkg-whitelist. */ + @Test + public void managedProfileUnlock_usingWhitelist() throws Exception { + assumeTrue(mHasManagedUserFeature); + final int origMode = getUserTypePackageWhitelistMode(); + setUserTypePackageWhitelistMode(USER_TYPE_PACKAGE_WHITELIST_MODE_ENFORCE + | USER_TYPE_PACKAGE_WHITELIST_MODE_IMPLICIT_WHITELIST); + + try { + while (mRunner.keepRunning()) { + mRunner.pauseTiming(); + final int userId = createManagedProfile(); + mRunner.resumeTiming(); + + startUserInBackground(userId); + + mRunner.pauseTiming(); + removeUser(userId); + mRunner.resumeTiming(); + } + } finally { + setUserTypePackageWhitelistMode(origMode); + } + } + /** Tests starting (unlocking) a newly-created profile NOT using the user-type-pkg-whitelist. */ + @Test + public void managedProfileUnlock_notUsingWhitelist() throws Exception { + assumeTrue(mHasManagedUserFeature); + final int origMode = getUserTypePackageWhitelistMode(); + setUserTypePackageWhitelistMode(USER_TYPE_PACKAGE_WHITELIST_MODE_DISABLE); + + try { + while (mRunner.keepRunning()) { + mRunner.pauseTiming(); + final int userId = createManagedProfile(); + mRunner.resumeTiming(); + + startUserInBackground(userId); + + mRunner.pauseTiming(); + removeUser(userId); + mRunner.resumeTiming(); + } + } finally { + setUserTypePackageWhitelistMode(origMode); + } + } + /** Creates a new user, returning its userId. */ private int createUserNoFlags() { return createUserWithFlags(/* flags= */ 0); @@ -458,6 +517,10 @@ public class UserLifecycleTests { private int createManagedProfile() { final UserInfo userInfo = mUm.createProfileForUser("TestProfile", UserInfo.FLAG_MANAGED_PROFILE, mAm.getCurrentUser()); + if (userInfo == null) { + throw new IllegalStateException("Creating managed profile failed. Most likely there is " + + "already a pre-existing profile on the device."); + } mUsersToRemove.add(userInfo.id); return userInfo.id; } @@ -627,6 +690,20 @@ public class UserLifecycleTests { } } + /** Gets the PACKAGE_WHITELIST_MODE_PROP System Property. */ + private int getUserTypePackageWhitelistMode() { + return SystemProperties.getInt(PACKAGE_WHITELIST_MODE_PROP, + USER_TYPE_PACKAGE_WHITELIST_MODE_DEVICE_DEFAULT); + } + + /** Sets the PACKAGE_WHITELIST_MODE_PROP System Property to the given value. */ + private void setUserTypePackageWhitelistMode(int mode) { + String result = ShellHelper.runShellCommand( + String.format("setprop %s %d", PACKAGE_WHITELIST_MODE_PROP, mode)); + attestFalse("Failed to set sysprop " + PACKAGE_WHITELIST_MODE_PROP + ": " + result, + result != null && result.contains("Failed")); + } + private void removeUser(int userId) { try { mUm.removeUser(userId); diff --git a/core/java/android/content/pm/PackageManagerInternal.java b/core/java/android/content/pm/PackageManagerInternal.java index 24ee21360ed8..a87e165374cb 100644 --- a/core/java/android/content/pm/PackageManagerInternal.java +++ b/core/java/android/content/pm/PackageManagerInternal.java @@ -308,6 +308,17 @@ public abstract class PackageManagerInternal { public abstract String getNameForUid(int uid); /** + * Marks a package as installed (or not installed) for a given user. + * + * @param pkg the package whose installation is to be set + * @param userId the user for whom to set it + * @param installed the new installed state + * @return true if the installed state changed as a result + */ + public abstract boolean setInstalled(PackageParser.Package pkg, + @UserIdInt int userId, boolean installed); + + /** * Request to perform the second phase of ephemeral resolution. * @param responseObj The response of the first phase of ephemeral resolution * @param origIntent The original intent that triggered ephemeral resolution diff --git a/core/java/android/content/pm/UserInfo.java b/core/java/android/content/pm/UserInfo.java index e65d761cb77e..df652f190d04 100644 --- a/core/java/android/content/pm/UserInfo.java +++ b/core/java/android/content/pm/UserInfo.java @@ -126,6 +126,13 @@ public class UserInfo implements Parcelable { public static final int FLAG_SYSTEM = 0x00000800; /** + * Indicates that this user is some sort of profile. Right now, the only profile type is + * {@link #FLAG_MANAGED_PROFILE}, but this can include other types of profiles too if any + * are created in the future. This is therefore not a flag, but an OR of several flags. + */ + public static final int PROFILE_FLAGS_MASK = FLAG_MANAGED_PROFILE; + + /** * @hide */ @IntDef(flag = true, prefix = "FLAG_", value = { diff --git a/core/java/com/android/server/SystemConfig.java b/core/java/com/android/server/SystemConfig.java index 7cd3e95c6499..697825dcefd7 100644 --- a/core/java/com/android/server/SystemConfig.java +++ b/core/java/com/android/server/SystemConfig.java @@ -35,6 +35,7 @@ import android.util.Slog; import android.util.SparseArray; import android.util.Xml; +import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.XmlUtils; import libcore.io.IoUtils; @@ -50,6 +51,7 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Map; +import java.util.Set; /** * Loads global system configuration info. @@ -209,6 +211,10 @@ public class SystemConfig { private final ArraySet<String> mBugreportWhitelistedPackages = new ArraySet<>(); + // Map of packagesNames to userTypes. Stored temporarily until cleared by UserManagerService(). + private ArrayMap<String, Set<String>> mPackageToUserTypeWhitelist = new ArrayMap<>(); + private ArrayMap<String, Set<String>> mPackageToUserTypeBlacklist = new ArrayMap<>(); + public static SystemConfig getInstance() { if (!isSystemProcess()) { Slog.wtf(TAG, "SystemConfig is being accessed by a process other than " @@ -359,7 +365,48 @@ public class SystemConfig { return mBugreportWhitelistedPackages; } + /** + * Gets map of packagesNames to userTypes, dictating on which user types each package should be + * initially installed, and then removes this map from SystemConfig. + * Called by UserManagerService when it is constructed. + */ + public ArrayMap<String, Set<String>> getAndClearPackageToUserTypeWhitelist() { + ArrayMap<String, Set<String>> r = mPackageToUserTypeWhitelist; + mPackageToUserTypeWhitelist = new ArrayMap<>(0); + return r; + } + + /** + * Gets map of packagesNames to userTypes, dictating on which user types each package should NOT + * be initially installed, even if they are whitelisted, and then removes this map from + * SystemConfig. + * Called by UserManagerService when it is constructed. + */ + public ArrayMap<String, Set<String>> getAndClearPackageToUserTypeBlacklist() { + ArrayMap<String, Set<String>> r = mPackageToUserTypeBlacklist; + mPackageToUserTypeBlacklist = new ArrayMap<>(0); + return r; + } + + /** + * Only use for testing. Do NOT use in production code. + * @param readPermissions false to create an empty SystemConfig; true to read the permissions. + */ + @VisibleForTesting + protected SystemConfig(boolean readPermissions) { + if (readPermissions) { + Slog.w(TAG, "Constructing a test SystemConfig"); + readAllPermissions(); + } else { + Slog.w(TAG, "Constructing an empty test SystemConfig"); + } + } + SystemConfig() { + readAllPermissions(); + } + + private void readAllPermissions() { // Read configuration from system readPermissions(Environment.buildPath( Environment.getRootDirectory(), "etc", "sysconfig"), ALLOW_ALL); @@ -419,7 +466,8 @@ public class SystemConfig { Environment.getSystemExtDirectory(), "etc", "permissions"), ALLOW_ALL); } - void readPermissions(File libraryDir, int permissionFlag) { + @VisibleForTesting + public void readPermissions(File libraryDir, int permissionFlag) { // Read permissions from given directory. if (!libraryDir.exists() || !libraryDir.isDirectory()) { if (permissionFlag == ALLOW_ALL) { @@ -954,6 +1002,11 @@ public class SystemConfig { } XmlUtils.skipCurrentTag(parser); } break; + case "install-in-user-type": { + // NB: We allow any directory permission to declare install-in-user-type. + readInstallInUserType(parser, + mPackageToUserTypeWhitelist, mPackageToUserTypeBlacklist); + } break; default: { Slog.w(TAG, "Tag " + name + " is unknown in " + permFile + " at " + parser.getPositionDescription()); @@ -1091,6 +1144,53 @@ public class SystemConfig { } } + private void readInstallInUserType(XmlPullParser parser, + Map<String, Set<String>> doInstallMap, + Map<String, Set<String>> nonInstallMap) + throws IOException, XmlPullParserException { + final String packageName = parser.getAttributeValue(null, "package"); + if (TextUtils.isEmpty(packageName)) { + Slog.w(TAG, "package is required for <install-in-user-type> in " + + parser.getPositionDescription()); + return; + } + + Set<String> userTypesYes = doInstallMap.get(packageName); + Set<String> userTypesNo = nonInstallMap.get(packageName); + final int depth = parser.getDepth(); + while (XmlUtils.nextElementWithin(parser, depth)) { + final String name = parser.getName(); + if ("install-in".equals(name)) { + final String userType = parser.getAttributeValue(null, "user-type"); + if (TextUtils.isEmpty(userType)) { + Slog.w(TAG, "user-type is required for <install-in-user-type> in " + + parser.getPositionDescription()); + continue; + } + if (userTypesYes == null) { + userTypesYes = new ArraySet<>(); + doInstallMap.put(packageName, userTypesYes); + } + userTypesYes.add(userType); + } else if ("do-not-install-in".equals(name)) { + final String userType = parser.getAttributeValue(null, "user-type"); + if (TextUtils.isEmpty(userType)) { + Slog.w(TAG, "user-type is required for <install-in-user-type> in " + + parser.getPositionDescription()); + continue; + } + if (userTypesNo == null) { + userTypesNo = new ArraySet<>(); + nonInstallMap.put(packageName, userTypesNo); + } + userTypesNo.add(userType); + } else { + Slog.w(TAG, "unrecognized tag in <install-in-user-type> in " + + parser.getPositionDescription()); + } + } + } + void readOemPermissions(XmlPullParser parser) throws IOException, XmlPullParserException { final String packageName = parser.getAttributeValue(null, "package"); if (TextUtils.isEmpty(packageName)) { diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 5fd53de9abc4..e3337b7f9ae5 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -2527,6 +2527,16 @@ will be locked. --> <bool name="config_multiuserDelayUserDataLocking">false</bool> + <!-- Whether to only install system packages on a user if they're whitelisted for that user + type. These are flags and can be freely combined. + 0 (0b000) - disable whitelist (install all system packages; no logging) + 1 (0b001) - enforce (only install system packages if they are whitelisted) + 2 (0b010) - log (log when a non-whitelisted package is run) + 4 (0b100) - treat any package not mentioned in the whitelist file as implicitly whitelisted + Note: This list must be kept current with PACKAGE_WHITELIST_MODE_PROP in + frameworks/base/services/core/java/com/android/server/pm/UserSystemPackageInstaller.java --> + <integer name="config_userTypePackageWhitelistMode">5</integer> <!-- 0b101 --> + <!-- Whether UI for multi user should be shown --> <bool name="config_enableMultiUserUI">false</bool> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 761e02fe5dad..3fe907c3b665 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -511,6 +511,7 @@ <java-symbol type="integer" name="config_multiuserMaximumUsers" /> <java-symbol type="integer" name="config_multiuserMaxRunningUsers" /> <java-symbol type="bool" name="config_multiuserDelayUserDataLocking" /> + <java-symbol type="integer" name="config_userTypePackageWhitelistMode"/> <java-symbol type="integer" name="config_safe_media_volume_index" /> <java-symbol type="integer" name="config_safe_media_volume_usb_mB" /> <java-symbol type="integer" name="config_mobile_mtu" /> diff --git a/data/etc/Android.bp b/data/etc/Android.bp index 4493f3a8dddc..976974aca87c 100644 --- a/data/etc/Android.bp +++ b/data/etc/Android.bp @@ -22,6 +22,12 @@ prebuilt_etc { } prebuilt_etc { + name: "preinstalled-packages-platform.xml", + sub_dir: "sysconfig", + src: "preinstalled-packages-platform.xml", +} + +prebuilt_etc { name: "hiddenapi-package-whitelist.xml", sub_dir: "sysconfig", src: "hiddenapi-package-whitelist.xml", diff --git a/data/etc/preinstalled-packages-platform.xml b/data/etc/preinstalled-packages-platform.xml new file mode 100644 index 000000000000..ccd8b5bb5347 --- /dev/null +++ b/data/etc/preinstalled-packages-platform.xml @@ -0,0 +1,90 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2019 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. +--> + +<!-- +This XML file declares which system packages should be initially installed for new users based on +the type of user. All system packages on the device should ideally have an entry in an xml file +(keys by its manifest name). + +Main user-types (every user will be at least one of these types) are: + SYSTEM (user 0) + FULL (any non-profile human user) + PROFILE (profile human user) + +Additional optional types are: GUEST, RESTRICTED, MANAGED_PROFILE, EPHEMERAL, DEMO + +The meaning of each of these user types is delineated by flags in +frameworks/base/core/java/android/content/pm/UserInfo.java. +See frameworks/base/services/core/java/com/android/server/pm/UserSystemPackageInstaller#getFlagsFromUserTypes + +The following three examples should cover most normal cases: + +1. For a system package to be pre-installed only in user 0: + + <install-in-user-type package="com.android.example"> + <install-in user-type="SYSTEM"> + </install-in-user-type> + + +2. For a system package to be pre-installed on all human users (e.g. a web browser), i.e. to be +installed on any user of type type FULL or PROFILE (since this covers all human users): + + <install-in-user-type package="com.android.example"> + <install-in user-type="FULL"> + <install-in user-type="PROFILE"> + </install-in-user-type> + + +3. For a system package to be pre-installed on all human users except for profile users (e.g. a +wallpaper app, since profiles cannot display wallpaper): + + <install-in-user-type package="com.android.example"> + <install-in user-type="FULL"> + </install-in-user-type> + + +Some system packages truly are required to be on all users, regardless of type, in which case use: + <install-in-user-type package="com.android.example"> + <install-in user-type="SYSTEM"> + <install-in user-type="FULL"> + <install-in user-type="PROFILE"> + </install-in-user-type> + +More fine-grained options are also available (see below). Additionally, packages can blacklist +user types. Blacklists override any whitelisting (in any file). +E.g. + <install-in-user-type package="com.android.example"> + <install-in user-type="FULL" /> + <do-not-install-in user-type="GUEST" /> + </install-in-user-type> + +If a user is of type FULL and GUEST, this package will NOT be installed, because the +'do-not-install-in' takes precedence over 'install-in'. + +The way that a device treats system packages that do not have any entry (for any user type) at all +is determined by the config resource value config_userTypePackageWhitelistMode. +See frameworks/base/core/res/res/values/config.xml#config_userTypePackageWhitelistMode. + +Changes to the whitelist during system updates can result in installing new system packages +to pre-existing users, but cannot uninstall system packages from pre-existing users. +--> +<config> + <install-in-user-type package="com.android.providers.settings"> + <install-in user-type="SYSTEM" /> + <install-in user-type="FULL" /> + <install-in user-type="PROFILE" /> + </install-in-user-type> +</config> diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 2a85c89fdccc..949478d33fef 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -137,7 +137,6 @@ import android.content.IntentSender; import android.content.IntentSender.SendIntentException; import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; -import android.content.pm.AppsQueryHelper; import android.content.pm.AuxiliaryResolveInfo; import android.content.pm.ChangedPackages; import android.content.pm.ComponentInfo; @@ -2473,54 +2472,21 @@ public class PackageManagerService extends IPackageManager.Stub PackageManagerService m = new PackageManagerService(injector, factoryTest, onlyCore); t.traceEnd(); // "create package manager" - m.enableSystemUserPackages(); + m.installWhitelistedSystemPackages(); ServiceManager.addService("package", m); final PackageManagerNative pmn = m.new PackageManagerNative(); ServiceManager.addService("package_native", pmn); return m; } - private void enableSystemUserPackages() { - if (!UserManager.isSplitSystemUser()) { - return; - } - // For system user, enable apps based on the following conditions: - // - app is whitelisted or belong to one of these groups: - // -- system app which has no launcher icons - // -- system app which has INTERACT_ACROSS_USERS permission - // -- system IME app - // - app is not in the blacklist - AppsQueryHelper queryHelper = new AppsQueryHelper(this); - Set<String> enableApps = new ArraySet<>(); - enableApps.addAll(queryHelper.queryApps(AppsQueryHelper.GET_NON_LAUNCHABLE_APPS - | AppsQueryHelper.GET_APPS_WITH_INTERACT_ACROSS_USERS_PERM - | AppsQueryHelper.GET_IMES, /* systemAppsOnly */ true, UserHandle.SYSTEM)); - ArraySet<String> wlApps = SystemConfig.getInstance().getSystemUserWhitelistedApps(); - enableApps.addAll(wlApps); - enableApps.addAll(queryHelper.queryApps(AppsQueryHelper.GET_REQUIRED_FOR_SYSTEM_USER, - /* systemAppsOnly */ false, UserHandle.SYSTEM)); - ArraySet<String> blApps = SystemConfig.getInstance().getSystemUserBlacklistedApps(); - enableApps.removeAll(blApps); - Log.i(TAG, "Applications installed for system user: " + enableApps); - List<String> allAps = queryHelper.queryApps(0, /* systemAppsOnly */ false, - UserHandle.SYSTEM); - final int allAppsSize = allAps.size(); + /** Install/uninstall system packages for all users based on their user-type, as applicable. */ + private void installWhitelistedSystemPackages() { synchronized (mLock) { - for (int i = 0; i < allAppsSize; i++) { - String pName = allAps.get(i); - PackageSetting pkgSetting = mSettings.mPackages.get(pName); - // Should not happen, but we shouldn't be failing if it does - if (pkgSetting == null) { - continue; - } - boolean install = enableApps.contains(pName); - if (pkgSetting.getInstalled(UserHandle.USER_SYSTEM) != install) { - Log.i(TAG, (install ? "Installing " : "Uninstalling ") + pName - + " for system user"); - pkgSetting.setInstalled(install, UserHandle.USER_SYSTEM); - } + final boolean scheduleWrite = mUserManager.installWhitelistedSystemPackages( + isFirstBoot(), isDeviceUpgrading()); + if (scheduleWrite) { + scheduleWritePackageRestrictionsLocked(UserHandle.USER_ALL); } - scheduleWritePackageRestrictionsLocked(UserHandle.USER_SYSTEM); } } @@ -22307,10 +22273,19 @@ public class PackageManagerService extends IPackageManager.Stub } } - /** Called by UserManagerService */ - void createNewUser(int userId, String[] disallowedPackages) { + /** + * Called by UserManagerService. + * + * @param installablePackages system packages that should be initially installed for this user, + * or {@code null} if all system packages should be installed + * @param disallowedPackages packages that should not be initially installed. Takes precedence + * over installablePackages. + */ + void createNewUser(int userId, @Nullable Set<String> installablePackages, + String[] disallowedPackages) { synchronized (mInstallLock) { - mSettings.createNewUserLI(this, mInstaller, userId, disallowedPackages); + mSettings.createNewUserLI(this, mInstaller, userId, + installablePackages, disallowedPackages); } synchronized (mLock) { scheduleWritePackageRestrictionsLocked(userId); @@ -23119,6 +23094,19 @@ public class PackageManagerService extends IPackageManager.Stub } @Override + public boolean setInstalled(PackageParser.Package pkg, @UserIdInt int userId, + boolean installed) { + synchronized (mLock) { + final PackageSetting ps = mSettings.mPackages.get(pkg.packageName); + if (ps.getInstalled(userId) != installed) { + ps.setInstalled(installed, userId); + return true; + } + return false; + } + } + + @Override public void requestInstantAppResolutionPhaseTwo(AuxiliaryResolveInfo responseObj, Intent origIntent, String resolvedType, String callingPackage, Bundle verificationBundle, int userId) { diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java index 1873a4ec98d9..4349ea75809c 100644 --- a/services/core/java/com/android/server/pm/Settings.java +++ b/services/core/java/com/android/server/pm/Settings.java @@ -4014,8 +4014,9 @@ public final class Settings { } } - void createNewUserLI(@NonNull PackageManagerService service, - @NonNull Installer installer, int userHandle, String[] disallowedPackages) { + void createNewUserLI(@NonNull PackageManagerService service, @NonNull Installer installer, + @UserIdInt int userHandle, @Nullable Set<String> installablePackages, + String[] disallowedPackages) { final TimingsTraceAndSlog t = new TimingsTraceAndSlog(TAG + "Timing", Trace.TRACE_TAG_PACKAGE_MANAGER); t.traceBegin("createNewUser-" + userHandle); @@ -4025,6 +4026,7 @@ public final class Settings { String[] seinfos; int[] targetSdkVersions; int packagesCount; + final boolean skipPackageWhitelist = installablePackages == null; synchronized (mLock) { Collection<PackageSetting> packages = mPackages.values(); packagesCount = packages.size(); @@ -4040,6 +4042,7 @@ public final class Settings { continue; } final boolean shouldInstall = ps.isSystem() && + (skipPackageWhitelist || installablePackages.contains(ps.name)) && !ArrayUtils.contains(disallowedPackages, ps.name) && !ps.pkg.applicationInfo.hiddenUntilInstalled; // Only system apps are initially installed. diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java index 9371c4473bb3..5f8670809bfb 100644 --- a/services/core/java/com/android/server/pm/UserManagerService.java +++ b/services/core/java/com/android/server/pm/UserManagerService.java @@ -250,6 +250,9 @@ public class UserManagerService extends IUserManager.Stub { private static final IBinder mUserRestriconToken = new Binder(); + /** Installs system packages based on user-type. */ + private final UserSystemPackageInstaller mSystemPackageInstaller; + /** * Internal non-parcelable wrapper for UserInfo that is not exposed to other system apps. */ @@ -550,6 +553,7 @@ public class UserManagerService extends IUserManager.Stub { readUserListLP(); sInstance = this; } + mSystemPackageInstaller = new UserSystemPackageInstaller(this); mLocalService = new LocalService(); LocalServices.addService(UserManagerInternal.class, mLocalService); mLockPatternUtils = new LockPatternUtils(mContext); @@ -2842,8 +2846,10 @@ public class UserManagerService extends IUserManager.Stub { StorageManager.FLAG_STORAGE_DE | StorageManager.FLAG_STORAGE_CE); t.traceEnd(); + final Set<String> installablePackages = + mSystemPackageInstaller.getInstallablePackagesForUserType(flags); t.traceBegin("PM.createNewUser"); - mPm.createNewUser(userId, disallowedPackages); + mPm.createNewUser(userId, installablePackages, disallowedPackages); t.traceEnd(); userInfo.partial = false; @@ -2877,6 +2883,11 @@ public class UserManagerService extends IUserManager.Stub { return userInfo; } + /** Install/uninstall system packages for all users based on their user-type, as applicable. */ + boolean installWhitelistedSystemPackages(boolean isFirstBoot, boolean isUpgrade) { + return mSystemPackageInstaller.installWhitelistedSystemPackages(isFirstBoot, isUpgrade); + } + @VisibleForTesting UserData putUserInfo(UserInfo userInfo) { final UserData userData = new UserData(); @@ -3863,6 +3874,10 @@ public class UserManagerService extends IUserManager.Stub { pw.println(" Is split-system user: " + UserManager.isSplitSystemUser()); pw.println(" Is headless-system mode: " + UserManager.isHeadlessSystemUserMode()); pw.println(" User version: " + mUserVersion); + + // Dump package whitelist + pw.println(); + mSystemPackageInstaller.dump(pw); } private static void dumpTimeAgo(PrintWriter pw, StringBuilder sb, long nowTime, long time) { diff --git a/services/core/java/com/android/server/pm/UserSystemPackageInstaller.java b/services/core/java/com/android/server/pm/UserSystemPackageInstaller.java new file mode 100644 index 000000000000..036d1e807f97 --- /dev/null +++ b/services/core/java/com/android/server/pm/UserSystemPackageInstaller.java @@ -0,0 +1,459 @@ +/* + * Copyright (C) 2019 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.server.pm; + +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.UserIdInt; +import android.content.pm.PackageManagerInternal; +import android.content.pm.PackageParser; +import android.content.pm.UserInfo; +import android.content.res.Resources; +import android.os.SystemProperties; +import android.os.UserHandle; +import android.util.ArrayMap; +import android.util.ArraySet; +import android.util.Slog; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.server.LocalServices; +import com.android.server.SystemConfig; + +import java.io.PrintWriter; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.Set; + +/** + * Responsible for un/installing system packages based on user type. + * + * <p>Uses the SystemConfig's install-in-user-type whitelist; + * see {@link SystemConfig#getAndClearPackageToUserTypeWhitelist} and + * {@link SystemConfig#getAndClearPackageToUserTypeBlacklist}. + * + * <p>If {@link #isEnforceMode()} is false, then all system packages are always installed for all + * users. The following applies when it is true. + * + * Any package can be in one of three states in the SystemConfig whitelist + * <ol> + * <li>Explicitly blacklisted for a particular user type</li> + * <li>Explicitly whitelisted for a particular user type</li> + * <li>Not mentioned at all, for any user type (neither whitelisted nor blacklisted)</li> + * </ol> + * Blacklisting always takes precedence - if a package is blacklisted for a particular user, + * it won't be installed on that type of user (even if it is also whitelisted for that user). + * Next comes whitelisting - if it is whitelisted for a particular user, it will be installed on + * that type of user (as long as it isn't blacklisted). + * Finally, if the package is not mentioned at all (i.e. neither whitelisted nor blacklisted for + * any user types) in the SystemConfig 'install-in-user-type' lists + * then: + * <ul> + * <li>If {@link #isImplicitWhitelistMode()}, the package is implicitly treated as whitelisted + * for all users</li> + * <li>Otherwise, the package is implicitly treated as blacklisted for all non-SYSTEM users</li> + * <li>Either way, for {@link UserHandle#USER_SYSTEM}, the package will be implicitly + * whitelisted so that it can be used for local development purposes.</li> + * </ul> + */ +class UserSystemPackageInstaller { + private static final String TAG = "UserManagerService"; + + /** + * System Property whether to only install system packages on a user if they're whitelisted for + * that user type. These are flags and can be freely combined. + * <ul> + * <li> 0 (0b000) - disable whitelist (install all system packages; no logging)</li> + * <li> 1 (0b001) - enforce (only install system packages if they are whitelisted)</li> + * <li> 2 (0b010) - log (log when a non-whitelisted package is run)</li> + * <li> 4 (0b100) - implicitly whitelist any package not mentioned in the whitelist</li> + * <li>-1 - use device default (as defined in res/res/values/config.xml)</li> + * </ul> + * Note: This list must be kept current with config_userTypePackageWhitelistMode in + * frameworks/base/core/res/res/values/config.xml + */ + static final String PACKAGE_WHITELIST_MODE_PROP = "persist.debug.user.package_whitelist_mode"; + static final int USER_TYPE_PACKAGE_WHITELIST_MODE_DISABLE = 0; + static final int USER_TYPE_PACKAGE_WHITELIST_MODE_ENFORCE = 0b001; + static final int USER_TYPE_PACKAGE_WHITELIST_MODE_LOG = 0b010; + static final int USER_TYPE_PACKAGE_WHITELIST_MODE_IMPLICIT_WHITELIST = 0b100; + static final int USER_TYPE_PACKAGE_WHITELIST_MODE_DEVICE_DEFAULT = -1; + + @IntDef(flag = true, prefix = "USER_TYPE_PACKAGE_WHITELIST_MODE_", value = { + USER_TYPE_PACKAGE_WHITELIST_MODE_DISABLE, + USER_TYPE_PACKAGE_WHITELIST_MODE_ENFORCE, + USER_TYPE_PACKAGE_WHITELIST_MODE_ENFORCE, + USER_TYPE_PACKAGE_WHITELIST_MODE_LOG, + USER_TYPE_PACKAGE_WHITELIST_MODE_IMPLICIT_WHITELIST, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface PackageWhitelistMode {} + + /** + * Maps system package manifest names to the user flags on which they should be initially + * installed. + * <p>Packages that are whitelisted, but then blacklisted so that they aren't to be installed on + * any user, are purposefully still present in this list. + */ + private final ArrayMap<String, Integer> mWhitelitsedPackagesForUserTypes; + + private final UserManagerService mUm; + + UserSystemPackageInstaller(UserManagerService ums) { + mUm = ums; + mWhitelitsedPackagesForUserTypes = + determineWhitelistedPackagesForUserTypes(SystemConfig.getInstance()); + } + + /** Constructor for testing purposes. */ + @VisibleForTesting + UserSystemPackageInstaller(UserManagerService ums, ArrayMap<String, Integer> whitelist) { + mUm = ums; + mWhitelitsedPackagesForUserTypes = whitelist; + } + + /** + * During OTAs and first boot, install/uninstall all system packages for all users based on the + * user's UserInfo flags and the SystemConfig whitelist. + * We do NOT uninstall packages during an OTA though. + * + * This is responsible for enforcing the whitelist for pre-existing users (i.e. USER_SYSTEM); + * enforcement for new users is done when they are created in UserManagerService.createUser(). + */ + boolean installWhitelistedSystemPackages(boolean isFirstBoot, boolean isUpgrade) { + final int mode = getWhitelistMode(); + checkWhitelistedSystemPackages(mode); + if (!isUpgrade && !isFirstBoot) { + return false; + } + Slog.i(TAG, "Reviewing whitelisted packages due to " + + (isFirstBoot ? "[firstBoot]" : "") + (isUpgrade ? "[upgrade]" : "")); + final PackageManagerInternal pmInt = LocalServices.getService(PackageManagerInternal.class); + // Install/uninstall system packages per user. + for (int userId : mUm.getUserIds()) { + final Set<String> userWhitelist = getInstallablePackagesForUserId(userId); + pmInt.forEachPackage(pkg -> { + if (!pkg.isSystem()) { + return; + } + final boolean install = + (userWhitelist == null || userWhitelist.contains(pkg.packageName)) + && !pkg.applicationInfo.hiddenUntilInstalled; + if (isUpgrade && !isFirstBoot && !install) { + return; // To be careful, we don’t uninstall apps during OTAs + } + final boolean changed = pmInt.setInstalled(pkg, userId, install); + if (changed) { + Slog.i(TAG, (install ? "Installed " : "Uninstalled ") + + pkg.packageName + " for user " + userId); + } + }); + } + return true; + } + + /** + * Checks whether the system packages and the mWhitelistedPackagesForUserTypes whitelist are + * in 1-to-1 correspondence. + */ + private void checkWhitelistedSystemPackages(@PackageWhitelistMode int mode) { + if (!isLogMode(mode) && !isEnforceMode(mode)) { + return; + } + Slog.v(TAG, "Checking that all system packages are whitelisted."); + final Set<String> allWhitelistedPackages = getWhitelistedSystemPackages(); + PackageManagerInternal pmInt = LocalServices.getService(PackageManagerInternal.class); + + // Check whether all whitelisted packages are indeed on the system. + for (String pkgName : allWhitelistedPackages) { + PackageParser.Package pkg = pmInt.getPackage(pkgName); + if (pkg == null) { + Slog.w(TAG, pkgName + " is whitelisted but not present."); + } else if (!pkg.isSystem()) { + Slog.w(TAG, pkgName + " is whitelisted and present but not a system package."); + } + } + + // Check whether all system packages are indeed whitelisted. + if (isImplicitWhitelistMode(mode) && !isLogMode(mode)) { + return; + } + final boolean doWtf = isEnforceMode(mode); + pmInt.forEachPackage(pkg -> { + if (pkg.isSystem() && !allWhitelistedPackages.contains(pkg.manifestPackageName)) { + final String msg = "System package " + pkg.manifestPackageName + + " is not whitelisted using 'install-in-user-type' in SystemConfig " + + "for any user types!"; + if (doWtf) { + Slog.wtf(TAG, msg); + } else { + Slog.e(TAG, msg); + } + } + }); + } + + /** Whether to only install system packages in new users for which they are whitelisted. */ + boolean isEnforceMode() { + return isEnforceMode(getWhitelistMode()); + } + + /** + * Whether to log a warning concerning potential problems with the user-type package whitelist. + */ + boolean isLogMode() { + return isLogMode(getWhitelistMode()); + } + + /** + * Whether to treat all packages that are not mentioned at all in the whitelist to be implicitly + * whitelisted for all users. + */ + boolean isImplicitWhitelistMode() { + return isImplicitWhitelistMode(getWhitelistMode()); + } + + /** See {@link #isEnforceMode()}. */ + private static boolean isEnforceMode(int whitelistMode) { + return (whitelistMode & USER_TYPE_PACKAGE_WHITELIST_MODE_ENFORCE) != 0; + } + + /** See {@link #isLogMode()}. */ + private static boolean isLogMode(int whitelistMode) { + return (whitelistMode & USER_TYPE_PACKAGE_WHITELIST_MODE_LOG) != 0; + } + + /** See {@link #isImplicitWhitelistMode()}. */ + private static boolean isImplicitWhitelistMode(int whitelistMode) { + return (whitelistMode & USER_TYPE_PACKAGE_WHITELIST_MODE_IMPLICIT_WHITELIST) != 0; + } + + /** Gets the PackageWhitelistMode for use of {@link #mWhitelitsedPackagesForUserTypes}. */ + private @PackageWhitelistMode int getWhitelistMode() { + final int runtimeMode = SystemProperties.getInt( + PACKAGE_WHITELIST_MODE_PROP, USER_TYPE_PACKAGE_WHITELIST_MODE_DEVICE_DEFAULT); + if (runtimeMode != USER_TYPE_PACKAGE_WHITELIST_MODE_DEVICE_DEFAULT) { + return runtimeMode; + } + return Resources.getSystem() + .getInteger(com.android.internal.R.integer.config_userTypePackageWhitelistMode); + } + + /** + * Gets the system packages names that should be installed on the given user. + * See {@link #getInstallablePackagesForUserType(int)}. + */ + private @Nullable Set<String> getInstallablePackagesForUserId(@UserIdInt int userId) { + return getInstallablePackagesForUserType(mUm.getUserInfo(userId).flags); + } + + /** + * Gets the system package names that should be installed on a user with the given flags, as + * determined by SystemConfig, the whitelist mode, and the apps actually on the device. + * Names are the {@link PackageParser.Package#packageName}, not necessarily the manifest names. + * + * Returns null if all system packages should be installed (due enforce-mode being off). + */ + @Nullable Set<String> getInstallablePackagesForUserType(int flags) { + final int mode = getWhitelistMode(); + if (!isEnforceMode(mode)) { + return null; + } + final boolean isSystemUser = (flags & UserInfo.FLAG_SYSTEM) != 0; + final boolean isImplicitWhitelistMode = isImplicitWhitelistMode(mode); + final Set<String> whitelistedPackages = getWhitelistedPackagesForUserType(flags); + + final Set<String> installPackages = new ArraySet<>(); + final PackageManagerInternal pmInt = LocalServices.getService(PackageManagerInternal.class); + pmInt.forEachPackage(pkg -> { + if (!pkg.isSystem()) { + return; + } + if (shouldInstallPackage(pkg, mWhitelitsedPackagesForUserTypes, + whitelistedPackages, isImplicitWhitelistMode, isSystemUser)) { + // Although the whitelist uses manifest names, this function returns packageNames. + installPackages.add(pkg.packageName); + } + }); + return installPackages; + } + + /** + * Returns whether the given system package should be installed on the given user, based on the + * the given whitelist of system packages. + * + * @param sysPkg the system package. Must be a system package; no verification for this is done. + * @param userTypeWhitelist map of package manifest names to user flags on which they should be + * installed + * @param userWhitelist set of package manifest names that should be installed on this + * particular user. This must be consistent with userTypeWhitelist, but is + * passed in separately to avoid repeatedly calculating it from + * userTypeWhitelist. + * @param isImplicitWhitelistMode whether non-mentioned packages are implicitly whitelisted. + * @param isSystemUser whether the user is USER_SYSTEM (which gets special treatment). + */ + @VisibleForTesting + static boolean shouldInstallPackage(PackageParser.Package sysPkg, + @NonNull ArrayMap<String, Integer> userTypeWhitelist, + @NonNull Set<String> userWhitelist, boolean isImplicitWhitelistMode, + boolean isSystemUser) { + + final String pkgName = sysPkg.manifestPackageName; + boolean install = (isImplicitWhitelistMode && !userTypeWhitelist.containsKey(pkgName)) + || userWhitelist.contains(pkgName); + + // For the purposes of local development, any package that isn't even mentioned in the + // whitelist at all is implicitly treated as whitelisted for the SYSTEM user. + if (!install && isSystemUser && !userTypeWhitelist.containsKey(pkgName)) { + install = true; + Slog.e(TAG, "System package " + pkgName + " is not mentioned " + + "in SystemConfig's 'install-in-user-type' but we are " + + "implicitly treating it as whitelisted for the SYSTEM user."); + } + return install; + } + + /** + * Gets the package manifest names that are whitelisted for a user with the given flags, + * as determined by SystemConfig. + */ + @VisibleForTesting + @NonNull Set<String> getWhitelistedPackagesForUserType(int flags) { + Set<String> installablePkgs = new ArraySet<>(mWhitelitsedPackagesForUserTypes.size()); + for (int i = 0; i < mWhitelitsedPackagesForUserTypes.size(); i++) { + String pkgName = mWhitelitsedPackagesForUserTypes.keyAt(i); + int whitelistedUserTypes = mWhitelitsedPackagesForUserTypes.valueAt(i); + if ((flags & whitelistedUserTypes) != 0) { + installablePkgs.add(pkgName); + } + } + return installablePkgs; + } + + /** + * Set of package manifest names that are included anywhere in the package-to-user-type + * whitelist, as determined by SystemConfig. + * + * Packages that are whitelisted, but then blacklisted so that they aren't to be installed on + * any user, are still present in this list, since that is a valid scenario (e.g. if an OEM + * completely blacklists an AOSP app). + */ + private Set<String> getWhitelistedSystemPackages() { + return mWhitelitsedPackagesForUserTypes.keySet(); + } + + /** + * Returns a map of package manifest names to the user flags on which it is to be installed. + * Also, clears this data from SystemConfig where it was stored inefficiently (and therefore + * should be called exactly once, even if the data isn't useful). + * + * Any system packages not present in this map should not even be on the device at all. + * To enforce this: + * <ul> + * <li>Illegal user types are ignored.</li> + * <li>Packages that never whitelisted at all (even if they are explicitly blacklisted) are + * ignored.</li> + * <li>Packages that are blacklisted whenever they are whitelisted will be stored with the + * flag 0 (since this is a valid scenario, e.g. if an OEM completely blacklists an AOSP + * app).</li> + * </ul> + */ + @VisibleForTesting + static ArrayMap<String, Integer> determineWhitelistedPackagesForUserTypes( + SystemConfig sysConfig) { + + final ArrayMap<String, Set<String>> whitelist = + sysConfig.getAndClearPackageToUserTypeWhitelist(); + // result maps packageName -> userTypes on which the package should be installed. + final ArrayMap<String, Integer> result = new ArrayMap<>(whitelist.size() + 1); + // First, do the whitelisted user types. + for (int i = 0; i < whitelist.size(); i++) { + final String pkgName = whitelist.keyAt(i); + final int flags = getFlagsFromUserTypes(whitelist.valueAt(i)); + if (flags != 0) { + result.put(pkgName, flags); + } + } + // Then, un-whitelist any blacklisted user types. + // TODO(b/141370854): Right now, the blacklist is actually just an 'unwhitelist'. Which + // direction we go depends on how we design user subtypes, which is still + // being designed. For now, unwhitelisting works for current use-cases. + final ArrayMap<String, Set<String>> blacklist = + sysConfig.getAndClearPackageToUserTypeBlacklist(); + for (int i = 0; i < blacklist.size(); i++) { + final String pkgName = blacklist.keyAt(i); + final int nonFlags = getFlagsFromUserTypes(blacklist.valueAt(i)); + final Integer flags = result.get(pkgName); + if (flags != null) { + result.put(pkgName, flags & ~nonFlags); + } + } + // Regardless of the whitelists/blacklists, ensure mandatory packages. + result.put("android", + UserInfo.FLAG_SYSTEM | UserInfo.FLAG_FULL | UserInfo.PROFILE_FLAGS_MASK); + return result; + } + + /** Converts a user types, as used in SystemConfig, to a UserInfo flag. */ + private static int getFlagsFromUserTypes(Iterable<String> userTypes) { + int flags = 0; + for (String type : userTypes) { + switch (type) { + case "GUEST": + flags |= UserInfo.FLAG_GUEST; + break; + case "RESTRICTED": + flags |= UserInfo.FLAG_RESTRICTED; + break; + case "MANAGED_PROFILE": + flags |= UserInfo.FLAG_MANAGED_PROFILE; + break; + case "EPHEMERAL": + flags |= UserInfo.FLAG_EPHEMERAL; + break; + case "DEMO": + flags |= UserInfo.FLAG_DEMO; + break; + case "FULL": + flags |= UserInfo.FLAG_FULL; + break; + case "SYSTEM": + flags |= UserInfo.FLAG_SYSTEM; + break; + case "PROFILE": + flags |= UserInfo.PROFILE_FLAGS_MASK; + break; + default: + Slog.w(TAG, "SystemConfig contained an invalid user type: " + type); + break; + // Other UserInfo flags are forbidden. + // In particular, FLAG_INITIALIZED, FLAG_DISABLED, FLAG_QUIET_MODE are inapplicable. + // The following are invalid now, but are reconsiderable: FLAG_PRIMARY, FLAG_ADMIN. + } + } + return flags; + } + + void dump(PrintWriter pw) { + for (int i = 0; i < mWhitelitsedPackagesForUserTypes.size(); i++) { + final String pkgName = mWhitelitsedPackagesForUserTypes.keyAt(i); + final String whitelistedUserTypes = + UserInfo.flagsToString(mWhitelitsedPackagesForUserTypes.valueAt(i)); + pw.println(" " + pkgName + ": " + whitelistedUserTypes); + } + } +} diff --git a/services/tests/servicestests/src/com/android/server/SystemConfigTest.java b/services/tests/servicestests/src/com/android/server/SystemConfigTest.java new file mode 100644 index 000000000000..ff03391ea031 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/SystemConfigTest.java @@ -0,0 +1,180 @@ +/* + * Copyright (C) 2019 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.server; + +import static org.junit.Assert.assertEquals; + +import android.util.ArrayMap; +import android.util.ArraySet; +import android.util.Log; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import org.junit.runner.RunWith; + +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.util.Arrays; +import java.util.Map; +import java.util.Scanner; +import java.util.Set; + +/** + * Tests for {@link SystemConfig}. + * + * Build/Install/Run: + * atest FrameworksServicesTests:SystemConfigTest + */ +@RunWith(AndroidJUnit4.class) +@SmallTest +public class SystemConfigTest { + private static final String LOG_TAG = "SystemConfigTest"; + + private SystemConfig mSysConfig; + + @Rule public TemporaryFolder mTemporaryFolder = new TemporaryFolder(); + + @Before + public void setUp() throws Exception { + mSysConfig = new SystemConfigTestClass(); + } + + /** + * Subclass of SystemConfig without running the constructor. + */ + private class SystemConfigTestClass extends SystemConfig { + SystemConfigTestClass() { + super(false); + } + } + + /** + * Tests that readPermissions works correctly for the tag: install-in-user-type + */ + @Test + public void testInstallInUserType() throws Exception { + final String contents1 = + "<permissions>\n" + + " <install-in-user-type package=\"com.android.package1\">\n" + + " <install-in user-type=\"FULL\" />\n" + + " <install-in user-type=\"PROFILE\" />\n" + + " </install-in-user-type>\n" + + " <install-in-user-type package=\"com.android.package2\">\n" + + " <install-in user-type=\"FULL\" />\n" + + " <install-in user-type=\"PROFILE\" />\n" + + " <do-not-install-in user-type=\"GUEST\" />\n" + + " </install-in-user-type>\n" + + "</permissions>"; + + final String contents2 = + "<permissions>\n" + + " <install-in-user-type package=\"com.android.package2\">\n" + + " <install-in user-type=\"SYSTEM\" />\n" + + " <do-not-install-in user-type=\"PROFILE\" />\n" + + " </install-in-user-type>\n" + + "</permissions>"; + + final String contents3 = + "<permissions>\n" + + " <install-in-user-type package=\"com.android.package2\">\n" + + " <install-in invalid-attribute=\"ADMIN\" />\n" // Ignore invalid attribute + + " </install-in-user-type>\n" + + " <install-in-user-type package=\"com.android.package2\">\n" + + " <install-in user-type=\"RESTRICTED\" />\n" // Valid + + " </install-in-user-type>\n" + + " <install-in-user-type>\n" // Ignored since missing package name + + " <install-in user-type=\"ADMIN\" />\n" + + " </install-in-user-type>\n" + + "</permissions>"; + + Map<String, Set<String>> expectedWhite = new ArrayMap<>(); + expectedWhite.put("com.android.package1", + new ArraySet<>(Arrays.asList("FULL", "PROFILE"))); + expectedWhite.put("com.android.package2", + new ArraySet<>(Arrays.asList("FULL", "PROFILE", "RESTRICTED", "SYSTEM"))); + + Map<String, Set<String>> expectedBlack = new ArrayMap<>(); + expectedBlack.put("com.android.package2", + new ArraySet<>(Arrays.asList("GUEST", "PROFILE"))); + + final File folder1 = createTempSubfolder("folder1"); + createTempFile(folder1, "permFile1.xml", contents1); + + final File folder2 = createTempSubfolder("folder2"); + createTempFile(folder2, "permFile2.xml", contents2); + + // Also, make a third file, but with the name folder1/permFile2.xml, to prove no conflicts. + createTempFile(folder1, "permFile2.xml", contents3); + + mSysConfig.readPermissions(folder1, /* No permission needed anyway */ 0); + mSysConfig.readPermissions(folder2, /* No permission needed anyway */ 0); + + Map<String, Set<String>> actualWhite = mSysConfig.getAndClearPackageToUserTypeWhitelist(); + Map<String, Set<String>> actualBlack = mSysConfig.getAndClearPackageToUserTypeBlacklist(); + + assertEquals("Whitelist was not cleared", 0, + mSysConfig.getAndClearPackageToUserTypeWhitelist().size()); + assertEquals("Blacklist was not cleared", 0, + mSysConfig.getAndClearPackageToUserTypeBlacklist().size()); + + assertEquals("Incorrect whitelist.", expectedWhite, actualWhite); + assertEquals("Incorrect blacklist", expectedBlack, actualBlack); + } + + /** + * Creates folderName/fileName in the mTemporaryFolder and fills it with the contents. + * @param folderName subdirectory of mTemporaryFolder to put the file, creating if needed + * @return the folder + */ + private File createTempSubfolder(String folderName) + throws IOException { + File folder = new File(mTemporaryFolder.getRoot(), folderName); + folder.mkdir(); + return folder; + } + + /** + * Creates folderName/fileName in the mTemporaryFolder and fills it with the contents. + * @param folder pre-existing subdirectory of mTemporaryFolder to put the file + * @param fileName name of the file (e.g. filename.xml) to create + * @param contents contents to write to the file + * @return the folder containing the newly created file (not the file itself!) + */ + private File createTempFile(File folder, String fileName, String contents) + throws IOException { + File file = new File(folder, fileName); + BufferedWriter bw = new BufferedWriter(new FileWriter(file)); + bw.write(contents); + bw.close(); + + // Print to logcat for test debugging. + Log.d(LOG_TAG, "Contents of file " + file.getAbsolutePath()); + Scanner input = new Scanner(file); + while (input.hasNextLine()) { + Log.d(LOG_TAG, input.nextLine()); + } + + return folder; + } +} diff --git a/services/tests/servicestests/src/com/android/server/pm/UserSystemPackageInstallerTest.java b/services/tests/servicestests/src/com/android/server/pm/UserSystemPackageInstallerTest.java new file mode 100644 index 000000000000..f0b0328ff7d4 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/pm/UserSystemPackageInstallerTest.java @@ -0,0 +1,396 @@ +/* + * Copyright (C) 2019 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.server.pm; + +import static android.content.pm.UserInfo.FLAG_FULL; +import static android.content.pm.UserInfo.FLAG_GUEST; +import static android.content.pm.UserInfo.FLAG_MANAGED_PROFILE; +import static android.content.pm.UserInfo.FLAG_SYSTEM; + +import static com.android.server.pm.UserSystemPackageInstaller.PACKAGE_WHITELIST_MODE_PROP; +import static com.android.server.pm.UserSystemPackageInstaller.USER_TYPE_PACKAGE_WHITELIST_MODE_DEVICE_DEFAULT; +import static com.android.server.pm.UserSystemPackageInstaller.USER_TYPE_PACKAGE_WHITELIST_MODE_DISABLE; +import static com.android.server.pm.UserSystemPackageInstaller.USER_TYPE_PACKAGE_WHITELIST_MODE_ENFORCE; +import static com.android.server.pm.UserSystemPackageInstaller.USER_TYPE_PACKAGE_WHITELIST_MODE_IMPLICIT_WHITELIST; +import static com.android.server.pm.UserSystemPackageInstaller.USER_TYPE_PACKAGE_WHITELIST_MODE_LOG; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import android.content.Context; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.content.pm.PackageParser; +import android.content.pm.UserInfo; +import android.os.Looper; +import android.os.SystemProperties; +import android.os.UserManager; +import android.os.UserManagerInternal; +import android.support.test.uiautomator.UiDevice; +import android.util.ArrayMap; +import android.util.ArraySet; +import android.util.Log; + +import androidx.test.InstrumentationRegistry; +import androidx.test.filters.MediumTest; +import androidx.test.runner.AndroidJUnit4; + +import com.android.server.LocalServices; +import com.android.server.SystemConfig; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Set; + +/** + * Tests for UserSystemPackageInstaller. + * + * <p>Run with:<pre> + * atest com.android.server.pm.UserSystemPackageInstallerTest + * </pre> + */ +@RunWith(AndroidJUnit4.class) +@MediumTest +public class UserSystemPackageInstallerTest { + private static final String TAG = "UserSystemPackageInstallerTest"; + + private UserSystemPackageInstaller mUserSystemPackageInstaller; + + private Context mContext; + + /** Any users created during this test, for them to be removed when it's done. */ + private final List<Integer> mRemoveUsers = new ArrayList<>(); + /** Original value of PACKAGE_WHITELIST_MODE_PROP before the test, to reset at end. */ + private final int mOriginalWhitelistMode = SystemProperties.getInt( + PACKAGE_WHITELIST_MODE_PROP, USER_TYPE_PACKAGE_WHITELIST_MODE_DEVICE_DEFAULT); + + @Before + public void setup() { + // Currently UserManagerService cannot be instantiated twice inside a VM without a cleanup + // TODO: Remove once UMS supports proper dependency injection + if (Looper.myLooper() == null) { + Looper.prepare(); + } + LocalServices.removeServiceForTest(UserManagerInternal.class); + UserManagerService ums = new UserManagerService(InstrumentationRegistry.getContext()); + + mUserSystemPackageInstaller = new UserSystemPackageInstaller(ums); + mContext = InstrumentationRegistry.getTargetContext(); + } + + @After + public void tearDown() { + UserManager um = UserManager.get(mContext); + for (int userId : mRemoveUsers) { + um.removeUser(userId); + } + setUserTypePackageWhitelistMode(mOriginalWhitelistMode); + } + + /** + * Subclass of SystemConfig without running the constructor. + */ + private class SystemConfigTestClass extends SystemConfig { + SystemConfigTestClass(boolean readPermissions) { + super(readPermissions); + } + } + + /** + * Test that determineWhitelistedPackagesForUserTypes reads SystemConfig information properly. + */ + @Test + public void testDetermineWhitelistedPackagesForUserTypes() { + SystemConfig sysConfig = new SystemConfigTestClass(false) { + @Override + public ArrayMap<String, Set<String>> getAndClearPackageToUserTypeWhitelist() { + ArrayMap<String, Set<String>> r = new ArrayMap<>(); + r.put("com.android.package1", new ArraySet<>(Arrays.asList( + "PROFILE", "SYSTEM", "GUEST", "FULL", "invalid-garbage1"))); + r.put("com.android.package2", new ArraySet<>(Arrays.asList( + "MANAGED_PROFILE"))); + return r; + } + + @Override + public ArrayMap<String, Set<String>> getAndClearPackageToUserTypeBlacklist() { + ArrayMap<String, Set<String>> r = new ArrayMap<>(); + r.put("com.android.package1", new ArraySet<>(Arrays.asList( + "FULL", "RESTRICTED", "invalid-garbage2"))); + return r; + } + }; + + final ArrayMap<String, Integer> expectedOutput = getNewPackageToWhitelistedFlagsMap(); + expectedOutput.put("com.android.package1", + UserInfo.PROFILE_FLAGS_MASK | FLAG_SYSTEM | FLAG_GUEST); + expectedOutput.put("com.android.package2", + UserInfo.FLAG_MANAGED_PROFILE); + + final ArrayMap<String, Integer> actualOutput = + mUserSystemPackageInstaller.determineWhitelistedPackagesForUserTypes(sysConfig); + + assertEquals("Incorrect package-to-user mapping.", expectedOutput, actualOutput); + } + + /** + * Test that determineWhitelistedPackagesForUserTypes does not include packages that were never + * whitelisted properly, but does include packages that were whitelisted but then blacklisted. + */ + @Test + public void testDetermineWhitelistedPackagesForUserTypes_noNetWhitelisting() { + SystemConfig sysConfig = new SystemConfigTestClass(false) { + @Override + public ArrayMap<String, Set<String>> getAndClearPackageToUserTypeWhitelist() { + ArrayMap<String, Set<String>> r = new ArrayMap<>(); + r.put("com.android.package1", new ArraySet<>(Arrays.asList("invalid1"))); + // com.android.package2 has no whitelisting + r.put("com.android.package3", new ArraySet<>(Arrays.asList("PROFILE", "FULL"))); + r.put("com.android.package4", new ArraySet<>(Arrays.asList("PROFILE"))); + r.put("com.android.package5", new ArraySet<>()); + // com.android.package6 has no whitelisting + return r; + } + + @Override + public ArrayMap<String, Set<String>> getAndClearPackageToUserTypeBlacklist() { + ArrayMap<String, Set<String>> r = new ArrayMap<>(); + // com.android.package1 has no blacklisting + r.put("com.android.package2", new ArraySet<>(Arrays.asList("FULL"))); + r.put("com.android.package3", new ArraySet<>(Arrays.asList("PROFILE", "FULL"))); + r.put("com.android.package4", new ArraySet<>(Arrays.asList("PROFILE", "invalid4"))); + // com.android.package5 has no blacklisting + r.put("com.android.package6", new ArraySet<>(Arrays.asList("invalid6"))); + return r; + } + }; + + final ArrayMap<String, Integer> expectedOutput = getNewPackageToWhitelistedFlagsMap(); + expectedOutput.put("com.android.package3", 0); + expectedOutput.put("com.android.package4", 0); + + final ArrayMap<String, Integer> actualOutput = + mUserSystemPackageInstaller.determineWhitelistedPackagesForUserTypes(sysConfig); + + assertEquals("Incorrect package-to-user mapping.", expectedOutput, actualOutput); + } + + /** + * Tests that shouldInstallPackage correctly determines which packages should be installed. + */ + @Test + public void testShouldInstallPackage() { + final String packageName1 = "pkg1"; // whitelisted + final String packageName2 = "pkg2"; // whitelisted and blacklisted + final String packageName3 = "pkg3"; // whitelisted for a different user type + final String packageName4 = "pkg4"; // not whitelisted nor blacklisted at all + + final ArrayMap<String, Integer> pkgFlgMap = new ArrayMap<>(); // Whitelist: pkgs per flags + pkgFlgMap.put(packageName1, FLAG_FULL); + pkgFlgMap.put(packageName2, 0); + pkgFlgMap.put(packageName3, FLAG_MANAGED_PROFILE); + + // Whitelist of pkgs for this specific user, i.e. subset of pkgFlagMap for this user. + final Set<String> userWhitelist = new ArraySet<>(); + userWhitelist.add(packageName1); + + final UserSystemPackageInstaller uspi = new UserSystemPackageInstaller(null, pkgFlgMap); + + final PackageParser.Package pkg1 = new PackageParser.Package(packageName1); + final PackageParser.Package pkg2 = new PackageParser.Package(packageName2); + final PackageParser.Package pkg3 = new PackageParser.Package(packageName3); + final PackageParser.Package pkg4 = new PackageParser.Package(packageName4); + + // No implicit whitelist, so only install pkg1. + boolean implicit = false; + boolean isSysUser = false; + assertTrue(uspi.shouldInstallPackage(pkg1, pkgFlgMap, userWhitelist, implicit, isSysUser)); + assertFalse(uspi.shouldInstallPackage(pkg2, pkgFlgMap, userWhitelist, implicit, isSysUser)); + assertFalse(uspi.shouldInstallPackage(pkg3, pkgFlgMap, userWhitelist, implicit, isSysUser)); + assertFalse(uspi.shouldInstallPackage(pkg4, pkgFlgMap, userWhitelist, implicit, isSysUser)); + + // Use implicit whitelist, so install pkg1 and pkg4 + implicit = true; + isSysUser = false; + assertTrue(uspi.shouldInstallPackage(pkg1, pkgFlgMap, userWhitelist, implicit, isSysUser)); + assertFalse(uspi.shouldInstallPackage(pkg2, pkgFlgMap, userWhitelist, implicit, isSysUser)); + assertFalse(uspi.shouldInstallPackage(pkg3, pkgFlgMap, userWhitelist, implicit, isSysUser)); + assertTrue(uspi.shouldInstallPackage(pkg4, pkgFlgMap, userWhitelist, implicit, isSysUser)); + + // For user 0 specifically, we always implicitly whitelist. + implicit = false; + isSysUser = true; + assertTrue(uspi.shouldInstallPackage(pkg1, pkgFlgMap, userWhitelist, implicit, isSysUser)); + assertFalse(uspi.shouldInstallPackage(pkg2, pkgFlgMap, userWhitelist, implicit, isSysUser)); + assertFalse(uspi.shouldInstallPackage(pkg3, pkgFlgMap, userWhitelist, implicit, isSysUser)); + assertTrue(uspi.shouldInstallPackage(pkg4, pkgFlgMap, userWhitelist, implicit, isSysUser)); + } + + /** + * Tests that getWhitelistedPackagesForUserType works properly, assuming that + * mWhitelistedPackagesForUserTypes (i.e. determineWhitelistedPackagesForUserTypes) is correct. + */ + @Test + public void testGetWhitelistedPackagesForUserType() { + final String packageName1 = "pkg1"; // whitelisted for FULL + final String packageName2 = "pkg2"; // blacklisted whenever whitelisted + final String packageName3 = "pkg3"; // whitelisted for SYSTEM + final String packageName4 = "pkg4"; // whitelisted for FULL + + final ArrayMap<String, Integer> pkgFlagMap = new ArrayMap<>(); // Whitelist: pkgs per flags + pkgFlagMap.put(packageName1, FLAG_FULL); + pkgFlagMap.put(packageName2, 0); + pkgFlagMap.put(packageName3, FLAG_SYSTEM); + pkgFlagMap.put(packageName4, FLAG_FULL); + + // Whitelist of pkgs for this specific user, i.e. subset of pkgFlagMap for this user. + final Set<String> expectedUserWhitelist = new ArraySet<>(); + expectedUserWhitelist.add(packageName1); + + UserSystemPackageInstaller uspi = new UserSystemPackageInstaller(null, pkgFlagMap); + + Set<String> output = uspi.getWhitelistedPackagesForUserType(FLAG_FULL); + assertEquals("Whitelist for FULL is the wrong size", 2, output.size()); + assertTrue("Whitelist for FULL doesn't contain pkg1", output.contains(packageName1)); + assertTrue("Whitelist for FULL doesn't contain pkg4", output.contains(packageName4)); + + output = uspi.getWhitelistedPackagesForUserType(FLAG_SYSTEM); + assertEquals("Whitelist for SYSTEM is the wrong size", 1, output.size()); + assertTrue("Whitelist for SYSTEM doesn't contain pkg1", output.contains(packageName3)); + } + + /** + * Test that a newly created FULL user has the expected system packages. + * + * Assumes that SystemConfig and UserManagerService.determineWhitelistedPackagesForUserTypes + * work correctly (they are tested separately). + */ + @Test + public void testPackagesForCreateUser_full() { + final int userFlags = UserInfo.FLAG_FULL; + setUserTypePackageWhitelistMode(USER_TYPE_PACKAGE_WHITELIST_MODE_ENFORCE); + PackageManager pm = mContext.getPackageManager(); + + final SystemConfig sysConfig = new SystemConfigTestClass(true); + final ArrayMap<String, Integer> packageMap = + mUserSystemPackageInstaller.determineWhitelistedPackagesForUserTypes(sysConfig); + final Set<String> expectedPackages = new ArraySet<>(packageMap.size()); + for (int i = 0; i < packageMap.size(); i++) { + if ((userFlags & packageMap.valueAt(i)) != 0) { + expectedPackages.add(packageMap.keyAt(i)); + } + } + + final UserManager um = UserManager.get(mContext); + final UserInfo user = um.createUser("Test User", userFlags); + assertNotNull(user); + mRemoveUsers.add(user.id); + + final List<PackageInfo> packageInfos = pm.getInstalledPackagesAsUser( + PackageManager.MATCH_SYSTEM_ONLY + | PackageManager.MATCH_DISABLED_COMPONENTS + | PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS, + user.id); + final Set<String> actualPackages = new ArraySet<>(packageInfos.size()); + for (PackageInfo p : packageInfos) { + actualPackages.add(p.packageName); + } + checkPackageDifferences(expectedPackages, actualPackages); + } + + /** Asserts that actual is a subset of expected. */ + private void checkPackageDifferences(Set<String> expected, Set<String> actual) { + final Set<String> uniqueToExpected = new ArraySet<>(expected); + uniqueToExpected.removeAll(actual); + final Set<String> uniqueToActual = new ArraySet<>(actual); + uniqueToActual.removeAll(expected); + + Log.v(TAG, "Expected list uniquely has " + uniqueToExpected); + Log.v(TAG, "Actual list uniquely has " + uniqueToActual); + + assertTrue("User's system packages includes non-whitelisted packages: " + uniqueToActual, + uniqueToActual.isEmpty()); + } + + /** + * Test that setEnableUserTypePackageWhitelist() has the correct effect. + */ + @Test + public void testSetWhitelistEnabledMode() { + setUserTypePackageWhitelistMode(USER_TYPE_PACKAGE_WHITELIST_MODE_DISABLE); + assertFalse(mUserSystemPackageInstaller.isLogMode()); + assertFalse(mUserSystemPackageInstaller.isEnforceMode()); + assertFalse(mUserSystemPackageInstaller.isImplicitWhitelistMode()); + + setUserTypePackageWhitelistMode(USER_TYPE_PACKAGE_WHITELIST_MODE_LOG); + assertTrue(mUserSystemPackageInstaller.isLogMode()); + assertFalse(mUserSystemPackageInstaller.isEnforceMode()); + assertFalse(mUserSystemPackageInstaller.isImplicitWhitelistMode()); + + setUserTypePackageWhitelistMode(USER_TYPE_PACKAGE_WHITELIST_MODE_ENFORCE); + assertFalse(mUserSystemPackageInstaller.isLogMode()); + assertTrue(mUserSystemPackageInstaller.isEnforceMode()); + assertFalse(mUserSystemPackageInstaller.isImplicitWhitelistMode()); + + setUserTypePackageWhitelistMode(USER_TYPE_PACKAGE_WHITELIST_MODE_IMPLICIT_WHITELIST); + assertFalse(mUserSystemPackageInstaller.isLogMode()); + assertFalse(mUserSystemPackageInstaller.isEnforceMode()); + assertTrue(mUserSystemPackageInstaller.isImplicitWhitelistMode()); + + setUserTypePackageWhitelistMode( + USER_TYPE_PACKAGE_WHITELIST_MODE_LOG | USER_TYPE_PACKAGE_WHITELIST_MODE_ENFORCE); + assertTrue(mUserSystemPackageInstaller.isLogMode()); + assertTrue(mUserSystemPackageInstaller.isEnforceMode()); + assertFalse(mUserSystemPackageInstaller.isImplicitWhitelistMode()); + + setUserTypePackageWhitelistMode(USER_TYPE_PACKAGE_WHITELIST_MODE_IMPLICIT_WHITELIST + | USER_TYPE_PACKAGE_WHITELIST_MODE_ENFORCE); + assertFalse(mUserSystemPackageInstaller.isLogMode()); + assertTrue(mUserSystemPackageInstaller.isEnforceMode()); + assertTrue(mUserSystemPackageInstaller.isImplicitWhitelistMode()); + } + + /** Sets the whitelist mode to the desired value via adb's setprop. */ + private void setUserTypePackageWhitelistMode(int mode) { + UiDevice mUiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()); + try { + String result = mUiDevice.executeShellCommand(String.format("setprop %s %d", + PACKAGE_WHITELIST_MODE_PROP, mode)); + assertFalse("Failed to set sysprop " + PACKAGE_WHITELIST_MODE_PROP + ": " + result, + result != null && result.contains("Failed")); + } catch (IOException e) { + fail("Failed to set sysprop " + PACKAGE_WHITELIST_MODE_PROP + ":\n" + e); + } + } + + private ArrayMap<String, Integer> getNewPackageToWhitelistedFlagsMap() { + final ArrayMap<String, Integer> pkgFlagMap = new ArrayMap<>(); + // "android" is always treated as whitelisted, regardless of the xml file. + pkgFlagMap.put("android", FLAG_SYSTEM | UserInfo.FLAG_FULL | UserInfo.PROFILE_FLAGS_MASK); + return pkgFlagMap; + } +} |