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

Commit 869f706d authored by Narayan Kamath's avatar Narayan Kamath
Browse files

RollbackManager: Support userdata snapshot / restore.

The only significant missing piece of functionality is the
handling of credential encrypted data in the case where one
or more users haven't unlocked their device. This will be handled
in a follow-up change.

Bug: 112431924
Test: atest RollbackTest
Change-Id: I4582018337ce0344379f6f73d6fd6c9ce31c58a0
parent c2888cbc
Loading
Loading
Loading
Loading
+12 −0
Original line number Diff line number Diff line
@@ -793,6 +793,12 @@ public abstract class PackageManagerInternal {
    public static final String EXTRA_ENABLE_ROLLBACK_INSTALL_FLAGS =
            "android.content.pm.extra.ENABLE_ROLLBACK_INSTALL_FLAGS";

    /**
     * Extra field name for the set of installed users for a given rollback package.
     */
    public static final String EXTRA_ENABLE_ROLLBACK_INSTALLED_USERS =
            "android.content.pm.extra.ENABLE_ROLLBACK_INSTALLED_USERS";

    /**
     * Used as the {@code enableRollbackCode} argument for
     * {@link PackageManagerInternal#setEnableRollbackCode} to indicate that
@@ -827,4 +833,10 @@ public abstract class PackageManagerInternal {
     * Ask the package manager to compile layouts in the given package.
     */
    public abstract boolean compileLayouts(String packageName);

    /*
     * Inform the package manager that the pending package install identified by
     * {@code token} can be completed.
     */
    public abstract void finishPackageInstall(int token, boolean didLaunch);
}
+6 −0
Original line number Diff line number Diff line
@@ -33,6 +33,12 @@ interface IRollbackManager {
    void executeRollback(in RollbackInfo rollback, String callerPackageName,
            in IntentSender statusReceiver);

    // Exposed for use from the system server only. Callback from the package
    // manager during the install flow when user data can be restored for a given
    // package.
    void restoreUserData(String packageName, int userId, int appId, long ceDataInode,
            String seInfo, int token);

    // Exposed for test purposes only.
    void reloadPersistedData();

+25 −0
Original line number Diff line number Diff line
@@ -611,6 +611,31 @@ public class Installer extends SystemService {
        }
    }

    public boolean snapshotAppData(String pkg, @UserIdInt int userId, int storageFlags)
            throws InstallerException {
        if (!checkBeforeRemote()) return false;

        try {
            mInstalld.snapshotAppData(null, pkg, userId, storageFlags);
            return true;
        } catch (Exception e) {
            throw InstallerException.from(e);
        }
    }

    public boolean restoreAppDataSnapshot(String pkg, @AppIdInt  int appId, long ceDataInode,
            String seInfo, @UserIdInt int userId, int storageFlags) throws InstallerException {
        if (!checkBeforeRemote()) return false;

        try {
            mInstalld.restoreAppDataSnapshot(null, pkg, appId, ceDataInode, seInfo, userId,
                    storageFlags);
            return true;
        } catch (Exception e) {
            throw InstallerException.from(e);
        }
    }

    private static void assertValidInstructionSet(String instructionSet)
            throws InstallerException {
        for (String abi : Build.SUPPORTED_ABIS) {
+42 −0
Original line number Diff line number Diff line
@@ -42,6 +42,7 @@ import static android.content.pm.PackageManager.FLAG_PERMISSION_REVOKE_ON_UPGRAD
import static android.content.pm.PackageManager.FLAG_PERMISSION_SYSTEM_FIXED;
import static android.content.pm.PackageManager.FLAG_PERMISSION_USER_FIXED;
import static android.content.pm.PackageManager.FLAG_PERMISSION_USER_SET;
import static android.content.pm.PackageManager.INSTALL_ALLOW_DOWNGRADE;
import static android.content.pm.PackageManager.INSTALL_FAILED_ALREADY_EXISTS;
import static android.content.pm.PackageManager.INSTALL_FAILED_DUPLICATE_PACKAGE;
import static android.content.pm.PackageManager.INSTALL_FAILED_DUPLICATE_PERMISSION;
@@ -201,6 +202,7 @@ import android.content.pm.VersionedPackage;
import android.content.pm.dex.ArtManager;
import android.content.pm.dex.DexMetadataHelper;
import android.content.pm.dex.IArtManager;
import android.content.rollback.IRollbackManager;
import android.content.res.Resources;
import android.database.ContentObserver;
import android.graphics.Bitmap;
@@ -13872,6 +13874,38 @@ public class PackageManagerService extends IPackageManager.Stub
            }
        }
        // If this is an update to a package that might be potentially downgraded, then we
        // need to check with the rollback manager whether there's any userdata that might
        // need to be restored for the package.
        //
        // TODO(narayan): Get this working for cases where userId == UserHandle.USER_ALL.
        if (res.returnCode == PackageManager.INSTALL_SUCCEEDED && !doRestore && update) {
            IRollbackManager rm = IRollbackManager.Stub.asInterface(
                    ServiceManager.getService(Context.ROLLBACK_SERVICE));
            final String packageName = res.pkg.applicationInfo.packageName;
            final String seInfo = res.pkg.applicationInfo.seInfo;
            final PackageSetting ps;
            int appId = -1;
            long ceDataInode = -1;
            synchronized (mSettings) {
                ps = mSettings.getPackageLPr(packageName);
                if (ps != null) {
                    appId = ps.appId;
                    ceDataInode = ps.getCeDataInode(userId);
                }
            }
            if (ps != null) {
                try {
                    rm.restoreUserData(packageName, userId, appId, ceDataInode, seInfo, token);
                } catch (RemoteException re) {
                    // Cannot happen, the RollbackManager is hosted in the same process.
                }
                doRestore = true;
            }
        }
        if (!doRestore) {
            // No restore possible, or the Backup Manager was mysteriously not
            // available -- just fire the post-install work request directly.
@@ -14569,6 +14603,9 @@ public class PackageManagerService extends IPackageManager.Stub
                    enableRollbackIntent.putExtra(
                            PackageManagerInternal.EXTRA_ENABLE_ROLLBACK_INSTALL_FLAGS,
                            installFlags);
                    enableRollbackIntent.putExtra(
                            PackageManagerInternal.EXTRA_ENABLE_ROLLBACK_INSTALLED_USERS,
                            resolveUserIds(args.user.getIdentifier()));
                    enableRollbackIntent.setDataAndType(Uri.fromFile(new File(origin.resolvedPath)),
                            PACKAGE_MIME_TYPE);
                    enableRollbackIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
@@ -23791,6 +23828,11 @@ public class PackageManagerService extends IPackageManager.Stub
            }
            return mArtManagerService.compileLayouts(pkg);
        }
        @Override
        public void finishPackageInstall(int token, boolean didLaunch) {
            PackageManagerService.this.finishPackageInstall(token, didLaunch);
        }
    }
    @GuardedBy("mPackages")
+106 −42
Original line number Diff line number Diff line
@@ -43,10 +43,14 @@ import android.os.Handler;
import android.os.HandlerThread;
import android.os.IBinder;
import android.os.ParcelFileDescriptor;
import android.os.Process;
import android.os.storage.StorageManager;
import android.util.Log;

import com.android.internal.annotations.GuardedBy;
import com.android.server.LocalServices;
import com.android.server.pm.Installer;
import com.android.server.pm.Installer.InstallerException;
import com.android.server.pm.PackageManagerServiceUtils;

import java.io.File;
@@ -56,12 +60,11 @@ import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;

/**
 * Implementation of service that manages APK level rollbacks.
@@ -103,9 +106,14 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub {

    private final Context mContext;
    private final HandlerThread mHandlerThread;
    private final Installer mInstaller;

    RollbackManagerServiceImpl(Context context) {
        mContext = context;
        // Note that we're calling onStart here because this object is only constructed on
        // SystemService#onStart.
        mInstaller = new Installer(mContext);
        mInstaller.onStart();
        mHandlerThread = new HandlerThread("RollbackManagerServiceHandler");
        mHandlerThread.start();

@@ -120,8 +128,8 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub {
        // expiration.
        getHandler().post(() -> ensureRollbackDataLoaded());

        PackageInstaller installer = mContext.getPackageManager().getPackageInstaller();
        installer.registerSessionCallback(new SessionCallback(), getHandler());
        PackageInstaller packageInstaller = mContext.getPackageManager().getPackageInstaller();
        packageInstaller.registerSessionCallback(new SessionCallback(), getHandler());

        IntentFilter filter = new IntentFilter();
        filter.addAction(Intent.ACTION_PACKAGE_REPLACED);
@@ -158,10 +166,13 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub {
                            PackageManagerInternal.EXTRA_ENABLE_ROLLBACK_TOKEN, -1);
                    int installFlags = intent.getIntExtra(
                            PackageManagerInternal.EXTRA_ENABLE_ROLLBACK_INSTALL_FLAGS, 0);
                    int[] installedUsers = intent.getIntArrayExtra(
                            PackageManagerInternal.EXTRA_ENABLE_ROLLBACK_INSTALLED_USERS);
                    File newPackageCodePath = new File(intent.getData().getPath());

                    getHandler().post(() -> {
                        boolean success = enableRollback(installFlags, newPackageCodePath);
                        boolean success = enableRollback(installFlags, newPackageCodePath,
                                installedUsers);
                        int ret = PackageManagerInternal.ENABLE_ROLLBACK_SUCCEEDED;
                        if (!success) {
                            ret = PackageManagerInternal.ENABLE_ROLLBACK_FAILED;
@@ -349,10 +360,8 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub {
                parentSession.addChildSessionId(sessionId);
            }

            final LocalIntentReceiver receiver = new LocalIntentReceiver();
            parentSession.commit(receiver.getIntentSender());

            Intent result = receiver.getResult();
            final LocalIntentReceiver receiver = new LocalIntentReceiver(
                    (Intent result) -> {
                        int status = result.getIntExtra(PackageInstaller.EXTRA_STATUS,
                                PackageInstaller.STATUS_FAILURE);
                        if (status != PackageInstaller.STATUS_SUCCESS) {
@@ -371,6 +380,10 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub {
                        // TODO: This call emits the warning "Calling a method in the
                        // system process without a qualified user". Fix that.
                        mContext.sendBroadcast(broadcast);
                    }
            );

            parentSession.commit(receiver.getIntentSender());
        } catch (IOException e) {
            Log.e(TAG, "Unable to roll back " + targetPackageName, e);
            sendFailure(statusReceiver, "IOException: " + e.toString());
@@ -613,12 +626,13 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub {
     * staged for install with rollback enabled. Called before the package has
     * been installed.
     *
     * @param id the id of the enable rollback request
     * @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.
     * @return true if enabling the rollback succeeds, false otherwise.
     */
    private boolean enableRollback(int installFlags, File newPackageCodePath) {
    private boolean enableRollback(int installFlags, File newPackageCodePath,
            int[] installedUsers) {
        if ((installFlags & PackageManager.INSTALL_INSTANT_APP) != 0) {
            Log.e(TAG, "Rollbacks not supported for instant app install");
            return false;
@@ -683,6 +697,25 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub {
        PackageRollbackInfo.PackageVersion installedVersion =
                new PackageRollbackInfo.PackageVersion(installedPackage.getLongVersionCode());

        for (int user : installedUsers) {
            final int storageFlags;
            if (StorageManager.isFileEncryptedNativeOrEmulated()
                    && !StorageManager.isUserKeyUnlocked(user)) {
                // We've encountered a user that hasn't unlocked on a FBE device, so we can't copy
                // across app user data until the user unlocks their device.
                Log.e(TAG, "User: " + user + " isn't unlocked, skipping CE userdata backup.");
                storageFlags = Installer.FLAG_STORAGE_DE;
            } else {
                storageFlags = Installer.FLAG_STORAGE_CE | Installer.FLAG_STORAGE_DE;
            }

            try {
                mInstaller.snapshotAppData(packageName, user, storageFlags);
            } catch (InstallerException ie) {
                Log.e(TAG, "Unable to create app data snapshot for: " + packageName, ie);
            }
        }

        PackageRollbackInfo info = new PackageRollbackInfo(
                packageName, newVersion, installedVersion);

@@ -715,33 +748,64 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub {
        return true;
    }

    // TODO: Don't copy this from PackageManagerShellCommand like this?
    private static class LocalIntentReceiver {
        private final LinkedBlockingQueue<Intent> mResult = new LinkedBlockingQueue<>();
    @Override
    public void restoreUserData(String packageName, int userId, int appId, long ceDataInode,
            String seInfo, int token) {
        if (Binder.getCallingUid() != Process.SYSTEM_UID) {
            throw new SecurityException("restoureUserData may only be called by the system.");
        }

        getHandler().post(() -> {
            PackageManagerInternal pmi = LocalServices.getService(PackageManagerInternal.class);
            // TODO(narayan): Should we make sure we're in the middle of a session commit for a
            // a package with this package name ? Otherwise it's possible we may roll back data
            // for some other downgrade.
            if (getRollbackForPackage(packageName) == null) {
                pmi.finishPackageInstall(token, false);
                return;
            }

            final int storageFlags;
            if (StorageManager.isFileEncryptedNativeOrEmulated()
                    && !StorageManager.isUserKeyUnlocked(userId)) {
                // We've encountered a user that hasn't unlocked on a FBE device, so we can't copy
                // across app user data until the user unlocks their device.
                Log.e(TAG, "User: " + userId + " isn't unlocked, skipping CE userdata restore.");

                storageFlags = Installer.FLAG_STORAGE_DE;
            } else {
                storageFlags = Installer.FLAG_STORAGE_CE | Installer.FLAG_STORAGE_DE;
            }

            try {
                mInstaller.restoreAppDataSnapshot(packageName, appId, ceDataInode,
                        seInfo, userId, storageFlags);
            } catch (InstallerException ie) {
                Log.e(TAG, "Unable to restore app data snapshot: " + packageName, ie);
            }

            pmi.finishPackageInstall(token, false);
        });
    }

    private class LocalIntentReceiver {
        final Consumer<Intent> mConsumer;

        LocalIntentReceiver(Consumer<Intent> consumer) {
            mConsumer = consumer;
        }

        private IIntentSender.Stub mLocalSender = new IIntentSender.Stub() {
            @Override
            public void send(int code, Intent intent, String resolvedType, IBinder whitelistToken,
                    IIntentReceiver finishedReceiver, String requiredPermission, Bundle options) {
                try {
                    mResult.offer(intent, 5, TimeUnit.SECONDS);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                getHandler().post(() -> mConsumer.accept(intent));
            }
        };

        public IntentSender getIntentSender() {
            return new IntentSender((IIntentSender) mLocalSender);
        }

        public Intent getResult() {
            try {
                return mResult.take();
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }

    /**
Loading