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

Commit c71ba54c authored by John Wu's avatar John Wu
Browse files

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
parent 8b0a6fa7
Loading
Loading
Loading
Loading
+79 −70
Original line number Original line Diff line number Diff line
@@ -26,8 +26,10 @@ import android.annotation.Nullable;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager;
import android.content.pm.SELinuxUtil;
import android.content.pm.SELinuxUtil;
import android.content.pm.UserInfo;
import android.content.pm.UserInfo;
import android.os.CreateAppDataArgs;
import android.os.Environment;
import android.os.Environment;
import android.os.FileUtils;
import android.os.FileUtils;
import android.os.Process;
import android.os.Trace;
import android.os.Trace;
import android.os.UserHandle;
import android.os.UserHandle;
import android.os.storage.StorageManager;
import android.os.storage.StorageManager;
@@ -86,12 +88,20 @@ final class AppDataHelper {
     * <p>
     * <p>
     * Verifies that directories exist and that ownership and labeling is
     * Verifies that directories exist and that ownership and labeling is
     * correct for all installed apps. If there is an ownership mismatch, it
     * 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
     * will wipe and recreate the data.
     * left intact.
     * <p>
     * <p>
     * <em>Note: To avoid a deadlock, do not call this method with {@code mLock} lock held</em>
     * <em>Note: To avoid a deadlock, do not call this method with {@code mLock} lock held</em>
     */
     */
    public void prepareAppDataAfterInstallLIF(AndroidPackage pkg) {
    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;
        final PackageSetting ps;
        synchronized (mPm.mLock) {
        synchronized (mPm.mLock) {
            ps = mPm.mSettings.getPackageLPr(pkg.getPackageName());
            ps = mPm.mSettings.getPackageLPr(pkg.getPackageName());
@@ -113,13 +123,9 @@ final class AppDataHelper {
                continue;
                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)) {
            if (ps.getInstalled(user.id)) {
                // TODO: when user data is locked, mark that we're still dirty
                // 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
                    // Note: this code block is executed with the Installer lock
                    // already held, since it's invoked as a side-effect of
                    // already held, since it's invoked as a side-effect of
                    // executeBatchLI()
                    // executeBatchLI()
@@ -147,22 +153,26 @@ final class AppDataHelper {
     * Prepare app data for the given app.
     * Prepare app data for the given app.
     * <p>
     * <p>
     * Verifies that directories exist and that ownership and labeling is
     * Verifies that directories exist and that ownership and labeling is
     * correct for all installed apps. If there is an ownership mismatch, this
     * correct for all installed apps. If there is an ownership mismatch:
     * will try recovering system apps by wiping data; third-party app data is
     * <ul>
     * left intact.
     * <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,
    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) {
        if (pkg == null) {
            Slog.wtf(TAG, "Package was null!", new Throwable());
            Slog.wtf(TAG, "Package was null!", new Throwable());
            return CompletableFuture.completedFuture(null);
            return CompletableFuture.completedFuture(null);
        }
        }
        return prepareAppDataLeaf(batch, pkg, userId, flags);
        return prepareAppDataLeaf(batch, pkg, previousAppId, userId, flags);
    }
    }


    private void prepareAppDataAndMigrate(@NonNull Installer.Batch batch,
    private void prepareAppDataAndMigrate(@NonNull Installer.Batch batch,
            @NonNull AndroidPackage pkg, int userId, int flags, boolean maybeMigrateAppData) {
            @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
            // Note: this code block is executed with the Installer lock
            // already held, since it's invoked as a side-effect of
            // already held, since it's invoked as a side-effect of
            // executeBatchLI()
            // executeBatchLI()
@@ -170,14 +180,14 @@ final class AppDataHelper {
                // We may have just shuffled around app data directories, so
                // We may have just shuffled around app data directories, so
                // prepare them one more time
                // prepare them one more time
                final Installer.Batch batchInner = new Installer.Batch();
                final Installer.Batch batchInner = new Installer.Batch();
                prepareAppData(batchInner, pkg, userId, flags);
                prepareAppData(batchInner, pkg, Process.INVALID_UID, userId, flags);
                executeBatchLI(batchInner);
                executeBatchLI(batchInner);
            }
            }
        });
        });
    }
    }


    private @NonNull CompletableFuture<?> prepareAppDataLeaf(@NonNull Installer.Batch batch,
    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) {
        if (DEBUG_APP_DATA) {
            Slog.v(TAG, "prepareAppData for " + pkg.getPackageName() + " u" + userId + " 0x"
            Slog.v(TAG, "prepareAppData for " + pkg.getPackageName() + " u" + userId + " 0x"
                    + Integer.toHexString(flags));
                    + Integer.toHexString(flags));
@@ -200,9 +210,11 @@ final class AppDataHelper {


        final String seInfo = pkgSeInfo + seInfoUser;
        final String seInfo = pkgSeInfo + seInfoUser;
        final int targetSdkVersion = pkg.getTargetSdkVersion();
        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,
        return batch.createAppData(args).whenComplete((ceDataInode, e) -> {
                targetSdkVersion).whenComplete((ceDataInode, e) -> {
            // Note: this code block is executed with the Installer lock
            // Note: this code block is executed with the Installer lock
            // already held, since it's invoked as a side-effect of
            // already held, since it's invoked as a side-effect of
            // executeBatchLI()
            // executeBatchLI()
@@ -211,8 +223,7 @@ final class AppDataHelper {
                        + ", but trying to recover: " + e);
                        + ", but trying to recover: " + e);
                destroyAppDataLeafLIF(pkg, userId, flags);
                destroyAppDataLeafLIF(pkg, userId, flags);
                try {
                try {
                            ceDataInode = mInstaller.createAppData(volumeUuid, packageName, userId,
                    ceDataInode = mInstaller.createAppData(args).ceDataInode;
                                    flags, appId, seInfo, pkg.getTargetSdkVersion());
                    logCriticalInfo(Log.DEBUG, "Recovery succeeded!");
                    logCriticalInfo(Log.DEBUG, "Recovery succeeded!");
                } catch (Installer.InstallerException e2) {
                } catch (Installer.InstallerException e2) {
                    logCriticalInfo(Log.DEBUG, "Recovery failed!");
                    logCriticalInfo(Log.DEBUG, "Recovery failed!");
@@ -251,11 +262,9 @@ final class AppDataHelper {
            if ((flags & StorageManager.FLAG_STORAGE_CE) != 0 && ceDataInode != -1) {
            if ((flags & StorageManager.FLAG_STORAGE_CE) != 0 && ceDataInode != -1) {
                // TODO: mark this structure as dirty so we persist it!
                // TODO: mark this structure as dirty so we persist it!
                synchronized (mPm.mLock) {
                synchronized (mPm.mLock) {
                            if (ps != null) {
                    ps.setCeDataInode(ceDataInode, userId);
                    ps.setCeDataInode(ceDataInode, userId);
                }
                }
            }
            }
                    }


            prepareAppDataContentsLeafLIF(pkg, ps, userId, flags);
            prepareAppDataContentsLeafLIF(pkg, ps, userId, flags);
        });
        });
+6 −1
Original line number Original line Diff line number Diff line
@@ -2433,7 +2433,12 @@ final class InstallPackageHelper {
                }
                }
                incrementalStorages.add(storage);
                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) {
            if (reconciledPkg.mPrepareResult.mClearCodeCache) {
                appDataHelper.clearAppDataLIF(pkg, UserHandle.USER_ALL,
                appDataHelper.clearAppDataLIF(pkg, UserHandle.USER_ALL,
                        FLAG_STORAGE_DE | FLAG_STORAGE_CE | FLAG_STORAGE_EXTERNAL
                        FLAG_STORAGE_DE | FLAG_STORAGE_CE | FLAG_STORAGE_EXTERNAL
+12 −35
Original line number Original line Diff line number Diff line
@@ -26,7 +26,6 @@ import android.os.Build;
import android.os.CreateAppDataArgs;
import android.os.CreateAppDataArgs;
import android.os.CreateAppDataResult;
import android.os.CreateAppDataResult;
import android.os.IBinder;
import android.os.IBinder;
import android.os.IBinder.DeathRecipient;
import android.os.IInstalld;
import android.os.IInstalld;
import android.os.RemoteException;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.ServiceManager;
@@ -148,12 +147,9 @@ public class Installer extends SystemService {
        IBinder binder = ServiceManager.getService("installd");
        IBinder binder = ServiceManager.getService("installd");
        if (binder != null) {
        if (binder != null) {
            try {
            try {
                binder.linkToDeath(new DeathRecipient() {
                binder.linkToDeath(() -> {
                    @Override
                    public void binderDied() {
                    Slog.w(TAG, "installd died; reconnecting");
                    Slog.w(TAG, "installd died; reconnecting");
                    connect();
                    connect();
                    }
                }, 0);
                }, 0);
            } catch (RemoteException e) {
            } catch (RemoteException e) {
                binder = null;
                binder = null;
@@ -168,9 +164,7 @@ public class Installer extends SystemService {
            }
            }
        } else {
        } else {
            Slog.w(TAG, "installd not found; trying again");
            Slog.w(TAG, "installd not found; trying again");
            BackgroundThread.getHandler().postDelayed(() -> {
            BackgroundThread.getHandler().postDelayed(this::connect, DateUtils.SECOND_IN_MILLIS);
                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) {
            int userId, int flags, int appId, String seInfo, int targetSdkVersion) {
        final CreateAppDataArgs args = new CreateAppDataArgs();
        final CreateAppDataArgs args = new CreateAppDataArgs();
        args.uuid = uuid;
        args.uuid = uuid;
@@ -213,23 +209,6 @@ public class Installer extends SystemService {
        return result;
        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)
    public @NonNull CreateAppDataResult createAppData(@NonNull CreateAppDataArgs args)
            throws InstallerException {
            throws InstallerException {
        if (!checkBeforeRemote()) {
        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
         * Callers of this method are not required to hold a monitor lock on an
         * {@link Installer} object.
         * {@link Installer} object.
         */
         */
        public synchronized @NonNull CompletableFuture<Long> createAppData(String uuid,
        @NonNull
                String packageName, int userId, int flags, int appId, String seInfo,
        public synchronized CompletableFuture<Long> createAppData(CreateAppDataArgs args) {
                int targetSdkVersion) {
            if (mExecuted) {
            if (mExecuted) throw new IllegalStateException();
                throw new IllegalStateException();

            }
            final CreateAppDataArgs args = buildCreateAppDataArgs(uuid, packageName, userId, flags,
                    appId, seInfo, targetSdkVersion);
            final CompletableFuture<Long> future = new CompletableFuture<>();
            final CompletableFuture<Long> future = new CompletableFuture<>();
            mArgs.add(args);
            mArgs.add(args);
            mFutures.add(future);
            mFutures.add(future);
+4 −1
Original line number Original line Diff line number Diff line
@@ -61,6 +61,7 @@ import android.content.pm.pkg.PackageUserStateUtils;
import android.net.Uri;
import android.net.Uri;
import android.os.Binder;
import android.os.Binder;
import android.os.Build;
import android.os.Build;
import android.os.CreateAppDataArgs;
import android.os.Environment;
import android.os.Environment;
import android.os.FileUtils;
import android.os.FileUtils;
import android.os.Handler;
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
                    // Accumulate all required args and call the installer after mPackages lock
                    // has been released
                    // has been released
                    final String seInfo = AndroidPackageUtils.getSeInfo(ps.getPkg(), ps);
                    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,
                            StorageManager.FLAG_STORAGE_CE | StorageManager.FLAG_STORAGE_DE,
                            ps.getAppId(), seInfo, ps.getPkg().getTargetSdkVersion());
                            ps.getAppId(), seInfo, ps.getPkg().getTargetSdkVersion());
                    batch.createAppData(args);
                } else {
                } else {
                    // Make sure the app is excluded from storage mapping for this user
                    // Make sure the app is excluded from storage mapping for this user
                    writeKernelMappingLPr(ps);
                    writeKernelMappingLPr(ps);