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
This commit is contained in:
John Wu
2021-11-02 14:10:16 -07:00
parent 8b0a6fa7c2
commit c71ba54c09
4 changed files with 101 additions and 107 deletions

View File

@@ -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 {
* <p>
* 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.
* <p>
* <em>Note: To avoid a deadlock, do not call this method with {@code mLock} lock held</em>
*/
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.
* <p>
* 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:
* <ul>
* <li>If previousAppId < 0, app data will be migrated to the new app ID
* <li>If previousAppId == 0, no migration will happen and data will be wiped and recreated
* <li>If previousAppId > 0, it will migrate all data owned by previousAppId
* to the new app ID
* </ul>
*/
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,

View File

@@ -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

View File

@@ -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<Long> 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<Long> createAppData(CreateAppDataArgs args) {
if (mExecuted) {
throw new IllegalStateException();
}
final CompletableFuture<Long> future = new CompletableFuture<>();
mArgs.add(args);
mFutures.add(future);

View File

@@ -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);