From b929a547830245e904ee36deac3c6a2706cd1ce0 Mon Sep 17 00:00:00 2001 From: Mohammad Samiul Islam Date: Tue, 10 Sep 2019 16:26:26 +0100 Subject: [PATCH 1/2] Refactor commitApkSession into two separate methods Bug: 139867014 Test: atest CtsStagedInstallHostTestCases RollbackTest Change-Id: I49e6b360bea09a6c68721ebc01167cef6730b4d6 Merged-In: I49e6b360bea09a6c68721ebc01167cef6730b4d6 --- .../com/android/server/pm/StagingManager.java | 151 ++++++++++-------- 1 file changed, 84 insertions(+), 67 deletions(-) diff --git a/services/core/java/com/android/server/pm/StagingManager.java b/services/core/java/com/android/server/pm/StagingManager.java index 1cea4ca1b945f..6b4ef698a8f4c 100644 --- a/services/core/java/com/android/server/pm/StagingManager.java +++ b/services/core/java/com/android/server/pm/StagingManager.java @@ -318,7 +318,7 @@ public class StagingManager { // The APEX part of the session is activated, proceed with the installation of APKs. try { Slog.d(TAG, "Installing APK packages in session " + session.sessionId); - installApksInSession(session, /* preReboot */ false); + installApksInSession(session); } catch (PackageManagerException e) { session.setStagedSessionFailed(e.error, e.getMessage()); @@ -410,72 +410,23 @@ public class StagingManager { return apkSession; } - private void commitApkSession(@NonNull PackageInstallerSession apkSession, - PackageInstallerSession originalSession, boolean preReboot) - throws PackageManagerException { - final int errorCode = preReboot ? SessionInfo.STAGED_SESSION_VERIFICATION_FAILED - : SessionInfo.STAGED_SESSION_ACTIVATION_FAILED; - if (preReboot) { - final LocalIntentReceiverAsync receiver = new LocalIntentReceiverAsync( - (Intent result) -> { - int status = result.getIntExtra(PackageInstaller.EXTRA_STATUS, - PackageInstaller.STATUS_FAILURE); - if (status != PackageInstaller.STATUS_SUCCESS) { - final String errorMessage = result.getStringExtra( - PackageInstaller.EXTRA_STATUS_MESSAGE); - Slog.e(TAG, "Failure to install APK staged session " - + originalSession.sessionId + " [" + errorMessage + "]"); - originalSession.setStagedSessionFailed(errorCode, errorMessage); - return; - } - mPreRebootVerificationHandler.notifyPreRebootVerification_Apk_Complete( - originalSession.sessionId); - }); - apkSession.commit(receiver.getIntentSender(), false); - return; - } - - if ((apkSession.params.installFlags & PackageManager.INSTALL_ENABLE_ROLLBACK) != 0) { - // If rollback is available for this session, notify the rollback - // manager of the apk session so it can properly enable rollback. - final IRollbackManager rm = IRollbackManager.Stub.asInterface( - ServiceManager.getService(Context.ROLLBACK_SERVICE)); - try { - rm.notifyStagedApkSession(originalSession.sessionId, apkSession.sessionId); - } catch (RemoteException re) { - Slog.e(TAG, "Failed to notifyStagedApkSession for session: " - + originalSession.sessionId, re); - } - } - - final LocalIntentReceiverSync receiver = new LocalIntentReceiverSync(); - apkSession.commit(receiver.getIntentSender(), false); - final Intent result = receiver.getResult(); - final int status = result.getIntExtra(PackageInstaller.EXTRA_STATUS, - PackageInstaller.STATUS_FAILURE); - if (status != PackageInstaller.STATUS_SUCCESS) { - final String errorMessage = result.getStringExtra( - PackageInstaller.EXTRA_STATUS_MESSAGE); - Slog.e(TAG, "Failure to install APK staged session " - + originalSession.sessionId + " [" + errorMessage + "]"); - throw new PackageManagerException(errorCode, errorMessage); - } - } - - private void installApksInSession(@NonNull PackageInstallerSession session, - boolean preReboot) throws PackageManagerException { + /** + * Extract apks in the given session into a new session. Returns {@code null} if there is no + * apks in the given session. Only parent session is returned for multi-package session. + */ + @Nullable + private PackageInstallerSession extractApksInSession(PackageInstallerSession session, + boolean preReboot) throws PackageManagerException { final int errorCode = preReboot ? SessionInfo.STAGED_SESSION_VERIFICATION_FAILED : SessionInfo.STAGED_SESSION_ACTIVATION_FAILED; if (!session.isMultiPackage() && !isApexSession(session)) { - // APK single-packaged staged session. Do a regular install. - PackageInstallerSession apkSession = createAndWriteApkSession(session, preReboot); - commitApkSession(apkSession, session, preReboot); + return createAndWriteApkSession(session, preReboot); } else if (session.isMultiPackage()) { // For multi-package staged sessions containing APKs, we identify which child sessions // contain an APK, and with those then create a new multi-package group of sessions, // carrying over all the session parameters and unmarking them as staged. On commit the // sessions will be installed atomically. - List childSessions; + final List childSessions; synchronized (mStagedSessions) { childSessions = Arrays.stream(session.getChildSessionIds()) @@ -487,18 +438,18 @@ public class StagingManager { } if (childSessions.isEmpty()) { // APEX-only multi-package staged session, nothing to do. - return; + return null; } - PackageInstaller.SessionParams params = session.params.copy(); + final PackageInstaller.SessionParams params = session.params.copy(); params.isStaged = false; if (preReboot) { params.installFlags &= ~PackageManager.INSTALL_ENABLE_ROLLBACK; } // TODO(b/129744602): use the userid from the original session. - int apkParentSessionId = mPi.createSession( + final int apkParentSessionId = mPi.createSession( params, session.getInstallerPackageName(), 0 /* UserHandle.SYSTEM */); - PackageInstallerSession apkParentSession = mPi.getSession(apkParentSessionId); + final PackageInstallerSession apkParentSession = mPi.getSession(apkParentSessionId); try { apkParentSession.open(); } catch (IOException e) { @@ -519,9 +470,75 @@ public class StagingManager { "Failed to add a child session " + apkChildSession.sessionId); } } - commitApkSession(apkParentSession, session, preReboot); + return apkParentSession; + } + return null; + } + + private void verifyApksInSession(PackageInstallerSession session) + throws PackageManagerException { + + final PackageInstallerSession apksToVerify = extractApksInSession( + session, /* preReboot */ true); + if (apksToVerify == null) { + return; + } + + final LocalIntentReceiverAsync receiver = new LocalIntentReceiverAsync( + (Intent result) -> { + int status = result.getIntExtra(PackageInstaller.EXTRA_STATUS, + PackageInstaller.STATUS_FAILURE); + if (status != PackageInstaller.STATUS_SUCCESS) { + final String errorMessage = result.getStringExtra( + PackageInstaller.EXTRA_STATUS_MESSAGE); + Slog.e(TAG, "Failure to verify APK staged session " + + session.sessionId + " [" + errorMessage + "]"); + session.setStagedSessionFailed( + SessionInfo.STAGED_SESSION_VERIFICATION_FAILED, errorMessage); + return; + } + mPreRebootVerificationHandler.notifyPreRebootVerification_Apk_Complete( + session.sessionId); + }); + + apksToVerify.commit(receiver.getIntentSender(), false); + } + + private void installApksInSession(@NonNull PackageInstallerSession session) + throws PackageManagerException { + + final PackageInstallerSession apksToInstall = extractApksInSession( + session, /* preReboot */ false); + if (apksToInstall == null) { + return; + } + + if ((apksToInstall.params.installFlags & PackageManager.INSTALL_ENABLE_ROLLBACK) != 0) { + // If rollback is available for this session, notify the rollback + // manager of the apk session so it can properly enable rollback. + final IRollbackManager rm = IRollbackManager.Stub.asInterface( + ServiceManager.getService(Context.ROLLBACK_SERVICE)); + try { + rm.notifyStagedApkSession(session.sessionId, apksToInstall.sessionId); + } catch (RemoteException re) { + Slog.e(TAG, "Failed to notifyStagedApkSession for session: " + + session.sessionId, re); + } + } + + final LocalIntentReceiverSync receiver = new LocalIntentReceiverSync(); + apksToInstall.commit(receiver.getIntentSender(), false); + final Intent result = receiver.getResult(); + final int status = result.getIntExtra(PackageInstaller.EXTRA_STATUS, + PackageInstaller.STATUS_FAILURE); + if (status != PackageInstaller.STATUS_SUCCESS) { + final String errorMessage = result.getStringExtra( + PackageInstaller.EXTRA_STATUS_MESSAGE); + Slog.e(TAG, "Failure to install APK staged session " + + session.sessionId + " [" + errorMessage + "]"); + throw new PackageManagerException( + SessionInfo.STAGED_SESSION_ACTIVATION_FAILED, errorMessage); } - // APEX single-package staged session, nothing to do. } void commitSession(@NonNull PackageInstallerSession session) { @@ -836,8 +853,8 @@ public class StagingManager { Slog.d(TAG, "Running a pre-reboot verification for APKs in session " + session.sessionId + " by performing a dry-run install"); - // installApksInSession will notify the handler when APK verification is complete - installApksInSession(session, /* preReboot */ true); + // verifyApksInSession will notify the handler when APK verification is complete + verifyApksInSession(session); // TODO(b/118865310): abort the session on apexd. } catch (PackageManagerException e) { session.setStagedSessionFailed(e.error, e.getMessage()); From 0a8c784d008d84345ce46fc558cec965d65a377f Mon Sep 17 00:00:00 2001 From: Mohammad Samiul Islam Date: Mon, 11 Nov 2019 17:53:47 +0000 Subject: [PATCH 2/2] Allow apex packages to be signed with key that has rollback capability When a key is rotated with a new key, it may continue to trust the old one. As such, trusted old key should be able to update. We no longer need to handle "key-downgrade" separately in any situation. An update will be installed iff it is signed by a trusted key (even during Rollbacks/Downgrades). Bug: 136002636 Test: atest StagedInstallTest#testTrustedOldKeyIsAccepted Change-Id: I3455bd00e13a9271fe25cfaac1476ad7e55eb5f3 Merged-In: I3455bd00e13a9271fe25cfaac1476ad7e55eb5f3 --- .../com/android/server/pm/StagingManager.java | 24 +++++++------------ 1 file changed, 8 insertions(+), 16 deletions(-) diff --git a/services/core/java/com/android/server/pm/StagingManager.java b/services/core/java/com/android/server/pm/StagingManager.java index 6b4ef698a8f4c..b5da3aa30ba2c 100644 --- a/services/core/java/com/android/server/pm/StagingManager.java +++ b/services/core/java/com/android/server/pm/StagingManager.java @@ -113,18 +113,17 @@ public class StagingManager { * Validates the signature used to sign the container of the new apex package * * @param newApexPkg The new apex package that is being installed - * @param installFlags flags related to the session * @throws PackageManagerException */ - private void validateApexSignature(PackageInfo newApexPkg, int installFlags) + private void validateApexSignature(PackageInfo newApexPkg) throws PackageManagerException { // Get signing details of the new package final String apexPath = newApexPkg.applicationInfo.sourceDir; final String packageName = newApexPkg.packageName; - final SigningDetails signingDetails; + final SigningDetails newSigningDetails; try { - signingDetails = ApkSignatureVerifier.verify(apexPath, SignatureSchemeVersion.JAR); + newSigningDetails = ApkSignatureVerifier.verify(apexPath, SignatureSchemeVersion.JAR); } catch (PackageParserException e) { throw new PackageManagerException(SessionInfo.STAGED_SESSION_VERIFICATION_FAILED, "Failed to parse APEX package " + apexPath, e); @@ -149,16 +148,10 @@ public class StagingManager { } // Verify signing details for upgrade - if (signingDetails.checkCapability(existingSigningDetails, - PackageParser.SigningDetails.CertCapabilities.INSTALLED_DATA)) { - return; - } - - // Verify signing details for downgrade - // Allow downgrading from B to A iff it is possible to upgrade from A to B - if (existingApexPkg.getLongVersionCode() > newApexPkg.getLongVersionCode() - && existingSigningDetails.checkCapability(signingDetails, - PackageParser.SigningDetails.CertCapabilities.INSTALLED_DATA)) { + if (newSigningDetails.checkCapability(existingSigningDetails, + SigningDetails.CertCapabilities.INSTALLED_DATA) + || existingSigningDetails.checkCapability(newSigningDetails, + SigningDetails.CertCapabilities.ROLLBACK)) { return; } @@ -825,8 +818,7 @@ public class StagingManager { final List apexPackages = submitSessionToApexService(session); for (PackageInfo apexPackage : apexPackages) { - validateApexSignature( - apexPackage, session.params.installFlags); + validateApexSignature(apexPackage); } } catch (PackageManagerException e) { session.setStagedSessionFailed(e.error, e.getMessage());