1 /* 2 * Copyright (C) 2018 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.server.pm; 18 19 import android.annotation.NonNull; 20 import android.apex.ApexInfo; 21 import android.apex.ApexInfoList; 22 import android.apex.ApexSessionInfo; 23 import android.apex.ApexSessionParams; 24 import android.content.BroadcastReceiver; 25 import android.content.Context; 26 import android.content.IIntentReceiver; 27 import android.content.IIntentSender; 28 import android.content.Intent; 29 import android.content.IntentFilter; 30 import android.content.IntentSender; 31 import android.content.pm.ApplicationInfo; 32 import android.content.pm.PackageInfo; 33 import android.content.pm.PackageInstaller; 34 import android.content.pm.PackageInstaller.SessionInfo; 35 import android.content.pm.PackageInstaller.SessionInfo.StagedSessionErrorCode; 36 import android.content.pm.PackageManager; 37 import android.content.pm.PackageManagerInternal; 38 import android.content.pm.PackageParser.PackageParserException; 39 import android.content.pm.PackageParser.SigningDetails; 40 import android.content.pm.PackageParser.SigningDetails.SignatureSchemeVersion; 41 import android.content.pm.parsing.PackageInfoWithoutStateUtils; 42 import android.content.rollback.RollbackInfo; 43 import android.content.rollback.RollbackManager; 44 import android.os.Bundle; 45 import android.os.Handler; 46 import android.os.IBinder; 47 import android.os.Looper; 48 import android.os.Message; 49 import android.os.PowerManager; 50 import android.os.RemoteException; 51 import android.os.SystemProperties; 52 import android.os.Trace; 53 import android.os.UserHandle; 54 import android.text.TextUtils; 55 import android.util.ArraySet; 56 import android.util.IntArray; 57 import android.util.Slog; 58 import android.util.SparseArray; 59 import android.util.TimingsTraceLog; 60 import android.util.apk.ApkSignatureVerifier; 61 62 import com.android.internal.annotations.GuardedBy; 63 import com.android.internal.annotations.VisibleForTesting; 64 import com.android.internal.content.PackageHelper; 65 import com.android.internal.os.BackgroundThread; 66 import com.android.internal.util.Preconditions; 67 import com.android.server.LocalServices; 68 import com.android.server.SystemService; 69 import com.android.server.SystemServiceManager; 70 import com.android.server.pm.parsing.PackageParser2; 71 import com.android.server.pm.parsing.pkg.AndroidPackage; 72 import com.android.server.pm.parsing.pkg.AndroidPackageUtils; 73 import com.android.server.pm.parsing.pkg.ParsedPackage; 74 import com.android.server.rollback.RollbackManagerInternal; 75 import com.android.server.rollback.WatchdogRollbackLogger; 76 77 import java.io.BufferedReader; 78 import java.io.BufferedWriter; 79 import java.io.File; 80 import java.io.FileReader; 81 import java.io.FileWriter; 82 import java.util.ArrayList; 83 import java.util.List; 84 import java.util.Set; 85 import java.util.concurrent.LinkedBlockingQueue; 86 import java.util.concurrent.TimeUnit; 87 import java.util.function.Predicate; 88 import java.util.function.Supplier; 89 90 /** 91 * This class handles staged install sessions, i.e. install sessions that require packages to 92 * be installed only after a reboot. 93 */ 94 public class StagingManager { 95 96 private static final String TAG = "StagingManager"; 97 98 private final ApexManager mApexManager; 99 private final PowerManager mPowerManager; 100 private final Context mContext; 101 private final PreRebootVerificationHandler mPreRebootVerificationHandler; 102 private final Supplier<PackageParser2> mPackageParserSupplier; 103 104 private final File mFailureReasonFile = new File("/metadata/staged-install/failure_reason.txt"); 105 private String mFailureReason; 106 107 @GuardedBy("mStagedSessions") 108 private final SparseArray<StagedSession> mStagedSessions = new SparseArray<>(); 109 110 @GuardedBy("mFailedPackageNames") 111 private final List<String> mFailedPackageNames = new ArrayList<>(); 112 private String mNativeFailureReason; 113 114 @GuardedBy("mSuccessfulStagedSessionIds") 115 private final List<Integer> mSuccessfulStagedSessionIds = new ArrayList<>(); 116 117 interface StagedSession { isMultiPackage()118 boolean isMultiPackage(); isApexSession()119 boolean isApexSession(); isCommitted()120 boolean isCommitted(); isInTerminalState()121 boolean isInTerminalState(); isDestroyed()122 boolean isDestroyed(); isSessionReady()123 boolean isSessionReady(); isSessionApplied()124 boolean isSessionApplied(); isSessionFailed()125 boolean isSessionFailed(); getChildSessions()126 List<StagedSession> getChildSessions(); getPackageName()127 String getPackageName(); getParentSessionId()128 int getParentSessionId(); sessionId()129 int sessionId(); sessionParams()130 PackageInstaller.SessionParams sessionParams(); sessionContains(Predicate<StagedSession> filter)131 boolean sessionContains(Predicate<StagedSession> filter); containsApkSession()132 boolean containsApkSession(); containsApexSession()133 boolean containsApexSession(); setSessionReady()134 void setSessionReady(); setSessionFailed(@tagedSessionErrorCode int errorCode, String errorMessage)135 void setSessionFailed(@StagedSessionErrorCode int errorCode, String errorMessage); setSessionApplied()136 void setSessionApplied(); installSession(IntentSender statusReceiver)137 void installSession(IntentSender statusReceiver); hasParentSessionId()138 boolean hasParentSessionId(); getCommittedMillis()139 long getCommittedMillis(); abandon()140 void abandon(); notifyStartPreRebootVerification()141 boolean notifyStartPreRebootVerification(); notifyEndPreRebootVerification()142 void notifyEndPreRebootVerification(); verifySession()143 void verifySession(); 144 } 145 StagingManager(Context context, Supplier<PackageParser2> packageParserSupplier)146 StagingManager(Context context, Supplier<PackageParser2> packageParserSupplier) { 147 this(context, packageParserSupplier, ApexManager.getInstance()); 148 } 149 150 @VisibleForTesting StagingManager(Context context, Supplier<PackageParser2> packageParserSupplier, ApexManager apexManager)151 StagingManager(Context context, Supplier<PackageParser2> packageParserSupplier, 152 ApexManager apexManager) { 153 mContext = context; 154 mPackageParserSupplier = packageParserSupplier; 155 156 mApexManager = apexManager; 157 mPowerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE); 158 mPreRebootVerificationHandler = new PreRebootVerificationHandler( 159 BackgroundThread.get().getLooper()); 160 161 if (mFailureReasonFile.exists()) { 162 try (BufferedReader reader = new BufferedReader(new FileReader(mFailureReasonFile))) { 163 mFailureReason = reader.readLine(); 164 } catch (Exception ignore) { } 165 } 166 } 167 168 /** 169 This class manages lifecycle events for StagingManager. 170 */ 171 public static final class Lifecycle extends SystemService { 172 private static StagingManager sStagingManager; 173 Lifecycle(Context context)174 public Lifecycle(Context context) { 175 super(context); 176 } 177 startService(StagingManager stagingManager)178 void startService(StagingManager stagingManager) { 179 sStagingManager = stagingManager; 180 LocalServices.getService(SystemServiceManager.class).startService(this); 181 } 182 183 @Override onStart()184 public void onStart() { 185 // no-op 186 } 187 188 @Override onBootPhase(int phase)189 public void onBootPhase(int phase) { 190 if (phase == SystemService.PHASE_BOOT_COMPLETED && sStagingManager != null) { 191 sStagingManager.markStagedSessionsAsSuccessful(); 192 sStagingManager.markBootCompleted(); 193 } 194 } 195 } 196 markBootCompleted()197 private void markBootCompleted() { 198 mApexManager.markBootCompleted(); 199 } 200 201 /** 202 * Validates the signature used to sign the container of the new apex package 203 * 204 * @param newApexPkg The new apex package that is being installed 205 */ validateApexSignature(PackageInfo newApexPkg)206 private void validateApexSignature(PackageInfo newApexPkg) 207 throws PackageManagerException { 208 // Get signing details of the new package 209 final String apexPath = newApexPkg.applicationInfo.sourceDir; 210 final String packageName = newApexPkg.packageName; 211 int minSignatureScheme = ApkSignatureVerifier.getMinimumSignatureSchemeVersionForTargetSdk( 212 newApexPkg.applicationInfo.targetSdkVersion); 213 214 final SigningDetails newSigningDetails; 215 try { 216 newSigningDetails = ApkSignatureVerifier.verify(apexPath, minSignatureScheme); 217 } catch (PackageParserException e) { 218 throw new PackageManagerException(SessionInfo.STAGED_SESSION_VERIFICATION_FAILED, 219 "Failed to parse APEX package " + apexPath + " : " + e, e); 220 } 221 222 // Get signing details of the existing package 223 final PackageInfo existingApexPkg = mApexManager.getPackageInfo(packageName, 224 ApexManager.MATCH_ACTIVE_PACKAGE); 225 if (existingApexPkg == null) { 226 // This should never happen, because submitSessionToApexService ensures that no new 227 // apexes were installed. 228 throw new IllegalStateException("Unknown apex package " + packageName); 229 } 230 231 final SigningDetails existingSigningDetails; 232 try { 233 existingSigningDetails = ApkSignatureVerifier.verify( 234 existingApexPkg.applicationInfo.sourceDir, SignatureSchemeVersion.JAR); 235 } catch (PackageParserException e) { 236 throw new PackageManagerException(SessionInfo.STAGED_SESSION_VERIFICATION_FAILED, 237 "Failed to parse APEX package " + existingApexPkg.applicationInfo.sourceDir 238 + " : " + e, e); 239 } 240 241 // Verify signing details for upgrade 242 if (newSigningDetails.checkCapability(existingSigningDetails, 243 SigningDetails.CertCapabilities.INSTALLED_DATA) 244 || existingSigningDetails.checkCapability(newSigningDetails, 245 SigningDetails.CertCapabilities.ROLLBACK)) { 246 return; 247 } 248 249 throw new PackageManagerException(SessionInfo.STAGED_SESSION_VERIFICATION_FAILED, 250 "APK-container signature of APEX package " + packageName + " with version " 251 + newApexPkg.versionCodeMajor + " and path " + apexPath + " is not" 252 + " compatible with the one currently installed on device"); 253 } 254 submitSessionToApexService(@onNull StagedSession session, int rollbackId)255 private List<PackageInfo> submitSessionToApexService(@NonNull StagedSession session, 256 int rollbackId) throws PackageManagerException { 257 final IntArray childSessionIds = new IntArray(); 258 if (session.isMultiPackage()) { 259 for (StagedSession s : session.getChildSessions()) { 260 if (s.isApexSession()) { 261 childSessionIds.add(s.sessionId()); 262 } 263 } 264 } 265 ApexSessionParams apexSessionParams = new ApexSessionParams(); 266 apexSessionParams.sessionId = session.sessionId(); 267 apexSessionParams.childSessionIds = childSessionIds.toArray(); 268 if (session.sessionParams().installReason == PackageManager.INSTALL_REASON_ROLLBACK) { 269 apexSessionParams.isRollback = true; 270 apexSessionParams.rollbackId = rollbackId; 271 } else { 272 if (rollbackId != -1) { 273 apexSessionParams.hasRollbackEnabled = true; 274 apexSessionParams.rollbackId = rollbackId; 275 } 276 } 277 // submitStagedSession will throw a PackageManagerException if apexd verification fails, 278 // which will be propagated to populate stagedSessionErrorMessage of this session. 279 final ApexInfoList apexInfoList = mApexManager.submitStagedSession(apexSessionParams); 280 final List<PackageInfo> result = new ArrayList<>(); 281 final List<String> apexPackageNames = new ArrayList<>(); 282 for (ApexInfo apexInfo : apexInfoList.apexInfos) { 283 final PackageInfo packageInfo; 284 final int flags = PackageManager.GET_META_DATA; 285 try (PackageParser2 packageParser = mPackageParserSupplier.get()) { 286 File apexFile = new File(apexInfo.modulePath); 287 final ParsedPackage parsedPackage = packageParser.parsePackage( 288 apexFile, flags, false); 289 packageInfo = PackageInfoWithoutStateUtils.generate(parsedPackage, apexInfo, flags); 290 if (packageInfo == null) { 291 throw new PackageManagerException( 292 SessionInfo.STAGED_SESSION_VERIFICATION_FAILED, 293 "Unable to generate package info: " + apexInfo.modulePath); 294 } 295 } catch (PackageParserException e) { 296 throw new PackageManagerException(SessionInfo.STAGED_SESSION_VERIFICATION_FAILED, 297 "Failed to parse APEX package " + apexInfo.modulePath + " : " + e, e); 298 } 299 final PackageInfo activePackage = mApexManager.getPackageInfo(packageInfo.packageName, 300 ApexManager.MATCH_ACTIVE_PACKAGE); 301 if (activePackage == null) { 302 Slog.w(TAG, "Attempting to install new APEX package " + packageInfo.packageName); 303 throw new PackageManagerException(SessionInfo.STAGED_SESSION_VERIFICATION_FAILED, 304 "It is forbidden to install new APEX packages."); 305 } 306 checkRequiredVersionCode(session, activePackage); 307 checkDowngrade(session, activePackage, packageInfo); 308 result.add(packageInfo); 309 apexPackageNames.add(packageInfo.packageName); 310 } 311 Slog.d(TAG, "Session " + session.sessionId() + " has following APEX packages: " 312 + apexPackageNames); 313 return result; 314 } 315 retrieveRollbackIdForCommitSession(int sessionId)316 private int retrieveRollbackIdForCommitSession(int sessionId) throws PackageManagerException { 317 RollbackManager rm = mContext.getSystemService(RollbackManager.class); 318 319 final List<RollbackInfo> rollbacks = rm.getRecentlyCommittedRollbacks(); 320 for (int i = 0, size = rollbacks.size(); i < size; i++) { 321 final RollbackInfo rollback = rollbacks.get(i); 322 if (rollback.getCommittedSessionId() == sessionId) { 323 return rollback.getRollbackId(); 324 } 325 } 326 throw new PackageManagerException( 327 "Could not find rollback id for commit session: " + sessionId); 328 } 329 checkRequiredVersionCode(final StagedSession session, final PackageInfo activePackage)330 private void checkRequiredVersionCode(final StagedSession session, 331 final PackageInfo activePackage) throws PackageManagerException { 332 if (session.sessionParams().requiredInstalledVersionCode 333 == PackageManager.VERSION_CODE_HIGHEST) { 334 return; 335 } 336 final long activeVersion = activePackage.applicationInfo.longVersionCode; 337 if (activeVersion != session.sessionParams().requiredInstalledVersionCode) { 338 throw new PackageManagerException( 339 SessionInfo.STAGED_SESSION_VERIFICATION_FAILED, 340 "Installed version of APEX package " + activePackage.packageName 341 + " does not match required. Active version: " + activeVersion 342 + " required: " + session.sessionParams().requiredInstalledVersionCode); 343 } 344 } 345 checkDowngrade(final StagedSession session, final PackageInfo activePackage, final PackageInfo newPackage)346 private void checkDowngrade(final StagedSession session, 347 final PackageInfo activePackage, final PackageInfo newPackage) 348 throws PackageManagerException { 349 final long activeVersion = activePackage.applicationInfo.longVersionCode; 350 final long newVersionCode = newPackage.applicationInfo.longVersionCode; 351 final boolean isAppDebuggable = (activePackage.applicationInfo.flags 352 & ApplicationInfo.FLAG_DEBUGGABLE) != 0; 353 final boolean allowsDowngrade = PackageManagerServiceUtils.isDowngradePermitted( 354 session.sessionParams().installFlags, isAppDebuggable); 355 if (activeVersion > newVersionCode && !allowsDowngrade) { 356 throw new PackageManagerException( 357 SessionInfo.STAGED_SESSION_VERIFICATION_FAILED, 358 "Downgrade of APEX package " + newPackage.packageName 359 + " is not allowed. Active version: " + activeVersion 360 + " attempted: " + newVersionCode); 361 } 362 } 363 364 // Reverts apex sessions and user data (if checkpoint is supported). Also reboots the device. abortCheckpoint(String failureReason, boolean supportsCheckpoint, boolean needsCheckpoint)365 private void abortCheckpoint(String failureReason, boolean supportsCheckpoint, 366 boolean needsCheckpoint) { 367 Slog.e(TAG, failureReason); 368 try { 369 if (supportsCheckpoint && needsCheckpoint) { 370 // Store failure reason for next reboot 371 try (BufferedWriter writer = 372 new BufferedWriter(new FileWriter(mFailureReasonFile))) { 373 writer.write(failureReason); 374 } catch (Exception e) { 375 Slog.w(TAG, "Failed to save failure reason: ", e); 376 } 377 378 // Only revert apex sessions if device supports updating apex 379 if (mApexManager.isApexSupported()) { 380 mApexManager.revertActiveSessions(); 381 } 382 383 PackageHelper.getStorageManager().abortChanges( 384 "abort-staged-install", false /*retry*/); 385 } 386 } catch (Exception e) { 387 Slog.wtf(TAG, "Failed to abort checkpoint", e); 388 // Only revert apex sessions if device supports updating apex 389 if (mApexManager.isApexSupported()) { 390 mApexManager.revertActiveSessions(); 391 } 392 mPowerManager.reboot(null); 393 } 394 } 395 396 /** 397 * Utility function for extracting apex sessions out of multi-package/single session. 398 */ extractApexSessions(StagedSession session)399 private List<StagedSession> extractApexSessions(StagedSession session) { 400 List<StagedSession> apexSessions = new ArrayList<>(); 401 if (session.isMultiPackage()) { 402 for (StagedSession s : session.getChildSessions()) { 403 if (s.containsApexSession()) { 404 apexSessions.add(s); 405 } 406 } 407 } else { 408 apexSessions.add(session); 409 } 410 return apexSessions; 411 } 412 413 /** 414 * Checks if all apk-in-apex were installed without errors for all of the apex sessions. Throws 415 * error for any apk-in-apex failed to install. 416 * 417 * @throws PackageManagerException if any apk-in-apex failed to install 418 */ checkInstallationOfApkInApexSuccessful(StagedSession session)419 private void checkInstallationOfApkInApexSuccessful(StagedSession session) 420 throws PackageManagerException { 421 final List<StagedSession> apexSessions = extractApexSessions(session); 422 if (apexSessions.isEmpty()) { 423 return; 424 } 425 426 for (StagedSession apexSession : apexSessions) { 427 String packageName = apexSession.getPackageName(); 428 String errorMsg = mApexManager.getApkInApexInstallError(packageName); 429 if (errorMsg != null) { 430 throw new PackageManagerException(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED, 431 "Failed to install apk-in-apex of " + packageName + " : " + errorMsg); 432 } 433 } 434 } 435 436 /** 437 * Perform snapshot and restore as required both for APEXes themselves and for apks in APEX. 438 * Apks inside apex are not installed using apk-install flow. They are scanned from the system 439 * directory directly by PackageManager, as such, RollbackManager need to handle their data 440 * separately here. 441 */ snapshotAndRestoreForApexSession(StagedSession session)442 private void snapshotAndRestoreForApexSession(StagedSession session) { 443 boolean doSnapshotOrRestore = 444 (session.sessionParams().installFlags & PackageManager.INSTALL_ENABLE_ROLLBACK) != 0 445 || session.sessionParams().installReason == PackageManager.INSTALL_REASON_ROLLBACK; 446 if (!doSnapshotOrRestore) { 447 return; 448 } 449 450 // Find all the apex sessions that needs processing 451 final List<StagedSession> apexSessions = extractApexSessions(session); 452 if (apexSessions.isEmpty()) { 453 return; 454 } 455 456 final UserManagerInternal um = LocalServices.getService(UserManagerInternal.class); 457 final int[] allUsers = um.getUserIds(); 458 RollbackManagerInternal rm = LocalServices.getService(RollbackManagerInternal.class); 459 460 for (int i = 0, sessionsSize = apexSessions.size(); i < sessionsSize; i++) { 461 final String packageName = apexSessions.get(i).getPackageName(); 462 // Perform any snapshots or restores for the APEX itself 463 snapshotAndRestoreApexUserData(packageName, allUsers, rm); 464 465 // Process the apks inside the APEX 466 final List<String> apksInApex = mApexManager.getApksInApex(packageName); 467 for (int j = 0, apksSize = apksInApex.size(); j < apksSize; j++) { 468 snapshotAndRestoreApkInApexUserData(apksInApex.get(j), allUsers, rm); 469 } 470 } 471 } 472 snapshotAndRestoreApexUserData( String packageName, int[] allUsers, RollbackManagerInternal rm)473 private void snapshotAndRestoreApexUserData( 474 String packageName, int[] allUsers, RollbackManagerInternal rm) { 475 // appId, ceDataInode, and seInfo are not needed for APEXes 476 rm.snapshotAndRestoreUserData(packageName, UserHandle.toUserHandles(allUsers), 0, 0, 477 null, 0 /*token*/); 478 } 479 snapshotAndRestoreApkInApexUserData( String packageName, int[] allUsers, RollbackManagerInternal rm)480 private void snapshotAndRestoreApkInApexUserData( 481 String packageName, int[] allUsers, RollbackManagerInternal rm) { 482 PackageManagerInternal mPmi = LocalServices.getService(PackageManagerInternal.class); 483 AndroidPackage pkg = mPmi.getPackage(packageName); 484 if (pkg == null) { 485 Slog.e(TAG, "Could not find package: " + packageName 486 + "for snapshotting/restoring user data."); 487 return; 488 } 489 490 int appId = -1; 491 long ceDataInode = -1; 492 final PackageSetting ps = mPmi.getPackageSetting(packageName); 493 if (ps != null) { 494 appId = ps.appId; 495 ceDataInode = ps.getCeDataInode(UserHandle.USER_SYSTEM); 496 // NOTE: We ignore the user specified in the InstallParam because we know this is 497 // an update, and hence need to restore data for all installed users. 498 final int[] installedUsers = ps.queryInstalledUsers(allUsers, true); 499 500 final String seInfo = AndroidPackageUtils.getSeInfo(pkg, ps); 501 rm.snapshotAndRestoreUserData(packageName, UserHandle.toUserHandles(installedUsers), 502 appId, ceDataInode, seInfo, 0 /*token*/); 503 } 504 } 505 506 /** 507 * Prepares for the logging of apexd reverts by storing the native failure reason if necessary, 508 * and adding the package name of the session which apexd reverted to the list of reverted 509 * session package names. 510 * Logging needs to wait until the ACTION_BOOT_COMPLETED broadcast is sent. 511 */ prepareForLoggingApexdRevert(@onNull StagedSession session, @NonNull String nativeFailureReason)512 private void prepareForLoggingApexdRevert(@NonNull StagedSession session, 513 @NonNull String nativeFailureReason) { 514 synchronized (mFailedPackageNames) { 515 mNativeFailureReason = nativeFailureReason; 516 if (session.getPackageName() != null) { 517 mFailedPackageNames.add(session.getPackageName()); 518 } 519 } 520 } 521 resumeSession(@onNull StagedSession session, boolean supportsCheckpoint, boolean needsCheckpoint)522 private void resumeSession(@NonNull StagedSession session, boolean supportsCheckpoint, 523 boolean needsCheckpoint) throws PackageManagerException { 524 Slog.d(TAG, "Resuming session " + session.sessionId()); 525 526 final boolean hasApex = session.containsApexSession(); 527 528 // Before we resume session, we check if revert is needed or not. Typically, we enter file- 529 // system checkpoint mode when we reboot first time in order to install staged sessions. We 530 // want to install staged sessions in this mode as rebooting now will revert user data. If 531 // something goes wrong, then we reboot again to enter fs-rollback mode. Rebooting now will 532 // have no effect on user data, so mark the sessions as failed instead. 533 // If checkpoint is supported, then we only resume sessions if we are in checkpointing mode. 534 // If not, we fail all sessions. 535 if (supportsCheckpoint && !needsCheckpoint) { 536 String revertMsg = "Reverting back to safe state. Marking " + session.sessionId() 537 + " as failed."; 538 final String reasonForRevert = getReasonForRevert(); 539 if (!TextUtils.isEmpty(reasonForRevert)) { 540 revertMsg += " Reason for revert: " + reasonForRevert; 541 } 542 Slog.d(TAG, revertMsg); 543 session.setSessionFailed(SessionInfo.STAGED_SESSION_UNKNOWN, revertMsg); 544 return; 545 } 546 547 // Handle apk and apk-in-apex installation 548 if (hasApex) { 549 checkInstallationOfApkInApexSuccessful(session); 550 checkDuplicateApkInApex(session); 551 snapshotAndRestoreForApexSession(session); 552 Slog.i(TAG, "APEX packages in session " + session.sessionId() 553 + " were successfully activated. Proceeding with APK packages, if any"); 554 } 555 // The APEX part of the session is activated, proceed with the installation of APKs. 556 Slog.d(TAG, "Installing APK packages in session " + session.sessionId()); 557 TimingsTraceLog t = new TimingsTraceLog( 558 "StagingManagerTiming", Trace.TRACE_TAG_PACKAGE_MANAGER); 559 t.traceBegin("installApksInSession"); 560 installApksInSession(session); 561 t.traceEnd(); 562 563 Slog.d(TAG, "Marking session " + session.sessionId() + " as applied"); 564 session.setSessionApplied(); 565 if (hasApex) { 566 if (supportsCheckpoint) { 567 // Store the session ID, which will be marked as successful by ApexManager upon 568 // boot completion. 569 synchronized (mSuccessfulStagedSessionIds) { 570 mSuccessfulStagedSessionIds.add(session.sessionId()); 571 } 572 } else { 573 // Mark sessions as successful immediately on non-checkpointing devices. 574 mApexManager.markStagedSessionSuccessful(session.sessionId()); 575 } 576 } 577 } 578 onInstallationFailure(StagedSession session, PackageManagerException e, boolean supportsCheckpoint, boolean needsCheckpoint)579 void onInstallationFailure(StagedSession session, PackageManagerException e, 580 boolean supportsCheckpoint, boolean needsCheckpoint) { 581 session.setSessionFailed(e.error, e.getMessage()); 582 abortCheckpoint("Failed to install sessionId: " + session.sessionId() 583 + " Error: " + e.getMessage(), supportsCheckpoint, needsCheckpoint); 584 585 // If checkpoint is not supported, we have to handle failure for one staged session. 586 if (!session.containsApexSession()) { 587 return; 588 } 589 590 if (!mApexManager.revertActiveSessions()) { 591 Slog.e(TAG, "Failed to abort APEXd session"); 592 } else { 593 Slog.e(TAG, 594 "Successfully aborted apexd session. Rebooting device in order to revert " 595 + "to the previous state of APEXd."); 596 mPowerManager.reboot(null); 597 } 598 } 599 getReasonForRevert()600 private String getReasonForRevert() { 601 if (!TextUtils.isEmpty(mFailureReason)) { 602 return mFailureReason; 603 } 604 if (!TextUtils.isEmpty(mNativeFailureReason)) { 605 return "Session reverted due to crashing native process: " + mNativeFailureReason; 606 } 607 return ""; 608 } 609 610 /** 611 * Throws a PackageManagerException if there are duplicate packages in apk and apk-in-apex. 612 */ checkDuplicateApkInApex(@onNull StagedSession session)613 private void checkDuplicateApkInApex(@NonNull StagedSession session) 614 throws PackageManagerException { 615 if (!session.isMultiPackage()) { 616 return; 617 } 618 final Set<String> apkNames = new ArraySet<>(); 619 for (StagedSession s : session.getChildSessions()) { 620 if (!s.isApexSession()) { 621 apkNames.add(s.getPackageName()); 622 } 623 } 624 final List<StagedSession> apexSessions = extractApexSessions(session); 625 for (StagedSession apexSession : apexSessions) { 626 String packageName = apexSession.getPackageName(); 627 for (String apkInApex : mApexManager.getApksInApex(packageName)) { 628 if (!apkNames.add(apkInApex)) { 629 throw new PackageManagerException( 630 SessionInfo.STAGED_SESSION_ACTIVATION_FAILED, 631 "Package: " + packageName + " in session: " 632 + apexSession.sessionId() + " has duplicate apk-in-apex: " 633 + apkInApex, null); 634 635 } 636 } 637 } 638 } 639 installApksInSession(StagedSession session)640 private void installApksInSession(StagedSession session) 641 throws PackageManagerException { 642 if (!session.containsApkSession()) { 643 return; 644 } 645 646 final LocalIntentReceiverSync receiver = new LocalIntentReceiverSync(); 647 session.installSession(receiver.getIntentSender()); 648 final Intent result = receiver.getResult(); 649 final int status = result.getIntExtra(PackageInstaller.EXTRA_STATUS, 650 PackageInstaller.STATUS_FAILURE); 651 if (status != PackageInstaller.STATUS_SUCCESS) { 652 final String errorMessage = result.getStringExtra( 653 PackageInstaller.EXTRA_STATUS_MESSAGE); 654 Slog.e(TAG, "Failure to install APK staged session " 655 + session.sessionId() + " [" + errorMessage + "]"); 656 throw new PackageManagerException( 657 SessionInfo.STAGED_SESSION_ACTIVATION_FAILED, errorMessage); 658 } 659 } 660 commitSession(@onNull StagedSession session)661 void commitSession(@NonNull StagedSession session) { 662 // Store this parent session which will be used to check overlapping later 663 createSession(session); 664 mPreRebootVerificationHandler.startPreRebootVerification(session); 665 } 666 getSessionIdForParentOrSelf(StagedSession session)667 private int getSessionIdForParentOrSelf(StagedSession session) { 668 return session.hasParentSessionId() ? session.getParentSessionId() : session.sessionId(); 669 } 670 getParentSessionOrSelf(StagedSession session)671 private StagedSession getParentSessionOrSelf(StagedSession session) { 672 return session.hasParentSessionId() 673 ? getStagedSession(session.getParentSessionId()) 674 : session; 675 } 676 isRollback(StagedSession session)677 private boolean isRollback(StagedSession session) { 678 final StagedSession root = getParentSessionOrSelf(session); 679 return root.sessionParams().installReason == PackageManager.INSTALL_REASON_ROLLBACK; 680 } 681 682 /** 683 * <p> Check if the session provided is non-overlapping with the active staged sessions. 684 * 685 * <p> A session is non-overlapping if it meets one of the following conditions: </p> 686 * <ul> 687 * <li>It is a parent session</li> 688 * <li>It is already one of the active sessions</li> 689 * <li>Its package name is not same as any of the active sessions</li> 690 * </ul> 691 * @throws PackageManagerException if session fails the check 692 */ 693 @VisibleForTesting checkNonOverlappingWithStagedSessions(@onNull StagedSession session)694 void checkNonOverlappingWithStagedSessions(@NonNull StagedSession session) 695 throws PackageManagerException { 696 if (session.isMultiPackage()) { 697 // We cannot say a parent session overlaps until we process its children 698 return; 699 } 700 701 String packageName = session.getPackageName(); 702 if (packageName == null) { 703 throw new PackageManagerException(SessionInfo.STAGED_SESSION_VERIFICATION_FAILED, 704 "Cannot stage session " + session.sessionId() + " with package name null"); 705 } 706 707 boolean supportsCheckpoint; 708 try { 709 supportsCheckpoint = PackageHelper.getStorageManager().supportsCheckpoint(); 710 } catch (RemoteException e) { 711 throw new PackageManagerException(SessionInfo.STAGED_SESSION_VERIFICATION_FAILED, 712 "Can't query fs-checkpoint status : " + e); 713 } 714 715 final boolean isRollback = isRollback(session); 716 717 synchronized (mStagedSessions) { 718 for (int i = 0; i < mStagedSessions.size(); i++) { 719 final StagedSession stagedSession = mStagedSessions.valueAt(i); 720 if (stagedSession.hasParentSessionId() || !stagedSession.isCommitted() 721 || stagedSession.isInTerminalState() 722 || stagedSession.isDestroyed()) { 723 continue; 724 } 725 726 if (stagedSession.getCommittedMillis() > session.getCommittedMillis()) { 727 // Ignore sessions that are committed after the provided session. When there are 728 // overlaps between sessions, we will fail the one committed later instead of 729 // the earlier one. 730 continue; 731 } 732 733 // From here on, stagedSession is a parent active staged session 734 735 // Check if session is one of the active sessions 736 if (getSessionIdForParentOrSelf(session) == stagedSession.sessionId()) { 737 Slog.w(TAG, "Session " + session.sessionId() + " is already staged"); 738 continue; 739 } 740 741 if (isRollback && !isRollback(stagedSession)) { 742 // If the new session is a rollback, then it gets priority. The existing 743 // session is failed to reduce risk and avoid an SDK extension dependency 744 // violation. 745 final StagedSession root = stagedSession; 746 if (!ensureActiveApexSessionIsAborted(root)) { 747 Slog.e(TAG, "Failed to abort apex session " + root.sessionId()); 748 // Safe to ignore active apex session abort failure since session 749 // will be marked failed on next step and staging directory for session 750 // will be deleted. 751 } 752 root.setSessionFailed( 753 SessionInfo.STAGED_SESSION_CONFLICT, 754 "Session was failed by rollback session: " + session.sessionId()); 755 Slog.i(TAG, "Session " + root.sessionId() + " is marked failed due to " 756 + "rollback session: " + session.sessionId()); 757 } else if (stagedSession.sessionContains( 758 s -> s.getPackageName().equals(packageName))) { 759 // New session cannot have same package name as one of the active sessions 760 throw new PackageManagerException( 761 SessionInfo.STAGED_SESSION_VERIFICATION_FAILED, 762 "Package: " + session.getPackageName() + " in session: " 763 + session.sessionId() 764 + " has been staged already by session: " 765 + stagedSession.sessionId(), null); 766 } 767 768 // Staging multiple root sessions is not allowed if device doesn't support 769 // checkpoint. If session and stagedSession do not have common ancestor, they are 770 // from two different root sessions. 771 if (!supportsCheckpoint) { 772 throw new PackageManagerException( 773 SessionInfo.STAGED_SESSION_VERIFICATION_FAILED, 774 "Cannot stage multiple sessions without checkpoint support", null); 775 } 776 } 777 } 778 } 779 780 /** 781 * Returns id of a committed and non-finalized stated session that contains same 782 * {@code packageName}, or {@code -1} if no sessions have this {@code packageName} staged. 783 */ getSessionIdByPackageName(@onNull String packageName)784 int getSessionIdByPackageName(@NonNull String packageName) { 785 synchronized (mStagedSessions) { 786 for (int i = 0; i < mStagedSessions.size(); i++) { 787 StagedSession stagedSession = mStagedSessions.valueAt(i); 788 if (!stagedSession.isCommitted() || stagedSession.isDestroyed() 789 || stagedSession.isInTerminalState()) { 790 continue; 791 } 792 if (stagedSession.getPackageName().equals(packageName)) { 793 return stagedSession.sessionId(); 794 } 795 } 796 } 797 return -1; 798 } 799 800 @VisibleForTesting createSession(@onNull StagedSession sessionInfo)801 void createSession(@NonNull StagedSession sessionInfo) { 802 synchronized (mStagedSessions) { 803 mStagedSessions.append(sessionInfo.sessionId(), sessionInfo); 804 } 805 } 806 abortSession(@onNull StagedSession session)807 void abortSession(@NonNull StagedSession session) { 808 synchronized (mStagedSessions) { 809 mStagedSessions.remove(session.sessionId()); 810 } 811 } 812 813 /** 814 * <p>Abort committed staged session 815 */ abortCommittedSession(@onNull StagedSession session)816 void abortCommittedSession(@NonNull StagedSession session) { 817 int sessionId = session.sessionId(); 818 if (session.isInTerminalState()) { 819 Slog.w(TAG, "Cannot abort session in final state: " + sessionId); 820 return; 821 } 822 if (!session.isDestroyed()) { 823 throw new IllegalStateException("Committed session must be destroyed before aborting it" 824 + " from StagingManager"); 825 } 826 if (getStagedSession(sessionId) == null) { 827 Slog.w(TAG, "Session " + sessionId + " has been abandoned already"); 828 return; 829 } 830 831 // A session could be marked ready once its pre-reboot verification ends 832 if (session.isSessionReady()) { 833 if (!ensureActiveApexSessionIsAborted(session)) { 834 // Failed to ensure apex session is aborted, so it can still be staged. We can still 835 // safely cleanup the staged session since pre-reboot verification is complete. 836 // Also, cleaning up the stageDir prevents the apex from being activated. 837 Slog.e(TAG, "Failed to abort apex session " + session.sessionId()); 838 } 839 } 840 841 // Session was successfully aborted from apexd (if required) and pre-reboot verification 842 // is also complete. It is now safe to clean up the session from system. 843 abortSession(session); 844 } 845 846 /** 847 * Ensure that there is no active apex session staged in apexd for the given session. 848 * 849 * @return returns true if it is ensured that there is no active apex session, otherwise false 850 */ ensureActiveApexSessionIsAborted(StagedSession session)851 private boolean ensureActiveApexSessionIsAborted(StagedSession session) { 852 if (!session.containsApexSession()) { 853 return true; 854 } 855 final ApexSessionInfo apexSession = mApexManager.getStagedSessionInfo(session.sessionId()); 856 if (apexSession == null || isApexSessionFinalized(apexSession)) { 857 return true; 858 } 859 return mApexManager.abortStagedSession(session.sessionId()); 860 } 861 isApexSessionFinalized(ApexSessionInfo session)862 private boolean isApexSessionFinalized(ApexSessionInfo session) { 863 /* checking if the session is in a final state, i.e., not active anymore */ 864 return session.isUnknown || session.isActivationFailed || session.isSuccess 865 || session.isReverted; 866 } 867 isApexSessionFailed(ApexSessionInfo apexSessionInfo)868 private static boolean isApexSessionFailed(ApexSessionInfo apexSessionInfo) { 869 // isRevertInProgress is included to cover the scenario, when a device is rebooted 870 // during the revert, and apexd fails to resume the revert after reboot. 871 return apexSessionInfo.isActivationFailed || apexSessionInfo.isUnknown 872 || apexSessionInfo.isReverted || apexSessionInfo.isRevertInProgress 873 || apexSessionInfo.isRevertFailed; 874 } 875 handleNonReadyAndDestroyedSessions(List<StagedSession> sessions)876 private void handleNonReadyAndDestroyedSessions(List<StagedSession> sessions) { 877 int j = sessions.size(); 878 for (int i = 0; i < j; ) { 879 // Maintain following invariant: 880 // * elements at positions [0, i) should be kept 881 // * elements at positions [j, n) should be remove. 882 // * n = sessions.size() 883 StagedSession session = sessions.get(i); 884 if (session.isDestroyed()) { 885 // Device rebooted before abandoned session was cleaned up. 886 session.abandon(); 887 StagedSession session2 = sessions.set(j - 1, session); 888 sessions.set(i, session2); 889 j--; 890 } else if (!session.isSessionReady()) { 891 // The framework got restarted before the pre-reboot verification could complete, 892 // restart the verification. 893 mPreRebootVerificationHandler.startPreRebootVerification(session); 894 StagedSession session2 = sessions.set(j - 1, session); 895 sessions.set(i, session2); 896 j--; 897 } else { 898 i++; 899 } 900 } 901 // Delete last j elements. 902 sessions.subList(j, sessions.size()).clear(); 903 } 904 restoreSessions(@onNull List<StagedSession> sessions, boolean isDeviceUpgrading)905 void restoreSessions(@NonNull List<StagedSession> sessions, boolean isDeviceUpgrading) { 906 TimingsTraceLog t = new TimingsTraceLog( 907 "StagingManagerTiming", Trace.TRACE_TAG_PACKAGE_MANAGER); 908 t.traceBegin("restoreSessions"); 909 910 // Do not resume sessions if boot completed already 911 if (SystemProperties.getBoolean("sys.boot_completed", false)) { 912 return; 913 } 914 915 for (int i = 0; i < sessions.size(); i++) { 916 StagedSession session = sessions.get(i); 917 // Quick check that PackageInstallerService gave us sessions we expected. 918 Preconditions.checkArgument(!session.hasParentSessionId(), 919 session.sessionId() + " is a child session"); 920 Preconditions.checkArgument(session.isCommitted(), 921 session.sessionId() + " is not committed"); 922 Preconditions.checkArgument(!session.isInTerminalState(), 923 session.sessionId() + " is in terminal state"); 924 // Store this parent session which will be used to check overlapping later 925 createSession(session); 926 } 927 928 if (isDeviceUpgrading) { 929 // TODO(ioffe): check that corresponding apex sessions are failed. 930 // The preconditions used during pre-reboot verification might have changed when device 931 // is upgrading. Fail all the sessions and exit early. 932 for (int i = 0; i < sessions.size(); i++) { 933 StagedSession session = sessions.get(i); 934 session.setSessionFailed(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED, 935 "Build fingerprint has changed"); 936 } 937 return; 938 } 939 940 boolean needsCheckpoint = false; 941 boolean supportsCheckpoint = false; 942 try { 943 supportsCheckpoint = PackageHelper.getStorageManager().supportsCheckpoint(); 944 needsCheckpoint = PackageHelper.getStorageManager().needsCheckpoint(); 945 } catch (RemoteException e) { 946 // This means that vold has crashed, and device is in a bad state. 947 throw new IllegalStateException("Failed to get checkpoint status", e); 948 } 949 950 if (sessions.size() > 1 && !supportsCheckpoint) { 951 throw new IllegalStateException("Detected multiple staged sessions on a device without " 952 + "fs-checkpoint support"); 953 } 954 955 // Do a set of quick checks before resuming individual sessions: 956 // 1. Schedule a pre-reboot verification for non-ready sessions. 957 // 2. Abandon destroyed sessions. 958 handleNonReadyAndDestroyedSessions(sessions); // mutates |sessions| 959 960 // 3. Check state of apex sessions is consistent. All non-applied sessions will be marked 961 // as failed. 962 final SparseArray<ApexSessionInfo> apexSessions = mApexManager.getSessions(); 963 boolean hasFailedApexSession = false; 964 boolean hasAppliedApexSession = false; 965 for (int i = 0; i < sessions.size(); i++) { 966 StagedSession session = sessions.get(i); 967 if (!session.containsApexSession()) { 968 // At this point we are only interested in apex sessions. 969 continue; 970 } 971 final ApexSessionInfo apexSession = apexSessions.get(session.sessionId()); 972 if (apexSession == null || apexSession.isUnknown) { 973 hasFailedApexSession = true; 974 session.setSessionFailed(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED, "apexd did " 975 + "not know anything about a staged session supposed to be activated"); 976 continue; 977 } else if (isApexSessionFailed(apexSession)) { 978 hasFailedApexSession = true; 979 if (!TextUtils.isEmpty(apexSession.crashingNativeProcess)) { 980 prepareForLoggingApexdRevert(session, apexSession.crashingNativeProcess); 981 } 982 String errorMsg = "APEX activation failed."; 983 final String reasonForRevert = getReasonForRevert(); 984 if (!TextUtils.isEmpty(reasonForRevert)) { 985 errorMsg += " Reason: " + reasonForRevert; 986 } else if (!TextUtils.isEmpty(apexSession.errorMessage)) { 987 errorMsg += " Error: " + apexSession.errorMessage; 988 } 989 Slog.d(TAG, errorMsg); 990 session.setSessionFailed(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED, errorMsg); 991 continue; 992 } else if (apexSession.isActivated || apexSession.isSuccess) { 993 hasAppliedApexSession = true; 994 continue; 995 } else if (apexSession.isStaged) { 996 // Apexd did not apply the session for some unknown reason. There is no guarantee 997 // that apexd will install it next time. Safer to proactively mark it as failed. 998 hasFailedApexSession = true; 999 session.setSessionFailed(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED, 1000 "Staged session " + session.sessionId() + " at boot didn't activate nor " 1001 + "fail. Marking it as failed anyway."); 1002 } else { 1003 Slog.w(TAG, "Apex session " + session.sessionId() + " is in impossible state"); 1004 hasFailedApexSession = true; 1005 session.setSessionFailed(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED, 1006 "Impossible state"); 1007 } 1008 } 1009 1010 if (hasAppliedApexSession && hasFailedApexSession) { 1011 abortCheckpoint("Found both applied and failed apex sessions", supportsCheckpoint, 1012 needsCheckpoint); 1013 return; 1014 } 1015 1016 if (hasFailedApexSession) { 1017 // Either of those means that we failed at least one apex session, hence we should fail 1018 // all other sessions. 1019 for (int i = 0; i < sessions.size(); i++) { 1020 StagedSession session = sessions.get(i); 1021 if (session.isSessionFailed()) { 1022 // Session has been already failed in the loop above. 1023 continue; 1024 } 1025 session.setSessionFailed(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED, 1026 "Another apex session failed"); 1027 } 1028 return; 1029 } 1030 1031 // Time to resume sessions. 1032 for (int i = 0; i < sessions.size(); i++) { 1033 StagedSession session = sessions.get(i); 1034 try { 1035 resumeSession(session, supportsCheckpoint, needsCheckpoint); 1036 } catch (PackageManagerException e) { 1037 onInstallationFailure(session, e, supportsCheckpoint, needsCheckpoint); 1038 } catch (Exception e) { 1039 Slog.e(TAG, "Staged install failed due to unhandled exception", e); 1040 onInstallationFailure(session, new PackageManagerException( 1041 SessionInfo.STAGED_SESSION_ACTIVATION_FAILED, 1042 "Staged install failed due to unhandled exception: " + e), 1043 supportsCheckpoint, needsCheckpoint); 1044 } 1045 } 1046 t.traceEnd(); 1047 } 1048 logFailedApexSessionsIfNecessary()1049 private void logFailedApexSessionsIfNecessary() { 1050 synchronized (mFailedPackageNames) { 1051 if (!mFailedPackageNames.isEmpty()) { 1052 WatchdogRollbackLogger.logApexdRevert(mContext, 1053 mFailedPackageNames, mNativeFailureReason); 1054 } 1055 } 1056 } 1057 markStagedSessionsAsSuccessful()1058 private void markStagedSessionsAsSuccessful() { 1059 synchronized (mSuccessfulStagedSessionIds) { 1060 for (int i = 0; i < mSuccessfulStagedSessionIds.size(); i++) { 1061 mApexManager.markStagedSessionSuccessful(mSuccessfulStagedSessionIds.get(i)); 1062 } 1063 } 1064 } 1065 systemReady()1066 void systemReady() { 1067 new Lifecycle(mContext).startService(this); 1068 // Register the receiver of boot completed intent for staging manager. 1069 mContext.registerReceiver(new BroadcastReceiver() { 1070 @Override 1071 public void onReceive(Context ctx, Intent intent) { 1072 onBootCompletedBroadcastReceived(); 1073 ctx.unregisterReceiver(this); 1074 } 1075 }, new IntentFilter(Intent.ACTION_BOOT_COMPLETED)); 1076 1077 mFailureReasonFile.delete(); 1078 } 1079 1080 @VisibleForTesting onBootCompletedBroadcastReceived()1081 void onBootCompletedBroadcastReceived() { 1082 mPreRebootVerificationHandler.readyToStart(); 1083 BackgroundThread.getExecutor().execute(() -> logFailedApexSessionsIfNecessary()); 1084 } 1085 1086 private static class LocalIntentReceiverSync { 1087 private final LinkedBlockingQueue<Intent> mResult = new LinkedBlockingQueue<>(); 1088 1089 private final IIntentSender.Stub mLocalSender = new IIntentSender.Stub() { 1090 @Override 1091 public void send(int code, Intent intent, String resolvedType, IBinder whitelistToken, 1092 IIntentReceiver finishedReceiver, String requiredPermission, 1093 Bundle options) { 1094 try { 1095 mResult.offer(intent, 5, TimeUnit.SECONDS); 1096 } catch (InterruptedException e) { 1097 throw new RuntimeException(e); 1098 } 1099 } 1100 }; 1101 getIntentSender()1102 public IntentSender getIntentSender() { 1103 return new IntentSender((IIntentSender) mLocalSender); 1104 } 1105 getResult()1106 public Intent getResult() { 1107 try { 1108 return mResult.take(); 1109 } catch (InterruptedException e) { 1110 throw new RuntimeException(e); 1111 } 1112 } 1113 } 1114 getStagedSession(int sessionId)1115 private StagedSession getStagedSession(int sessionId) { 1116 StagedSession session; 1117 synchronized (mStagedSessions) { 1118 session = mStagedSessions.get(sessionId); 1119 } 1120 return session; 1121 } 1122 1123 // TODO(b/136257624): Temporary API to let PMS communicate with StagingManager. When all 1124 // verification logic is extracted out of StagingManager into PMS, we can remove 1125 // this. notifyVerificationComplete(StagedSession session)1126 void notifyVerificationComplete(StagedSession session) { 1127 mPreRebootVerificationHandler.onPreRebootVerificationComplete(session); 1128 } 1129 1130 // TODO(b/136257624): Temporary API to let PMS communicate with StagingManager. When all 1131 // verification logic is extracted out of StagingManager into PMS, we can remove 1132 // this. notifyPreRebootVerification_Apk_Complete(@onNull StagedSession session)1133 void notifyPreRebootVerification_Apk_Complete(@NonNull StagedSession session) { 1134 mPreRebootVerificationHandler.notifyPreRebootVerification_Apk_Complete(session); 1135 } 1136 1137 private final class PreRebootVerificationHandler extends Handler { 1138 // Hold sessions before handler gets ready to do the verification. 1139 private List<StagedSession> mPendingSessions; 1140 private boolean mIsReady; 1141 PreRebootVerificationHandler(Looper looper)1142 PreRebootVerificationHandler(Looper looper) { 1143 super(looper); 1144 } 1145 1146 /** 1147 * Handler for states of pre reboot verification. The states are arranged linearly (shown 1148 * below) with each state either calling the next state, or calling some other method that 1149 * eventually calls the next state. 1150 * 1151 * <p><ul> 1152 * <li>MSG_PRE_REBOOT_VERIFICATION_START</li> 1153 * <li>MSG_PRE_REBOOT_VERIFICATION_APEX</li> 1154 * <li>MSG_PRE_REBOOT_VERIFICATION_APK</li> 1155 * <li>MSG_PRE_REBOOT_VERIFICATION_END</li> 1156 * </ul></p> 1157 * 1158 * Details about each of state can be found in corresponding handler of node. 1159 */ 1160 private static final int MSG_PRE_REBOOT_VERIFICATION_START = 1; 1161 private static final int MSG_PRE_REBOOT_VERIFICATION_APEX = 2; 1162 private static final int MSG_PRE_REBOOT_VERIFICATION_APK = 3; 1163 private static final int MSG_PRE_REBOOT_VERIFICATION_END = 4; 1164 1165 @Override handleMessage(Message msg)1166 public void handleMessage(Message msg) { 1167 final int sessionId = msg.arg1; 1168 final int rollbackId = msg.arg2; 1169 final StagedSession session = (StagedSession) msg.obj; 1170 if (session.isDestroyed() || session.isSessionFailed()) { 1171 // No point in running verification on a destroyed/failed session 1172 onPreRebootVerificationComplete(session); 1173 return; 1174 } 1175 try { 1176 switch (msg.what) { 1177 case MSG_PRE_REBOOT_VERIFICATION_START: 1178 handlePreRebootVerification_Start(session); 1179 break; 1180 case MSG_PRE_REBOOT_VERIFICATION_APEX: 1181 handlePreRebootVerification_Apex(session, rollbackId); 1182 break; 1183 case MSG_PRE_REBOOT_VERIFICATION_APK: 1184 handlePreRebootVerification_Apk(session); 1185 break; 1186 case MSG_PRE_REBOOT_VERIFICATION_END: 1187 handlePreRebootVerification_End(session); 1188 break; 1189 } 1190 } catch (Exception e) { 1191 Slog.e(TAG, "Pre-reboot verification failed due to unhandled exception", e); 1192 onPreRebootVerificationFailure(session, 1193 SessionInfo.STAGED_SESSION_ACTIVATION_FAILED, 1194 "Pre-reboot verification failed due to unhandled exception: " + e); 1195 } 1196 } 1197 1198 // Notify the handler that system is ready, and reschedule the pre-reboot verifications. readyToStart()1199 private synchronized void readyToStart() { 1200 mIsReady = true; 1201 if (mPendingSessions != null) { 1202 for (int i = 0; i < mPendingSessions.size(); i++) { 1203 StagedSession session = mPendingSessions.get(i); 1204 startPreRebootVerification(session); 1205 } 1206 mPendingSessions = null; 1207 } 1208 } 1209 1210 // Method for starting the pre-reboot verification startPreRebootVerification( @onNull StagedSession session)1211 private synchronized void startPreRebootVerification( 1212 @NonNull StagedSession session) { 1213 if (!mIsReady) { 1214 if (mPendingSessions == null) { 1215 mPendingSessions = new ArrayList<>(); 1216 } 1217 mPendingSessions.add(session); 1218 return; 1219 } 1220 1221 if (session.notifyStartPreRebootVerification()) { 1222 int sessionId = session.sessionId(); 1223 Slog.d(TAG, "Starting preRebootVerification for session " + sessionId); 1224 obtainMessage(MSG_PRE_REBOOT_VERIFICATION_START, sessionId, -1, session) 1225 .sendToTarget(); 1226 } 1227 } 1228 onPreRebootVerificationFailure(StagedSession session, @SessionInfo.StagedSessionErrorCode int errorCode, String errorMessage)1229 private void onPreRebootVerificationFailure(StagedSession session, 1230 @SessionInfo.StagedSessionErrorCode int errorCode, String errorMessage) { 1231 if (!ensureActiveApexSessionIsAborted(session)) { 1232 Slog.e(TAG, "Failed to abort apex session " + session.sessionId()); 1233 // Safe to ignore active apex session abortion failure since session will be marked 1234 // failed on next step and staging directory for session will be deleted. 1235 } 1236 session.setSessionFailed(errorCode, errorMessage); 1237 onPreRebootVerificationComplete(session); 1238 } 1239 1240 // Things to do when pre-reboot verification completes for a particular sessionId onPreRebootVerificationComplete(StagedSession session)1241 private void onPreRebootVerificationComplete(StagedSession session) { 1242 int sessionId = session.sessionId(); 1243 Slog.d(TAG, "Stopping preRebootVerification for session " + sessionId); 1244 session.notifyEndPreRebootVerification(); 1245 } 1246 notifyPreRebootVerification_Start_Complete( @onNull StagedSession session, int rollbackId)1247 private void notifyPreRebootVerification_Start_Complete( 1248 @NonNull StagedSession session, int rollbackId) { 1249 obtainMessage(MSG_PRE_REBOOT_VERIFICATION_APEX, session.sessionId(), rollbackId, 1250 session).sendToTarget(); 1251 } 1252 notifyPreRebootVerification_Apex_Complete( @onNull StagedSession session)1253 private void notifyPreRebootVerification_Apex_Complete( 1254 @NonNull StagedSession session) { 1255 obtainMessage(MSG_PRE_REBOOT_VERIFICATION_APK, session.sessionId(), -1, session) 1256 .sendToTarget(); 1257 } 1258 notifyPreRebootVerification_Apk_Complete( @onNull StagedSession session)1259 private void notifyPreRebootVerification_Apk_Complete( 1260 @NonNull StagedSession session) { 1261 obtainMessage(MSG_PRE_REBOOT_VERIFICATION_END, session.sessionId(), -1, session) 1262 .sendToTarget(); 1263 } 1264 1265 /** 1266 * A placeholder state for starting the pre reboot verification. 1267 * 1268 * See {@link PreRebootVerificationHandler} to see all nodes of pre reboot verification 1269 */ handlePreRebootVerification_Start(@onNull StagedSession session)1270 private void handlePreRebootVerification_Start(@NonNull StagedSession session) { 1271 try { 1272 if (session.isMultiPackage()) { 1273 for (StagedSession s : session.getChildSessions()) { 1274 checkNonOverlappingWithStagedSessions(s); 1275 } 1276 } else { 1277 checkNonOverlappingWithStagedSessions(session); 1278 } 1279 } catch (PackageManagerException e) { 1280 onPreRebootVerificationFailure(session, e.error, e.getMessage()); 1281 return; 1282 } 1283 1284 int rollbackId = -1; 1285 if ((session.sessionParams().installFlags & PackageManager.INSTALL_ENABLE_ROLLBACK) 1286 != 0) { 1287 // If rollback is enabled for this session, we call through to the RollbackManager 1288 // with the list of sessions it must enable rollback for. Note that 1289 // notifyStagedSession is a synchronous operation. 1290 final RollbackManagerInternal rm = 1291 LocalServices.getService(RollbackManagerInternal.class); 1292 try { 1293 // NOTE: To stay consistent with the non-staged install flow, we don't fail the 1294 // entire install if rollbacks can't be enabled. 1295 rollbackId = rm.notifyStagedSession(session.sessionId()); 1296 } catch (RuntimeException re) { 1297 Slog.e(TAG, "Failed to notifyStagedSession for session: " 1298 + session.sessionId(), re); 1299 } 1300 } else if (session.sessionParams().installReason 1301 == PackageManager.INSTALL_REASON_ROLLBACK) { 1302 try { 1303 rollbackId = retrieveRollbackIdForCommitSession(session.sessionId()); 1304 } catch (PackageManagerException e) { 1305 onPreRebootVerificationFailure(session, e.error, e.getMessage()); 1306 return; 1307 } 1308 } 1309 1310 notifyPreRebootVerification_Start_Complete(session, rollbackId); 1311 } 1312 1313 /** 1314 * Pre-reboot verification state for apex files: 1315 * 1316 * <p><ul> 1317 * <li>submits session to apex service</li> 1318 * <li>validates signatures of apex files</li> 1319 * </ul></p> 1320 */ handlePreRebootVerification_Apex( @onNull StagedSession session, int rollbackId)1321 private void handlePreRebootVerification_Apex( 1322 @NonNull StagedSession session, int rollbackId) { 1323 final boolean hasApex = session.containsApexSession(); 1324 1325 // APEX checks. For single-package sessions, check if they contain an APEX. For 1326 // multi-package sessions, find all the child sessions that contain an APEX. 1327 if (hasApex) { 1328 final List<PackageInfo> apexPackages; 1329 try { 1330 apexPackages = submitSessionToApexService(session, rollbackId); 1331 for (int i = 0, size = apexPackages.size(); i < size; i++) { 1332 validateApexSignature(apexPackages.get(i)); 1333 } 1334 } catch (PackageManagerException e) { 1335 onPreRebootVerificationFailure(session, e.error, e.getMessage()); 1336 return; 1337 } 1338 1339 final PackageManagerInternal packageManagerInternal = 1340 LocalServices.getService(PackageManagerInternal.class); 1341 packageManagerInternal.pruneCachedApksInApex(apexPackages); 1342 } 1343 1344 notifyPreRebootVerification_Apex_Complete(session); 1345 } 1346 1347 /** 1348 * Pre-reboot verification state for apk files. Session is sent to 1349 * {@link PackageManagerService} for verification and it notifies back the result via 1350 * {@link #notifyPreRebootVerification_Apk_Complete} 1351 */ handlePreRebootVerification_Apk(@onNull StagedSession session)1352 private void handlePreRebootVerification_Apk(@NonNull StagedSession session) { 1353 if (!session.containsApkSession()) { 1354 notifyPreRebootVerification_Apk_Complete(session); 1355 return; 1356 } 1357 session.verifySession(); 1358 } 1359 1360 /** 1361 * Pre-reboot verification state for wrapping up: 1362 * <p><ul> 1363 * <li>enables rollback if required</li> 1364 * <li>marks session as ready</li> 1365 * </ul></p> 1366 */ handlePreRebootVerification_End(@onNull StagedSession session)1367 private void handlePreRebootVerification_End(@NonNull StagedSession session) { 1368 // Before marking the session as ready, start checkpoint service if available 1369 try { 1370 if (PackageHelper.getStorageManager().supportsCheckpoint()) { 1371 PackageHelper.getStorageManager().startCheckpoint(2); 1372 } 1373 } catch (Exception e) { 1374 // Failed to get hold of StorageManager 1375 Slog.e(TAG, "Failed to get hold of StorageManager", e); 1376 onPreRebootVerificationFailure(session, SessionInfo.STAGED_SESSION_UNKNOWN, 1377 "Failed to get hold of StorageManager"); 1378 return; 1379 } 1380 1381 // Stop pre-reboot verification before marking session ready. From this point on, if we 1382 // abandon the session then it will be cleaned up immediately. If session is abandoned 1383 // after this point, then even if for some reason system tries to install the session 1384 // or activate its apex, there won't be any files to work with as they will be cleaned 1385 // up by the system as part of abandonment. If session is abandoned before this point, 1386 // then the session is already destroyed and cannot be marked ready anymore. 1387 onPreRebootVerificationComplete(session); 1388 1389 // Proactively mark session as ready before calling apexd. Although this call order 1390 // looks counter-intuitive, this is the easiest way to ensure that session won't end up 1391 // in the inconsistent state: 1392 // - If device gets rebooted right before call to apexd, then apexd will never activate 1393 // apex files of this staged session. This will result in StagingManager failing 1394 // the session. 1395 // On the other hand, if the order of the calls was inverted (first call apexd, then 1396 // mark session as ready), then if a device gets rebooted right after the call to apexd, 1397 // only apex part of the train will be applied, leaving device in an inconsistent state. 1398 Slog.d(TAG, "Marking session " + session.sessionId() + " as ready"); 1399 session.setSessionReady(); 1400 if (session.isSessionReady()) { 1401 final boolean hasApex = session.containsApexSession(); 1402 if (hasApex) { 1403 try { 1404 mApexManager.markStagedSessionReady(session.sessionId()); 1405 } catch (PackageManagerException e) { 1406 session.setSessionFailed(e.error, e.getMessage()); 1407 return; 1408 } 1409 } 1410 } 1411 } 1412 } 1413 } 1414