From c71ba54c09cdb8201302befc60ce74efa9522e19 Mon Sep 17 00:00:00 2001 From: John Wu Date: Tue, 2 Nov 2021 14:10:16 -0700 Subject: [PATCH] Migrate data when apps leave sharedUserId Add logic in installd to recursively chown the apps' internal data and profile directory to its new app ID. In the case of an app upgrade and the user profile unlocked, the previous appId is also passed over to verify and only chown files that the app owns. Bug: 179284822 Test: atest SharedUserMigrationTest#testDataMigration Change-Id: Iee42619801aef90ac1c7849ddcbd8c08e2314547 --- .../com/android/server/pm/AppDataHelper.java | 149 ++++++++++-------- .../server/pm/InstallPackageHelper.java | 7 +- .../java/com/android/server/pm/Installer.java | 47 ++---- .../java/com/android/server/pm/Settings.java | 5 +- 4 files changed, 101 insertions(+), 107 deletions(-) diff --git a/services/core/java/com/android/server/pm/AppDataHelper.java b/services/core/java/com/android/server/pm/AppDataHelper.java index 9696d3d14ce91..6ee198133281e 100644 --- a/services/core/java/com/android/server/pm/AppDataHelper.java +++ b/services/core/java/com/android/server/pm/AppDataHelper.java @@ -26,8 +26,10 @@ import android.annotation.Nullable; import android.content.pm.PackageManager; import android.content.pm.SELinuxUtil; import android.content.pm.UserInfo; +import android.os.CreateAppDataArgs; import android.os.Environment; import android.os.FileUtils; +import android.os.Process; import android.os.Trace; import android.os.UserHandle; import android.os.storage.StorageManager; @@ -86,12 +88,20 @@ final class AppDataHelper { *

* Verifies that directories exist and that ownership and labeling is * correct for all installed apps. If there is an ownership mismatch, it - * will try recovering system apps by wiping data; third-party app data is - * left intact. + * will wipe and recreate the data. *

* Note: To avoid a deadlock, do not call this method with {@code mLock} lock held */ public void prepareAppDataAfterInstallLIF(AndroidPackage pkg) { + prepareAppDataPostCommitLIF(pkg, 0 /* previousAppId */); + } + + /** + * For more details about data verification and previousAppId, check + * {@link #prepareAppData(Installer.Batch, AndroidPackage, int, int, int)} + * @see #prepareAppDataAfterInstallLIF(AndroidPackage) + */ + public void prepareAppDataPostCommitLIF(AndroidPackage pkg, int previousAppId) { final PackageSetting ps; synchronized (mPm.mLock) { ps = mPm.mSettings.getPackageLPr(pkg.getPackageName()); @@ -113,13 +123,9 @@ final class AppDataHelper { continue; } - // TODO@ashfall check ScanResult.mNeedsNewAppId, and if true instead - // of creating app data, migrate / change ownership of existing - // data. - if (ps.getInstalled(user.id)) { // TODO: when user data is locked, mark that we're still dirty - prepareAppData(batch, pkg, user.id, flags).thenRun(() -> { + prepareAppData(batch, pkg, previousAppId, user.id, flags).thenRun(() -> { // Note: this code block is executed with the Installer lock // already held, since it's invoked as a side-effect of // executeBatchLI() @@ -147,22 +153,26 @@ final class AppDataHelper { * Prepare app data for the given app. *

* Verifies that directories exist and that ownership and labeling is - * correct for all installed apps. If there is an ownership mismatch, this - * will try recovering system apps by wiping data; third-party app data is - * left intact. + * correct for all installed apps. If there is an ownership mismatch: + *

*/ private @NonNull CompletableFuture prepareAppData(@NonNull Installer.Batch batch, - @Nullable AndroidPackage pkg, int userId, int flags) { + @Nullable AndroidPackage pkg, int previousAppId, int userId, int flags) { if (pkg == null) { Slog.wtf(TAG, "Package was null!", new Throwable()); return CompletableFuture.completedFuture(null); } - return prepareAppDataLeaf(batch, pkg, userId, flags); + return prepareAppDataLeaf(batch, pkg, previousAppId, userId, flags); } private void prepareAppDataAndMigrate(@NonNull Installer.Batch batch, @NonNull AndroidPackage pkg, int userId, int flags, boolean maybeMigrateAppData) { - prepareAppData(batch, pkg, userId, flags).thenRun(() -> { + prepareAppData(batch, pkg, Process.INVALID_UID, userId, flags).thenRun(() -> { // Note: this code block is executed with the Installer lock // already held, since it's invoked as a side-effect of // executeBatchLI() @@ -170,14 +180,14 @@ final class AppDataHelper { // We may have just shuffled around app data directories, so // prepare them one more time final Installer.Batch batchInner = new Installer.Batch(); - prepareAppData(batchInner, pkg, userId, flags); + prepareAppData(batchInner, pkg, Process.INVALID_UID, userId, flags); executeBatchLI(batchInner); } }); } private @NonNull CompletableFuture prepareAppDataLeaf(@NonNull Installer.Batch batch, - @NonNull AndroidPackage pkg, int userId, int flags) { + @NonNull AndroidPackage pkg, int previousAppId, int userId, int flags) { if (DEBUG_APP_DATA) { Slog.v(TAG, "prepareAppData for " + pkg.getPackageName() + " u" + userId + " 0x" + Integer.toHexString(flags)); @@ -200,65 +210,64 @@ final class AppDataHelper { final String seInfo = pkgSeInfo + seInfoUser; final int targetSdkVersion = pkg.getTargetSdkVersion(); + final CreateAppDataArgs args = Installer.buildCreateAppDataArgs(volumeUuid, packageName, + userId, flags, appId, seInfo, targetSdkVersion); + args.previousAppId = previousAppId; - return batch.createAppData(volumeUuid, packageName, userId, flags, appId, seInfo, - targetSdkVersion).whenComplete((ceDataInode, e) -> { - // Note: this code block is executed with the Installer lock - // already held, since it's invoked as a side-effect of - // executeBatchLI() - if (e != null) { - logCriticalInfo(Log.WARN, "Failed to create app data for " + packageName - + ", but trying to recover: " + e); - destroyAppDataLeafLIF(pkg, userId, flags); - try { - ceDataInode = mInstaller.createAppData(volumeUuid, packageName, userId, - flags, appId, seInfo, pkg.getTargetSdkVersion()); - logCriticalInfo(Log.DEBUG, "Recovery succeeded!"); - } catch (Installer.InstallerException e2) { - logCriticalInfo(Log.DEBUG, "Recovery failed!"); - } - } + return batch.createAppData(args).whenComplete((ceDataInode, e) -> { + // Note: this code block is executed with the Installer lock + // already held, since it's invoked as a side-effect of + // executeBatchLI() + if (e != null) { + logCriticalInfo(Log.WARN, "Failed to create app data for " + packageName + + ", but trying to recover: " + e); + destroyAppDataLeafLIF(pkg, userId, flags); + try { + ceDataInode = mInstaller.createAppData(args).ceDataInode; + logCriticalInfo(Log.DEBUG, "Recovery succeeded!"); + } catch (Installer.InstallerException e2) { + logCriticalInfo(Log.DEBUG, "Recovery failed!"); + } + } - // Prepare the application profiles only for upgrades and - // first boot (so that we don't repeat the same operation at - // each boot). - // - // We only have to cover the upgrade and first boot here - // because for app installs we prepare the profiles before - // invoking dexopt (in installPackageLI). - // - // We also have to cover non system users because we do not - // call the usual install package methods for them. - // - // NOTE: in order to speed up first boot time we only create - // the current profile and do not update the content of the - // reference profile. A system image should already be - // configured with the right profile keys and the profiles - // for the speed-profile prebuilds should already be copied. - // That's done in #performDexOptUpgrade. - // - // TODO(calin, mathieuc): We should use .dm files for - // prebuilds profiles instead of manually copying them in - // #performDexOptUpgrade. When we do that we should have a - // more granular check here and only update the existing - // profiles. - if (mPm.isDeviceUpgrading() || mPm.isFirstBoot() - || (userId != UserHandle.USER_SYSTEM)) { - mArtManagerService.prepareAppProfiles(pkg, userId, - /* updateReferenceProfileContent= */ false); - } + // Prepare the application profiles only for upgrades and + // first boot (so that we don't repeat the same operation at + // each boot). + // + // We only have to cover the upgrade and first boot here + // because for app installs we prepare the profiles before + // invoking dexopt (in installPackageLI). + // + // We also have to cover non system users because we do not + // call the usual install package methods for them. + // + // NOTE: in order to speed up first boot time we only create + // the current profile and do not update the content of the + // reference profile. A system image should already be + // configured with the right profile keys and the profiles + // for the speed-profile prebuilds should already be copied. + // That's done in #performDexOptUpgrade. + // + // TODO(calin, mathieuc): We should use .dm files for + // prebuilds profiles instead of manually copying them in + // #performDexOptUpgrade. When we do that we should have a + // more granular check here and only update the existing + // profiles. + if (mPm.isDeviceUpgrading() || mPm.isFirstBoot() + || (userId != UserHandle.USER_SYSTEM)) { + mArtManagerService.prepareAppProfiles(pkg, userId, + /* updateReferenceProfileContent= */ false); + } - if ((flags & StorageManager.FLAG_STORAGE_CE) != 0 && ceDataInode != -1) { - // TODO: mark this structure as dirty so we persist it! - synchronized (mPm.mLock) { - if (ps != null) { - ps.setCeDataInode(ceDataInode, userId); - } - } - } + if ((flags & StorageManager.FLAG_STORAGE_CE) != 0 && ceDataInode != -1) { + // TODO: mark this structure as dirty so we persist it! + synchronized (mPm.mLock) { + ps.setCeDataInode(ceDataInode, userId); + } + } - prepareAppDataContentsLeafLIF(pkg, ps, userId, flags); - }); + prepareAppDataContentsLeafLIF(pkg, ps, userId, flags); + }); } public void prepareAppDataContentsLIF(AndroidPackage pkg, @Nullable PackageSetting pkgSetting, diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java index 30d32ad197ae7..53e76a7a594d0 100644 --- a/services/core/java/com/android/server/pm/InstallPackageHelper.java +++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java @@ -2433,7 +2433,12 @@ final class InstallPackageHelper { } incrementalStorages.add(storage); } - appDataHelper.prepareAppDataAfterInstallLIF(pkg); + int previousAppId = 0; + if (reconciledPkg.mScanResult.needsNewAppId()) { + // Only set previousAppId if the app is migrating out of shared UID + previousAppId = reconciledPkg.mScanResult.mPreviousAppId; + } + appDataHelper.prepareAppDataPostCommitLIF(pkg, previousAppId); if (reconciledPkg.mPrepareResult.mClearCodeCache) { appDataHelper.clearAppDataLIF(pkg, UserHandle.USER_ALL, FLAG_STORAGE_DE | FLAG_STORAGE_CE | FLAG_STORAGE_EXTERNAL diff --git a/services/core/java/com/android/server/pm/Installer.java b/services/core/java/com/android/server/pm/Installer.java index 7e002bf8c14f5..55355d81ffe2d 100644 --- a/services/core/java/com/android/server/pm/Installer.java +++ b/services/core/java/com/android/server/pm/Installer.java @@ -26,7 +26,6 @@ import android.os.Build; import android.os.CreateAppDataArgs; import android.os.CreateAppDataResult; import android.os.IBinder; -import android.os.IBinder.DeathRecipient; import android.os.IInstalld; import android.os.RemoteException; import android.os.ServiceManager; @@ -148,12 +147,9 @@ public class Installer extends SystemService { IBinder binder = ServiceManager.getService("installd"); if (binder != null) { try { - binder.linkToDeath(new DeathRecipient() { - @Override - public void binderDied() { - Slog.w(TAG, "installd died; reconnecting"); - connect(); - } + binder.linkToDeath(() -> { + Slog.w(TAG, "installd died; reconnecting"); + connect(); }, 0); } catch (RemoteException e) { binder = null; @@ -168,9 +164,7 @@ public class Installer extends SystemService { } } else { Slog.w(TAG, "installd not found; trying again"); - BackgroundThread.getHandler().postDelayed(() -> { - connect(); - }, DateUtils.SECOND_IN_MILLIS); + BackgroundThread.getHandler().postDelayed(this::connect, DateUtils.SECOND_IN_MILLIS); } } @@ -192,7 +186,9 @@ public class Installer extends SystemService { } } - private static CreateAppDataArgs buildCreateAppDataArgs(String uuid, String packageName, + // We explicitly do NOT set previousAppId because the default value should always be 0. + // Manually override previousAppId after building CreateAppDataArgs for specific behaviors. + static CreateAppDataArgs buildCreateAppDataArgs(String uuid, String packageName, int userId, int flags, int appId, String seInfo, int targetSdkVersion) { final CreateAppDataArgs args = new CreateAppDataArgs(); args.uuid = uuid; @@ -213,23 +209,6 @@ public class Installer extends SystemService { return result; } - /** - * @deprecated callers are encouraged to migrate to using {@link Batch} to - * more efficiently handle operations in bulk. - */ - @Deprecated - public long createAppData(String uuid, String packageName, int userId, int flags, int appId, - String seInfo, int targetSdkVersion) throws InstallerException { - final CreateAppDataArgs args = buildCreateAppDataArgs(uuid, packageName, userId, flags, - appId, seInfo, targetSdkVersion); - final CreateAppDataResult result = createAppData(args); - if (result.exceptionCode == 0) { - return result.ceDataInode; - } else { - throw new InstallerException(result.exceptionMessage); - } - } - public @NonNull CreateAppDataResult createAppData(@NonNull CreateAppDataArgs args) throws InstallerException { if (!checkBeforeRemote()) { @@ -284,13 +263,11 @@ public class Installer extends SystemService { * Callers of this method are not required to hold a monitor lock on an * {@link Installer} object. */ - public synchronized @NonNull CompletableFuture createAppData(String uuid, - String packageName, int userId, int flags, int appId, String seInfo, - int targetSdkVersion) { - if (mExecuted) throw new IllegalStateException(); - - final CreateAppDataArgs args = buildCreateAppDataArgs(uuid, packageName, userId, flags, - appId, seInfo, targetSdkVersion); + @NonNull + public synchronized CompletableFuture createAppData(CreateAppDataArgs args) { + if (mExecuted) { + throw new IllegalStateException(); + } final CompletableFuture future = new CompletableFuture<>(); mArgs.add(args); mFutures.add(future); diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java index 6df100640e26c..3877b45045a22 100644 --- a/services/core/java/com/android/server/pm/Settings.java +++ b/services/core/java/com/android/server/pm/Settings.java @@ -61,6 +61,7 @@ import android.content.pm.pkg.PackageUserStateUtils; import android.net.Uri; import android.os.Binder; import android.os.Build; +import android.os.CreateAppDataArgs; import android.os.Environment; import android.os.FileUtils; import android.os.Handler; @@ -4016,9 +4017,11 @@ public final class Settings implements Watchable, Snappable { // Accumulate all required args and call the installer after mPackages lock // has been released final String seInfo = AndroidPackageUtils.getSeInfo(ps.getPkg(), ps); - batch.createAppData(ps.getVolumeUuid(), ps.getPackageName(), userHandle, + final CreateAppDataArgs args = Installer.buildCreateAppDataArgs( + ps.getVolumeUuid(), ps.getPackageName(), userHandle, StorageManager.FLAG_STORAGE_CE | StorageManager.FLAG_STORAGE_DE, ps.getAppId(), seInfo, ps.getPkg().getTargetSdkVersion()); + batch.createAppData(args); } else { // Make sure the app is excluded from storage mapping for this user writeKernelMappingLPr(ps);