• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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