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.content.Context; 25 import android.content.IIntentReceiver; 26 import android.content.IIntentSender; 27 import android.content.Intent; 28 import android.content.IntentSender; 29 import android.content.pm.PackageInfo; 30 import android.content.pm.PackageInstaller; 31 import android.content.pm.PackageInstaller.SessionInfo; 32 import android.content.pm.PackageManager; 33 import android.content.pm.PackageParser; 34 import android.content.pm.PackageParser.PackageParserException; 35 import android.content.pm.PackageParser.SigningDetails; 36 import android.content.pm.PackageParser.SigningDetails.SignatureSchemeVersion; 37 import android.content.pm.ParceledListSlice; 38 import android.content.rollback.IRollbackManager; 39 import android.os.Bundle; 40 import android.os.Handler; 41 import android.os.IBinder; 42 import android.os.Looper; 43 import android.os.Message; 44 import android.os.ParcelFileDescriptor; 45 import android.os.ParcelableException; 46 import android.os.PowerManager; 47 import android.os.RemoteException; 48 import android.os.ServiceManager; 49 import android.os.storage.IStorageManager; 50 import android.os.storage.StorageManager; 51 import android.util.IntArray; 52 import android.util.Slog; 53 import android.util.SparseArray; 54 import android.util.apk.ApkSignatureVerifier; 55 56 import com.android.internal.annotations.GuardedBy; 57 import com.android.internal.content.PackageHelper; 58 import com.android.internal.os.BackgroundThread; 59 60 import java.io.File; 61 import java.io.IOException; 62 import java.util.ArrayList; 63 import java.util.Arrays; 64 import java.util.List; 65 import java.util.concurrent.LinkedBlockingQueue; 66 import java.util.concurrent.TimeUnit; 67 import java.util.function.Consumer; 68 import java.util.function.Predicate; 69 import java.util.stream.Collectors; 70 71 /** 72 * This class handles staged install sessions, i.e. install sessions that require packages to 73 * be installed only after a reboot. 74 */ 75 public class StagingManager { 76 77 private static final String TAG = "StagingManager"; 78 79 private final PackageInstallerService mPi; 80 private final ApexManager mApexManager; 81 private final PowerManager mPowerManager; 82 private final Context mContext; 83 private final PreRebootVerificationHandler mPreRebootVerificationHandler; 84 85 @GuardedBy("mStagedSessions") 86 private final SparseArray<PackageInstallerSession> mStagedSessions = new SparseArray<>(); 87 StagingManager(PackageInstallerService pi, ApexManager am, Context context)88 StagingManager(PackageInstallerService pi, ApexManager am, Context context) { 89 mPi = pi; 90 mApexManager = am; 91 mContext = context; 92 mPowerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE); 93 mPreRebootVerificationHandler = new PreRebootVerificationHandler( 94 BackgroundThread.get().getLooper()); 95 } 96 updateStoredSession(@onNull PackageInstallerSession sessionInfo)97 private void updateStoredSession(@NonNull PackageInstallerSession sessionInfo) { 98 synchronized (mStagedSessions) { 99 PackageInstallerSession storedSession = mStagedSessions.get(sessionInfo.sessionId); 100 // storedSession might be null if a call to abortSession was made before the session 101 // is updated. 102 if (storedSession != null) { 103 mStagedSessions.put(sessionInfo.sessionId, sessionInfo); 104 } 105 } 106 } 107 getSessions()108 ParceledListSlice<PackageInstaller.SessionInfo> getSessions() { 109 final List<PackageInstaller.SessionInfo> result = new ArrayList<>(); 110 synchronized (mStagedSessions) { 111 for (int i = 0; i < mStagedSessions.size(); i++) { 112 result.add(mStagedSessions.valueAt(i).generateInfo(false)); 113 } 114 } 115 return new ParceledListSlice<>(result); 116 } 117 118 /** 119 * Validates the signature used to sign the container of the new apex package 120 * 121 * @param newApexPkg The new apex package that is being installed 122 * @throws PackageManagerException 123 */ validateApexSignature(PackageInfo newApexPkg)124 private void validateApexSignature(PackageInfo newApexPkg) 125 throws PackageManagerException { 126 // Get signing details of the new package 127 final String apexPath = newApexPkg.applicationInfo.sourceDir; 128 final String packageName = newApexPkg.packageName; 129 130 final SigningDetails newSigningDetails; 131 try { 132 newSigningDetails = ApkSignatureVerifier.verify(apexPath, SignatureSchemeVersion.JAR); 133 } catch (PackageParserException e) { 134 throw new PackageManagerException(SessionInfo.STAGED_SESSION_VERIFICATION_FAILED, 135 "Failed to parse APEX package " + apexPath, e); 136 } 137 138 // Get signing details of the existing package 139 final PackageInfo existingApexPkg = mApexManager.getPackageInfo(packageName, 140 ApexManager.MATCH_ACTIVE_PACKAGE); 141 if (existingApexPkg == null) { 142 // This should never happen, because submitSessionToApexService ensures that no new 143 // apexes were installed. 144 throw new IllegalStateException("Unknown apex package " + packageName); 145 } 146 147 final SigningDetails existingSigningDetails; 148 try { 149 existingSigningDetails = ApkSignatureVerifier.verify( 150 existingApexPkg.applicationInfo.sourceDir, SignatureSchemeVersion.JAR); 151 } catch (PackageParserException e) { 152 throw new PackageManagerException(SessionInfo.STAGED_SESSION_VERIFICATION_FAILED, 153 "Failed to parse APEX package " + existingApexPkg.applicationInfo.sourceDir, e); 154 } 155 156 // Verify signing details for upgrade 157 if (newSigningDetails.checkCapability(existingSigningDetails, 158 SigningDetails.CertCapabilities.INSTALLED_DATA) 159 || existingSigningDetails.checkCapability(newSigningDetails, 160 SigningDetails.CertCapabilities.ROLLBACK)) { 161 return; 162 } 163 164 throw new PackageManagerException(SessionInfo.STAGED_SESSION_VERIFICATION_FAILED, 165 "APK-container signature of APEX package " + packageName + " with version " 166 + newApexPkg.versionCodeMajor + " and path " + apexPath + " is not" 167 + " compatible with the one currently installed on device"); 168 } 169 submitSessionToApexService( @onNull PackageInstallerSession session)170 private List<PackageInfo> submitSessionToApexService( 171 @NonNull PackageInstallerSession session) throws PackageManagerException { 172 final IntArray childSessionsIds = new IntArray(); 173 if (session.isMultiPackage()) { 174 for (int id : session.getChildSessionIds()) { 175 if (isApexSession(mStagedSessions.get(id))) { 176 childSessionsIds.add(id); 177 } 178 } 179 } 180 // submitStagedSession will throw a PackageManagerException if apexd verification fails, 181 // which will be propagated to populate stagedSessionErrorMessage of this session. 182 final ApexInfoList apexInfoList = mApexManager.submitStagedSession(session.sessionId, 183 childSessionsIds.toArray()); 184 final List<PackageInfo> result = new ArrayList<>(); 185 for (ApexInfo apexInfo : apexInfoList.apexInfos) { 186 final PackageInfo packageInfo; 187 int flags = PackageManager.GET_META_DATA; 188 PackageParser.Package pkg; 189 try { 190 File apexFile = new File(apexInfo.modulePath); 191 PackageParser pp = new PackageParser(); 192 pkg = pp.parsePackage(apexFile, flags, false); 193 } catch (PackageParserException e) { 194 throw new PackageManagerException(SessionInfo.STAGED_SESSION_VERIFICATION_FAILED, 195 "Failed to parse APEX package " + apexInfo.modulePath, e); 196 } 197 packageInfo = PackageParser.generatePackageInfo(pkg, apexInfo, flags); 198 final PackageInfo activePackage = mApexManager.getPackageInfo(packageInfo.packageName, 199 ApexManager.MATCH_ACTIVE_PACKAGE); 200 if (activePackage == null) { 201 Slog.w(TAG, "Attempting to install new APEX package " + packageInfo.packageName); 202 throw new PackageManagerException(SessionInfo.STAGED_SESSION_VERIFICATION_FAILED, 203 "It is forbidden to install new APEX packages."); 204 } 205 checkRequiredVersionCode(session, activePackage); 206 checkDowngrade(session, activePackage, packageInfo); 207 result.add(packageInfo); 208 } 209 Slog.d(TAG, "Session " + session.sessionId + " has following APEX packages: [" 210 + result.stream().map(p -> p.packageName).collect(Collectors.joining(",")) + "]"); 211 return result; 212 } 213 checkRequiredVersionCode(final PackageInstallerSession session, final PackageInfo activePackage)214 private void checkRequiredVersionCode(final PackageInstallerSession session, 215 final PackageInfo activePackage) throws PackageManagerException { 216 if (session.params.requiredInstalledVersionCode == PackageManager.VERSION_CODE_HIGHEST) { 217 return; 218 } 219 final long activeVersion = activePackage.applicationInfo.longVersionCode; 220 if (activeVersion != session.params.requiredInstalledVersionCode) { 221 if (!mApexManager.abortStagedSession(session.sessionId)) { 222 Slog.e(TAG, "Failed to abort apex session " + session.sessionId); 223 } 224 throw new PackageManagerException( 225 SessionInfo.STAGED_SESSION_VERIFICATION_FAILED, 226 "Installed version of APEX package " + activePackage.packageName 227 + " does not match required. Active version: " + activeVersion 228 + " required: " + session.params.requiredInstalledVersionCode); 229 } 230 } 231 checkDowngrade(final PackageInstallerSession session, final PackageInfo activePackage, final PackageInfo newPackage)232 private void checkDowngrade(final PackageInstallerSession session, 233 final PackageInfo activePackage, final PackageInfo newPackage) 234 throws PackageManagerException { 235 final long activeVersion = activePackage.applicationInfo.longVersionCode; 236 final long newVersionCode = newPackage.applicationInfo.longVersionCode; 237 final boolean allowsDowngrade = PackageManagerServiceUtils.isDowngradePermitted( 238 session.params.installFlags, activePackage.applicationInfo.flags); 239 if (activeVersion > newVersionCode && !allowsDowngrade) { 240 if (!mApexManager.abortStagedSession(session.sessionId)) { 241 Slog.e(TAG, "Failed to abort apex session " + session.sessionId); 242 } 243 throw new PackageManagerException( 244 SessionInfo.STAGED_SESSION_VERIFICATION_FAILED, 245 "Downgrade of APEX package " + newPackage.packageName 246 + " is not allowed. Active version: " + activeVersion 247 + " attempted: " + newVersionCode); 248 } 249 } 250 isApexSession(@onNull PackageInstallerSession session)251 private static boolean isApexSession(@NonNull PackageInstallerSession session) { 252 return (session.params.installFlags & PackageManager.INSTALL_APEX) != 0; 253 } 254 sessionContains(@onNull PackageInstallerSession session, Predicate<PackageInstallerSession> filter)255 private boolean sessionContains(@NonNull PackageInstallerSession session, 256 Predicate<PackageInstallerSession> filter) { 257 if (!session.isMultiPackage()) { 258 return filter.test(session); 259 } 260 synchronized (mStagedSessions) { 261 return !(Arrays.stream(session.getChildSessionIds()) 262 // Retrieve cached sessions matching ids. 263 .mapToObj(i -> mStagedSessions.get(i)) 264 // Filter only the ones containing APEX. 265 .filter(childSession -> filter.test(childSession)) 266 .collect(Collectors.toList()) 267 .isEmpty()); 268 } 269 } 270 sessionContainsApex(@onNull PackageInstallerSession session)271 private boolean sessionContainsApex(@NonNull PackageInstallerSession session) { 272 return sessionContains(session, (s) -> isApexSession(s)); 273 } 274 sessionContainsApk(@onNull PackageInstallerSession session)275 private boolean sessionContainsApk(@NonNull PackageInstallerSession session) { 276 return sessionContains(session, (s) -> !isApexSession(s)); 277 } 278 279 // Reverts apex sessions and user data (if checkpoint is supported). Also reboots the device. abortCheckpoint()280 private void abortCheckpoint() { 281 try { 282 if (supportsCheckpoint() && needsCheckpoint()) { 283 mApexManager.revertActiveSessions(); 284 PackageHelper.getStorageManager().abortChanges( 285 "StagingManager initiated", false /*retry*/); 286 } 287 } catch (Exception e) { 288 Slog.wtf(TAG, "Failed to abort checkpoint", e); 289 mApexManager.revertActiveSessions(); 290 mPowerManager.reboot(null); 291 } 292 } 293 supportsCheckpoint()294 private boolean supportsCheckpoint() throws RemoteException { 295 return PackageHelper.getStorageManager().supportsCheckpoint(); 296 } 297 needsCheckpoint()298 private boolean needsCheckpoint() throws RemoteException { 299 return PackageHelper.getStorageManager().needsCheckpoint(); 300 } 301 resumeSession(@onNull PackageInstallerSession session)302 private void resumeSession(@NonNull PackageInstallerSession session) { 303 Slog.d(TAG, "Resuming session " + session.sessionId); 304 305 final boolean hasApex = sessionContainsApex(session); 306 ApexSessionInfo apexSessionInfo = null; 307 if (hasApex) { 308 // Check with apexservice whether the apex packages have been activated. 309 apexSessionInfo = mApexManager.getStagedSessionInfo(session.sessionId); 310 311 if (apexSessionInfo != null && apexSessionInfo.isVerified) { 312 // Session has been previously submitted to apexd, but didn't complete all the 313 // pre-reboot verification, perhaps because the device rebooted in the meantime. 314 // Greedily re-trigger the pre-reboot verification. We want to avoid marking it as 315 // failed when not in checkpoint mode, hence it is being processed separately. 316 Slog.d(TAG, "Found pending staged session " + session.sessionId + " still to " 317 + "be verified, resuming pre-reboot verification"); 318 mPreRebootVerificationHandler.startPreRebootVerification(session.sessionId); 319 return; 320 } 321 } 322 323 // Before we resume session, we check if revert is needed or not. Typically, we enter file- 324 // system checkpoint mode when we reboot first time in order to install staged sessions. We 325 // want to install staged sessions in this mode as rebooting now will revert user data. If 326 // something goes wrong, then we reboot again to enter fs-rollback mode. Rebooting now will 327 // have no effect on user data, so mark the sessions as failed instead. 328 try { 329 // If checkpoint is supported, then we only resume sessions if we are in checkpointing 330 // mode. If not, we fail all sessions. 331 if (supportsCheckpoint() && !needsCheckpoint()) { 332 // TODO(b/146343545): Persist failure reason across checkpoint reboot 333 session.setStagedSessionFailed(SessionInfo.STAGED_SESSION_UNKNOWN, 334 "Reverting back to safe state"); 335 return; 336 } 337 } catch (RemoteException e) { 338 // Cannot continue staged install without knowing if fs-checkpoint is supported 339 Slog.e(TAG, "Checkpoint support unknown. Aborting staged install for session " 340 + session.sessionId, e); 341 // TODO: Mark all staged sessions together and reboot only once 342 session.setStagedSessionFailed(SessionInfo.STAGED_SESSION_UNKNOWN, 343 "Checkpoint support unknown. Aborting staged install."); 344 if (hasApex) { 345 mApexManager.revertActiveSessions(); 346 } 347 mPowerManager.reboot("Checkpoint support unknown"); 348 return; 349 } 350 351 if (hasApex) { 352 if (apexSessionInfo == null) { 353 session.setStagedSessionFailed(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED, 354 "apexd did not know anything about a staged session supposed to be" 355 + "activated"); 356 abortCheckpoint(); 357 return; 358 } 359 if (isApexSessionFailed(apexSessionInfo)) { 360 session.setStagedSessionFailed(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED, 361 "APEX activation failed. Check logcat messages from apexd for " 362 + "more information."); 363 abortCheckpoint(); 364 return; 365 } 366 if (!apexSessionInfo.isActivated && !apexSessionInfo.isSuccess) { 367 // Apexd did not apply the session for some unknown reason. There is no guarantee 368 // that apexd will install it next time. Safer to proactively mark as failed. 369 session.setStagedSessionFailed(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED, 370 "Staged session " + session.sessionId + "at boot didn't " 371 + "activate nor fail. Marking it as failed anyway."); 372 abortCheckpoint(); 373 return; 374 } 375 Slog.i(TAG, "APEX packages in session " + session.sessionId 376 + " were successfully activated. Proceeding with APK packages, if any"); 377 } 378 // The APEX part of the session is activated, proceed with the installation of APKs. 379 try { 380 Slog.d(TAG, "Installing APK packages in session " + session.sessionId); 381 installApksInSession(session); 382 } catch (PackageManagerException e) { 383 session.setStagedSessionFailed(e.error, e.getMessage()); 384 abortCheckpoint(); 385 386 // If checkpoint is not supported, we have to handle failure for one staged session. 387 if (!hasApex) { 388 return; 389 } 390 391 if (!mApexManager.revertActiveSessions()) { 392 Slog.e(TAG, "Failed to abort APEXd session"); 393 } else { 394 Slog.e(TAG, 395 "Successfully aborted apexd session. Rebooting device in order to revert " 396 + "to the previous state of APEXd."); 397 mPowerManager.reboot(null); 398 } 399 return; 400 } 401 402 Slog.d(TAG, "Marking session " + session.sessionId + " as applied"); 403 session.setStagedSessionApplied(); 404 if (hasApex) { 405 mApexManager.markStagedSessionSuccessful(session.sessionId); 406 } 407 } 408 findAPKsInDir(File stageDir)409 private List<String> findAPKsInDir(File stageDir) { 410 List<String> ret = new ArrayList<>(); 411 if (stageDir != null && stageDir.exists()) { 412 for (File file : stageDir.listFiles()) { 413 if (file.getAbsolutePath().toLowerCase().endsWith(".apk")) { 414 ret.add(file.getAbsolutePath()); 415 } 416 } 417 } 418 return ret; 419 } 420 421 @NonNull createAndWriteApkSession( @onNull PackageInstallerSession originalSession, boolean preReboot)422 private PackageInstallerSession createAndWriteApkSession( 423 @NonNull PackageInstallerSession originalSession, boolean preReboot) 424 throws PackageManagerException { 425 final int errorCode = preReboot ? SessionInfo.STAGED_SESSION_VERIFICATION_FAILED 426 : SessionInfo.STAGED_SESSION_ACTIVATION_FAILED; 427 if (originalSession.stageDir == null) { 428 Slog.wtf(TAG, "Attempting to install a staged APK session with no staging dir"); 429 throw new PackageManagerException(errorCode, 430 "Attempting to install a staged APK session with no staging dir"); 431 } 432 List<String> apkFilePaths = findAPKsInDir(originalSession.stageDir); 433 if (apkFilePaths.isEmpty()) { 434 Slog.w(TAG, "Can't find staged APK in " + originalSession.stageDir.getAbsolutePath()); 435 throw new PackageManagerException(errorCode, 436 "Can't find staged APK in " + originalSession.stageDir.getAbsolutePath()); 437 } 438 439 PackageInstaller.SessionParams params = originalSession.params.copy(); 440 params.isStaged = false; 441 params.installFlags |= PackageManager.INSTALL_STAGED; 442 // TODO(b/129744602): use the userid from the original session. 443 if (preReboot) { 444 params.installFlags &= ~PackageManager.INSTALL_ENABLE_ROLLBACK; 445 params.installFlags |= PackageManager.INSTALL_DRY_RUN; 446 } else { 447 params.installFlags |= PackageManager.INSTALL_DISABLE_VERIFICATION; 448 } 449 try { 450 int apkSessionId = mPi.createSession( 451 params, originalSession.getInstallerPackageName(), 452 0 /* UserHandle.SYSTEM */); 453 PackageInstallerSession apkSession = mPi.getSession(apkSessionId); 454 apkSession.open(); 455 for (String apkFilePath : apkFilePaths) { 456 File apkFile = new File(apkFilePath); 457 ParcelFileDescriptor pfd = ParcelFileDescriptor.open(apkFile, 458 ParcelFileDescriptor.MODE_READ_ONLY); 459 long sizeBytes = (pfd == null) ? -1 : pfd.getStatSize(); 460 if (sizeBytes < 0) { 461 Slog.e(TAG, "Unable to get size of: " + apkFilePath); 462 throw new PackageManagerException(errorCode, 463 "Unable to get size of: " + apkFilePath); 464 } 465 apkSession.write(apkFile.getName(), 0, sizeBytes, pfd); 466 } 467 return apkSession; 468 } catch (IOException | ParcelableException e) { 469 Slog.e(TAG, "Failure to install APK staged session " + originalSession.sessionId, e); 470 throw new PackageManagerException(errorCode, "Failed to write APK session", e); 471 } 472 } 473 474 /** 475 * Extract apks in the given session into a new session. Returns {@code null} if there is no 476 * apks in the given session. Only parent session is returned for multi-package session. 477 */ 478 @Nullable extractApksInSession(PackageInstallerSession session, boolean preReboot)479 private PackageInstallerSession extractApksInSession(PackageInstallerSession session, 480 boolean preReboot) throws PackageManagerException { 481 final int errorCode = preReboot ? SessionInfo.STAGED_SESSION_VERIFICATION_FAILED 482 : SessionInfo.STAGED_SESSION_ACTIVATION_FAILED; 483 if (!session.isMultiPackage() && !isApexSession(session)) { 484 return createAndWriteApkSession(session, preReboot); 485 } else if (session.isMultiPackage()) { 486 // For multi-package staged sessions containing APKs, we identify which child sessions 487 // contain an APK, and with those then create a new multi-package group of sessions, 488 // carrying over all the session parameters and unmarking them as staged. On commit the 489 // sessions will be installed atomically. 490 final List<PackageInstallerSession> childSessions; 491 synchronized (mStagedSessions) { 492 childSessions = 493 Arrays.stream(session.getChildSessionIds()) 494 // Retrieve cached sessions matching ids. 495 .mapToObj(i -> mStagedSessions.get(i)) 496 // Filter only the ones containing APKs.s 497 .filter(childSession -> !isApexSession(childSession)) 498 .collect(Collectors.toList()); 499 } 500 if (childSessions.isEmpty()) { 501 // APEX-only multi-package staged session, nothing to do. 502 return null; 503 } 504 final PackageInstaller.SessionParams params = session.params.copy(); 505 params.isStaged = false; 506 if (preReboot) { 507 params.installFlags &= ~PackageManager.INSTALL_ENABLE_ROLLBACK; 508 } 509 // TODO(b/129744602): use the userid from the original session. 510 final int apkParentSessionId = mPi.createSession( 511 params, session.getInstallerPackageName(), 512 0 /* UserHandle.SYSTEM */); 513 final PackageInstallerSession apkParentSession = mPi.getSession(apkParentSessionId); 514 try { 515 apkParentSession.open(); 516 } catch (IOException e) { 517 Slog.e(TAG, "Unable to prepare multi-package session for staged session " 518 + session.sessionId); 519 throw new PackageManagerException(errorCode, 520 "Unable to prepare multi-package session for staged session"); 521 } 522 523 for (PackageInstallerSession sessionToClone : childSessions) { 524 PackageInstallerSession apkChildSession = 525 createAndWriteApkSession(sessionToClone, preReboot); 526 try { 527 apkParentSession.addChildSessionId(apkChildSession.sessionId); 528 } catch (IllegalStateException e) { 529 Slog.e(TAG, "Failed to add a child session for installing the APK files", e); 530 throw new PackageManagerException(errorCode, 531 "Failed to add a child session " + apkChildSession.sessionId); 532 } 533 } 534 return apkParentSession; 535 } 536 return null; 537 } 538 verifyApksInSession(PackageInstallerSession session)539 private void verifyApksInSession(PackageInstallerSession session) 540 throws PackageManagerException { 541 542 final PackageInstallerSession apksToVerify = extractApksInSession( 543 session, /* preReboot */ true); 544 if (apksToVerify == null) { 545 return; 546 } 547 548 final LocalIntentReceiverAsync receiver = new LocalIntentReceiverAsync( 549 (Intent result) -> { 550 int status = result.getIntExtra(PackageInstaller.EXTRA_STATUS, 551 PackageInstaller.STATUS_FAILURE); 552 if (status != PackageInstaller.STATUS_SUCCESS) { 553 final String errorMessage = result.getStringExtra( 554 PackageInstaller.EXTRA_STATUS_MESSAGE); 555 Slog.e(TAG, "Failure to verify APK staged session " 556 + session.sessionId + " [" + errorMessage + "]"); 557 session.setStagedSessionFailed( 558 SessionInfo.STAGED_SESSION_VERIFICATION_FAILED, errorMessage); 559 return; 560 } 561 mPreRebootVerificationHandler.notifyPreRebootVerification_Apk_Complete( 562 session.sessionId); 563 }); 564 565 apksToVerify.commit(receiver.getIntentSender(), false); 566 } 567 installApksInSession(@onNull PackageInstallerSession session)568 private void installApksInSession(@NonNull PackageInstallerSession session) 569 throws PackageManagerException { 570 571 final PackageInstallerSession apksToInstall = extractApksInSession( 572 session, /* preReboot */ false); 573 if (apksToInstall == null) { 574 return; 575 } 576 577 if ((apksToInstall.params.installFlags & PackageManager.INSTALL_ENABLE_ROLLBACK) != 0) { 578 // If rollback is available for this session, notify the rollback 579 // manager of the apk session so it can properly enable rollback. 580 final IRollbackManager rm = IRollbackManager.Stub.asInterface( 581 ServiceManager.getService(Context.ROLLBACK_SERVICE)); 582 try { 583 rm.notifyStagedApkSession(session.sessionId, apksToInstall.sessionId); 584 } catch (RemoteException re) { 585 Slog.e(TAG, "Failed to notifyStagedApkSession for session: " 586 + session.sessionId, re); 587 } 588 } 589 590 final LocalIntentReceiverSync receiver = new LocalIntentReceiverSync(); 591 apksToInstall.commit(receiver.getIntentSender(), false); 592 final Intent result = receiver.getResult(); 593 final int status = result.getIntExtra(PackageInstaller.EXTRA_STATUS, 594 PackageInstaller.STATUS_FAILURE); 595 if (status != PackageInstaller.STATUS_SUCCESS) { 596 final String errorMessage = result.getStringExtra( 597 PackageInstaller.EXTRA_STATUS_MESSAGE); 598 Slog.e(TAG, "Failure to install APK staged session " 599 + session.sessionId + " [" + errorMessage + "]"); 600 throw new PackageManagerException( 601 SessionInfo.STAGED_SESSION_ACTIVATION_FAILED, errorMessage); 602 } 603 } 604 commitSession(@onNull PackageInstallerSession session)605 void commitSession(@NonNull PackageInstallerSession session) { 606 updateStoredSession(session); 607 mPreRebootVerificationHandler.startPreRebootVerification(session.sessionId); 608 } 609 parentOrOwnSessionId(PackageInstallerSession session)610 private int parentOrOwnSessionId(PackageInstallerSession session) { 611 return session.hasParentSessionId() ? session.getParentSessionId() : session.sessionId; 612 } 613 614 /** 615 * <p> Check if the session provided is non-overlapping with the active staged sessions. 616 * 617 * <p> A session is non-overlapping if it meets one of the following conditions: </p> 618 * <ul> 619 * <li>It is a parent session</li> 620 * <li>It is already one of the active sessions</li> 621 * <li>Its package name is not same as any of the active sessions</li> 622 * </ul> 623 * @throws PackageManagerException if session fails the check 624 */ checkNonOverlappingWithStagedSessions(@onNull PackageInstallerSession session)625 void checkNonOverlappingWithStagedSessions(@NonNull PackageInstallerSession session) 626 throws PackageManagerException { 627 if (session.isMultiPackage()) { 628 // We cannot say a parent session overlaps until we process its children 629 return; 630 } 631 if (session.getPackageName() == null) { 632 throw new PackageManagerException(PackageManager.INSTALL_FAILED_INVALID_APK, 633 "Cannot stage session " + session.sessionId + " with package name null"); 634 } 635 636 boolean supportsCheckpoint = ((StorageManager) mContext.getSystemService( 637 Context.STORAGE_SERVICE)).isCheckpointSupported(); 638 639 synchronized (mStagedSessions) { 640 for (int i = 0; i < mStagedSessions.size(); i++) { 641 final PackageInstallerSession stagedSession = mStagedSessions.valueAt(i); 642 if (!stagedSession.isCommitted() || stagedSession.isStagedAndInTerminalState()) { 643 continue; 644 } 645 if (stagedSession.isMultiPackage()) { 646 // This active parent staged session is useless as it doesn't have a package 647 // name and the session we are checking is not a parent session either. 648 continue; 649 } 650 651 // From here on, stagedSession is a non-parent active staged session 652 653 // Check if stagedSession has an active parent session or not 654 if (stagedSession.hasParentSessionId()) { 655 int parentId = stagedSession.getParentSessionId(); 656 PackageInstallerSession parentSession = mStagedSessions.get(parentId); 657 if (parentSession == null || parentSession.isStagedAndInTerminalState()) { 658 // Parent session has been abandoned or terminated already 659 continue; 660 } 661 } 662 663 // Check if session is one of the active sessions 664 if (session.sessionId == stagedSession.sessionId) { 665 Slog.w(TAG, "Session " + session.sessionId + " is already staged"); 666 continue; 667 } 668 669 // If session is not among the active sessions, then it cannot have same package 670 // name as any of the active sessions. 671 if (session.getPackageName().equals(stagedSession.getPackageName())) { 672 throw new PackageManagerException( 673 PackageManager.INSTALL_FAILED_OTHER_STAGED_SESSION_IN_PROGRESS, 674 "Package: " + session.getPackageName() + " in session: " 675 + session.sessionId + " has been staged already by session: " 676 + stagedSession.sessionId, null); 677 } 678 679 // Staging multiple root sessions is not allowed if device doesn't support 680 // checkpoint. If session and stagedSession do not have common ancestor, they are 681 // from two different root sessions. 682 if (!supportsCheckpoint 683 && parentOrOwnSessionId(session) != parentOrOwnSessionId(stagedSession)) { 684 throw new PackageManagerException( 685 PackageManager.INSTALL_FAILED_OTHER_STAGED_SESSION_IN_PROGRESS, 686 "Cannot stage multiple sessions without checkpoint support", null); 687 } 688 } 689 } 690 } 691 createSession(@onNull PackageInstallerSession sessionInfo)692 void createSession(@NonNull PackageInstallerSession sessionInfo) { 693 synchronized (mStagedSessions) { 694 mStagedSessions.append(sessionInfo.sessionId, sessionInfo); 695 } 696 } 697 abortSession(@onNull PackageInstallerSession session)698 void abortSession(@NonNull PackageInstallerSession session) { 699 synchronized (mStagedSessions) { 700 mStagedSessions.remove(session.sessionId); 701 } 702 } 703 abortCommittedSession(@onNull PackageInstallerSession session)704 void abortCommittedSession(@NonNull PackageInstallerSession session) { 705 if (session.isStagedSessionApplied()) { 706 Slog.w(TAG, "Cannot abort applied session : " + session.sessionId); 707 return; 708 } 709 abortSession(session); 710 711 boolean hasApex = sessionContainsApex(session); 712 if (hasApex) { 713 ApexSessionInfo apexSession = mApexManager.getStagedSessionInfo(session.sessionId); 714 if (apexSession == null || isApexSessionFinalized(apexSession)) { 715 Slog.w(TAG, 716 "Cannot abort session " + session.sessionId 717 + " because it is not active or APEXD is not reachable"); 718 return; 719 } 720 try { 721 mApexManager.abortStagedSession(session.sessionId); 722 } catch (Exception ignore) { 723 } 724 } 725 } 726 isApexSessionFinalized(ApexSessionInfo session)727 private boolean isApexSessionFinalized(ApexSessionInfo session) { 728 /* checking if the session is in a final state, i.e., not active anymore */ 729 return session.isUnknown || session.isActivationFailed || session.isSuccess 730 || session.isReverted; 731 } 732 isApexSessionFailed(ApexSessionInfo apexSessionInfo)733 private static boolean isApexSessionFailed(ApexSessionInfo apexSessionInfo) { 734 // isRevertInProgress is included to cover the scenario, when a device is rebooted 735 // during the revert, and apexd fails to resume the revert after reboot. 736 return apexSessionInfo.isActivationFailed || apexSessionInfo.isUnknown 737 || apexSessionInfo.isReverted || apexSessionInfo.isRevertInProgress 738 || apexSessionInfo.isRevertFailed; 739 } 740 741 @GuardedBy("mStagedSessions") isMultiPackageSessionComplete(@onNull PackageInstallerSession session)742 private boolean isMultiPackageSessionComplete(@NonNull PackageInstallerSession session) { 743 // This method assumes that the argument is either a parent session of a multi-package 744 // i.e. isMultiPackage() returns true, or that it is a child session, i.e. 745 // hasParentSessionId() returns true. 746 if (session.isMultiPackage()) { 747 // Parent session of a multi-package group. Check that we restored all the children. 748 for (int childSession : session.getChildSessionIds()) { 749 if (mStagedSessions.get(childSession) == null) { 750 return false; 751 } 752 } 753 return true; 754 } 755 if (session.hasParentSessionId()) { 756 PackageInstallerSession parent = mStagedSessions.get(session.getParentSessionId()); 757 if (parent == null) { 758 return false; 759 } 760 return isMultiPackageSessionComplete(parent); 761 } 762 Slog.wtf(TAG, "Attempting to restore an invalid multi-package session."); 763 return false; 764 } 765 restoreSession(@onNull PackageInstallerSession session, boolean isDeviceUpgrading)766 void restoreSession(@NonNull PackageInstallerSession session, boolean isDeviceUpgrading) { 767 PackageInstallerSession sessionToResume = session; 768 synchronized (mStagedSessions) { 769 mStagedSessions.append(session.sessionId, session); 770 // For multi-package sessions, we don't know in which order they will be restored. We 771 // need to wait until we have restored all the session in a group before restoring them. 772 if (session.isMultiPackage() || session.hasParentSessionId()) { 773 if (!isMultiPackageSessionComplete(session)) { 774 // Still haven't recovered all sessions of the group, return. 775 return; 776 } 777 // Group recovered, find the parent if necessary and resume the installation. 778 if (session.hasParentSessionId()) { 779 sessionToResume = mStagedSessions.get(session.getParentSessionId()); 780 } 781 } 782 } 783 // The preconditions used during pre-reboot verification might have changed when device 784 // is upgrading. Updated staged sessions to activation failed before we resume the session. 785 if (isDeviceUpgrading && !sessionToResume.isStagedAndInTerminalState()) { 786 sessionToResume.setStagedSessionFailed(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED, 787 "Build fingerprint has changed"); 788 return; 789 } 790 checkStateAndResume(sessionToResume); 791 } 792 checkStateAndResume(@onNull PackageInstallerSession session)793 private void checkStateAndResume(@NonNull PackageInstallerSession session) { 794 if (!session.isCommitted()) { 795 // Session hasn't been committed yet, ignore. 796 return; 797 } 798 // Check the state of the session and decide what to do next. 799 if (session.isStagedSessionFailed() || session.isStagedSessionApplied()) { 800 // Final states, nothing to do. 801 return; 802 } 803 if (!session.isStagedSessionReady()) { 804 // The framework got restarted before the pre-reboot verification could complete, 805 // restart the verification. 806 mPreRebootVerificationHandler.startPreRebootVerification(session.sessionId); 807 } else { 808 // Session had already being marked ready. Start the checks to verify if there is any 809 // follow-up work. 810 resumeSession(session); 811 } 812 } 813 814 private static class LocalIntentReceiverAsync { 815 final Consumer<Intent> mConsumer; 816 LocalIntentReceiverAsync(Consumer<Intent> consumer)817 LocalIntentReceiverAsync(Consumer<Intent> consumer) { 818 mConsumer = consumer; 819 } 820 821 private IIntentSender.Stub mLocalSender = new IIntentSender.Stub() { 822 @Override 823 public void send(int code, Intent intent, String resolvedType, IBinder whitelistToken, 824 IIntentReceiver finishedReceiver, String requiredPermission, Bundle options) { 825 mConsumer.accept(intent); 826 } 827 }; 828 getIntentSender()829 public IntentSender getIntentSender() { 830 return new IntentSender((IIntentSender) mLocalSender); 831 } 832 } 833 834 private static class LocalIntentReceiverSync { 835 private final LinkedBlockingQueue<Intent> mResult = new LinkedBlockingQueue<>(); 836 837 private IIntentSender.Stub mLocalSender = new IIntentSender.Stub() { 838 @Override 839 public void send(int code, Intent intent, String resolvedType, IBinder whitelistToken, 840 IIntentReceiver finishedReceiver, String requiredPermission, 841 Bundle options) { 842 try { 843 mResult.offer(intent, 5, TimeUnit.SECONDS); 844 } catch (InterruptedException e) { 845 throw new RuntimeException(e); 846 } 847 } 848 }; 849 getIntentSender()850 public IntentSender getIntentSender() { 851 return new IntentSender((IIntentSender) mLocalSender); 852 } 853 getResult()854 public Intent getResult() { 855 try { 856 return mResult.take(); 857 } catch (InterruptedException e) { 858 throw new RuntimeException(e); 859 } 860 } 861 } 862 863 private final class PreRebootVerificationHandler extends Handler { 864 PreRebootVerificationHandler(Looper looper)865 PreRebootVerificationHandler(Looper looper) { 866 super(looper); 867 } 868 869 /** 870 * Handler for states of pre reboot verification. The states are arranged linearly (shown 871 * below) with each state either calling the next state, or calling some other method that 872 * eventually calls the next state. 873 * 874 * <p><ul> 875 * <li>MSG_PRE_REBOOT_VERIFICATION_START</li> 876 * <li>MSG_PRE_REBOOT_VERIFICATION_APEX</li> 877 * <li>MSG_PRE_REBOOT_VERIFICATION_APK</li> 878 * <li>MSG_PRE_REBOOT_VERIFICATION_END</li> 879 * </ul></p> 880 * 881 * Details about each of state can be found in corresponding handler of node. 882 */ 883 private static final int MSG_PRE_REBOOT_VERIFICATION_START = 1; 884 private static final int MSG_PRE_REBOOT_VERIFICATION_APEX = 2; 885 private static final int MSG_PRE_REBOOT_VERIFICATION_APK = 3; 886 private static final int MSG_PRE_REBOOT_VERIFICATION_END = 4; 887 888 @Override handleMessage(Message msg)889 public void handleMessage(Message msg) { 890 final int sessionId = msg.arg1; 891 final PackageInstallerSession session; 892 synchronized (mStagedSessions) { 893 session = mStagedSessions.get(sessionId); 894 } 895 // Maybe session was aborted before pre-reboot verification was complete 896 if (session == null) { 897 Slog.d(TAG, "Stopping pre-reboot verification for sessionId: " + sessionId); 898 return; 899 } 900 switch (msg.what) { 901 case MSG_PRE_REBOOT_VERIFICATION_START: 902 handlePreRebootVerification_Start(session); 903 break; 904 case MSG_PRE_REBOOT_VERIFICATION_APEX: 905 handlePreRebootVerification_Apex(session); 906 break; 907 case MSG_PRE_REBOOT_VERIFICATION_APK: 908 handlePreRebootVerification_Apk(session); 909 break; 910 case MSG_PRE_REBOOT_VERIFICATION_END: 911 handlePreRebootVerification_End(session); 912 break; 913 } 914 } 915 916 // Method for starting the pre-reboot verification startPreRebootVerification(int sessionId)917 private void startPreRebootVerification(int sessionId) { 918 obtainMessage(MSG_PRE_REBOOT_VERIFICATION_START, sessionId, 0).sendToTarget(); 919 } 920 notifyPreRebootVerification_Start_Complete(int sessionId)921 private void notifyPreRebootVerification_Start_Complete(int sessionId) { 922 obtainMessage(MSG_PRE_REBOOT_VERIFICATION_APEX, sessionId, 0).sendToTarget(); 923 } 924 notifyPreRebootVerification_Apex_Complete(int sessionId)925 private void notifyPreRebootVerification_Apex_Complete(int sessionId) { 926 obtainMessage(MSG_PRE_REBOOT_VERIFICATION_APK, sessionId, 0).sendToTarget(); 927 } 928 notifyPreRebootVerification_Apk_Complete(int sessionId)929 private void notifyPreRebootVerification_Apk_Complete(int sessionId) { 930 obtainMessage(MSG_PRE_REBOOT_VERIFICATION_END, sessionId, 0).sendToTarget(); 931 } 932 933 /** 934 * A dummy state for starting the pre reboot verification. 935 * 936 * See {@link PreRebootVerificationHandler} to see all nodes of pre reboot verification 937 */ handlePreRebootVerification_Start(@onNull PackageInstallerSession session)938 private void handlePreRebootVerification_Start(@NonNull PackageInstallerSession session) { 939 Slog.d(TAG, "Starting preRebootVerification for session " + session.sessionId); 940 notifyPreRebootVerification_Start_Complete(session.sessionId); 941 } 942 943 /** 944 * Pre-reboot verification state for apex files: 945 * 946 * <p><ul> 947 * <li>submits session to apex service</li> 948 * <li>validates signatures of apex files</li> 949 * </ul></p> 950 */ handlePreRebootVerification_Apex(@onNull PackageInstallerSession session)951 private void handlePreRebootVerification_Apex(@NonNull PackageInstallerSession session) { 952 final boolean hasApex = sessionContainsApex(session); 953 954 // APEX checks. For single-package sessions, check if they contain an APEX. For 955 // multi-package sessions, find all the child sessions that contain an APEX. 956 if (hasApex) { 957 try { 958 final List<PackageInfo> apexPackages = 959 submitSessionToApexService(session); 960 for (PackageInfo apexPackage : apexPackages) { 961 validateApexSignature(apexPackage); 962 } 963 } catch (PackageManagerException e) { 964 session.setStagedSessionFailed(e.error, e.getMessage()); 965 return; 966 } 967 } 968 969 notifyPreRebootVerification_Apex_Complete(session.sessionId); 970 } 971 972 /** 973 * Pre-reboot verification state for apk files: 974 * <p><ul> 975 * <li>performs a dry-run install of apk</li> 976 * </ul></p> 977 */ handlePreRebootVerification_Apk(@onNull PackageInstallerSession session)978 private void handlePreRebootVerification_Apk(@NonNull PackageInstallerSession session) { 979 if (!sessionContainsApk(session)) { 980 notifyPreRebootVerification_Apk_Complete(session.sessionId); 981 return; 982 } 983 984 try { 985 Slog.d(TAG, "Running a pre-reboot verification for APKs in session " 986 + session.sessionId + " by performing a dry-run install"); 987 988 // verifyApksInSession will notify the handler when APK verification is complete 989 verifyApksInSession(session); 990 // TODO(b/118865310): abort the session on apexd. 991 } catch (PackageManagerException e) { 992 session.setStagedSessionFailed(e.error, e.getMessage()); 993 } 994 } 995 996 /** 997 * Pre-reboot verification state for wrapping up: 998 * <p><ul> 999 * <li>enables rollback if required</li> 1000 * <li>marks session as ready</li> 1001 * </ul></p> 1002 */ handlePreRebootVerification_End(@onNull PackageInstallerSession session)1003 private void handlePreRebootVerification_End(@NonNull PackageInstallerSession session) { 1004 if ((session.params.installFlags & PackageManager.INSTALL_ENABLE_ROLLBACK) != 0) { 1005 // If rollback is enabled for this session, we call through to the RollbackManager 1006 // with the list of sessions it must enable rollback for. Note that 1007 // notifyStagedSession is a synchronous operation. 1008 final IRollbackManager rm = IRollbackManager.Stub.asInterface( 1009 ServiceManager.getService(Context.ROLLBACK_SERVICE)); 1010 try { 1011 // NOTE: To stay consistent with the non-staged install flow, we don't fail the 1012 // entire install if rollbacks can't be enabled. 1013 if (!rm.notifyStagedSession(session.sessionId)) { 1014 Slog.e(TAG, "Unable to enable rollback for session: " 1015 + session.sessionId); 1016 } 1017 } catch (RemoteException re) { 1018 Slog.e(TAG, "Failed to notifyStagedSession for session: " 1019 + session.sessionId, re); 1020 } 1021 } 1022 // Before marking the session as ready, start checkpoint service if available 1023 try { 1024 IStorageManager storageManager = PackageHelper.getStorageManager(); 1025 if (storageManager.supportsCheckpoint()) { 1026 storageManager.startCheckpoint(1); 1027 } 1028 } catch (Exception e) { 1029 // Failed to get hold of StorageManager 1030 Slog.e(TAG, "Failed to get hold of StorageManager", e); 1031 session.setStagedSessionFailed(SessionInfo.STAGED_SESSION_UNKNOWN, 1032 "Failed to get hold of StorageManager"); 1033 return; 1034 } 1035 1036 // Proactively mark session as ready before calling apexd. Although this call order 1037 // looks counter-intuitive, this is the easiest way to ensure that session won't end up 1038 // in the inconsistent state: 1039 // - If device gets rebooted right before call to apexd, then apexd will never activate 1040 // apex files of this staged session. This will result in StagingManager failing 1041 // the session. 1042 // On the other hand, if the order of the calls was inverted (first call apexd, then 1043 // mark session as ready), then if a device gets rebooted right after the call to apexd, 1044 // only apex part of the train will be applied, leaving device in an inconsistent state. 1045 Slog.d(TAG, "Marking session " + session.sessionId + " as ready"); 1046 session.setStagedSessionReady(); 1047 final boolean hasApex = sessionContainsApex(session); 1048 if (!hasApex) { 1049 // Session doesn't contain apex, nothing to do. 1050 return; 1051 } 1052 try { 1053 mApexManager.markStagedSessionReady(session.sessionId); 1054 } catch (PackageManagerException e) { 1055 session.setStagedSessionFailed(e.error, e.getMessage()); 1056 } 1057 } 1058 } 1059 } 1060