From 23ab7f54d96386db2b45dc0b352b3451f665e331 Mon Sep 17 00:00:00 2001 From: Amith Yamasani Date: Sun, 22 Jan 2017 17:43:24 -0800 Subject: [PATCH] Kernel mapping for package scope by user Inform the kernel via configfs, of userids that should be excluded when an app is not installed for them. Also push userid to remove_userid when a user is removed so that the exclude list of that user can be cleaned up in one command for all packages. Test: runtest -x ....KernelPackageMappingTests.java Change-Id: Ib94b1a0b737f45b2d03deb9650f0f0eb68e363d9 --- core/tests/packagemanagertests/Android.mk | 20 ++++ .../packagemanagertests/AndroidManifest.xml | 31 +++++ .../content/pm/KernelPackageMappingTests.java | 109 ++++++++++++++++++ .../server/pm/PackageManagerService.java | 20 ++++ .../android/server/pm/PackageSettingBase.java | 22 ++++ .../java/com/android/server/pm/Settings.java | 88 +++++++++++--- 6 files changed, 275 insertions(+), 15 deletions(-) create mode 100644 core/tests/packagemanagertests/Android.mk create mode 100644 core/tests/packagemanagertests/AndroidManifest.xml create mode 100644 core/tests/packagemanagertests/src/android/content/pm/KernelPackageMappingTests.java diff --git a/core/tests/packagemanagertests/Android.mk b/core/tests/packagemanagertests/Android.mk new file mode 100644 index 0000000000000..c1e8c981579c0 --- /dev/null +++ b/core/tests/packagemanagertests/Android.mk @@ -0,0 +1,20 @@ +LOCAL_PATH:= $(call my-dir) +include $(CLEAR_VARS) + +# We only want this apk build for tests. +LOCAL_MODULE_TAGS := tests + +# Include all test java files. +LOCAL_SRC_FILES := \ + $(call all-java-files-under, src) + +LOCAL_STATIC_JAVA_LIBRARIES := \ + android-support-test \ + frameworks-base-testutils + +LOCAL_JAVA_LIBRARIES := android.test.runner +LOCAL_PACKAGE_NAME := FrameworksCorePackageManagerTests + +LOCAL_CERTIFICATE := platform + +include $(BUILD_PACKAGE) diff --git a/core/tests/packagemanagertests/AndroidManifest.xml b/core/tests/packagemanagertests/AndroidManifest.xml new file mode 100644 index 0000000000000..8f490083b16e0 --- /dev/null +++ b/core/tests/packagemanagertests/AndroidManifest.xml @@ -0,0 +1,31 @@ + + + + + + + + + + + + diff --git a/core/tests/packagemanagertests/src/android/content/pm/KernelPackageMappingTests.java b/core/tests/packagemanagertests/src/android/content/pm/KernelPackageMappingTests.java new file mode 100644 index 0000000000000..1097bc731de8e --- /dev/null +++ b/core/tests/packagemanagertests/src/android/content/pm/KernelPackageMappingTests.java @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2017 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 android.content.pm; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import android.content.Context; +import android.os.FileUtils; +import android.os.Process; +import android.os.ServiceManager; +import android.os.UserManager; +import android.support.test.InstrumentationRegistry; +import android.support.test.runner.AndroidJUnit4; +import android.util.Log; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.io.File; +import java.io.IOException; + +/** + * This test needs to be run without any secondary users on the device, + * and selinux needs to be disabled with "adb shell setenforce 0". + */ +@RunWith(AndroidJUnit4.class) +public class KernelPackageMappingTests { + + private static final String TAG = "KernelPackageMapping"; + private static final String SDCARDFS_PATH = "/config/sdcardfs"; + + private UserInfo mSecondaryUser; + + private static File getKernelPackageDir(String packageName) { + return new File(new File(SDCARDFS_PATH), packageName); + } + + private static File getKernelPackageFile(String packageName, String filename) { + return new File(getKernelPackageDir(packageName), filename); + } + + private UserManager getUserManager() { + UserManager um = (UserManager) InstrumentationRegistry.getContext().getSystemService( + Context.USER_SERVICE); + return um; + } + + private IPackageManager getIPackageManager() { + IPackageManager ipm = IPackageManager.Stub.asInterface( + ServiceManager.getService("package")); + return ipm; + } + + private static String getContent(File file) { + try { + return FileUtils.readTextFile(file, 0, null).trim(); + } catch (IOException ioe) { + Log.w(TAG, "Couldn't read file " + file.getAbsolutePath() + "\n" + ioe); + return ""; + } + } + + @Test + public void testInstalledPrimary() throws Exception { + assertEquals("1000", getContent(getKernelPackageFile("com.android.settings", "appid"))); + } + + @Test + public void testInstalledAll() throws Exception { + assertEquals("", getContent(getKernelPackageFile("com.android.settings", + "excluded_userids"))); + } + + @Test + public void testNotInstalledSecondary() throws Exception { + mSecondaryUser = getUserManager().createUser("Secondary", 0); + assertEquals(Integer.toString(mSecondaryUser.id), + getContent( + getKernelPackageFile("com.android.frameworks.coretests.packagemanager", + "excluded_userids"))); + } + + @After + public void shutDown() throws Exception { + if (mSecondaryUser != null) { + getUserManager().removeUser(mSecondaryUser.id); + } + } +} diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index ebd0b343f9cf1..9ec27b3972749 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -2141,6 +2141,7 @@ public class PackageManagerService extends IPackageManager.Stub { pkgSetting.setInstalled(install, UserHandle.USER_SYSTEM); } } + scheduleWritePackageRestrictionsLocked(UserHandle.USER_SYSTEM); } } @@ -13251,6 +13252,7 @@ public class PackageManagerService extends IPackageManager.Stub { pkgSetting.setHidden(false, userId); pkgSetting.setInstallReason(installReason, userId); mSettings.writePackageRestrictionsLPr(userId); + mSettings.writeKernelMappingLPr(pkgSetting); installed = true; } } @@ -16345,6 +16347,7 @@ public class PackageManagerService extends IPackageManager.Stub { //note that the new package setting would have already been //added to mPackages. It hasn't been persisted yet. mSettings.setInstallStatus(pkgName, PackageSettingBase.PKG_INSTALL_INCOMPLETE); + // TODO: Remove this write? It's also written at the end of this method Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "writeSettings"); mSettings.writeLPr(); Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); @@ -16418,6 +16421,7 @@ public class PackageManagerService extends IPackageManager.Stub { } else if (!previousUserIds.contains(userId)) { ps.setInstallReason(installReason, userId); } + mSettings.writeKernelMappingLPr(ps); } res.name = pkgName; res.uid = newPackage.applicationInfo.uid; @@ -17597,6 +17601,7 @@ public class PackageManagerService extends IPackageManager.Stub { // writer synchronized (mPackages) { + boolean installedStateChanged = false; if (deletedPs != null) { if ((flags&PackageManager.DELETE_KEEP_DATA) == 0) { clearIntentFilterVerificationsLPw(deletedPs.name, UserHandle.USER_ALL); @@ -17644,6 +17649,9 @@ public class PackageManagerService extends IPackageManager.Stub { if (DEBUG_REMOVE) { Slog.d(TAG, " user " + userId + " => " + installed); } + if (installed != ps.getInstalled(userId)) { + installedStateChanged = true; + } ps.setInstalled(installed, userId); } } @@ -17653,6 +17661,9 @@ public class PackageManagerService extends IPackageManager.Stub { // Save settings now mSettings.writeLPr(); } + if (installedStateChanged) { + mSettings.writeKernelMappingLPr(ps); + } } if (removedAppId != -1) { // A user ID was deleted here. Go through all users and remove it @@ -17795,6 +17806,7 @@ public class PackageManagerService extends IPackageManager.Stub { UPDATE_PERMISSIONS_ALL | UPDATE_PERMISSIONS_REPLACE_PKG); if (applyUserRestrictions) { + boolean installedStateChanged = false; if (DEBUG_REMOVE) { Slog.d(TAG, "Propagating install state across reinstall"); } @@ -17803,6 +17815,9 @@ public class PackageManagerService extends IPackageManager.Stub { if (DEBUG_REMOVE) { Slog.d(TAG, " user " + userId + " => " + installed); } + if (installed != ps.getInstalled(userId)) { + installedStateChanged = true; + } ps.setInstalled(installed, userId); mSettings.writeRuntimePermissionsForUserLPr(userId, false); @@ -17810,6 +17825,9 @@ public class PackageManagerService extends IPackageManager.Stub { // Regardless of writeSettings we need to ensure that this restriction // state propagation is persisted mSettings.writeAllUsersPackageRestrictionsLPr(); + if (installedStateChanged) { + mSettings.writeKernelMappingLPr(ps); + } } // can downgrade to reader here if (writeSettings) { @@ -18013,6 +18031,7 @@ public class PackageManagerService extends IPackageManager.Stub { // broadcasts will be sent correctly. if (DEBUG_REMOVE) Slog.d(TAG, "Not installed by other users, full delete"); ps.setInstalled(true, user.getIdentifier()); + mSettings.writeKernelMappingLPr(ps); } } else { // This is a system app, so we assume that the @@ -18122,6 +18141,7 @@ public class PackageManagerService extends IPackageManager.Stub { ps.readUserState(nextUserId).domainVerificationStatus, 0, PackageManager.INSTALL_REASON_UNKNOWN); } + mSettings.writeKernelMappingLPr(ps); } private boolean clearPackageStateForUserLIF(PackageSetting ps, int userId, diff --git a/services/core/java/com/android/server/pm/PackageSettingBase.java b/services/core/java/com/android/server/pm/PackageSettingBase.java index b63edfdd0d737..0e11b0cec4c27 100644 --- a/services/core/java/com/android/server/pm/PackageSettingBase.java +++ b/services/core/java/com/android/server/pm/PackageSettingBase.java @@ -40,6 +40,9 @@ import java.util.Set; * Settings base class for pending and resolved classes. */ abstract class PackageSettingBase extends SettingBase { + + private static final int[] EMPTY_INT_ARRAY = new int[0]; + /** * Indicates the state of installation. Used by PackageManager to figure out * incomplete installations. Say a package is being installed (the state is @@ -502,6 +505,25 @@ abstract class PackageSettingBase extends SettingBase { userState.delete(userId); } + public int[] getNotInstalledUserIds() { + int count = 0; + int userStateCount = userState.size(); + for (int i = 0; i < userStateCount; i++) { + if (userState.valueAt(i).installed == false) { + count++; + } + } + if (count == 0) return EMPTY_INT_ARRAY; + int[] excludedUserIds = new int[count]; + int idx = 0; + for (int i = 0; i < userStateCount; i++) { + if (userState.valueAt(i).installed == false) { + excludedUserIds[idx++] = userState.keyAt(i); + } + } + return excludedUserIds; + } + IntentFilterVerificationInfo getIntentFilterVerificationInfo() { return verificationInfo; } diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java index 281e445e59b41..61568022eff20 100644 --- a/services/core/java/com/android/server/pm/Settings.java +++ b/services/core/java/com/android/server/pm/Settings.java @@ -252,6 +252,7 @@ final class Settings { private final File mPackageListFilename; private final File mStoppedPackagesFilename; private final File mBackupStoppedPackagesFilename; + /** The top level directory in configfs for sdcardfs to push the package->uid,userId mappings */ private final File mKernelMappingFilename; /** Map from package name to settings */ @@ -260,8 +261,8 @@ final class Settings { /** List of packages that installed other packages */ final ArraySet mInstallerPackages = new ArraySet<>(); - /** Map from package name to appId */ - private final ArrayMap mKernelMapping = new ArrayMap<>(); + /** Map from package name to appId and excluded userids */ + private final ArrayMap mKernelMapping = new ArrayMap<>(); // List of replaced system applications private final ArrayMap mDisabledSysPackages = @@ -271,6 +272,11 @@ final class Settings { private final ArrayMap mRestoredIntentFilterVerifications = new ArrayMap(); + private static final class KernelPackageState { + int appId; + int[] excludedUserIds; + } + // Bookkeeping for restored user permission grants final class RestoredPermissionGrant { String permissionName; @@ -2512,6 +2518,15 @@ final class Settings { //Debug.stopMethodTracing(); } + private void writeKernelRemoveUserLPr(int userId) { + if (mKernelMappingFilename == null) return; + + File removeUserIdFile = new File(mKernelMappingFilename, "remove_userid"); + if (DEBUG_KERNEL) Slog.d(TAG, "Writing " + userId + " to " + removeUserIdFile + .getAbsolutePath()); + writeIntToFile(removeUserIdFile, userId); + } + void writeKernelMappingLPr() { if (mKernelMappingFilename == null) return; @@ -2538,27 +2553,63 @@ final class Settings { } void writeKernelMappingLPr(PackageSetting ps) { - if (mKernelMappingFilename == null) return; + if (mKernelMappingFilename == null || ps == null || ps.name == null) return; - final Integer cur = mKernelMapping.get(ps.name); - if (cur != null && cur.intValue() == ps.appId) { - // Ignore when mapping already matches - return; + KernelPackageState cur = mKernelMapping.get(ps.name); + final boolean firstTime = cur == null; + int[] excludedUserIds = ps.getNotInstalledUserIds(); + final boolean userIdsChanged = firstTime + || !Arrays.equals(excludedUserIds, cur.excludedUserIds); + + // Package directory + final File dir = new File(mKernelMappingFilename, ps.name); + + if (firstTime) { + dir.mkdir(); + // Create a new mapping state + cur = new KernelPackageState(); + mKernelMapping.put(ps.name, cur); } - if (DEBUG_KERNEL) Slog.d(TAG, "Mapping " + ps.name + " to " + ps.appId); + // If mapping is incorrect or non-existent, write the appid file + if (cur.appId != ps.appId) { + final File appIdFile = new File(dir, "appid"); + writeIntToFile(appIdFile, ps.appId); + if (DEBUG_KERNEL) Slog.d(TAG, "Mapping " + ps.name + " to " + ps.appId); + } - final File dir = new File(mKernelMappingFilename, ps.name); - dir.mkdir(); + if (userIdsChanged) { + // Build the exclusion list -- the ids to add to the exclusion list + for (int i = 0; i < excludedUserIds.length; i++) { + if (cur.excludedUserIds == null || !ArrayUtils.contains(cur.excludedUserIds, + excludedUserIds[i])) { + writeIntToFile(new File(dir, "excluded_userids"), excludedUserIds[i]); + if (DEBUG_KERNEL) Slog.d(TAG, "Writing " + excludedUserIds[i] + " to " + + ps.name + "/excluded_userids"); + } + } + // Build the inclusion list -- the ids to remove from the exclusion list + if (cur.excludedUserIds != null) { + for (int i = 0; i < cur.excludedUserIds.length; i++) { + if (!ArrayUtils.contains(excludedUserIds, cur.excludedUserIds[i])) { + writeIntToFile(new File(dir, "clear_userid"), + cur.excludedUserIds[i]); + if (DEBUG_KERNEL) Slog.d(TAG, "Writing " + cur.excludedUserIds[i] + " to " + + ps.name + "/clear_userid"); - final File file = new File(dir, "appid"); + } + } + } + cur.excludedUserIds = excludedUserIds; + } + } + + private void writeIntToFile(File file, int value) { try { - // Note that the use of US_ASCII here is safe, we're only writing a decimal - // number to the file. FileUtils.bytesToFile(file.getAbsolutePath(), - Integer.toString(ps.appId).getBytes(StandardCharsets.US_ASCII)); - mKernelMapping.put(ps.name, ps.appId); + Integer.toString(value).getBytes(StandardCharsets.US_ASCII)); } catch (IOException ignored) { + Slog.w(TAG, "Couldn't write " + value + " to " + file.getAbsolutePath()); } } @@ -4081,6 +4132,9 @@ final class Settings { !ArrayUtils.contains(disallowedPackages, ps.name); // Only system apps are initially installed. ps.setInstalled(shouldInstall, userHandle); + if (!shouldInstall) { + writeKernelMappingLPr(ps); + } // Need to create a data directory for all apps under this user. Accumulate all // required args and call the installer after mPackages lock has been released volumeUuids[i] = ps.volumeUuid; @@ -4123,6 +4177,10 @@ final class Settings { mRuntimePermissionsPersistence.onUserRemovedLPw(userId); writePackageListLPr(); + + // Inform kernel that the user was removed, so that packages are marked uninstalled + // for sdcardfs + writeKernelRemoveUserLPr(userId); } void removeCrossProfileIntentFiltersLPw(int userId) {