Merge changes from topic "181092466_1"

* changes:
  Move version code checking out of StagingManager (3/n)
  Restart verification for committed sessions that are not READY (2/n)
  Do APK verification before committing sessions to StagingManager (1/n)
This commit is contained in:
JW Wang
2021-03-22 02:18:20 +00:00
committed by Android (Google) Code Review
4 changed files with 95 additions and 176 deletions

View File

@@ -699,28 +699,22 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
}
/**
* Notified by the staging manager that pre-reboot verification is about to start. The
* return value should be checked to decide whether it is OK to start pre-reboot
* verification. In the case of a destroyed session, {@code false} is returned and there is
* no need to start pre-reboot verification.
* Called when pre-reboot verification is about to start. This shouldn't be called
* on a destroyed session.
*/
@Override
public boolean notifyStartPreRebootVerification() {
private void notifyStartPreRebootVerification() {
synchronized (mLock) {
Preconditions.checkState(!mDestroyed);
if (mInPreRebootVerification) {
throw new IllegalStateException("Pre-reboot verification has started");
}
if (mDestroyed) {
return false;
}
mInPreRebootVerification = true;
return true;
}
}
/**
* Notified by the staging manager that pre-reboot verification has ended. Now it is safe to
* clean up the session if {@link #abandon()} has been called previously.
* Notified by the staging manager or PIS that pre-reboot verification has ended.
* Now it is safe to clean up the session if {@link #abandon()} has been called previously.
*/
@Override
public void notifyEndPreRebootVerification() {
@@ -744,6 +738,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
assertCallerIsOwnerOrRootOrSystemLocked();
Preconditions.checkArgument(isCommitted());
Preconditions.checkArgument(!mSessionApplied && !mSessionFailed);
notifyStartPreRebootVerification();
verify();
}
@@ -2038,9 +2033,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
if (isStaged()) {
mStagedSession.setSessionFailed(
SessionInfo.STAGED_SESSION_VERIFICATION_FAILED, msgWithErrorCode);
// TODO(b/136257624): Remove this once all verification logic has been transferred out
// of StagingManager.
mStagingManager.notifyVerificationComplete(mStagedSession);
mStagedSession.notifyEndPreRebootVerification();
} else {
// Dispatch message to remove session from PackageInstallerService.
dispatchSessionFinished(error, msg, null);
@@ -2160,20 +2153,14 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
.write();
}
if (params.isStaged) {
mStagingManager.commitSession(mStagedSession);
// TODO(b/136257624): CTS test fails if we don't send session finished broadcast, even
// though ideally, we just need to send session committed broadcast.
dispatchSessionFinished(INSTALL_SUCCEEDED, "Session staged", null);
return;
}
if (isApexSession()) {
destroyInternal();
dispatchSessionFinished(PackageManager.INSTALL_FAILED_INTERNAL_ERROR,
"APEX packages can only be installed using staged sessions.", null);
return;
mStagedSession.verifySession();
} else {
verify();
}
verify();
}
private void verify() {
@@ -2338,16 +2325,14 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
"Session not sealed");
}
// TODO(b/136257624): Some logic in this if block probably belongs in
// makeInstallParams().
if (!isMultiPackage() && !isApexSession()) {
if (!isMultiPackage()) {
Objects.requireNonNull(mPackageName);
Objects.requireNonNull(mSigningDetails);
Objects.requireNonNull(mResolvedBaseFile);
// Inherit any packages and native libraries from existing install that
// haven't been overridden.
if (params.mode == SessionParams.MODE_INHERIT_EXISTING) {
if (!isApexSession() && params.mode == SessionParams.MODE_INHERIT_EXISTING) {
try {
final List<File> fromFiles = mResolvedInheritedFiles;
final File toDir = stageDir;
@@ -2394,18 +2379,23 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
"Failed to inherit existing install", e);
}
}
// For mode inherit existing, it would link/copy existing files to stage dir in the
// above block. Therefore, we need to parse the complete package in stage dir here.
// Besides, PackageLite may be null for staged sessions that don't complete pre-reboot
// verification.
mPackageLite = getOrParsePackageLiteLocked(stageDir, /* flags */ 0);
// TODO: surface more granular state from dexopt
mInternalProgress = 0.5f;
computeProgressLocked(true);
if (!isApexSession()) {
// For mode inherit existing, it would link/copy existing files to stage dir in the
// above block. Therefore, we need to parse the complete package in stage dir here.
// Besides, PackageLite may be null for staged sessions that don't complete
// pre-reboot verification.
mPackageLite = getOrParsePackageLiteLocked(stageDir, /* flags */ 0);
extractNativeLibraries(mPackageLite, stageDir, params.abiOverride,
mayInheritNativeLibs());
// TODO: surface more granular state from dexopt
mInternalProgress = 0.5f;
computeProgressLocked(true);
extractNativeLibraries(mPackageLite, stageDir, params.abiOverride,
mayInheritNativeLibs());
} else {
mPackageLite = getOrParsePackageLiteLocked(mResolvedBaseFile, /* flags */ 0);
}
}
final IPackageInstallObserver2 localObserver;
@@ -2452,16 +2442,17 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
}
private void onVerificationComplete() {
// Staged sessions will be installed later during boot
// APK verification is done. Continue the installation depending on whether it is a
// staged session or not. For a staged session, we will hand it over to the staging
// manager to complete the installation.
if (isStaged()) {
// TODO(b/136257624): Remove this once all verification logic has been transferred out
// of StagingManager.
mStagingManager.notifyPreRebootVerification_Apk_Complete(mStagedSession);
// TODO(b/136257624): We also need to destroy internals for verified staged session,
// otherwise file descriptors are never closed for verified staged session until reboot
mStagingManager.commitSession(mStagedSession);
return;
}
// APEX sessions should be handled above
Preconditions.checkState(!isApexSession());
install();
}
@@ -2694,6 +2685,8 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
mPackageName = apk.getPackageName();
mVersionCode = apk.getLongVersionCode();
}
mSigningDetails = apk.getSigningDetails();
}
/**

View File

@@ -17423,13 +17423,6 @@ public class PackageManagerService extends IPackageManager.Stub
}
public void handleStartCopy() {
if ((installFlags & PackageManager.INSTALL_APEX) != 0) {
// Apex packages get verified in StagingManager currently.
// TODO(b/136257624): Move apex verification logic out of StagingManager
mRet = INSTALL_SUCCEEDED;
return;
}
PackageInfoLite pkgLite = PackageManagerServiceUtils.getMinimalPackageInfo(mContext,
mPackageLite, origin.resolvedPath, installFlags, packageAbiOverride);
@@ -17441,7 +17434,10 @@ public class PackageManagerService extends IPackageManager.Stub
// Perform package verification and enable rollback (unless we are simply moving the
// package).
if (!origin.existing) {
sendApkVerificationRequest(pkgLite);
if ((installFlags & PackageManager.INSTALL_APEX) == 0) {
// TODO(b/182426975): treat APEX as APK when APK verification is concerned
sendApkVerificationRequest(pkgLite);
}
if ((installFlags & PackageManager.INSTALL_ENABLE_ROLLBACK) != 0) {
sendEnableRollbackRequest();
}
@@ -25894,6 +25890,11 @@ public class PackageManagerService extends IPackageManager.Stub
private int verifyReplacingVersionCode(PackageInfoLite pkgLite,
long requiredInstalledVersionCode, int installFlags) {
if ((installFlags & PackageManager.INSTALL_APEX) != 0) {
return verifyReplacingVersionCodeForApex(
pkgLite, requiredInstalledVersionCode, installFlags);
}
String packageName = pkgLite.packageName;
synchronized (mLock) {
// Package which currently owns the data that the new package will own if installed.
@@ -25940,6 +25941,40 @@ public class PackageManagerService extends IPackageManager.Stub
return PackageManager.INSTALL_SUCCEEDED;
}
private int verifyReplacingVersionCodeForApex(PackageInfoLite pkgLite,
long requiredInstalledVersionCode, int installFlags) {
String packageName = pkgLite.packageName;
final PackageInfo activePackage = mApexManager.getPackageInfo(packageName,
ApexManager.MATCH_ACTIVE_PACKAGE);
if (activePackage == null) {
Slog.w(TAG, "Attempting to install new APEX package " + packageName);
return PackageManager.INSTALL_FAILED_WRONG_INSTALLED_VERSION;
}
final long activeVersion = activePackage.getLongVersionCode();
if (requiredInstalledVersionCode != PackageManager.VERSION_CODE_HIGHEST
&& activeVersion != requiredInstalledVersionCode) {
Slog.w(TAG, "Installed version of APEX package " + packageName
+ " does not match required. Active version: " + activeVersion
+ " required: " + requiredInstalledVersionCode);
return PackageManager.INSTALL_FAILED_WRONG_INSTALLED_VERSION;
}
final boolean isAppDebuggable = (activePackage.applicationInfo.flags
& ApplicationInfo.FLAG_DEBUGGABLE) != 0;
final long newVersionCode = pkgLite.getLongVersionCode();
if (!PackageManagerServiceUtils.isDowngradePermitted(installFlags, isAppDebuggable)
&& newVersionCode < activeVersion) {
Slog.w(TAG, "Downgrade of APEX package " + packageName
+ " is not allowed. Active version: " + activeVersion
+ " attempted: " + newVersionCode);
return PackageManager.INSTALL_FAILED_VERSION_DOWNGRADE;
}
return PackageManager.INSTALL_SUCCEEDED;
}
/**
* Check and throw if the given before/after packages would be considered a
* downgrade.

View File

@@ -28,7 +28,6 @@ import android.content.IIntentSender;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.IntentSender;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageInstaller;
import android.content.pm.PackageInstaller.SessionInfo;
@@ -80,6 +79,7 @@ import java.io.FileWriter;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.function.Predicate;
@@ -112,6 +112,8 @@ public class StagingManager {
@GuardedBy("mSuccessfulStagedSessionIds")
private final List<Integer> mSuccessfulStagedSessionIds = new ArrayList<>();
private final CompletableFuture<Void> mBootCompleted = new CompletableFuture<>();
interface StagedSession {
boolean isMultiPackage();
boolean isApexSession();
@@ -136,7 +138,6 @@ public class StagingManager {
boolean hasParentSessionId();
long getCommittedMillis();
void abandon();
boolean notifyStartPreRebootVerification();
void notifyEndPreRebootVerification();
void verifySession();
}
@@ -294,15 +295,6 @@ public class StagingManager {
throw new PackageManagerException(SessionInfo.STAGED_SESSION_VERIFICATION_FAILED,
"Failed to parse APEX package " + apexInfo.modulePath + " : " + e, e);
}
final PackageInfo activePackage = mApexManager.getPackageInfo(packageInfo.packageName,
ApexManager.MATCH_ACTIVE_PACKAGE);
if (activePackage == null) {
Slog.w(TAG, "Attempting to install new APEX package " + packageInfo.packageName);
throw new PackageManagerException(SessionInfo.STAGED_SESSION_VERIFICATION_FAILED,
"It is forbidden to install new APEX packages.");
}
checkRequiredVersionCode(session, activePackage);
checkDowngrade(session, activePackage, packageInfo);
result.add(packageInfo);
apexPackageNames.add(packageInfo.packageName);
}
@@ -325,40 +317,6 @@ public class StagingManager {
"Could not find rollback id for commit session: " + sessionId);
}
private void checkRequiredVersionCode(final StagedSession session,
final PackageInfo activePackage) throws PackageManagerException {
if (session.sessionParams().requiredInstalledVersionCode
== PackageManager.VERSION_CODE_HIGHEST) {
return;
}
final long activeVersion = activePackage.applicationInfo.longVersionCode;
if (activeVersion != session.sessionParams().requiredInstalledVersionCode) {
throw new PackageManagerException(
SessionInfo.STAGED_SESSION_VERIFICATION_FAILED,
"Installed version of APEX package " + activePackage.packageName
+ " does not match required. Active version: " + activeVersion
+ " required: " + session.sessionParams().requiredInstalledVersionCode);
}
}
private void checkDowngrade(final StagedSession session,
final PackageInfo activePackage, final PackageInfo newPackage)
throws PackageManagerException {
final long activeVersion = activePackage.applicationInfo.longVersionCode;
final long newVersionCode = newPackage.applicationInfo.longVersionCode;
final boolean isAppDebuggable = (activePackage.applicationInfo.flags
& ApplicationInfo.FLAG_DEBUGGABLE) != 0;
final boolean allowsDowngrade = PackageManagerServiceUtils.isDowngradePermitted(
session.sessionParams().installFlags, isAppDebuggable);
if (activeVersion > newVersionCode && !allowsDowngrade) {
throw new PackageManagerException(
SessionInfo.STAGED_SESSION_VERIFICATION_FAILED,
"Downgrade of APEX package " + newPackage.packageName
+ " is not allowed. Active version: " + activeVersion
+ " attempted: " + newVersionCode);
}
}
// Reverts apex sessions and user data (if checkpoint is supported). Also reboots the device.
private void abortCheckpoint(String failureReason, boolean supportsCheckpoint,
boolean needsCheckpoint) {
@@ -863,7 +821,8 @@ public class StagingManager {
} else if (!session.isSessionReady()) {
// The framework got restarted before the pre-reboot verification could complete,
// restart the verification.
mPreRebootVerificationHandler.startPreRebootVerification(session);
Slog.i(TAG, "Restart verification for session=" + session.sessionId());
mBootCompleted.thenRun(() -> session.verifySession());
StagedSession session2 = sessions.set(j - 1, session);
sessions.set(i, session2);
j--;
@@ -1043,7 +1002,7 @@ public class StagingManager {
@VisibleForTesting
void onBootCompletedBroadcastReceived() {
mPreRebootVerificationHandler.readyToStart();
mBootCompleted.complete(null);
BackgroundThread.getExecutor().execute(() -> logFailedApexSessionsIfNecessary());
}
@@ -1084,24 +1043,7 @@ public class StagingManager {
return session;
}
// TODO(b/136257624): Temporary API to let PMS communicate with StagingManager. When all
// verification logic is extracted out of StagingManager into PMS, we can remove
// this.
void notifyVerificationComplete(StagedSession session) {
mPreRebootVerificationHandler.onPreRebootVerificationComplete(session);
}
// TODO(b/136257624): Temporary API to let PMS communicate with StagingManager. When all
// verification logic is extracted out of StagingManager into PMS, we can remove
// this.
void notifyPreRebootVerification_Apk_Complete(@NonNull StagedSession session) {
mPreRebootVerificationHandler.notifyPreRebootVerification_Apk_Complete(session);
}
private final class PreRebootVerificationHandler extends Handler {
// Hold sessions before handler gets ready to do the verification.
private List<StagedSession> mPendingSessions;
private boolean mIsReady;
PreRebootVerificationHandler(Looper looper) {
super(looper);
@@ -1115,7 +1057,6 @@ public class StagingManager {
* <p><ul>
* <li>MSG_PRE_REBOOT_VERIFICATION_START</li>
* <li>MSG_PRE_REBOOT_VERIFICATION_APEX</li>
* <li>MSG_PRE_REBOOT_VERIFICATION_APK</li>
* <li>MSG_PRE_REBOOT_VERIFICATION_END</li>
* </ul></p>
*
@@ -1123,8 +1064,7 @@ public class StagingManager {
*/
private static final int MSG_PRE_REBOOT_VERIFICATION_START = 1;
private static final int MSG_PRE_REBOOT_VERIFICATION_APEX = 2;
private static final int MSG_PRE_REBOOT_VERIFICATION_APK = 3;
private static final int MSG_PRE_REBOOT_VERIFICATION_END = 4;
private static final int MSG_PRE_REBOOT_VERIFICATION_END = 3;
@Override
public void handleMessage(Message msg) {
@@ -1144,9 +1084,6 @@ public class StagingManager {
case MSG_PRE_REBOOT_VERIFICATION_APEX:
handlePreRebootVerification_Apex(session, rollbackId);
break;
case MSG_PRE_REBOOT_VERIFICATION_APK:
handlePreRebootVerification_Apk(session);
break;
case MSG_PRE_REBOOT_VERIFICATION_END:
handlePreRebootVerification_End(session);
break;
@@ -1159,35 +1096,15 @@ public class StagingManager {
}
}
// Notify the handler that system is ready, and reschedule the pre-reboot verifications.
private synchronized void readyToStart() {
mIsReady = true;
if (mPendingSessions != null) {
for (int i = 0; i < mPendingSessions.size(); i++) {
StagedSession session = mPendingSessions.get(i);
startPreRebootVerification(session);
}
mPendingSessions = null;
}
}
// Method for starting the pre-reboot verification
private synchronized void startPreRebootVerification(
@NonNull StagedSession session) {
if (!mIsReady) {
if (mPendingSessions == null) {
mPendingSessions = new ArrayList<>();
}
mPendingSessions.add(session);
return;
}
if (session.notifyStartPreRebootVerification()) {
mBootCompleted.thenRun(() -> {
int sessionId = session.sessionId();
Slog.d(TAG, "Starting preRebootVerification for session " + sessionId);
obtainMessage(MSG_PRE_REBOOT_VERIFICATION_START, sessionId, -1, session)
.sendToTarget();
}
});
}
private void onPreRebootVerificationFailure(StagedSession session,
@@ -1216,12 +1133,6 @@ public class StagingManager {
private void notifyPreRebootVerification_Apex_Complete(
@NonNull StagedSession session) {
obtainMessage(MSG_PRE_REBOOT_VERIFICATION_APK, session.sessionId(), -1, session)
.sendToTarget();
}
private void notifyPreRebootVerification_Apk_Complete(
@NonNull StagedSession session) {
obtainMessage(MSG_PRE_REBOOT_VERIFICATION_END, session.sessionId(), -1, session)
.sendToTarget();
}
@@ -1308,19 +1219,6 @@ public class StagingManager {
notifyPreRebootVerification_Apex_Complete(session);
}
/**
* Pre-reboot verification state for apk files. Session is sent to
* {@link PackageManagerService} for verification and it notifies back the result via
* {@link #notifyPreRebootVerification_Apk_Complete}
*/
private void handlePreRebootVerification_Apk(@NonNull StagedSession session) {
if (!session.containsApkSession()) {
notifyPreRebootVerification_Apk_Complete(session);
return;
}
session.verifySession();
}
/**
* Pre-reboot verification state for wrapping up:
* <p><ul>

View File

@@ -227,8 +227,8 @@ public class StagingManagerTest {
assertThat(destroyedNonReadySession.isDestroyed()).isTrue();
mStagingManager.onBootCompletedBroadcastReceived();
assertThat(nonReadyApkSession.hasPreRebootVerificationStarted()).isTrue();
assertThat(nonReadyApexSession.hasPreRebootVerificationStarted()).isTrue();
assertThat(nonReadyApkSession.hasVerificationStarted()).isTrue();
assertThat(nonReadyApexSession.hasVerificationStarted()).isTrue();
}
@Test
@@ -519,7 +519,7 @@ public class StagingManagerTest {
private int mParentSessionId = -1;
private String mPackageName;
private boolean mIsAbandonded = false;
private boolean mPreRebootVerificationStarted = false;
private boolean mVerificationStarted = false;
private final List<StagingManager.StagedSession> mChildSessions = new ArrayList<>();
private FakeStagedSession(int sessionId) {
@@ -550,8 +550,8 @@ public class StagingManagerTest {
return mIsAbandonded;
}
private boolean hasPreRebootVerificationStarted() {
return mPreRebootVerificationStarted;
private boolean hasVerificationStarted() {
return mVerificationStarted;
}
private FakeStagedSession addChildSession(FakeStagedSession session) {
@@ -705,13 +705,6 @@ public class StagingManagerTest {
mIsAbandonded = true;
}
@Override
public boolean notifyStartPreRebootVerification() {
mPreRebootVerificationStarted = true;
// TODO(ioffe): change to true when tests for pre-reboot verification are added.
return false;
}
@Override
public void notifyEndPreRebootVerification() {
throw new UnsupportedOperationException();
@@ -719,7 +712,7 @@ public class StagingManagerTest {
@Override
public void verifySession() {
throw new UnsupportedOperationException();
mVerificationStarted = true;
}
}
}