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

Commit cff138a5 authored by JW Wang's avatar JW Wang Committed by Android (Google) Code Review
Browse files

Merge changes I713741cf,I8e3021ec,I8a206034,I3d31cdef

* changes:
  Merge mRelinquished and mInPreRebootVerification (7/n)
  Add unit tests (6/n)
  Check package overlaps for rebootless APEX (5/n)
  Move the code about APK verification (4/n)
parents 112fe310 0621084c
Loading
Loading
Loading
Loading
+47 −145
Original line number Diff line number Diff line
@@ -354,8 +354,13 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {

    private final AtomicBoolean mCommitted = new AtomicBoolean(false);

    /**
     * True if staging files are being used by external entities like {@link PackageSessionVerifier}
     * or {@link PackageManagerService} which means it is not safe for {@link #abandon()} to clean
     * up the files.
     */
    @GuardedBy("mLock")
    private boolean mRelinquished = false;
    private boolean mStageDirInUse = false;

    /** Permissions have been accepted by the user (see {@link #setPermissionsResult}) */
    @GuardedBy("mLock")
@@ -466,12 +471,6 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
        @GuardedBy("mLock")
        @Nullable
        private Runnable mPendingAbandonCallback;
        /**
         * {@code true} if pre-reboot verification is ongoing which means it is not safe for
         * {@link #abandon()} to clean up staging directories.
         */
        @GuardedBy("mLock")
        private boolean mInPreRebootVerification;

        StagedSession(boolean isReady, boolean isApplied, boolean isFailed, int errorCode,
                String errorMessage) {
@@ -707,7 +706,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
                    dispatchSessionFinished(INSTALL_FAILED_ABORTED, "Session was abandoned", null);
                    maybeCleanUpChildSessions();
                };
                if (mInPreRebootVerification) {
                if (mStageDirInUse) {
                    // Pre-reboot verification is ongoing, not safe to clean up the session yet.
                    mPendingAbandonCallback = r;
                    mCallback.onStagedSessionChanged(PackageInstallerSession.this);
@@ -717,30 +716,14 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
            r.run();
        }

        /**
         * Called when pre-reboot verification is about to start. This shouldn't be called
         * on a destroyed session.
         */
        private void notifyStartPreRebootVerification() {
            synchronized (mLock) {
                Preconditions.checkState(!mDestroyed);
                if (mInPreRebootVerification) {
                    throw new IllegalStateException("Pre-reboot verification has started");
                }
                mInPreRebootVerification = true;
            }
        }

        /**
         * Called when pre-reboot verification has ended.
         * Now it is safe to clean up the session if {@link #abandon()} has been called previously.
         */
        private void notifyEndPreRebootVerification() {
            synchronized (mLock) {
                if (!mInPreRebootVerification) {
                    throw new IllegalStateException("Pre-reboot verification not started");
                }
                mInPreRebootVerification = false;
                Preconditions.checkState(mStageDirInUse);
                mStageDirInUse = false;
            }
            dispatchPendingAbandonCallback();
        }
@@ -756,7 +739,6 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
            assertCallerIsOwnerOrRootOrSystem();
            Preconditions.checkArgument(isCommitted());
            Preconditions.checkArgument(!isInTerminalState());
            notifyStartPreRebootVerification();
            verify();
        }

@@ -2351,21 +2333,6 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
                    return;
                }
            }

            if (!params.isStaged) {
                // For non-staged APEX installs also check if there is a staged session that
                // contains the same APEX. If that's the case, we should fail this session.
                synchronized (mLock) {
                    int sessionId = mStagingManager.getSessionIdByPackageName(mPackageName);
                    if (sessionId != -1) {
                        onSessionValidationFailure(
                                PackageManager.INSTALL_FAILED_VERIFICATION_FAILURE,
                                "Staged session " + sessionId + " already contains "
                                        + mPackageName);
                        return;
                    }
                }
            }
        }

        if (params.isStaged) {
@@ -2418,9 +2385,9 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
            return;
        }
        synchronized (mLock) {
            if (mRelinquished) {
            if (mStageDirInUse) {
                throw new PackageManagerException(INSTALL_FAILED_INTERNAL_ERROR,
                        "Session relinquished");
                        "Session files in use");
            }
            if (mDestroyed) {
                throw new PackageManagerException(INSTALL_FAILED_INTERNAL_ERROR,
@@ -2482,9 +2449,9 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {

    private void parseApkAndExtractNativeLibraries() throws PackageManagerException {
        synchronized (mLock) {
            if (mRelinquished) {
            if (mStageDirInUse) {
                throw new PackageManagerException(INSTALL_FAILED_INTERNAL_ERROR,
                        "Session relinquished");
                        "Session files in use");
            }
            if (mDestroyed) {
                throw new PackageManagerException(INSTALL_FAILED_INTERNAL_ERROR,
@@ -2524,34 +2491,27 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {

    private void verifyNonStaged()
            throws PackageManagerException {
        final VerificationParams verifyingSession = prepareForVerification();
        if (isMultiPackage()) {
            final List<PackageInstallerSession> childSessions = getChildSessions();
            List<VerificationParams> verifyingChildSessions =
                    new ArrayList<>(childSessions.size());
            boolean success = true;
            PackageManagerException failure = null;
            for (int i = 0; i < childSessions.size(); ++i) {
                final PackageInstallerSession session = childSessions.get(i);
                try {
                    final VerificationParams verifyingChildSession =
                            session.prepareForVerification();
                    verifyingChildSessions.add(verifyingChildSession);
                } catch (PackageManagerException e) {
                    failure = e;
                    success = false;
                }
        synchronized (mLock) {
            if (mDestroyed) {
                throw new PackageManagerException(INSTALL_FAILED_INTERNAL_ERROR,
                        "Session destroyed");
            }
            if (!success) {
                sendOnPackageInstalled(mContext, getRemoteStatusReceiver(), sessionId,
                        isInstallerDeviceOwnerOrAffiliatedProfileOwner(), userId, null,
                        failure.error, failure.getLocalizedMessage(), null);
                return;
            // Set this flag to prevent abandon() from deleting staging files while verification is
            // in progress. For staged sessions, we will reset this flag when verification is done
            // so abandon() can take effect. For non-staged sessions, the staging files will be
            // deleted when install is completed (no matter success or not). No need to reset
            // the flag.
            mStageDirInUse = true;
        }
            verifyingSession.verifyStage(verifyingChildSessions);
        mSessionProvider.getSessionVerifier().verifyNonStaged(this, (error, msg) -> {
            mHandler.post(() -> {
                if (error == INSTALL_SUCCEEDED) {
                    onVerificationComplete();
                } else {
            verifyingSession.verifyStage();
                    onSessionVerificationFailure(error, msg);
                }
            });
        });
    }

    private void install() {
@@ -2600,32 +2560,6 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
        }
    }

    /**
     * Stages this session for verification and returns a
     * {@link VerificationParams} representing this new staged state or null
     * in case permissions need to be requested before verification can proceed.
     */
    @NonNull
    private VerificationParams prepareForVerification() throws PackageManagerException {
        assertNotLocked("makeSessionActive");

        synchronized (mLock) {
            if (mRelinquished) {
                throw new PackageManagerException(INSTALL_FAILED_INTERNAL_ERROR,
                        "Session relinquished");
            }
            if (mDestroyed) {
                throw new PackageManagerException(INSTALL_FAILED_INTERNAL_ERROR,
                        "Session destroyed");
            }
            if (!mSealed) {
                throw new PackageManagerException(INSTALL_FAILED_INTERNAL_ERROR,
                        "Session not sealed");
            }
            return makeVerificationParamsLocked();
        }
    }

    private void sendPendingUserActionIntent() {
        // User needs to confirm installation;
        // give installer an intent they can use to involve
@@ -2641,50 +2575,6 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
        closeInternal(false);
    }

    @GuardedBy("mLock")
    @Nullable
    /**
     * Returns a {@link com.android.server.pm.VerificationParams}
     */
    private VerificationParams makeVerificationParamsLocked() {
        final IPackageInstallObserver2 localObserver;
        if (!hasParentSessionId()) {
            // Avoid attaching this observer to child session since they won't use it.
            localObserver = new IPackageInstallObserver2.Stub() {
                @Override
                public void onUserActionRequired(Intent intent) {
                    throw new IllegalStateException();
                }

                @Override
                public void onPackageInstalled(String basePackageName, int returnCode, String msg,
                        Bundle extras) {
                    mHandler.post(() -> {
                        if (returnCode == INSTALL_SUCCEEDED) {
                            onVerificationComplete();
                        } else {
                            onSessionVerificationFailure(returnCode, msg);
                        }
                    });
                }
            };
        } else {
            localObserver = null;
        }

        final UserHandle user;
        if ((params.installFlags & PackageManager.INSTALL_ALL_USERS) != 0) {
            user = UserHandle.ALL;
        } else {
            user = new UserHandle(userId);
        }

        mRelinquished = true;

        return new VerificationParams(user, stageDir, localObserver, params,
                mInstallSource, mInstallerUid, mSigningDetails, sessionId, mPackageLite, mPm);
    }

    @WorkerThread
    private void onVerificationComplete() {
        // APK verification is done. Continue the installation depending on whether it is a
@@ -3610,6 +3500,18 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
        }
    }

    SigningDetails getSigningDetails() {
        synchronized (mLock) {
            return mSigningDetails;
        }
    }

    PackageLite getPackageLite() {
        synchronized (mLock) {
            return mPackageLite;
        }
    }

    private static String getRelativePath(File file, File base) throws IOException {
        final String pathStr = file.getAbsolutePath();
        final String baseStr = base.getAbsolutePath();
@@ -3816,8 +3718,8 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
        synchronized (mLock) {
            assertNotChild("abandonNonStaged");
            assertCallerIsOwnerOrRootOrSystem();
            if (mRelinquished) {
                if (LOGD) Slog.d(TAG, "Ignoring abandon after commit relinquished control");
            if (mStageDirInUse) {
                if (LOGD) Slog.d(TAG, "Ignoring abandon for staging files are in use");
                return;
            }
            destroyInternal();
@@ -4445,7 +4347,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
        pw.printPair("mCommitted", mCommitted);
        pw.printPair("mSealed", mSealed);
        pw.printPair("mPermissionsManuallyAccepted", mPermissionsManuallyAccepted);
        pw.printPair("mRelinquished", mRelinquished);
        pw.printPair("mStageDirInUse", mStageDirInUse);
        pw.printPair("mDestroyed", mDestroyed);
        pw.printPair("mFds", mFds.size());
        pw.printPair("mBridges", mBridges.size());
+121 −7
Original line number Diff line number Diff line
@@ -21,6 +21,8 @@ import android.apex.ApexInfoList;
import android.apex.ApexSessionInfo;
import android.apex.ApexSessionParams;
import android.content.Context;
import android.content.Intent;
import android.content.pm.IPackageInstallObserver2;
import android.content.pm.PackageInfo;
import android.content.pm.PackageInstaller.SessionInfo;
import android.content.pm.PackageManager;
@@ -31,13 +33,16 @@ import android.content.pm.parsing.result.ParseResult;
import android.content.pm.parsing.result.ParseTypeImpl;
import android.content.rollback.RollbackInfo;
import android.content.rollback.RollbackManager;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.RemoteException;
import android.os.UserHandle;
import android.util.IntArray;
import android.util.Slog;
import android.util.apk.ApkSignatureVerifier;

import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.content.PackageHelper;
import com.android.server.LocalServices;
import com.android.server.pm.parsing.PackageParser2;
@@ -76,6 +81,79 @@ final class PackageSessionVerifier {
        mHandler = new Handler(looper);
    }

    @VisibleForTesting
    PackageSessionVerifier() {
        mContext = null;
        mPm = null;
        mApexManager = null;
        mPackageParserSupplier = null;
        mHandler = null;
    }

    /**
     * Runs verifications that are common to both staged and non-staged sessions.
     */
    public void verifyNonStaged(PackageInstallerSession session, Callback callback) {
        mHandler.post(() -> {
            try {
                storeSession(session.mStagedSession);
                if (session.isMultiPackage()) {
                    for (PackageInstallerSession child : session.getChildSessions()) {
                        checkRebootlessApex(child);
                    }
                } else {
                    checkRebootlessApex(session);
                }
                verifyAPK(session, callback);
            } catch (PackageManagerException e) {
                callback.onResult(e.error, e.getMessage());
            }
        });
    }

    /**
     * Runs verifications particular to APK. This includes APEX sessions since an APEX can also
     * be treated as APK.
     */
    private void verifyAPK(PackageInstallerSession session, Callback callback)
            throws PackageManagerException {
        final IPackageInstallObserver2 observer = new IPackageInstallObserver2.Stub() {
            @Override
            public void onUserActionRequired(Intent intent) {
                throw new IllegalStateException();
            }
            @Override
            public void onPackageInstalled(String basePackageName, int returnCode, String msg,
                    Bundle extras) {
                callback.onResult(returnCode, msg);
            }
        };
        final VerificationParams verifyingSession = makeVerificationParams(session, observer);
        if (session.isMultiPackage()) {
            final List<PackageInstallerSession> childSessions = session.getChildSessions();
            List<VerificationParams> verifyingChildSessions = new ArrayList<>(childSessions.size());
            for (PackageInstallerSession child : childSessions) {
                verifyingChildSessions.add(makeVerificationParams(child, null));
            }
            verifyingSession.verifyStage(verifyingChildSessions);
        } else {
            verifyingSession.verifyStage();
        }
    }

    private VerificationParams makeVerificationParams(
            PackageInstallerSession session, IPackageInstallObserver2 observer) {
        final UserHandle user;
        if ((session.params.installFlags & PackageManager.INSTALL_ALL_USERS) != 0) {
            user = UserHandle.ALL;
        } else {
            user = new UserHandle(session.userId);
        }
        return new VerificationParams(user, session.stageDir, observer, session.params,
                session.getInstallSource(), session.getInstallerUid(), session.getSigningDetails(),
                session.sessionId, session.getPackageLite(), mPm);
    }

    /**
     * Starts pre-reboot verification for the staged-session. This operation is broken into the
     * following phases:
@@ -94,7 +172,6 @@ final class PackageSessionVerifier {
        Slog.d(TAG, "Starting preRebootVerification for session " + session.sessionId());
        mHandler.post(() -> {
            try {
                storeSession(session);
                checkActiveSessions();
                checkRollbacks(session);
                if (session.isMultiPackage()) {
@@ -114,9 +191,12 @@ final class PackageSessionVerifier {
    /**
     * Stores staged-sessions for checking package overlapping and rollback conflicts.
     */
    private void storeSession(StagingManager.StagedSession session) {
    @VisibleForTesting
    void storeSession(StagingManager.StagedSession session) {
        if (session != null) {
            mStagedSessions.add(session);
        }
    }

    private void onVerificationSuccess(StagingManager.StagedSession session, Callback callback) {
        callback.onResult(SessionInfo.STAGED_SESSION_NO_ERROR, null);
@@ -381,18 +461,50 @@ final class PackageSessionVerifier {
        return mApexManager.abortStagedSession(sessionId);
    }

    /**
     * Fails this rebootless APEX session if the same package name found in any staged sessions.
     */
    @VisibleForTesting
    void checkRebootlessApex(PackageInstallerSession session)
            throws PackageManagerException {
        if (session.isStaged() || !session.isApexSession()) {
            return;
        }
        String packageName = session.getPackageName();
        if (packageName == null) {
            throw new PackageManagerException(
                    PackageManager.INSTALL_FAILED_VERIFICATION_FAILURE,
                    "Invalid session " + session.sessionId + " with package name null");
        }
        for (StagingManager.StagedSession stagedSession : mStagedSessions) {
            if (stagedSession.isDestroyed() || stagedSession.isInTerminalState()) {
                continue;
            }
            if (stagedSession.sessionContains(s -> packageName.equals(s.getPackageName()))) {
                // Staged-sessions take priority over rebootless APEX
                throw new PackageManagerException(
                        PackageManager.INSTALL_FAILED_VERIFICATION_FAILURE,
                        "Staged session " + stagedSession.sessionId() + " already contains "
                                + packageName);
            }
        }
    }

    /**
     * Checks if multiple staged-sessions are supported. It is supported only when the system
     * supports checkpoint.
     */
    private void checkActiveSessions() throws PackageManagerException {
        final boolean supportsCheckpoint;
        try {
            supportsCheckpoint = PackageHelper.getStorageManager().supportsCheckpoint();
            checkActiveSessions(PackageHelper.getStorageManager().supportsCheckpoint());
        } catch (RemoteException e) {
            throw new PackageManagerException(SessionInfo.STAGED_SESSION_VERIFICATION_FAILED,
                    "Can't query fs-checkpoint status : " + e);
        }
    }

    @VisibleForTesting
    void checkActiveSessions(boolean supportsCheckpoint) throws PackageManagerException {
        int activeSessions = 0;
        for (StagingManager.StagedSession stagedSession : mStagedSessions) {
            if (stagedSession.isDestroyed() || stagedSession.isInTerminalState()) {
@@ -412,7 +524,8 @@ final class PackageSessionVerifier {
     * downgrade of SDK extension which in turn will result in dependency violation of other
     * non-rollback sessions.
     */
    private void checkRollbacks(StagingManager.StagedSession session)
    @VisibleForTesting
    void checkRollbacks(StagingManager.StagedSession session)
            throws PackageManagerException {
        for (StagingManager.StagedSession stagedSession : mStagedSessions) {
            if (stagedSession.isDestroyed() || stagedSession.isInTerminalState()) {
@@ -449,7 +562,8 @@ final class PackageSessionVerifier {
     * @param child The child session whose package name will be checked.
     *              This will equal to {@code parent} for a single-package session.
     */
    private void checkOverlaps(StagingManager.StagedSession parent,
    @VisibleForTesting
    void checkOverlaps(StagingManager.StagedSession parent,
            StagingManager.StagedSession child) throws PackageManagerException {
        final String packageName = child.getPackageName();
        if (packageName == null) {
+134 −0

File added.

Preview size limit exceeded, changes collapsed.