Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 04d7ae5c authored by Bookatz's avatar Bookatz
Browse files

Whitelist packages for user types

Creates a new SystemConfig xml entry which allows a device to whitelist
system packages to be installed on users when they are created, based on
the type of user.

System packages will be installed on users when they are created, or
during OTAs, based on this whitelist. The whitelist can be
enabled/disabled via a Config resource.

For any user type, system packages can be whitelisted or blacklisted.
If it is both (for the same user type), the blacklist takes priority.
If it is neither, it won't be installed (since it isn't whitelisted).

If a system package isn't mentioned in the whitelist file at all, for
any user, then its behaviour depends on the Config resource value, which
can optionally implicitly whitelist all such apps on all users.

For now, the list is mostly empty and the default config is set to be
enabled but implicitly whitelist all system packages that are not
mentioned.

Test: atest FrameworksServicesTests:SystemConfigTest
Test: atest com.android.server.pm.UserManagerServicePackageWhitelistTest
Test: manually test user 0 by flashall -w and checking packages
Test: manually test OTA by setting setprop persist.pm.mock-upgrade 1
Bug: 134605778

Change-Id: Ia098c1f597f66a1c946cfcc9b7771c25e8ceabf7
parent 679a4248
Loading
Loading
Loading
Loading
+1 −0
Original line number Original line Diff line number Diff line
@@ -22,6 +22,7 @@
    <uses-permission android:name="android.permission.INSTALL_PACKAGES" />
    <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" />
    <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" />
    <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" />
    <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />


    <application>
    <application>
        <uses-library android:name="android.test.runner" />
        <uses-library android:name="android.test.runner" />
+77 −0
Original line number Original line Diff line number Diff line
@@ -38,8 +38,10 @@ import android.os.Bundle;
import android.os.IBinder;
import android.os.IBinder;
import android.os.IProgressListener;
import android.os.IProgressListener;
import android.os.RemoteException;
import android.os.RemoteException;
import android.os.SystemProperties;
import android.os.UserHandle;
import android.os.UserHandle;
import android.os.UserManager;
import android.os.UserManager;
import android.perftests.utils.ShellHelper;
import android.util.Log;
import android.util.Log;
import android.view.WindowManagerGlobal;
import android.view.WindowManagerGlobal;


@@ -85,6 +87,14 @@ public class UserLifecycleTests {


    private static final String DUMMY_PACKAGE_NAME = "perftests.multiuser.apps.dummyapp";
    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 UserManager mUm;
    private ActivityManager mAm;
    private ActivityManager mAm;
    private IActivityManager mIam;
    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. */
    /** Creates a new user, returning its userId. */
    private int createUserNoFlags() {
    private int createUserNoFlags() {
        return createUserWithFlags(/* flags= */ 0);
        return createUserWithFlags(/* flags= */ 0);
@@ -458,6 +517,10 @@ public class UserLifecycleTests {
    private int createManagedProfile() {
    private int createManagedProfile() {
        final UserInfo userInfo = mUm.createProfileForUser("TestProfile",
        final UserInfo userInfo = mUm.createProfileForUser("TestProfile",
                UserInfo.FLAG_MANAGED_PROFILE, mAm.getCurrentUser());
                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);
        mUsersToRemove.add(userInfo.id);
        return 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) {
    private void removeUser(int userId) {
        try {
        try {
            mUm.removeUser(userId);
            mUm.removeUser(userId);
+11 −0
Original line number Original line Diff line number Diff line
@@ -307,6 +307,17 @@ public abstract class PackageManagerInternal {
     */
     */
    public abstract String getNameForUid(int uid);
    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.
     * Request to perform the second phase of ephemeral resolution.
     * @param responseObj The response of the first phase of ephemeral resolution
     * @param responseObj The response of the first phase of ephemeral resolution
+7 −0
Original line number Original line Diff line number Diff line
@@ -125,6 +125,13 @@ public class UserInfo implements Parcelable {
     */
     */
    public static final int FLAG_SYSTEM = 0x00000800;
    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
     * @hide
     */
     */
+101 −1
Original line number Original line Diff line number Diff line
@@ -35,6 +35,7 @@ import android.util.Slog;
import android.util.SparseArray;
import android.util.SparseArray;
import android.util.Xml;
import android.util.Xml;


import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.XmlUtils;
import com.android.internal.util.XmlUtils;


import libcore.io.IoUtils;
import libcore.io.IoUtils;
@@ -50,6 +51,7 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.Collections;
import java.util.List;
import java.util.List;
import java.util.Map;
import java.util.Map;
import java.util.Set;


/**
/**
 * Loads global system configuration info.
 * Loads global system configuration info.
@@ -209,6 +211,10 @@ public class SystemConfig {


    private final ArraySet<String> mBugreportWhitelistedPackages = new ArraySet<>();
    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() {
    public static SystemConfig getInstance() {
        if (!isSystemProcess()) {
        if (!isSystemProcess()) {
            Slog.wtf(TAG, "SystemConfig is being accessed by a process other than "
            Slog.wtf(TAG, "SystemConfig is being accessed by a process other than "
@@ -359,7 +365,48 @@ public class SystemConfig {
        return mBugreportWhitelistedPackages;
        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() {
    SystemConfig() {
        readAllPermissions();
    }

    private void readAllPermissions() {
        // Read configuration from system
        // Read configuration from system
        readPermissions(Environment.buildPath(
        readPermissions(Environment.buildPath(
                Environment.getRootDirectory(), "etc", "sysconfig"), ALLOW_ALL);
                Environment.getRootDirectory(), "etc", "sysconfig"), ALLOW_ALL);
@@ -419,7 +466,8 @@ public class SystemConfig {
                Environment.getSystemExtDirectory(), "etc", "permissions"), ALLOW_ALL);
                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.
        // Read permissions from given directory.
        if (!libraryDir.exists() || !libraryDir.isDirectory()) {
        if (!libraryDir.exists() || !libraryDir.isDirectory()) {
            if (permissionFlag == ALLOW_ALL) {
            if (permissionFlag == ALLOW_ALL) {
@@ -954,6 +1002,11 @@ public class SystemConfig {
                        }
                        }
                        XmlUtils.skipCurrentTag(parser);
                        XmlUtils.skipCurrentTag(parser);
                    } break;
                    } break;
                    case "install-in-user-type": {
                        // NB: We allow any directory permission to declare install-in-user-type.
                        readInstallInUserType(parser,
                                mPackageToUserTypeWhitelist, mPackageToUserTypeBlacklist);
                    } break;
                    default: {
                    default: {
                        Slog.w(TAG, "Tag " + name + " is unknown in "
                        Slog.w(TAG, "Tag " + name + " is unknown in "
                                + permFile + " at " + parser.getPositionDescription());
                                + 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 {
    void readOemPermissions(XmlPullParser parser) throws IOException, XmlPullParserException {
        final String packageName = parser.getAttributeValue(null, "package");
        final String packageName = parser.getAttributeValue(null, "package");
        if (TextUtils.isEmpty(packageName)) {
        if (TextUtils.isEmpty(packageName)) {
Loading