From 7eb1893c08130163852a7df2a53cb321dc493acc Mon Sep 17 00:00:00 2001 From: Mohammad Samiul Islam Date: Thu, 15 Oct 2020 00:59:32 +0100 Subject: [PATCH] Prevent exceptions in pre-reboot verification from crashing system server An unhandled exception during pre-reboot verification will cause system server to crash and restart. Upon restart, pre-reboot verification will be retried and it will cause system server to crash again. Thus creating a loop. Instead of allowing the exception to crash the system server, we now catch it and fail the corresponding staged session with appropriate message. Bug: 170784748 Test: manual Test: verified that without this fix, any unhandled exception sends system server into a crash loop Change-Id: Ie3343681601c072add0b2d50cbaa2e088a27d94b Merged-In: If100796687906be67bc02ea63a78e1b6e291ce7a (cherry picked from commit 5a2548589560404f9fce4914216564b0333895d4) --- .../com/android/server/pm/StagingManager.java | 133 +++++++++--------- 1 file changed, 70 insertions(+), 63 deletions(-) diff --git a/services/core/java/com/android/server/pm/StagingManager.java b/services/core/java/com/android/server/pm/StagingManager.java index dd7133121b217..96dd001008786 100644 --- a/services/core/java/com/android/server/pm/StagingManager.java +++ b/services/core/java/com/android/server/pm/StagingManager.java @@ -202,85 +202,92 @@ public class StagingManager { } private void preRebootVerification(@NonNull PackageInstallerSession session) { - boolean success = true; + try { + boolean success = true; - final ApexInfoList apexInfoList = new ApexInfoList(); - // 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); + final ApexInfoList apexInfoList = new ApexInfoList(); + // 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 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. - } + } else if (session.isMultiPackage()) { + List 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 (sessionContainsApk(session)) { - if (!installApksInSession(session, /* preReboot */ true)) { - session.setStagedSessionFailed(SessionInfo.STAGED_SESSION_VERIFICATION_FAILED, - "APK verification failed. Check logcat messages for " - + "more information."); - // TODO(b/118865310): abort the session on apexd. + if (!success) { + // submitSessionToApexService will populate error. return; } - } - 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)) { + if (sessionContainsApk(session)) { + if (!installApksInSession(session, /* preReboot */ true)) { 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."); + "APK verification failed. Check logcat messages for " + + "more information."); // 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 - // is a synchronous operation. - final IRollbackManager rm = IRollbackManager.Stub.asInterface( - ServiceManager.getService(Context.ROLLBACK_SERVICE)); - try { - // NOTE: To stay consistent with the non-staged install flow, we don't fail the - // entire install if rollbacks can't be enabled. - if (!rm.notifyStagedSession(session.sessionId)) { - Slog.e(TAG, "Unable to enable rollback for session: " + session.sessionId); + 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; + } } - } catch (RemoteException re) { - // Cannot happen, the rollback manager is in the same process. } - } - session.setStagedSessionReady(); - if (sessionContainsApex(session) - && !mApexManager.markStagedSessionReady(session.sessionId)) { + 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 is a synchronous operation. + final IRollbackManager rm = IRollbackManager.Stub.asInterface( + ServiceManager.getService(Context.ROLLBACK_SERVICE)); + try { + // NOTE: To stay consistent with the non-staged install flow, we don't fail the + // entire install if rollbacks can't be enabled. + if (!rm.notifyStagedSession(session.sessionId)) { + Slog.e(TAG, "Unable to enable rollback for session: " + session.sessionId); + } + } catch (RemoteException re) { + // Cannot happen, the rollback manager is in the same process. + } + } + + 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."); + } + } catch (Exception e) { + Slog.e(TAG, "Pre-reboot verification failed due to unhandled exception", e); session.setStagedSessionFailed(SessionInfo.STAGED_SESSION_VERIFICATION_FAILED, - "APEX staging failed, check logcat messages from apexd for more " - + "details."); + "Pre-reboot verification failed due to unhandled exception: " + e); } }