diff options
Diffstat (limited to 'services/core/java/com/android/server/pm/StagingManager.java')
-rw-r--r-- | services/core/java/com/android/server/pm/StagingManager.java | 225 |
1 files changed, 118 insertions, 107 deletions
diff --git a/services/core/java/com/android/server/pm/StagingManager.java b/services/core/java/com/android/server/pm/StagingManager.java index 88d681f973c4..2705455078ff 100644 --- a/services/core/java/com/android/server/pm/StagingManager.java +++ b/services/core/java/com/android/server/pm/StagingManager.java @@ -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 apexModuleName) { + 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(apexModuleName); - + 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 " + apexModuleName + ". 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,99 @@ 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); - if (!submittedToApexd) { - session.setStagedSessionFailed( - SessionInfo.STAGED_SESSION_VERIFICATION_FAILED, - "APEX staging failed, check logcat messages from apexd for more details."); - return false; + 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); + } + } } - for (ApexInfo newModule : apexInfoList.apexInfos) { - PackageInfo activePackage = mApexManager.getPackageInfoForApexName( - newModule.moduleName); + // submitStagedSession will throw a PackageManagerException if apexd verification fails, + // which will be propagated to populate stagedSessionErrorMessage of this session. + final ApexInfoList apexInfoList = mApexManager.submitStagedSession(session.sessionId, + childSessionsIds.toArray()); + final List<PackageInfo> result = new ArrayList<>(); + for (ApexInfo apexInfo : apexInfoList.apexInfos) { + final PackageInfo packageInfo; + int flags = PackageManager.GET_META_DATA; + PackageParser.Package pkg; + try { + File apexFile = new File(apexInfo.modulePath); + PackageParser pp = new PackageParser(); + pkg = pp.parsePackage(apexFile, flags, false); + } catch (PackageParserException e) { + throw new PackageManagerException(SessionInfo.STAGED_SESSION_VERIFICATION_FAILED, + "Failed to parse APEX package " + apexInfo.modulePath, e); + } + packageInfo = PackageParser.generatePackageInfo(pkg, apexInfo, flags); + final PackageInfo activePackage = mApexManager.getPackageInfo(packageInfo.packageName, + ApexManager.MATCH_ACTIVE_PACKAGE); if (activePackage == null) { - continue; - } - 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 " + activePackage.packageName - + " does not match required. Active version: " + activeVersion - + " required: " + session.params.requiredInstalledVersionCode); - - if (!mApexManager.abortActiveSession()) { - Slog.e(TAG, "Failed to abort apex session " + session.sessionId); - } - return false; - } + Slog.w(TAG, "Attempting to install new APEX package " + packageInfo.packageName); + throw new PackageManagerException(SessionInfo.STAGED_SESSION_VERIFICATION_FAILED, + "It is forbidden to install new APEX packages."); } + checkRequiredVersionCode(session, activePackage); + checkDowngrade(session, activePackage, packageInfo); + result.add(packageInfo); + } + return result; + } - boolean allowsDowngrade = PackageManagerServiceUtils.isDowngradePermitted( - session.params.installFlags, activePackage.applicationInfo.flags); - if (activeVersion > newModule.versionCode && !allowsDowngrade) { - session.setStagedSessionFailed( - SessionInfo.STAGED_SESSION_VERIFICATION_FAILED, - "Downgrade of APEX package " + activePackage.packageName - + " is not allowed. Active version: " + activeVersion - + " attempted: " + newModule.versionCode); + 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); + } + 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); + } + } - if (!mApexManager.abortActiveSession()) { - Slog.e(TAG, "Failed to abort apex session " + session.sessionId); - } - return false; + 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 > newVersionCode && !allowsDowngrade) { + if (!mApexManager.abortActiveSession()) { + Slog.e(TAG, "Failed to abort apex session " + session.sessionId); } + 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,31 +229,20 @@ 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 (!success) { - // submitSessionToApexService will populate error. - return; + if (hasApex) { + try { + final List<PackageInfo> apexPackages = submitSessionToApexService(session); + for (PackageInfo apexPackage : apexPackages) { + validateApexSignature(apexPackage.applicationInfo.sourceDir, + apexPackage.packageName); + } + } catch (PackageManagerException e) { + session.setStagedSessionFailed(e.error, e.getMessage()); + return; + } } if (sessionContainsApk(session)) { @@ -237,25 +255,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 apexModule : apexInfoList.apexInfos) { - if (!validateApexSignature(apexModule.modulePath, - apexModule.moduleName)) { - session.setStagedSessionFailed(SessionInfo.STAGED_SESSION_VERIFICATION_FAILED, - "APK-container signature verification failed for package " - + apexModule.moduleName + ". Signature of file " - + apexModule.modulePath + " 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,12 +272,24 @@ 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)) { - session.setStagedSessionFailed(SessionInfo.STAGED_SESSION_VERIFICATION_FAILED, - "APEX staging failed, check logcat messages from apexd for more " - + "details."); + if (!hasApex) { + // Session doesn't contain apex, nothing to do. + return; + } + try { + mApexManager.markStagedSessionReady(session.sessionId); + } catch (PackageManagerException e) { + session.setStagedSessionFailed(e.error, e.getMessage()); } } |