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

Commit e0dbc98f authored by Nikita Ioffe's avatar Nikita Ioffe
Browse files

Don't use ApexInfo.packageName in StagingManager

* ApexInfo.packageName has a misleading name since it represents name
  of apex module (e.g. com.android.tzdata), not the packageName from
  AndroidManifest.xml of an apex (e.g. com.google.android.tzdata). Later
  should be used in PackageManager-world while former is apexd-internal
  name.
* Restructured code a little bit to make the pre-reboot verification
  flow easier follow.

Test: atest CtsStagedInstallHostTestCases
Test: adb shell pull /system/apex/com.android.resolv.apex /tmp/resolv.apex &&
      adb install /tmp/resolv.apex &&
      adb reboot &&
      <verify staged session was applied>
Bug: 132324953

Change-Id: Iedfe87f6e70a2e1d2792487ac7b2891693ceb72a
parent e87acb22
Loading
Loading
Loading
Loading
+0 −28
Original line number Diff line number Diff line
@@ -36,7 +36,6 @@ import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.ServiceManager.ServiceNotFoundException;
import android.sysprop.ApexProperties;
import android.util.ArrayMap;
import android.util.Slog;

import com.android.internal.annotations.GuardedBy;
@@ -70,17 +69,6 @@ class ApexManager {
      */
    @GuardedBy("mLock")
    private List<PackageInfo> mAllPackagesCache;
    /**
     * A map from {@code apexName} to the {@Link PackageInfo} generated from the {@code
     * AndroidManifest.xml}.
     *
     * <p>Note that key of this map is {@code apexName} field which corresponds to the {@code name}
     * field of {@code apex_manifest.json}.
     */
    // TODO(b/132324953): remove.
    @GuardedBy("mLock")
    private ArrayMap<String, PackageInfo> mApexNameToPackageInfoCache;


    ApexManager(Context context) {
        mContext = context;
@@ -121,7 +109,6 @@ class ApexManager {
            if (mAllPackagesCache != null) {
                return;
            }
            mApexNameToPackageInfoCache = new ArrayMap<>();
            try {
                mAllPackagesCache = new ArrayList<>();
                HashSet<String> activePackagesSet = new HashSet<>();
@@ -145,8 +132,6 @@ class ApexManager {
                                                + pkg.packageName);
                            }
                            activePackagesSet.add(ai.packageName);
                            // TODO(b/132324953): remove.
                            mApexNameToPackageInfoCache.put(ai.packageName, pkg);
                        }
                        if (ai.isFactory) {
                            if (factoryPackagesSet.contains(pkg.packageName)) {
@@ -196,19 +181,6 @@ class ApexManager {
        return null;
    }

    /**
     * Returns a {@link PackageInfo} for an active APEX package keyed by it's {@code apexName}.
     *
     * @deprecated this API will soon be deleted, please don't depend on it.
     */
    // TODO(b/132324953): delete.
    @Deprecated
    @Nullable PackageInfo getPackageInfoForApexName(String apexName) {
        if (!isApexSupported()) return null;
        populateAllPackagesCacheIfNeeded();
        return mApexNameToPackageInfoCache.get(apexName);
    }

    /**
     * Retrieves information about all active APEX packages.
     *
+107 −101
Original line number Diff line number Diff line
@@ -30,6 +30,7 @@ import android.content.pm.PackageInfo;
import android.content.pm.PackageInstaller;
import android.content.pm.PackageInstaller.SessionInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageParser;
import android.content.pm.PackageParser.PackageParserException;
import android.content.pm.PackageParser.SigningDetails;
import android.content.pm.PackageParser.SigningDetails.SignatureSchemeVersion;
@@ -43,6 +44,7 @@ import android.os.ParcelFileDescriptor;
import android.os.PowerManager;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.util.IntArray;
import android.util.Slog;
import android.util.SparseArray;
import android.util.apk.ApkSignatureVerifier;
@@ -104,21 +106,22 @@ public class StagingManager {
        return new ParceledListSlice<>(result);
    }

    private boolean validateApexSignature(String apexPath, String packageName) {
    private void validateApexSignature(String apexPath, String packageName)
            throws PackageManagerException {
        final SigningDetails signingDetails;
        try {
            signingDetails = ApkSignatureVerifier.verify(apexPath, SignatureSchemeVersion.JAR);
        } catch (PackageParserException e) {
            Slog.e(TAG, "Unable to parse APEX package: " + apexPath, e);
            return false;
            throw new PackageManagerException(SessionInfo.STAGED_SESSION_VERIFICATION_FAILED,
                    "Failed to parse APEX package " + apexPath, e);
        }

        final PackageInfo packageInfo = mApexManager.getPackageInfoForApexName(packageName);

        final PackageInfo packageInfo = mApexManager.getPackageInfo(packageName,
                ApexManager.MATCH_ACTIVE_PACKAGE);
        if (packageInfo == null) {
            // Don't allow installation of new APEX.
            Slog.e(TAG, "Attempted to install a new apex " + packageName + ". Rejecting");
            return false;
            // This should never happen, because submitSessionToApexService ensures that no new
            // apexes were installed.
            throw new IllegalStateException("Unknown apex package " + packageName);
        }

        final SigningDetails existingSigningDetails;
@@ -126,73 +129,98 @@ public class StagingManager {
            existingSigningDetails = ApkSignatureVerifier.verify(
                packageInfo.applicationInfo.sourceDir, SignatureSchemeVersion.JAR);
        } catch (PackageParserException e) {
            Slog.e(TAG, "Unable to parse APEX package: "
                    + packageInfo.applicationInfo.sourceDir, e);
            return false;
            throw new PackageManagerException(SessionInfo.STAGED_SESSION_VERIFICATION_FAILED,
                    "Failed to parse APEX package " + packageInfo.applicationInfo.sourceDir, e);
        }

        // Now that we have both sets of signatures, demand that they're an exact match.
        if (Signature.areExactMatch(existingSigningDetails.signatures, signingDetails.signatures)) {
            return true;
            return;
        }

        return false;
        throw new PackageManagerException(SessionInfo.STAGED_SESSION_VERIFICATION_FAILED,
                "APK-container signature verification failed for package "
                        + packageName + ". Signature of file "
                        + apexPath + " does not match the signature of "
                        + " the package already installed.");
    }

    private boolean submitSessionToApexService(@NonNull PackageInstallerSession session,
                                               List<PackageInstallerSession> childSessions,
                                               ApexInfoList apexInfoList) {
        boolean submittedToApexd = mApexManager.submitStagedSession(
                session.sessionId,
                childSessions != null
                        ? childSessions.stream().mapToInt(s -> s.sessionId).toArray() :
                        new int[]{},
                apexInfoList);
    private List<PackageInfo> submitSessionToApexService(
            @NonNull PackageInstallerSession session) throws PackageManagerException {
        final IntArray childSessionsIds = new IntArray();
        if (session.isMultiPackage()) {
            for (int id : session.getChildSessionIds()) {
                if (isApexSession(mStagedSessions.get(id))) {
                    childSessionsIds.add(id);
                }
            }
        }
        final ApexInfoList apexInfoList = new ApexInfoList();
        boolean submittedToApexd = mApexManager.submitStagedSession(session.sessionId,
                childSessionsIds.toArray(), apexInfoList);
        if (!submittedToApexd) {
            session.setStagedSessionFailed(
                    SessionInfo.STAGED_SESSION_VERIFICATION_FAILED,
            throw new PackageManagerException(SessionInfo.STAGED_SESSION_VERIFICATION_FAILED,
                    "APEX staging failed, check logcat messages from apexd for more details.");
            return false;
        }
        final List<PackageInfo> result = new ArrayList<>();
        for (ApexInfo newPackage : apexInfoList.apexInfos) {
            PackageInfo activePackage = mApexManager.getPackageInfoForApexName(
                    newPackage.packageName);
            final PackageInfo pkg;
            try {
                pkg = PackageParser.generatePackageInfoFromApex(newPackage,
                        PackageManager.GET_META_DATA);
            } catch (PackageParserException e) {
                throw new PackageManagerException(SessionInfo.STAGED_SESSION_VERIFICATION_FAILED,
                        "Failed to parse APEX package " + newPackage.packagePath, e);
            }
            final PackageInfo activePackage = mApexManager.getPackageInfo(pkg.packageName,
                    ApexManager.MATCH_ACTIVE_PACKAGE);
            if (activePackage == null) {
                continue;
                Slog.w(TAG, "Attempting to install new APEX package " + pkg.packageName);
                throw new PackageManagerException(SessionInfo.STAGED_SESSION_VERIFICATION_FAILED,
                        "It is forbidden to install new APEX packages.");
            }
            checkRequiredVersionCode(session, activePackage);
            checkDowngrade(session, activePackage, pkg);
            result.add(pkg);
        }
        return result;
    }
            long activeVersion = activePackage.applicationInfo.longVersionCode;
            if (session.params.requiredInstalledVersionCode
                    != PackageManager.VERSION_CODE_HIGHEST) {
                if (activeVersion != session.params.requiredInstalledVersionCode) {
                    session.setStagedSessionFailed(
                            SessionInfo.STAGED_SESSION_VERIFICATION_FAILED,
                            "Installed version of APEX package " + newPackage.packageName
                            + " does not match required. Active version: " + activeVersion
                            + " required: " + session.params.requiredInstalledVersionCode);

    private void checkRequiredVersionCode(final PackageInstallerSession session,
            final PackageInfo activePackage) throws PackageManagerException {
        if (session.params.requiredInstalledVersionCode == PackageManager.VERSION_CODE_HIGHEST) {
            return;
        }
        final long activeVersion = activePackage.applicationInfo.longVersionCode;
        if (activeVersion != session.params.requiredInstalledVersionCode) {
            if (!mApexManager.abortActiveSession()) {
                Slog.e(TAG, "Failed to abort apex session " + session.sessionId);
            }
                    return false;
            throw new PackageManagerException(
                    SessionInfo.STAGED_SESSION_VERIFICATION_FAILED,
                    "Installed version of APEX package " + activePackage.packageName
                            + " does not match required. Active version: " + activeVersion
                            + " required: " + session.params.requiredInstalledVersionCode);
        }
    }

    private void checkDowngrade(final PackageInstallerSession session,
            final PackageInfo activePackage, final PackageInfo newPackage)
            throws PackageManagerException {
        final long activeVersion = activePackage.applicationInfo.longVersionCode;
        final long newVersionCode = newPackage.applicationInfo.longVersionCode;
        boolean allowsDowngrade = PackageManagerServiceUtils.isDowngradePermitted(
                session.params.installFlags, activePackage.applicationInfo.flags);
            if (activeVersion > newPackage.versionCode && !allowsDowngrade) {
                session.setStagedSessionFailed(
                        SessionInfo.STAGED_SESSION_VERIFICATION_FAILED,
                        "Downgrade of APEX package " + newPackage.packageName
                                + " is not allowed. Active version: " + activeVersion
                                + " attempted: " + newPackage.versionCode);

        if (activeVersion > newVersionCode && !allowsDowngrade) {
            if (!mApexManager.abortActiveSession()) {
                Slog.e(TAG, "Failed to abort apex session " + session.sessionId);
            }
                return false;
            }
            throw new PackageManagerException(
                    SessionInfo.STAGED_SESSION_VERIFICATION_FAILED,
                    "Downgrade of APEX package " + newPackage.packageName
                            + " is not allowed. Active version: " + activeVersion
                            + " attempted: " + newVersionCode);
        }
        return true;
    }

    private static boolean isApexSession(@NonNull PackageInstallerSession session) {
@@ -200,32 +228,21 @@ public class StagingManager {
    }

    private void preRebootVerification(@NonNull PackageInstallerSession session) {
        boolean success = true;

        final ApexInfoList apexInfoList = new ApexInfoList();
        final boolean hasApex = sessionContainsApex(session);
        // APEX checks. For single-package sessions, check if they contain an APEX. For
        // multi-package sessions, find all the child sessions that contain an APEX.
        if (!session.isMultiPackage()
                && isApexSession(session)) {
            success = submitSessionToApexService(session, null, apexInfoList);

        } else if (session.isMultiPackage()) {
            List<PackageInstallerSession> childSessions =
                    Arrays.stream(session.getChildSessionIds())
                            // Retrieve cached sessions matching ids.
                            .mapToObj(i -> mStagedSessions.get(i))
                            // Filter only the ones containing APEX.
                            .filter(childSession -> isApexSession(childSession))
                            .collect(Collectors.toList());
            if (!childSessions.isEmpty()) {
                success = submitSessionToApexService(session, childSessions, apexInfoList);
            } // else this is a staged multi-package session with no APEX files.
        if (hasApex) {
            try {
                final List<PackageInfo> apexPackages = submitSessionToApexService(session);
                for (PackageInfo apexPackage : apexPackages) {
                    validateApexSignature(apexPackage.applicationInfo.sourceDir,
                            apexPackage.packageName);
                }

        if (!success) {
            // submitSessionToApexService will populate error.
            } catch (PackageManagerException e) {
                session.setStagedSessionFailed(e.error, e.getMessage());
                return;
            }
        }

        if (sessionContainsApk(session)) {
            if (!installApksInSession(session, /* preReboot */ true)) {
@@ -237,25 +254,6 @@ public class StagingManager {
            }
        }

        if (apexInfoList.apexInfos != null && apexInfoList.apexInfos.length > 0) {
            // For APEXes, we validate the signature here before we mark the session as ready,
            // so we fail the session early if there is a signature mismatch. For APKs, the
            // signature verification will be done by the package manager at the point at which
            // it applies the staged install.
            for (ApexInfo apexPackage : apexInfoList.apexInfos) {
                if (!validateApexSignature(apexPackage.packagePath,
                        apexPackage.packageName)) {
                    session.setStagedSessionFailed(SessionInfo.STAGED_SESSION_VERIFICATION_FAILED,
                            "APK-container signature verification failed for package "
                                    + apexPackage.packageName + ". Signature of file "
                                    + apexPackage.packagePath + " does not match the signature of "
                                    + " the package already installed.");
                    // TODO(b/118865310): abort the session on apexd.
                    return;
                }
            }
        }

        if ((session.params.installFlags & PackageManager.INSTALL_ENABLE_ROLLBACK) != 0) {
            // If rollback is enabled for this session, we call through to the RollbackManager
            // with the list of sessions it must enable rollback for. Note that notifyStagedSession
@@ -273,9 +271,17 @@ public class StagingManager {
            }
        }

        // Proactively mark session as ready before calling apexd. Although this call order looks
        // counter-intuitive, this is the easiest way to ensure that session won't end up in the
        // inconsistent state:
        //  - If device gets rebooted right before call to apexd, then apexd will never activate
        //      apex files of this staged session. This will result in StagingManager failing the
        //      session.
        // On the other hand, if the order of the calls was inverted (first call apexd, then mark
        // session as ready), then if a device gets rebooted right after the call to apexd, only
        // apex part of the train will be applied, leaving device in an inconsistent state.
        session.setStagedSessionReady();
        if (sessionContainsApex(session)
                && !mApexManager.markStagedSessionReady(session.sessionId)) {
        if (hasApex && !mApexManager.markStagedSessionReady(session.sessionId)) {
            session.setStagedSessionFailed(SessionInfo.STAGED_SESSION_VERIFICATION_FAILED,
                            "APEX staging failed, check logcat messages from apexd for more "
                            + "details.");