From d81ff978667a776884362ef2461b3015c56117d1 Mon Sep 17 00:00:00 2001 From: Richard Uhler Date: Mon, 1 Apr 2019 13:02:31 +0100 Subject: [PATCH] Fix rollbacks in multi-user case. There are a few assumptions in the rollback manager that fail in the multi-user case that need to be fixed: * getAllSessions only returns sessions for the specific user. * Session callbacks are only called on sessions associated with the registered user. * getPackageInfo only returns info for the specific user. Fix these issues so that rollbacks will work properly, in particular when the initial install session is owned by a non-system user. Bug: 129809507 Bug: 129397974 Test: On single user device: atest RollbackTest StagedRollbackTest Test: On primary user of multi-user device: atest RollbackTest StagedRollbackTest Test: On multi user device manually: adb install RollbackTestAppAv1.apk adb install --user 10 --enable-rollback RollbackTestAppAv2.apk -- verify the install succeeded -- adb shell pm rollback-app com.android.tests.rollback.testapp.A -- verify the rollback succeeded -- Test: On multi user device manually: adb install RollbackTestAppAv1.apk adb install --staged --user 10 --enable-rollback RollbackTestAppAv2.apk adb reboot -- verify the install succeeded -- adb shell pm rollback-app com.android.tests.rollback.testapp.A adb reboot -- verify the rollback succeeded -- Change-Id: I1a7cf101b3bc3575421629c4bf0ff63418eb8731 --- .../content/pm/PackageManagerInternal.java | 7 ++ .../server/pm/PackageManagerService.java | 3 + .../rollback/RollbackManagerServiceImpl.java | 115 ++++++++++++++---- 3 files changed, 99 insertions(+), 26 deletions(-) diff --git a/core/java/android/content/pm/PackageManagerInternal.java b/core/java/android/content/pm/PackageManagerInternal.java index b3cc627712e45..cd324afb04797 100644 --- a/core/java/android/content/pm/PackageManagerInternal.java +++ b/core/java/android/content/pm/PackageManagerInternal.java @@ -862,6 +862,13 @@ public abstract class PackageManagerInternal { public static final String EXTRA_ENABLE_ROLLBACK_INSTALLED_USERS = "android.content.pm.extra.ENABLE_ROLLBACK_INSTALLED_USERS"; + /** + * Extra field name for the user id an install is associated with when + * enabling rollback. + */ + public static final String EXTRA_ENABLE_ROLLBACK_USER = + "android.content.pm.extra.ENABLE_ROLLBACK_USER"; + /** * Used as the {@code enableRollbackCode} argument for * {@link PackageManagerInternal#setEnableRollbackCode} to indicate that diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 86a1400e3d04a..c6309b1b67d54 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -14859,6 +14859,9 @@ public class PackageManagerService extends IPackageManager.Stub enableRollbackIntent.putExtra( PackageManagerInternal.EXTRA_ENABLE_ROLLBACK_INSTALLED_USERS, installedUsers); + enableRollbackIntent.putExtra( + PackageManagerInternal.EXTRA_ENABLE_ROLLBACK_USER, + getUser().getIdentifier()); enableRollbackIntent.setDataAndType(Uri.fromFile(new File(origin.resolvedPath)), PACKAGE_MIME_TYPE); enableRollbackIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); diff --git a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java index 068e78a2bd8a8..615472661f9e1 100644 --- a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java +++ b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java @@ -18,6 +18,7 @@ package com.android.server.rollback; import android.Manifest; import android.annotation.NonNull; +import android.annotation.UserIdInt; import android.app.AppOpsManager; import android.content.BroadcastReceiver; import android.content.Context; @@ -32,6 +33,7 @@ import android.content.pm.PackageManager; import android.content.pm.PackageManagerInternal; import android.content.pm.PackageParser; import android.content.pm.ParceledListSlice; +import android.content.pm.UserInfo; import android.content.pm.VersionedPackage; import android.content.rollback.IRollbackManager; import android.content.rollback.PackageRollbackInfo; @@ -43,6 +45,8 @@ import android.os.Handler; import android.os.HandlerThread; import android.os.ParcelFileDescriptor; import android.os.Process; +import android.os.UserHandle; // duped to avoid merge conflict +import android.os.UserManager; // out of order to avoid merge conflict import android.os.SystemClock; import android.os.UserHandle; import android.provider.DeviceConfig; @@ -155,27 +159,12 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub { // expiration. getHandler().post(() -> ensureRollbackDataLoaded()); - PackageInstaller packageInstaller = mContext.getPackageManager().getPackageInstaller(); - packageInstaller.registerSessionCallback(new SessionCallback(), getHandler()); - - IntentFilter filter = new IntentFilter(); - filter.addAction(Intent.ACTION_PACKAGE_REPLACED); - filter.addAction(Intent.ACTION_PACKAGE_FULLY_REMOVED); - filter.addDataScheme("package"); - mContext.registerReceiver(new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - String action = intent.getAction(); - if (Intent.ACTION_PACKAGE_REPLACED.equals(action)) { - String packageName = intent.getData().getSchemeSpecificPart(); - onPackageReplaced(packageName); - } - if (Intent.ACTION_PACKAGE_FULLY_REMOVED.equals(action)) { - String packageName = intent.getData().getSchemeSpecificPart(); - onPackageFullyRemoved(packageName); - } - } - }, filter, null, getHandler()); + // TODO: Make sure to register these call backs when a new user is + // added too. + SessionCallback sessionCallback = new SessionCallback(); + for (UserInfo userInfo : UserManager.get(mContext).getUsers(true)) { + registerUserCallbacks(userInfo.getUserHandle()); + } IntentFilter enableRollbackFilter = new IntentFilter(); enableRollbackFilter.addAction(Intent.ACTION_PACKAGE_ENABLE_ROLLBACK); @@ -195,11 +184,14 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub { PackageManagerInternal.EXTRA_ENABLE_ROLLBACK_INSTALL_FLAGS, 0); int[] installedUsers = intent.getIntArrayExtra( PackageManagerInternal.EXTRA_ENABLE_ROLLBACK_INSTALLED_USERS); + int user = intent.getIntExtra( + PackageManagerInternal.EXTRA_ENABLE_ROLLBACK_USER, 0); + File newPackageCodePath = new File(intent.getData().getPath()); getHandler().post(() -> { boolean success = enableRollback(installFlags, newPackageCodePath, - installedUsers); + installedUsers, user); int ret = PackageManagerInternal.ENABLE_ROLLBACK_SUCCEEDED; if (!success) { ret = PackageManagerInternal.ENABLE_ROLLBACK_FAILED; @@ -221,6 +213,39 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub { registerTimeChangeReceiver(); } + private void registerUserCallbacks(UserHandle user) { + Context context = getContextAsUser(user); + if (context == null) { + Log.e(TAG, "Unable to register user callbacks for user " + user); + return; + } + + // TODO: Reuse the same SessionCallback and broadcast receiver + // instances, rather than creating new instances for each user. + + context.getPackageManager().getPackageInstaller() + .registerSessionCallback(new SessionCallback(), getHandler()); + + IntentFilter filter = new IntentFilter(); + filter.addAction(Intent.ACTION_PACKAGE_REPLACED); + filter.addAction(Intent.ACTION_PACKAGE_FULLY_REMOVED); + filter.addDataScheme("package"); + context.registerReceiver(new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + String action = intent.getAction(); + if (Intent.ACTION_PACKAGE_REPLACED.equals(action)) { + String packageName = intent.getData().getSchemeSpecificPart(); + onPackageReplaced(packageName); + } + if (Intent.ACTION_PACKAGE_FULLY_REMOVED.equals(action)) { + String packageName = intent.getData().getSchemeSpecificPart(); + onPackageFullyRemoved(packageName); + } + } + }, filter, null, getHandler()); + } + @Override public ParceledListSlice getAvailableRollbacks() { enforceManageRollbacks("getAvailableRollbacks"); @@ -781,6 +806,14 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub { return false; } + private Context getContextAsUser(UserHandle user) { + try { + return mContext.createPackageContextAsUser(mContext.getPackageName(), 0, user); + } catch (PackageManager.NameNotFoundException e) { + return null; + } + } + /** * Called via broadcast by the package manager when a package is being * staged for install with rollback enabled. Called before the package has @@ -789,10 +822,11 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub { * @param installFlags information about what is being installed. * @param newPackageCodePath path to the package about to be installed. * @param installedUsers the set of users for which a given package is installed. + * @param user the user that owns the install session to enable rollback on. * @return true if enabling the rollback succeeds, false otherwise. */ private boolean enableRollback(int installFlags, File newPackageCodePath, - int[] installedUsers) { + int[] installedUsers, @UserIdInt int user) { // Find the session id associated with this install. // TODO: It would be nice if package manager or package installer told @@ -800,8 +834,17 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub { // ourselves. PackageInstaller.SessionInfo session = null; + // getAllSessions only returns sessions for the associated user. + // Create a context with the right user so we can find the matching + // session. + final Context context = getContextAsUser(UserHandle.of(user)); + if (context == null) { + Log.e(TAG, "Unable to create context for install session user."); + return false; + } + int parentSessionId = -1; - PackageInstaller installer = mContext.getPackageManager().getPackageInstaller(); + PackageInstaller installer = context.getPackageManager().getPackageInstaller(); for (PackageInstaller.SessionInfo info : installer.getAllSessions()) { if (info.isMultiPackage()) { for (int childId : info.getChildSessionIds()) { @@ -918,7 +961,7 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub { PackageManager pm = mContext.getPackageManager(); final PackageInfo pkgInfo; try { - pkgInfo = pm.getPackageInfo(packageName, isApex ? PackageManager.MATCH_APEX : 0); + pkgInfo = getPackageInfo(packageName); } catch (PackageManager.NameNotFoundException e) { // TODO: Support rolling back fresh package installs rather than // fail here. Test this case. @@ -1154,7 +1197,7 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub { PackageManager pm = mContext.getPackageManager(); PackageInfo pkgInfo = null; try { - pkgInfo = pm.getPackageInfo(packageName, PackageManager.MATCH_APEX); + pkgInfo = getPackageInfo(packageName); } catch (PackageManager.NameNotFoundException e) { return null; } @@ -1162,6 +1205,26 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub { return new VersionedPackage(packageName, pkgInfo.getLongVersionCode()); } + /** + * Gets PackageInfo for the given package. + * Matches any user and apex. Returns null if no such package is + * installed. + */ + private PackageInfo getPackageInfo(String packageName) + throws PackageManager.NameNotFoundException { + PackageManager pm = mContext.getPackageManager(); + try { + // The MATCH_ANY_USER flag doesn't mix well with the MATCH_APEX + // flag, so make two separate attempts to get the package info. + // We don't need both flags at the same time because we assume + // apex files are always installed for all users. + return pm.getPackageInfo(packageName, PackageManager.MATCH_ANY_USER); + } catch (PackageManager.NameNotFoundException e) { + return pm.getPackageInfo(packageName, PackageManager.MATCH_APEX); + } + } + + private boolean packageVersionsEqual(VersionedPackage a, VersionedPackage b) { return a.getPackageName().equals(b.getPackageName()) && a.getLongVersionCode() == b.getLongVersionCode();