Loading services/core/java/com/android/server/pm/PackageInstallerService.java +5 −4 Original line number Diff line number Diff line Loading @@ -676,7 +676,7 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements session = new PackageInstallerSession(mInternalCallback, mContext, mPm, this, mInstallThread.getLooper(), mStagingManager, sessionId, userId, callingUid, installSource, params, createdMillis, stageDir, stageCid, null, false, false, false, null, SessionInfo.INVALID_ID, stageDir, stageCid, null, false, false, false, false, null, SessionInfo.INVALID_ID, false, false, false, SessionInfo.STAGED_SESSION_NO_ERROR, ""); synchronized (mSessions) { Loading Loading @@ -830,7 +830,7 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements synchronized (mSessions) { final PackageInstallerSession session = mSessions.get(sessionId); return session != null return (session != null && !(session.isStaged() && session.isDestroyed())) ? session.generateInfoForCaller(true /*withIcon*/, Binder.getCallingUid()) : null; } Loading @@ -851,7 +851,8 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements synchronized (mSessions) { for (int i = 0; i < mSessions.size(); i++) { final PackageInstallerSession session = mSessions.valueAt(i); if (session.userId == userId && !session.hasParentSessionId()) { if (session.userId == userId && !session.hasParentSessionId() && !(session.isStaged() && session.isDestroyed())) { result.add(session.generateInfoForCaller(false, callingUid)); } } Loading Loading @@ -1269,7 +1270,7 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements public void onStagedSessionChanged(PackageInstallerSession session) { session.markUpdated(); writeSessionsAsync(); if (mOkToSendBroadcasts) { if (mOkToSendBroadcasts && !session.isDestroyed()) { // we don't scrub the data here as this is sent only to the installer several // privileged system packages mPm.sendSessionUpdatedBroadcast( Loading services/core/java/com/android/server/pm/PackageInstallerSession.java +42 −8 Original line number Diff line number Diff line Loading @@ -190,6 +190,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { private static final String ATTR_SESSION_STAGE_CID = "sessionStageCid"; private static final String ATTR_PREPARED = "prepared"; private static final String ATTR_COMMITTED = "committed"; private static final String ATTR_DESTROYED = "destroyed"; private static final String ATTR_SEALED = "sealed"; private static final String ATTR_MULTI_PACKAGE = "multiPackage"; private static final String ATTR_PARENT_SESSION_ID = "parentSessionId"; Loading Loading @@ -533,7 +534,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { int sessionId, int userId, int installerUid, @NonNull InstallSource installSource, SessionParams params, long createdMillis, File stageDir, String stageCid, InstallationFile[] files, boolean prepared, boolean committed, boolean sealed, boolean committed, boolean destroyed, boolean sealed, @Nullable int[] childSessionIds, int parentSessionId, boolean isReady, boolean isFailed, boolean isApplied, int stagedSessionErrorCode, String stagedSessionErrorMessage) { Loading Loading @@ -579,6 +580,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { mPrepared = prepared; mCommitted = committed; mDestroyed = destroyed; mStagedSessionReady = isReady; mStagedSessionFailed = isFailed; mStagedSessionApplied = isApplied; Loading Loading @@ -713,6 +715,13 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { } } /** {@hide} */ boolean isDestroyed() { synchronized (mLock) { return mDestroyed; } } /** Returns true if a staged session has reached a final state and can be forgotten about */ public boolean isStagedAndInTerminalState() { synchronized (mLock) { Loading Loading @@ -1067,6 +1076,19 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { } } /** * Check if the caller is the owner of this session. Otherwise throw a * {@link SecurityException}. */ @GuardedBy("mLock") private void assertCallerIsOwnerOrRootOrSystemLocked() { final int callingUid = Binder.getCallingUid(); if (callingUid != Process.ROOT_UID && callingUid != mInstallerUid && callingUid != Process.SYSTEM_UID) { throw new SecurityException("Session does not belong to uid " + callingUid); } } /** * If anybody is reading or writing data of the session, throw an {@link SecurityException}. */ Loading Loading @@ -2552,7 +2574,13 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { + mParentSessionId + " and may not be abandoned directly."); } synchronized (mLock) { if (params.isStaged && mDestroyed) { // If a user abandons staged session in an unsafe state, then system will try to // abandon the destroyed staged session when it is safe on behalf of the user. assertCallerIsOwnerOrRootOrSystemLocked(); } else { assertCallerIsOwnerOrRootLocked(); } if (isStagedAndInTerminalState()) { // We keep the session in the database if it's in a finalized state. It will be Loading @@ -2562,11 +2590,12 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { return; } if (mCommitted && params.isStaged) { synchronized (mLock) { mDestroyed = true; if (!mStagingManager.abortCommittedSessionLocked(this)) { // Do not clean up the staged session from system. It is not safe yet. mCallback.onStagedSessionChanged(this); return; } mStagingManager.abortCommittedSession(this); cleanStageDir(); } Loading Loading @@ -2926,6 +2955,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { /** {@hide} */ void setStagedSessionReady() { synchronized (mLock) { if (mDestroyed) return; // Do not allow destroyed staged session to change state mStagedSessionReady = true; mStagedSessionApplied = false; mStagedSessionFailed = false; Loading @@ -2939,6 +2969,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { void setStagedSessionFailed(@StagedSessionErrorCode int errorCode, String errorMessage) { synchronized (mLock) { if (mDestroyed) return; // Do not allow destroyed staged session to change state mStagedSessionReady = false; mStagedSessionApplied = false; mStagedSessionFailed = true; Loading @@ -2953,6 +2984,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { /** {@hide} */ void setStagedSessionApplied() { synchronized (mLock) { if (mDestroyed) return; // Do not allow destroyed staged session to change state mStagedSessionReady = false; mStagedSessionApplied = true; mStagedSessionFailed = false; Loading Loading @@ -3197,7 +3229,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { */ void write(@NonNull XmlSerializer out, @NonNull File sessionsDir) throws IOException { synchronized (mLock) { if (mDestroyed) { if (mDestroyed && !params.isStaged) { return; } Loading @@ -3223,6 +3255,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { } writeBooleanAttribute(out, ATTR_PREPARED, isPrepared()); writeBooleanAttribute(out, ATTR_COMMITTED, isCommitted()); writeBooleanAttribute(out, ATTR_DESTROYED, isDestroyed()); writeBooleanAttribute(out, ATTR_SEALED, isSealed()); writeBooleanAttribute(out, ATTR_MULTI_PACKAGE, params.isMultiPackage); Loading Loading @@ -3352,6 +3385,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { final String stageCid = readStringAttribute(in, ATTR_SESSION_STAGE_CID); final boolean prepared = readBooleanAttribute(in, ATTR_PREPARED, true); final boolean committed = readBooleanAttribute(in, ATTR_COMMITTED); final boolean destroyed = readBooleanAttribute(in, ATTR_DESTROYED); final boolean sealed = readBooleanAttribute(in, ATTR_SEALED); final int parentSessionId = readIntAttribute(in, ATTR_PARENT_SESSION_ID, SessionInfo.INVALID_ID); Loading Loading @@ -3473,7 +3507,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { return new PackageInstallerSession(callback, context, pm, sessionProvider, installerThread, stagingManager, sessionId, userId, installerUid, installSource, params, createdMillis, stageDir, stageCid, fileArray, prepared, committed, sealed, childSessionIdsArray, parentSessionId, prepared, committed, destroyed, sealed, childSessionIdsArray, parentSessionId, isReady, isFailed, isApplied, stagedSessionErrorCode, stagedSessionErrorMessage); } } services/core/java/com/android/server/pm/StagingManager.java +141 −35 Original line number Diff line number Diff line Loading @@ -61,6 +61,7 @@ import android.text.TextUtils; import android.util.IntArray; import android.util.Slog; import android.util.SparseArray; import android.util.SparseBooleanArray; import android.util.SparseIntArray; import android.util.apk.ApkSignatureVerifier; Loading Loading @@ -137,6 +138,9 @@ public class StagingManager { synchronized (mStagedSessions) { for (int i = 0; i < mStagedSessions.size(); i++) { final PackageInstallerSession stagedSession = mStagedSessions.valueAt(i); if (stagedSession.isDestroyed()) { continue; } result.add(stagedSession.generateInfoForCaller(false /*icon*/, callingUid)); } } Loading Loading @@ -202,7 +206,7 @@ public class StagingManager { final IntArray childSessionIds = new IntArray(); if (session.isMultiPackage()) { for (int id : session.getChildSessionIds()) { if (isApexSession(mStagedSessions.get(id))) { if (isApexSession(getStagedSession(id))) { childSessionIds.add(id); } } Loading Loading @@ -797,6 +801,8 @@ public class StagingManager { + session.sessionId + " [" + errorMessage + "]"); session.setStagedSessionFailed( SessionInfo.STAGED_SESSION_VERIFICATION_FAILED, errorMessage); mPreRebootVerificationHandler.onPreRebootVerificationComplete( session.sessionId); return; } mPreRebootVerificationHandler.notifyPreRebootVerification_Apk_Complete( Loading Loading @@ -880,7 +886,8 @@ public class StagingManager { synchronized (mStagedSessions) { for (int i = 0; i < mStagedSessions.size(); i++) { final PackageInstallerSession stagedSession = mStagedSessions.valueAt(i); if (!stagedSession.isCommitted() || stagedSession.isStagedAndInTerminalState()) { if (!stagedSession.isCommitted() || stagedSession.isStagedAndInTerminalState() || stagedSession.isDestroyed()) { continue; } if (stagedSession.isMultiPackage()) { Loading Loading @@ -943,29 +950,70 @@ public class StagingManager { } } void abortCommittedSession(@NonNull PackageInstallerSession session) { /** * <p>Abort committed staged session * * <p>This method must be called while holding {@link PackageInstallerSession.mLock}. * * <p>The method returns {@code false} to indicate it is not safe to clean up the session from * system yet. When it is safe, the method returns {@code true}. * * <p> When it is safe to clean up, {@link StagingManager} will call * {@link PackageInstallerSession#abandon()} on the session again. * * @return {@code true} if it is safe to cleanup the session resources, otherwise {@code false}. */ boolean abortCommittedSessionLocked(@NonNull PackageInstallerSession session) { int sessionId = session.sessionId; if (session.isStagedSessionApplied()) { Slog.w(TAG, "Cannot abort applied session : " + session.sessionId); return; Slog.w(TAG, "Cannot abort applied session : " + sessionId); return false; } if (!session.isDestroyed()) { throw new IllegalStateException("Committed session must be destroyed before aborting it" + " from StagingManager"); } if (getStagedSession(sessionId) == null) { Slog.w(TAG, "Session " + sessionId + " has been abandoned already"); return false; } abortSession(session); boolean hasApex = sessionContainsApex(session); if (hasApex) { ApexSessionInfo apexSession = mApexManager.getStagedSessionInfo(session.sessionId); // If pre-reboot verification is running, then return false. StagingManager will call // abandon again when pre-reboot verification ends. if (mPreRebootVerificationHandler.isVerificationRunning(sessionId)) { Slog.w(TAG, "Session " + sessionId + " aborted before pre-reboot " + "verification completed."); return false; } // A session could be marked ready once its pre-reboot verification ends if (session.isStagedSessionReady()) { if (sessionContainsApex(session)) { try { ApexSessionInfo apexSession = mApexManager.getStagedSessionInfo(session.sessionId); if (apexSession == null || isApexSessionFinalized(apexSession)) { Slog.w(TAG, "Cannot abort session " + session.sessionId + " because it is not active or APEXD is not reachable"); return; } try { + " because it is not active."); } else { mApexManager.abortStagedSession(session.sessionId); } catch (Exception ignore) { } } catch (Exception e) { // Failed to contact apexd service. The apex might still be staged. We can still // safely cleanup the staged session since pre-reboot verification is complete. // Also, cleaning up the stageDir prevents the apex from being activated. Slog.w(TAG, "Could not contact apexd to abort staged session " + sessionId); } } } // Session was successfully aborted from apexd (if required) and pre-reboot verification // is also complete. It is now safe to clean up the session from system. abortSession(session); return true; } private boolean isApexSessionFinalized(ApexSessionInfo session) { /* checking if the session is in a final state, i.e., not active anymore */ return session.isUnknown || session.isActivationFailed || session.isSuccess Loading Loading @@ -1042,6 +1090,11 @@ public class StagingManager { // Final states, nothing to do. return; } if (session.isDestroyed()) { // Device rebooted before abandoned session was cleaned up. session.abandon(); return; } if (!session.isStagedSessionReady()) { // The framework got restarted before the pre-reboot verification could complete, // restart the verification. Loading Loading @@ -1124,10 +1177,20 @@ public class StagingManager { } } private PackageInstallerSession getStagedSession(int sessionId) { PackageInstallerSession session; synchronized (mStagedSessions) { session = mStagedSessions.get(sessionId); } return session; } private final class PreRebootVerificationHandler extends Handler { // Hold session ids before handler gets ready to do the verification. private IntArray mPendingSessionIds; private boolean mIsReady; @GuardedBy("mVerificationRunning") private final SparseBooleanArray mVerificationRunning = new SparseBooleanArray(); PreRebootVerificationHandler(Looper looper) { super(looper); Loading Loading @@ -1155,13 +1218,15 @@ public class StagingManager { @Override public void handleMessage(Message msg) { final int sessionId = msg.arg1; final PackageInstallerSession session; synchronized (mStagedSessions) { session = mStagedSessions.get(sessionId); } // Maybe session was aborted before pre-reboot verification was complete final PackageInstallerSession session = getStagedSession(sessionId); if (session == null) { Slog.d(TAG, "Stopping pre-reboot verification for sessionId: " + sessionId); Slog.wtf(TAG, "Session disappeared in the middle of pre-reboot verification: " + sessionId); return; } if (session.isDestroyed()) { // No point in running verification on a destroyed session onPreRebootVerificationComplete(sessionId); return; } switch (msg.what) { Loading Loading @@ -1200,9 +1265,40 @@ public class StagingManager { mPendingSessionIds.add(sessionId); return; } PackageInstallerSession session = getStagedSession(sessionId); synchronized (mVerificationRunning) { // Do not start verification on a session that has been abandoned if (session == null || session.isDestroyed()) { return; } Slog.d(TAG, "Starting preRebootVerification for session " + sessionId); mVerificationRunning.put(sessionId, true); } obtainMessage(MSG_PRE_REBOOT_VERIFICATION_START, sessionId, 0).sendToTarget(); } // Things to do when pre-reboot verification completes for a particular sessionId private void onPreRebootVerificationComplete(int sessionId) { // Remove it from mVerificationRunning so that verification is considered complete synchronized (mVerificationRunning) { Slog.d(TAG, "Stopping preRebootVerification for session " + sessionId); mVerificationRunning.delete(sessionId); } // Check if the session was destroyed while pre-reboot verification was running. If so, // abandon it again. PackageInstallerSession session = getStagedSession(sessionId); if (session != null && session.isDestroyed()) { session.abandon(); } } private boolean isVerificationRunning(int sessionId) { synchronized (mVerificationRunning) { return mVerificationRunning.get(sessionId); } } private void notifyPreRebootVerification_Start_Complete(int sessionId) { obtainMessage(MSG_PRE_REBOOT_VERIFICATION_APEX, sessionId, 0).sendToTarget(); } Loading @@ -1221,8 +1317,6 @@ public class StagingManager { * See {@link PreRebootVerificationHandler} to see all nodes of pre reboot verification */ private void handlePreRebootVerification_Start(@NonNull PackageInstallerSession session) { Slog.d(TAG, "Starting preRebootVerification for session " + 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 Loading Loading @@ -1269,6 +1363,7 @@ public class StagingManager { } } catch (PackageManagerException e) { session.setStagedSessionFailed(e.error, e.getMessage()); onPreRebootVerificationComplete(session.sessionId); return; } Loading Loading @@ -1301,6 +1396,7 @@ public class StagingManager { // TODO(b/118865310): abort the session on apexd. } catch (PackageManagerException e) { session.setStagedSessionFailed(e.error, e.getMessage()); onPreRebootVerificationComplete(session.sessionId); } } Loading @@ -1323,9 +1419,18 @@ public class StagingManager { Slog.e(TAG, "Failed to get hold of StorageManager", e); session.setStagedSessionFailed(SessionInfo.STAGED_SESSION_UNKNOWN, "Failed to get hold of StorageManager"); onPreRebootVerificationComplete(session.sessionId); return; } // Stop pre-reboot verification before marking session ready. From this point on, if we // abandon the session then it will be cleaned up immediately. If session is abandoned // after this point, then even if for some reason system tries to install the session // or activate its apex, there won't be any files to work with as they will be cleaned // up by the system as part of abandonment. If session is abandoned before this point, // then the session is already destroyed and cannot be marked ready anymore. onPreRebootVerificationComplete(session.sessionId); // 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: Loading @@ -1337,15 +1442,16 @@ public class StagingManager { // only apex part of the train will be applied, leaving device in an inconsistent state. Slog.d(TAG, "Marking session " + session.sessionId + " as ready"); session.setStagedSessionReady(); if (session.isStagedSessionReady()) { final boolean hasApex = sessionContainsApex(session); if (!hasApex) { // Session doesn't contain apex, nothing to do. return; } if (hasApex) { try { mApexManager.markStagedSessionReady(session.sessionId); } catch (PackageManagerException e) { session.setStagedSessionFailed(e.error, e.getMessage()); return; } } } } } Loading services/tests/servicestests/src/com/android/server/pm/PackageInstallerSessionTest.java +1 −0 Original line number Diff line number Diff line Loading @@ -178,6 +178,7 @@ public class PackageInstallerSessionTest { /* files */ null, /* prepared */ true, /* committed */ true, /* destroyed */ staged ? true : false, /* sealed */ false, // Setting to true would trigger some PM logic. /* childSessionIds */ childSessionIds != null ? childSessionIds : new int[0], /* parentSessionId */ parentSessionId, Loading Loading
services/core/java/com/android/server/pm/PackageInstallerService.java +5 −4 Original line number Diff line number Diff line Loading @@ -676,7 +676,7 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements session = new PackageInstallerSession(mInternalCallback, mContext, mPm, this, mInstallThread.getLooper(), mStagingManager, sessionId, userId, callingUid, installSource, params, createdMillis, stageDir, stageCid, null, false, false, false, null, SessionInfo.INVALID_ID, stageDir, stageCid, null, false, false, false, false, null, SessionInfo.INVALID_ID, false, false, false, SessionInfo.STAGED_SESSION_NO_ERROR, ""); synchronized (mSessions) { Loading Loading @@ -830,7 +830,7 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements synchronized (mSessions) { final PackageInstallerSession session = mSessions.get(sessionId); return session != null return (session != null && !(session.isStaged() && session.isDestroyed())) ? session.generateInfoForCaller(true /*withIcon*/, Binder.getCallingUid()) : null; } Loading @@ -851,7 +851,8 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements synchronized (mSessions) { for (int i = 0; i < mSessions.size(); i++) { final PackageInstallerSession session = mSessions.valueAt(i); if (session.userId == userId && !session.hasParentSessionId()) { if (session.userId == userId && !session.hasParentSessionId() && !(session.isStaged() && session.isDestroyed())) { result.add(session.generateInfoForCaller(false, callingUid)); } } Loading Loading @@ -1269,7 +1270,7 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements public void onStagedSessionChanged(PackageInstallerSession session) { session.markUpdated(); writeSessionsAsync(); if (mOkToSendBroadcasts) { if (mOkToSendBroadcasts && !session.isDestroyed()) { // we don't scrub the data here as this is sent only to the installer several // privileged system packages mPm.sendSessionUpdatedBroadcast( Loading
services/core/java/com/android/server/pm/PackageInstallerSession.java +42 −8 Original line number Diff line number Diff line Loading @@ -190,6 +190,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { private static final String ATTR_SESSION_STAGE_CID = "sessionStageCid"; private static final String ATTR_PREPARED = "prepared"; private static final String ATTR_COMMITTED = "committed"; private static final String ATTR_DESTROYED = "destroyed"; private static final String ATTR_SEALED = "sealed"; private static final String ATTR_MULTI_PACKAGE = "multiPackage"; private static final String ATTR_PARENT_SESSION_ID = "parentSessionId"; Loading Loading @@ -533,7 +534,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { int sessionId, int userId, int installerUid, @NonNull InstallSource installSource, SessionParams params, long createdMillis, File stageDir, String stageCid, InstallationFile[] files, boolean prepared, boolean committed, boolean sealed, boolean committed, boolean destroyed, boolean sealed, @Nullable int[] childSessionIds, int parentSessionId, boolean isReady, boolean isFailed, boolean isApplied, int stagedSessionErrorCode, String stagedSessionErrorMessage) { Loading Loading @@ -579,6 +580,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { mPrepared = prepared; mCommitted = committed; mDestroyed = destroyed; mStagedSessionReady = isReady; mStagedSessionFailed = isFailed; mStagedSessionApplied = isApplied; Loading Loading @@ -713,6 +715,13 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { } } /** {@hide} */ boolean isDestroyed() { synchronized (mLock) { return mDestroyed; } } /** Returns true if a staged session has reached a final state and can be forgotten about */ public boolean isStagedAndInTerminalState() { synchronized (mLock) { Loading Loading @@ -1067,6 +1076,19 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { } } /** * Check if the caller is the owner of this session. Otherwise throw a * {@link SecurityException}. */ @GuardedBy("mLock") private void assertCallerIsOwnerOrRootOrSystemLocked() { final int callingUid = Binder.getCallingUid(); if (callingUid != Process.ROOT_UID && callingUid != mInstallerUid && callingUid != Process.SYSTEM_UID) { throw new SecurityException("Session does not belong to uid " + callingUid); } } /** * If anybody is reading or writing data of the session, throw an {@link SecurityException}. */ Loading Loading @@ -2552,7 +2574,13 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { + mParentSessionId + " and may not be abandoned directly."); } synchronized (mLock) { if (params.isStaged && mDestroyed) { // If a user abandons staged session in an unsafe state, then system will try to // abandon the destroyed staged session when it is safe on behalf of the user. assertCallerIsOwnerOrRootOrSystemLocked(); } else { assertCallerIsOwnerOrRootLocked(); } if (isStagedAndInTerminalState()) { // We keep the session in the database if it's in a finalized state. It will be Loading @@ -2562,11 +2590,12 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { return; } if (mCommitted && params.isStaged) { synchronized (mLock) { mDestroyed = true; if (!mStagingManager.abortCommittedSessionLocked(this)) { // Do not clean up the staged session from system. It is not safe yet. mCallback.onStagedSessionChanged(this); return; } mStagingManager.abortCommittedSession(this); cleanStageDir(); } Loading Loading @@ -2926,6 +2955,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { /** {@hide} */ void setStagedSessionReady() { synchronized (mLock) { if (mDestroyed) return; // Do not allow destroyed staged session to change state mStagedSessionReady = true; mStagedSessionApplied = false; mStagedSessionFailed = false; Loading @@ -2939,6 +2969,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { void setStagedSessionFailed(@StagedSessionErrorCode int errorCode, String errorMessage) { synchronized (mLock) { if (mDestroyed) return; // Do not allow destroyed staged session to change state mStagedSessionReady = false; mStagedSessionApplied = false; mStagedSessionFailed = true; Loading @@ -2953,6 +2984,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { /** {@hide} */ void setStagedSessionApplied() { synchronized (mLock) { if (mDestroyed) return; // Do not allow destroyed staged session to change state mStagedSessionReady = false; mStagedSessionApplied = true; mStagedSessionFailed = false; Loading Loading @@ -3197,7 +3229,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { */ void write(@NonNull XmlSerializer out, @NonNull File sessionsDir) throws IOException { synchronized (mLock) { if (mDestroyed) { if (mDestroyed && !params.isStaged) { return; } Loading @@ -3223,6 +3255,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { } writeBooleanAttribute(out, ATTR_PREPARED, isPrepared()); writeBooleanAttribute(out, ATTR_COMMITTED, isCommitted()); writeBooleanAttribute(out, ATTR_DESTROYED, isDestroyed()); writeBooleanAttribute(out, ATTR_SEALED, isSealed()); writeBooleanAttribute(out, ATTR_MULTI_PACKAGE, params.isMultiPackage); Loading Loading @@ -3352,6 +3385,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { final String stageCid = readStringAttribute(in, ATTR_SESSION_STAGE_CID); final boolean prepared = readBooleanAttribute(in, ATTR_PREPARED, true); final boolean committed = readBooleanAttribute(in, ATTR_COMMITTED); final boolean destroyed = readBooleanAttribute(in, ATTR_DESTROYED); final boolean sealed = readBooleanAttribute(in, ATTR_SEALED); final int parentSessionId = readIntAttribute(in, ATTR_PARENT_SESSION_ID, SessionInfo.INVALID_ID); Loading Loading @@ -3473,7 +3507,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { return new PackageInstallerSession(callback, context, pm, sessionProvider, installerThread, stagingManager, sessionId, userId, installerUid, installSource, params, createdMillis, stageDir, stageCid, fileArray, prepared, committed, sealed, childSessionIdsArray, parentSessionId, prepared, committed, destroyed, sealed, childSessionIdsArray, parentSessionId, isReady, isFailed, isApplied, stagedSessionErrorCode, stagedSessionErrorMessage); } }
services/core/java/com/android/server/pm/StagingManager.java +141 −35 Original line number Diff line number Diff line Loading @@ -61,6 +61,7 @@ import android.text.TextUtils; import android.util.IntArray; import android.util.Slog; import android.util.SparseArray; import android.util.SparseBooleanArray; import android.util.SparseIntArray; import android.util.apk.ApkSignatureVerifier; Loading Loading @@ -137,6 +138,9 @@ public class StagingManager { synchronized (mStagedSessions) { for (int i = 0; i < mStagedSessions.size(); i++) { final PackageInstallerSession stagedSession = mStagedSessions.valueAt(i); if (stagedSession.isDestroyed()) { continue; } result.add(stagedSession.generateInfoForCaller(false /*icon*/, callingUid)); } } Loading Loading @@ -202,7 +206,7 @@ public class StagingManager { final IntArray childSessionIds = new IntArray(); if (session.isMultiPackage()) { for (int id : session.getChildSessionIds()) { if (isApexSession(mStagedSessions.get(id))) { if (isApexSession(getStagedSession(id))) { childSessionIds.add(id); } } Loading Loading @@ -797,6 +801,8 @@ public class StagingManager { + session.sessionId + " [" + errorMessage + "]"); session.setStagedSessionFailed( SessionInfo.STAGED_SESSION_VERIFICATION_FAILED, errorMessage); mPreRebootVerificationHandler.onPreRebootVerificationComplete( session.sessionId); return; } mPreRebootVerificationHandler.notifyPreRebootVerification_Apk_Complete( Loading Loading @@ -880,7 +886,8 @@ public class StagingManager { synchronized (mStagedSessions) { for (int i = 0; i < mStagedSessions.size(); i++) { final PackageInstallerSession stagedSession = mStagedSessions.valueAt(i); if (!stagedSession.isCommitted() || stagedSession.isStagedAndInTerminalState()) { if (!stagedSession.isCommitted() || stagedSession.isStagedAndInTerminalState() || stagedSession.isDestroyed()) { continue; } if (stagedSession.isMultiPackage()) { Loading Loading @@ -943,29 +950,70 @@ public class StagingManager { } } void abortCommittedSession(@NonNull PackageInstallerSession session) { /** * <p>Abort committed staged session * * <p>This method must be called while holding {@link PackageInstallerSession.mLock}. * * <p>The method returns {@code false} to indicate it is not safe to clean up the session from * system yet. When it is safe, the method returns {@code true}. * * <p> When it is safe to clean up, {@link StagingManager} will call * {@link PackageInstallerSession#abandon()} on the session again. * * @return {@code true} if it is safe to cleanup the session resources, otherwise {@code false}. */ boolean abortCommittedSessionLocked(@NonNull PackageInstallerSession session) { int sessionId = session.sessionId; if (session.isStagedSessionApplied()) { Slog.w(TAG, "Cannot abort applied session : " + session.sessionId); return; Slog.w(TAG, "Cannot abort applied session : " + sessionId); return false; } if (!session.isDestroyed()) { throw new IllegalStateException("Committed session must be destroyed before aborting it" + " from StagingManager"); } if (getStagedSession(sessionId) == null) { Slog.w(TAG, "Session " + sessionId + " has been abandoned already"); return false; } abortSession(session); boolean hasApex = sessionContainsApex(session); if (hasApex) { ApexSessionInfo apexSession = mApexManager.getStagedSessionInfo(session.sessionId); // If pre-reboot verification is running, then return false. StagingManager will call // abandon again when pre-reboot verification ends. if (mPreRebootVerificationHandler.isVerificationRunning(sessionId)) { Slog.w(TAG, "Session " + sessionId + " aborted before pre-reboot " + "verification completed."); return false; } // A session could be marked ready once its pre-reboot verification ends if (session.isStagedSessionReady()) { if (sessionContainsApex(session)) { try { ApexSessionInfo apexSession = mApexManager.getStagedSessionInfo(session.sessionId); if (apexSession == null || isApexSessionFinalized(apexSession)) { Slog.w(TAG, "Cannot abort session " + session.sessionId + " because it is not active or APEXD is not reachable"); return; } try { + " because it is not active."); } else { mApexManager.abortStagedSession(session.sessionId); } catch (Exception ignore) { } } catch (Exception e) { // Failed to contact apexd service. The apex might still be staged. We can still // safely cleanup the staged session since pre-reboot verification is complete. // Also, cleaning up the stageDir prevents the apex from being activated. Slog.w(TAG, "Could not contact apexd to abort staged session " + sessionId); } } } // Session was successfully aborted from apexd (if required) and pre-reboot verification // is also complete. It is now safe to clean up the session from system. abortSession(session); return true; } private boolean isApexSessionFinalized(ApexSessionInfo session) { /* checking if the session is in a final state, i.e., not active anymore */ return session.isUnknown || session.isActivationFailed || session.isSuccess Loading Loading @@ -1042,6 +1090,11 @@ public class StagingManager { // Final states, nothing to do. return; } if (session.isDestroyed()) { // Device rebooted before abandoned session was cleaned up. session.abandon(); return; } if (!session.isStagedSessionReady()) { // The framework got restarted before the pre-reboot verification could complete, // restart the verification. Loading Loading @@ -1124,10 +1177,20 @@ public class StagingManager { } } private PackageInstallerSession getStagedSession(int sessionId) { PackageInstallerSession session; synchronized (mStagedSessions) { session = mStagedSessions.get(sessionId); } return session; } private final class PreRebootVerificationHandler extends Handler { // Hold session ids before handler gets ready to do the verification. private IntArray mPendingSessionIds; private boolean mIsReady; @GuardedBy("mVerificationRunning") private final SparseBooleanArray mVerificationRunning = new SparseBooleanArray(); PreRebootVerificationHandler(Looper looper) { super(looper); Loading Loading @@ -1155,13 +1218,15 @@ public class StagingManager { @Override public void handleMessage(Message msg) { final int sessionId = msg.arg1; final PackageInstallerSession session; synchronized (mStagedSessions) { session = mStagedSessions.get(sessionId); } // Maybe session was aborted before pre-reboot verification was complete final PackageInstallerSession session = getStagedSession(sessionId); if (session == null) { Slog.d(TAG, "Stopping pre-reboot verification for sessionId: " + sessionId); Slog.wtf(TAG, "Session disappeared in the middle of pre-reboot verification: " + sessionId); return; } if (session.isDestroyed()) { // No point in running verification on a destroyed session onPreRebootVerificationComplete(sessionId); return; } switch (msg.what) { Loading Loading @@ -1200,9 +1265,40 @@ public class StagingManager { mPendingSessionIds.add(sessionId); return; } PackageInstallerSession session = getStagedSession(sessionId); synchronized (mVerificationRunning) { // Do not start verification on a session that has been abandoned if (session == null || session.isDestroyed()) { return; } Slog.d(TAG, "Starting preRebootVerification for session " + sessionId); mVerificationRunning.put(sessionId, true); } obtainMessage(MSG_PRE_REBOOT_VERIFICATION_START, sessionId, 0).sendToTarget(); } // Things to do when pre-reboot verification completes for a particular sessionId private void onPreRebootVerificationComplete(int sessionId) { // Remove it from mVerificationRunning so that verification is considered complete synchronized (mVerificationRunning) { Slog.d(TAG, "Stopping preRebootVerification for session " + sessionId); mVerificationRunning.delete(sessionId); } // Check if the session was destroyed while pre-reboot verification was running. If so, // abandon it again. PackageInstallerSession session = getStagedSession(sessionId); if (session != null && session.isDestroyed()) { session.abandon(); } } private boolean isVerificationRunning(int sessionId) { synchronized (mVerificationRunning) { return mVerificationRunning.get(sessionId); } } private void notifyPreRebootVerification_Start_Complete(int sessionId) { obtainMessage(MSG_PRE_REBOOT_VERIFICATION_APEX, sessionId, 0).sendToTarget(); } Loading @@ -1221,8 +1317,6 @@ public class StagingManager { * See {@link PreRebootVerificationHandler} to see all nodes of pre reboot verification */ private void handlePreRebootVerification_Start(@NonNull PackageInstallerSession session) { Slog.d(TAG, "Starting preRebootVerification for session " + 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 Loading Loading @@ -1269,6 +1363,7 @@ public class StagingManager { } } catch (PackageManagerException e) { session.setStagedSessionFailed(e.error, e.getMessage()); onPreRebootVerificationComplete(session.sessionId); return; } Loading Loading @@ -1301,6 +1396,7 @@ public class StagingManager { // TODO(b/118865310): abort the session on apexd. } catch (PackageManagerException e) { session.setStagedSessionFailed(e.error, e.getMessage()); onPreRebootVerificationComplete(session.sessionId); } } Loading @@ -1323,9 +1419,18 @@ public class StagingManager { Slog.e(TAG, "Failed to get hold of StorageManager", e); session.setStagedSessionFailed(SessionInfo.STAGED_SESSION_UNKNOWN, "Failed to get hold of StorageManager"); onPreRebootVerificationComplete(session.sessionId); return; } // Stop pre-reboot verification before marking session ready. From this point on, if we // abandon the session then it will be cleaned up immediately. If session is abandoned // after this point, then even if for some reason system tries to install the session // or activate its apex, there won't be any files to work with as they will be cleaned // up by the system as part of abandonment. If session is abandoned before this point, // then the session is already destroyed and cannot be marked ready anymore. onPreRebootVerificationComplete(session.sessionId); // 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: Loading @@ -1337,15 +1442,16 @@ public class StagingManager { // only apex part of the train will be applied, leaving device in an inconsistent state. Slog.d(TAG, "Marking session " + session.sessionId + " as ready"); session.setStagedSessionReady(); if (session.isStagedSessionReady()) { final boolean hasApex = sessionContainsApex(session); if (!hasApex) { // Session doesn't contain apex, nothing to do. return; } if (hasApex) { try { mApexManager.markStagedSessionReady(session.sessionId); } catch (PackageManagerException e) { session.setStagedSessionFailed(e.error, e.getMessage()); return; } } } } } Loading
services/tests/servicestests/src/com/android/server/pm/PackageInstallerSessionTest.java +1 −0 Original line number Diff line number Diff line Loading @@ -178,6 +178,7 @@ public class PackageInstallerSessionTest { /* files */ null, /* prepared */ true, /* committed */ true, /* destroyed */ staged ? true : false, /* sealed */ false, // Setting to true would trigger some PM logic. /* childSessionIds */ childSessionIds != null ? childSessionIds : new int[0], /* parentSessionId */ parentSessionId, Loading