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