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