diff options
| -rw-r--r-- | services/core/java/com/android/server/pm/PackageInstallerService.java | 10 | ||||
| -rw-r--r-- | services/core/java/com/android/server/pm/StagingManager.java | 216 |
2 files changed, 146 insertions, 80 deletions
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java index 1c9d56683dba..6a47c4c544e8 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerService.java +++ b/services/core/java/com/android/server/pm/PackageInstallerService.java @@ -218,6 +218,7 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements public void systemReady() { mAppOps = mContext.getSystemService(AppOpsManager.class); + mStagingManager.systemReady(); synchronized (mSessions) { readSessionsLocked(); @@ -380,6 +381,8 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements Slog.w(TAG, "Abandoning old session created at " + session.createdMillis); valid = false; + } else if (isExtraSessionForStagedInstall(session)) { + valid = false; } else { valid = true; } @@ -410,6 +413,13 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements } } + // Extra sessions are created during staged install on temporary basis. They should not be + // allowed to live across system server restart. + private boolean isExtraSessionForStagedInstall(PackageInstallerSession session) { + return (session.params.installFlags & PackageManager.INSTALL_DRY_RUN) != 0 + || (session.params.installFlags & PackageManager.INSTALL_DISABLE_VERIFICATION) != 0; + } + @GuardedBy("mSessions") private void addHistoricalSessionLocked(PackageInstallerSession session) { CharArrayWriter writer = new CharArrayWriter(); diff --git a/services/core/java/com/android/server/pm/StagingManager.java b/services/core/java/com/android/server/pm/StagingManager.java index dd7133121b21..2210ff86133c 100644 --- a/services/core/java/com/android/server/pm/StagingManager.java +++ b/services/core/java/com/android/server/pm/StagingManager.java @@ -21,10 +21,12 @@ import android.annotation.Nullable; import android.apex.ApexInfo; import android.apex.ApexInfoList; import android.apex.ApexSessionInfo; +import android.content.BroadcastReceiver; import android.content.Context; import android.content.IIntentReceiver; import android.content.IIntentSender; import android.content.Intent; +import android.content.IntentFilter; import android.content.IntentSender; import android.content.pm.PackageInfo; import android.content.pm.PackageInstaller; @@ -44,6 +46,7 @@ import android.os.ParcelableException; import android.os.PowerManager; import android.os.RemoteException; import android.os.ServiceManager; +import android.os.SystemProperties; import android.util.Slog; import android.util.SparseArray; import android.util.apk.ApkSignatureVerifier; @@ -72,12 +75,16 @@ public class StagingManager { private final PackageInstallerService mPi; private final ApexManager mApexManager; private final PowerManager mPowerManager; + private final Context mContext; private final Handler mBgHandler; + private PackageInstallerSession mPendingSession; + private boolean mIsReady; @GuardedBy("mStagedSessions") private final SparseArray<PackageInstallerSession> mStagedSessions = new SparseArray<>(); StagingManager(PackageInstallerService pi, ApexManager am, Context context) { + mContext = context; mPi = pi; mApexManager = am; mPowerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE); @@ -202,85 +209,97 @@ public class StagingManager { } private void preRebootVerification(@NonNull PackageInstallerSession session) { - boolean success = true; + try { + if (!mIsReady) { + mPendingSession = session; + return; + } - 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); + boolean success = true; - } 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; - } + 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); - 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. + } 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 (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; + } + } + } + + 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. } - } catch (RemoteException re) { - // Cannot happen, the rollback manager is in the same process. } - } - session.setStagedSessionReady(); - if (sessionContainsApex(session) - && !mApexManager.markStagedSessionReady(session.sessionId)) { + 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); } } @@ -346,22 +365,9 @@ public class StagingManager { } // The APEX part of the session is activated, proceed with the installation of APKs. if (!installApksInSession(session, /* preReboot */ false)) { - session.setStagedSessionFailed(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED, - "Staged installation of APKs failed. Check logcat messages for" - + "more information."); - - if (!hasApex) { - return; - } - - if (!mApexManager.abortActiveSession()) { - Slog.e(TAG, "Failed to abort APEXd session"); - } else { - Slog.e(TAG, - "Successfully aborted apexd session. Rebooting device in order to revert " - + "to the previous state of APEXd."); - mPowerManager.reboot(null); - } + onInstallationFailure(session, new PackageManagerException( + SessionInfo.STAGED_SESSION_ACTIVATION_FAILED, "Staged installation of APKs " + + "failed. Check logcat messages for more information.")); return; } @@ -371,6 +377,21 @@ public class StagingManager { } } + void onInstallationFailure(PackageInstallerSession session, PackageManagerException e) { + session.setStagedSessionFailed(e.error, e.getMessage()); + if (!sessionContainsApex(session)) { + return; + } + if (!mApexManager.abortActiveSession()) { + Slog.e(TAG, "Failed to abort APEXd session"); + } else { + Slog.e(TAG, + "Successfully aborted apexd session. Rebooting device in order to revert " + + "to the previous state of APEXd."); + mPowerManager.reboot(null); + } + } + private List<String> findAPKsInDir(File stageDir) { List<String> ret = new ArrayList<>(); if (stageDir != null && stageDir.exists()) { @@ -648,6 +669,11 @@ public class StagingManager { } private void checkStateAndResume(@NonNull PackageInstallerSession session) { + // Do not resume session if boot completed already + if (SystemProperties.getBoolean("sys.boot_completed", false)) { + return; + } + if (!session.isCommitted()) { // Session hasn't been committed yet, ignore. return; @@ -664,7 +690,37 @@ public class StagingManager { } else { // Session had already being marked ready. Start the checks to verify if there is any // follow-up work. - resumeSession(session); + try { + resumeSession(session); + } catch (Exception e) { + Slog.e(TAG, "Staged install failed due to unhandled exception", e); + onInstallationFailure(session, new PackageManagerException( + SessionInfo.STAGED_SESSION_ACTIVATION_FAILED, + "Staged install failed due to unhandled exception: " + e)); + + } + } + } + + void systemReady() { + // Register the receiver of boot completed intent for staging manager. + mContext.registerReceiver(new BroadcastReceiver() { + @Override + public void onReceive(Context ctx, Intent intent) { + readyToStart(); + ctx.unregisterReceiver(this); + } + }, new IntentFilter(Intent.ACTION_BOOT_COMPLETED)); + } + + // Notify the handler that system is ready, and reschedule the pre-reboot verifications. + private synchronized void readyToStart() { + mIsReady = true; + if (mPendingSession != null) { + mBgHandler.post(() -> { + preRebootVerification(mPendingSession); + mPendingSession = null; + }); } } |