Merge changes from topic "abandon-race" into rvc-dev am: 6b58f8ca37

Change-Id: If8b8b5802f903ed69ba68539fc0c78471c750f43
This commit is contained in:
Mohammad Samiul Islam
2020-05-07 13:01:57 +00:00
committed by Automerger Merge Worker
3 changed files with 167 additions and 41 deletions

View File

@@ -1270,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(

View File

@@ -1076,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}.
*/
@@ -2561,7 +2574,13 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
+ mParentSessionId + " and may not be abandoned directly.");
}
synchronized (mLock) {
assertCallerIsOwnerOrRootLocked();
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
@@ -2571,11 +2590,12 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
return;
}
if (mCommitted && params.isStaged) {
synchronized (mLock) {
mDestroyed = true;
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();
}
@@ -2935,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;
@@ -2948,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;
@@ -2962,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;

View File

@@ -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;
@@ -205,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);
}
}
@@ -800,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(
@@ -883,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()) {
@@ -946,27 +950,68 @@ 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 (apexSession == null || isApexSessionFinalized(apexSession)) {
Slog.w(TAG,
"Cannot abort session " + session.sessionId
+ " because it is not active or APEXD is not reachable");
return;
}
try {
mApexManager.abortStagedSession(session.sessionId);
} catch (Exception ignore) {
// 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.");
} else {
mApexManager.abortStagedSession(session.sessionId);
}
} 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) {
@@ -1045,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.
@@ -1127,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);
@@ -1158,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) {
@@ -1203,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();
}
@@ -1224,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
@@ -1272,6 +1363,7 @@ public class StagingManager {
}
} catch (PackageManagerException e) {
session.setStagedSessionFailed(e.error, e.getMessage());
onPreRebootVerificationComplete(session.sessionId);
return;
}
@@ -1304,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);
}
}
@@ -1326,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:
@@ -1340,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();
final boolean hasApex = sessionContainsApex(session);
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());
if (session.isStagedSessionReady()) {
final boolean hasApex = sessionContainsApex(session);
if (hasApex) {
try {
mApexManager.markStagedSessionReady(session.sessionId);
} catch (PackageManagerException e) {
session.setStagedSessionFailed(e.error, e.getMessage());
return;
}
}
}
}
}