1 /*
2  * Copyright (C) 2014 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 static android.content.pm.PackageManager.INSTALL_FAILED_ABORTED;
20 import static android.content.pm.PackageManager.INSTALL_FAILED_BAD_SIGNATURE;
21 import static android.content.pm.PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE;
22 import static android.content.pm.PackageManager.INSTALL_FAILED_INTERNAL_ERROR;
23 import static android.content.pm.PackageManager.INSTALL_FAILED_INVALID_APK;
24 import static android.content.pm.PackageManager.INSTALL_FAILED_MISSING_SPLIT;
25 import static android.content.pm.PackageParser.APEX_FILE_EXTENSION;
26 import static android.content.pm.PackageParser.APK_FILE_EXTENSION;
27 import static android.system.OsConstants.O_CREAT;
28 import static android.system.OsConstants.O_RDONLY;
29 import static android.system.OsConstants.O_WRONLY;
30 
31 import static com.android.internal.util.XmlUtils.readBitmapAttribute;
32 import static com.android.internal.util.XmlUtils.readBooleanAttribute;
33 import static com.android.internal.util.XmlUtils.readIntAttribute;
34 import static com.android.internal.util.XmlUtils.readLongAttribute;
35 import static com.android.internal.util.XmlUtils.readStringAttribute;
36 import static com.android.internal.util.XmlUtils.readUriAttribute;
37 import static com.android.internal.util.XmlUtils.writeBooleanAttribute;
38 import static com.android.internal.util.XmlUtils.writeIntAttribute;
39 import static com.android.internal.util.XmlUtils.writeLongAttribute;
40 import static com.android.internal.util.XmlUtils.writeStringAttribute;
41 import static com.android.internal.util.XmlUtils.writeUriAttribute;
42 import static com.android.server.pm.PackageInstallerService.prepareStageDir;
43 
44 import android.Manifest;
45 import android.annotation.NonNull;
46 import android.annotation.Nullable;
47 import android.app.admin.DevicePolicyEventLogger;
48 import android.app.admin.DevicePolicyManagerInternal;
49 import android.content.Context;
50 import android.content.IIntentReceiver;
51 import android.content.IIntentSender;
52 import android.content.Intent;
53 import android.content.IntentSender;
54 import android.content.pm.ApplicationInfo;
55 import android.content.pm.IPackageInstallObserver2;
56 import android.content.pm.IPackageInstallerSession;
57 import android.content.pm.PackageInfo;
58 import android.content.pm.PackageInstaller;
59 import android.content.pm.PackageInstaller.SessionInfo;
60 import android.content.pm.PackageInstaller.SessionInfo.StagedSessionErrorCode;
61 import android.content.pm.PackageInstaller.SessionParams;
62 import android.content.pm.PackageManager;
63 import android.content.pm.PackageParser;
64 import android.content.pm.PackageParser.ApkLite;
65 import android.content.pm.PackageParser.PackageLite;
66 import android.content.pm.PackageParser.PackageParserException;
67 import android.content.pm.dex.DexMetadataHelper;
68 import android.graphics.Bitmap;
69 import android.graphics.BitmapFactory;
70 import android.os.Binder;
71 import android.os.Bundle;
72 import android.os.FileBridge;
73 import android.os.FileUtils;
74 import android.os.Handler;
75 import android.os.IBinder;
76 import android.os.Looper;
77 import android.os.Message;
78 import android.os.ParcelFileDescriptor;
79 import android.os.ParcelableException;
80 import android.os.Process;
81 import android.os.RevocableFileDescriptor;
82 import android.os.SystemProperties;
83 import android.os.UserHandle;
84 import android.os.storage.StorageManager;
85 import android.stats.devicepolicy.DevicePolicyEnums;
86 import android.system.ErrnoException;
87 import android.system.Int64Ref;
88 import android.system.Os;
89 import android.system.OsConstants;
90 import android.system.StructStat;
91 import android.text.TextUtils;
92 import android.util.ArraySet;
93 import android.util.ExceptionUtils;
94 import android.util.MathUtils;
95 import android.util.Slog;
96 import android.util.SparseIntArray;
97 import android.util.apk.ApkSignatureVerifier;
98 
99 import com.android.internal.annotations.GuardedBy;
100 import com.android.internal.content.NativeLibraryHelper;
101 import com.android.internal.content.PackageHelper;
102 import com.android.internal.os.SomeArgs;
103 import com.android.internal.util.ArrayUtils;
104 import com.android.internal.util.IndentingPrintWriter;
105 import com.android.internal.util.Preconditions;
106 import com.android.server.LocalServices;
107 import com.android.server.pm.Installer.InstallerException;
108 import com.android.server.pm.dex.DexManager;
109 import com.android.server.security.VerityUtils;
110 
111 import libcore.io.IoUtils;
112 
113 import org.xmlpull.v1.XmlPullParser;
114 import org.xmlpull.v1.XmlPullParserException;
115 import org.xmlpull.v1.XmlSerializer;
116 
117 import java.io.File;
118 import java.io.FileDescriptor;
119 import java.io.FileFilter;
120 import java.io.FileOutputStream;
121 import java.io.IOException;
122 import java.util.ArrayList;
123 import java.util.Arrays;
124 import java.util.LinkedList;
125 import java.util.List;
126 import java.util.concurrent.atomic.AtomicInteger;
127 
128 public class PackageInstallerSession extends IPackageInstallerSession.Stub {
129     private static final String TAG = "PackageInstallerSession";
130     private static final boolean LOGD = true;
131     private static final String REMOVE_MARKER_EXTENSION = ".removed";
132 
133     private static final int MSG_COMMIT = 1;
134     private static final int MSG_ON_PACKAGE_INSTALLED = 2;
135 
136     /** XML constants used for persisting a session */
137     static final String TAG_SESSION = "session";
138     static final String TAG_CHILD_SESSION = "childSession";
139     private static final String TAG_GRANTED_RUNTIME_PERMISSION = "granted-runtime-permission";
140     private static final String TAG_WHITELISTED_RESTRICTED_PERMISSION =
141             "whitelisted-restricted-permission";
142     private static final String ATTR_SESSION_ID = "sessionId";
143     private static final String ATTR_USER_ID = "userId";
144     private static final String ATTR_INSTALLER_PACKAGE_NAME = "installerPackageName";
145     private static final String ATTR_INSTALLER_UID = "installerUid";
146     private static final String ATTR_CREATED_MILLIS = "createdMillis";
147     private static final String ATTR_UPDATED_MILLIS = "updatedMillis";
148     private static final String ATTR_SESSION_STAGE_DIR = "sessionStageDir";
149     private static final String ATTR_SESSION_STAGE_CID = "sessionStageCid";
150     private static final String ATTR_PREPARED = "prepared";
151     private static final String ATTR_COMMITTED = "committed";
152     private static final String ATTR_SEALED = "sealed";
153     private static final String ATTR_MULTI_PACKAGE = "multiPackage";
154     private static final String ATTR_PARENT_SESSION_ID = "parentSessionId";
155     private static final String ATTR_STAGED_SESSION = "stagedSession";
156     private static final String ATTR_IS_READY = "isReady";
157     private static final String ATTR_IS_FAILED = "isFailed";
158     private static final String ATTR_IS_APPLIED = "isApplied";
159     private static final String ATTR_STAGED_SESSION_ERROR_CODE = "errorCode";
160     private static final String ATTR_STAGED_SESSION_ERROR_MESSAGE = "errorMessage";
161     private static final String ATTR_MODE = "mode";
162     private static final String ATTR_INSTALL_FLAGS = "installFlags";
163     private static final String ATTR_INSTALL_LOCATION = "installLocation";
164     private static final String ATTR_SIZE_BYTES = "sizeBytes";
165     private static final String ATTR_APP_PACKAGE_NAME = "appPackageName";
166     @Deprecated
167     private static final String ATTR_APP_ICON = "appIcon";
168     private static final String ATTR_APP_LABEL = "appLabel";
169     private static final String ATTR_ORIGINATING_URI = "originatingUri";
170     private static final String ATTR_ORIGINATING_UID = "originatingUid";
171     private static final String ATTR_REFERRER_URI = "referrerUri";
172     private static final String ATTR_ABI_OVERRIDE = "abiOverride";
173     private static final String ATTR_VOLUME_UUID = "volumeUuid";
174     private static final String ATTR_NAME = "name";
175     private static final String ATTR_INSTALL_REASON = "installRason";
176 
177     private static final String PROPERTY_NAME_INHERIT_NATIVE = "pi.inherit_native_on_dont_kill";
178     private static final int[] EMPTY_CHILD_SESSION_ARRAY = {};
179 
180     // TODO: enforce INSTALL_ALLOW_TEST
181     // TODO: enforce INSTALL_ALLOW_DOWNGRADE
182 
183     private final PackageInstallerService.InternalCallback mCallback;
184     private final Context mContext;
185     private final PackageManagerService mPm;
186     private final Handler mHandler;
187     private final PackageSessionProvider mSessionProvider;
188     private final StagingManager mStagingManager;
189 
190     final int sessionId;
191     final int userId;
192     final SessionParams params;
193     final long createdMillis;
194 
195     /** Staging location where client data is written. */
196     final File stageDir;
197     final String stageCid;
198 
199     private final AtomicInteger mActiveCount = new AtomicInteger();
200 
201     private final Object mLock = new Object();
202 
203     /** Timestamp of the last time this session changed state  */
204     @GuardedBy("mLock")
205     private long updatedMillis;
206 
207     /** Uid of the creator of this session. */
208     private final int mOriginalInstallerUid;
209 
210     /** Package of the owner of the installer session */
211     @GuardedBy("mLock")
212     private @Nullable String mInstallerPackageName;
213 
214     /** Uid of the owner of the installer session */
215     @GuardedBy("mLock")
216     private int mInstallerUid;
217 
218     @GuardedBy("mLock")
219     private float mClientProgress = 0;
220     @GuardedBy("mLock")
221     private float mInternalProgress = 0;
222 
223     @GuardedBy("mLock")
224     private float mProgress = 0;
225     @GuardedBy("mLock")
226     private float mReportedProgress = -1;
227 
228     /** State of the session. */
229     @GuardedBy("mLock")
230     private boolean mPrepared = false;
231     @GuardedBy("mLock")
232     private boolean mSealed = false;
233     @GuardedBy("mLock")
234     private boolean mShouldBeSealed = false;
235     @GuardedBy("mLock")
236     private boolean mCommitted = false;
237     @GuardedBy("mLock")
238     private boolean mRelinquished = false;
239     @GuardedBy("mLock")
240     private boolean mDestroyed = false;
241 
242     /** Permissions have been accepted by the user (see {@link #setPermissionsResult}) */
243     @GuardedBy("mLock")
244     private boolean mPermissionsManuallyAccepted = false;
245 
246     @GuardedBy("mLock")
247     private int mFinalStatus;
248     @GuardedBy("mLock")
249     private String mFinalMessage;
250 
251     @GuardedBy("mLock")
252     private final ArrayList<RevocableFileDescriptor> mFds = new ArrayList<>();
253     @GuardedBy("mLock")
254     private final ArrayList<FileBridge> mBridges = new ArrayList<>();
255 
256     @GuardedBy("mLock")
257     private IntentSender mRemoteStatusReceiver;
258 
259     /** Fields derived from commit parsing */
260     @GuardedBy("mLock")
261     private String mPackageName;
262     @GuardedBy("mLock")
263     private long mVersionCode;
264     @GuardedBy("mLock")
265     private PackageParser.SigningDetails mSigningDetails;
266     @GuardedBy("mLock")
267     private SparseIntArray mChildSessionIds = new SparseIntArray();
268     @GuardedBy("mLock")
269     private int mParentSessionId;
270 
271     @GuardedBy("mLock")
272     private boolean mStagedSessionApplied;
273     @GuardedBy("mLock")
274     private boolean mStagedSessionReady;
275     @GuardedBy("mLock")
276     private boolean mStagedSessionFailed;
277     @GuardedBy("mLock")
278     private int mStagedSessionErrorCode = SessionInfo.STAGED_SESSION_NO_ERROR;
279     @GuardedBy("mLock")
280     private String mStagedSessionErrorMessage;
281 
282     /**
283      * Path to the validated base APK for this session, which may point at an
284      * APK inside the session (when the session defines the base), or it may
285      * point at the existing base APK (when adding splits to an existing app).
286      * <p>
287      * This is used when confirming permissions, since we can't fully stage the
288      * session inside an ASEC before confirming with user.
289      */
290     @GuardedBy("mLock")
291     private File mResolvedBaseFile;
292 
293     @GuardedBy("mLock")
294     private final List<File> mResolvedStagedFiles = new ArrayList<>();
295     @GuardedBy("mLock")
296     private final List<File> mResolvedInheritedFiles = new ArrayList<>();
297     @GuardedBy("mLock")
298     private final List<String> mResolvedInstructionSets = new ArrayList<>();
299     @GuardedBy("mLock")
300     private final List<String> mResolvedNativeLibPaths = new ArrayList<>();
301     @GuardedBy("mLock")
302     private File mInheritedFilesBase;
303     @GuardedBy("mLock")
304     private boolean mVerityFound;
305 
306     private static final FileFilter sAddedFilter = new FileFilter() {
307         @Override
308         public boolean accept(File file) {
309             // Installers can't stage directories, so it's fine to ignore
310             // entries like "lost+found".
311             if (file.isDirectory()) return false;
312             if (file.getName().endsWith(REMOVE_MARKER_EXTENSION)) return false;
313             if (DexMetadataHelper.isDexMetadataFile(file)) return false;
314             if (VerityUtils.isFsveritySignatureFile(file)) return false;
315             return true;
316         }
317     };
318     private static final FileFilter sRemovedFilter = new FileFilter() {
319         @Override
320         public boolean accept(File file) {
321             if (file.isDirectory()) return false;
322             if (!file.getName().endsWith(REMOVE_MARKER_EXTENSION)) return false;
323             return true;
324         }
325     };
326 
327     private final Handler.Callback mHandlerCallback = new Handler.Callback() {
328         @Override
329         public boolean handleMessage(Message msg) {
330             switch (msg.what) {
331                 case MSG_COMMIT:
332                     handleCommit();
333                     break;
334                 case MSG_ON_PACKAGE_INSTALLED:
335                     final SomeArgs args = (SomeArgs) msg.obj;
336                     final String packageName = (String) args.arg1;
337                     final String message = (String) args.arg2;
338                     final Bundle extras = (Bundle) args.arg3;
339                     final IntentSender statusReceiver = (IntentSender) args.arg4;
340                     final int returnCode = args.argi1;
341                     args.recycle();
342 
343                     PackageInstallerService.sendOnPackageInstalled(mContext,
344                             statusReceiver, sessionId,
345                             isInstallerDeviceOwnerOrAffiliatedProfileOwnerLocked(), userId,
346                             packageName, returnCode, message, extras);
347 
348                     break;
349             }
350 
351             return true;
352         }
353     };
354 
355     /**
356      * @return {@code true} iff the installing is app an device owner or affiliated profile owner.
357      */
358     @GuardedBy("mLock")
isInstallerDeviceOwnerOrAffiliatedProfileOwnerLocked()359     private boolean isInstallerDeviceOwnerOrAffiliatedProfileOwnerLocked() {
360         if (userId != UserHandle.getUserId(mInstallerUid)) {
361             return false;
362         }
363         DevicePolicyManagerInternal dpmi =
364                 LocalServices.getService(DevicePolicyManagerInternal.class);
365         return dpmi != null && dpmi.canSilentlyInstallPackage(mInstallerPackageName, mInstallerUid);
366     }
367 
368     /**
369      * Checks if the permissions still need to be confirmed.
370      *
371      * <p>This is dependant on the identity of the installer, hence this cannot be cached if the
372      * installer might still {@link #transfer(String) change}.
373      *
374      * @return {@code true} iff we need to ask to confirm the permissions?
375      */
376     @GuardedBy("mLock")
needToAskForPermissionsLocked()377     private boolean needToAskForPermissionsLocked() {
378         if (mPermissionsManuallyAccepted) {
379             return false;
380         }
381 
382         final boolean isInstallPermissionGranted =
383                 (mPm.checkUidPermission(android.Manifest.permission.INSTALL_PACKAGES,
384                         mInstallerUid) == PackageManager.PERMISSION_GRANTED);
385         final boolean isSelfUpdatePermissionGranted =
386                 (mPm.checkUidPermission(android.Manifest.permission.INSTALL_SELF_UPDATES,
387                         mInstallerUid) == PackageManager.PERMISSION_GRANTED);
388         final boolean isUpdatePermissionGranted =
389                 (mPm.checkUidPermission(android.Manifest.permission.INSTALL_PACKAGE_UPDATES,
390                         mInstallerUid) == PackageManager.PERMISSION_GRANTED);
391         final int targetPackageUid = mPm.getPackageUid(mPackageName, 0, userId);
392         final boolean isPermissionGranted = isInstallPermissionGranted
393                 || (isUpdatePermissionGranted && targetPackageUid != -1)
394                 || (isSelfUpdatePermissionGranted && targetPackageUid == mInstallerUid);
395         final boolean isInstallerRoot = (mInstallerUid == Process.ROOT_UID);
396         final boolean isInstallerSystem = (mInstallerUid == Process.SYSTEM_UID);
397         final boolean forcePermissionPrompt =
398                 (params.installFlags & PackageManager.INSTALL_FORCE_PERMISSION_PROMPT) != 0;
399 
400         // Device owners and affiliated profile owners  are allowed to silently install packages, so
401         // the permission check is waived if the installer is the device owner.
402         return forcePermissionPrompt || !(isPermissionGranted || isInstallerRoot
403                 || isInstallerSystem || isInstallerDeviceOwnerOrAffiliatedProfileOwnerLocked());
404     }
405 
PackageInstallerSession(PackageInstallerService.InternalCallback callback, Context context, PackageManagerService pm, PackageSessionProvider sessionProvider, Looper looper, StagingManager stagingManager, int sessionId, int userId, String installerPackageName, int installerUid, SessionParams params, long createdMillis, File stageDir, String stageCid, boolean prepared, boolean committed, boolean sealed, @Nullable int[] childSessionIds, int parentSessionId, boolean isReady, boolean isFailed, boolean isApplied, int stagedSessionErrorCode, String stagedSessionErrorMessage)406     public PackageInstallerSession(PackageInstallerService.InternalCallback callback,
407             Context context, PackageManagerService pm,
408             PackageSessionProvider sessionProvider, Looper looper, StagingManager stagingManager,
409             int sessionId, int userId,
410             String installerPackageName, int installerUid, SessionParams params, long createdMillis,
411             File stageDir, String stageCid, boolean prepared, boolean committed, boolean sealed,
412             @Nullable int[] childSessionIds, int parentSessionId, boolean isReady,
413             boolean isFailed, boolean isApplied, int stagedSessionErrorCode,
414             String stagedSessionErrorMessage) {
415         mCallback = callback;
416         mContext = context;
417         mPm = pm;
418         mSessionProvider = sessionProvider;
419         mHandler = new Handler(looper, mHandlerCallback);
420         mStagingManager = stagingManager;
421 
422         this.sessionId = sessionId;
423         this.userId = userId;
424         mOriginalInstallerUid = installerUid;
425         mInstallerPackageName = installerPackageName;
426         mInstallerUid = installerUid;
427         this.params = params;
428         this.createdMillis = createdMillis;
429         this.updatedMillis = createdMillis;
430         this.stageDir = stageDir;
431         this.stageCid = stageCid;
432         this.mShouldBeSealed = sealed;
433         if (childSessionIds != null) {
434             for (int childSessionId : childSessionIds) {
435                 mChildSessionIds.put(childSessionId, 0);
436             }
437         }
438         this.mParentSessionId = parentSessionId;
439 
440         if (!params.isMultiPackage && (stageDir == null) == (stageCid == null)) {
441             throw new IllegalArgumentException(
442                     "Exactly one of stageDir or stageCid stage must be set");
443         }
444 
445         mPrepared = prepared;
446         mCommitted = committed;
447         mStagedSessionReady = isReady;
448         mStagedSessionFailed = isFailed;
449         mStagedSessionApplied = isApplied;
450         mStagedSessionErrorCode = stagedSessionErrorCode;
451         mStagedSessionErrorMessage =
452                 stagedSessionErrorMessage != null ? stagedSessionErrorMessage : "";
453     }
454 
generateInfo()455     public SessionInfo generateInfo() {
456         return generateInfo(true);
457     }
458 
generateInfo(boolean includeIcon)459     public SessionInfo generateInfo(boolean includeIcon) {
460         final SessionInfo info = new SessionInfo();
461         synchronized (mLock) {
462             info.sessionId = sessionId;
463             info.userId = userId;
464             info.installerPackageName = mInstallerPackageName;
465             info.resolvedBaseCodePath = (mResolvedBaseFile != null) ?
466                     mResolvedBaseFile.getAbsolutePath() : null;
467             info.progress = mProgress;
468             info.sealed = mSealed;
469             info.isCommitted = mCommitted;
470             info.active = mActiveCount.get() > 0;
471 
472             info.mode = params.mode;
473             info.installReason = params.installReason;
474             info.sizeBytes = params.sizeBytes;
475             info.appPackageName = params.appPackageName;
476             if (includeIcon) {
477                 info.appIcon = params.appIcon;
478             }
479             info.appLabel = params.appLabel;
480 
481             info.installLocation = params.installLocation;
482             info.originatingUri = params.originatingUri;
483             info.originatingUid = params.originatingUid;
484             info.referrerUri = params.referrerUri;
485             info.grantedRuntimePermissions = params.grantedRuntimePermissions;
486             info.whitelistedRestrictedPermissions = params.whitelistedRestrictedPermissions;
487             info.installFlags = params.installFlags;
488             info.isMultiPackage = params.isMultiPackage;
489             info.isStaged = params.isStaged;
490             info.parentSessionId = mParentSessionId;
491             info.childSessionIds = mChildSessionIds.copyKeys();
492             if (info.childSessionIds == null) {
493                 info.childSessionIds = EMPTY_CHILD_SESSION_ARRAY;
494             }
495             info.isStagedSessionApplied = mStagedSessionApplied;
496             info.isStagedSessionReady = mStagedSessionReady;
497             info.isStagedSessionFailed = mStagedSessionFailed;
498             info.setStagedSessionErrorCode(mStagedSessionErrorCode, mStagedSessionErrorMessage);
499             info.updatedMillis = updatedMillis;
500         }
501         return info;
502     }
503 
isPrepared()504     public boolean isPrepared() {
505         synchronized (mLock) {
506             return mPrepared;
507         }
508     }
509 
isSealed()510     public boolean isSealed() {
511         synchronized (mLock) {
512             return mSealed;
513         }
514     }
515 
516     /** {@hide} */
isCommitted()517     boolean isCommitted() {
518         synchronized (mLock) {
519             return mCommitted;
520         }
521     }
522 
523     /** Returns true if a staged session has reached a final state and can be forgotten about  */
isStagedAndInTerminalState()524     public boolean isStagedAndInTerminalState() {
525         synchronized (mLock) {
526             return params.isStaged && (mStagedSessionApplied || mStagedSessionFailed);
527         }
528     }
529 
530     @GuardedBy("mLock")
assertPreparedAndNotSealedLocked(String cookie)531     private void assertPreparedAndNotSealedLocked(String cookie) {
532         assertPreparedAndNotCommittedOrDestroyedLocked(cookie);
533         if (mSealed) {
534             throw new SecurityException(cookie + " not allowed after sealing");
535         }
536     }
537 
538     @GuardedBy("mLock")
assertPreparedAndNotCommittedOrDestroyedLocked(String cookie)539     private void assertPreparedAndNotCommittedOrDestroyedLocked(String cookie) {
540         assertPreparedAndNotDestroyedLocked(cookie);
541         if (mCommitted) {
542             throw new SecurityException(cookie + " not allowed after commit");
543         }
544     }
545 
546     @GuardedBy("mLock")
assertPreparedAndNotDestroyedLocked(String cookie)547     private void assertPreparedAndNotDestroyedLocked(String cookie) {
548         if (!mPrepared) {
549             throw new IllegalStateException(cookie + " before prepared");
550         }
551         if (mDestroyed) {
552             throw new SecurityException(cookie + " not allowed after destruction");
553         }
554     }
555 
556     @Override
setClientProgress(float progress)557     public void setClientProgress(float progress) {
558         synchronized (mLock) {
559             assertCallerIsOwnerOrRootLocked();
560 
561             // Always publish first staging movement
562             final boolean forcePublish = (mClientProgress == 0);
563             mClientProgress = progress;
564             computeProgressLocked(forcePublish);
565         }
566     }
567 
568     @Override
addClientProgress(float progress)569     public void addClientProgress(float progress) {
570         synchronized (mLock) {
571             assertCallerIsOwnerOrRootLocked();
572 
573             setClientProgress(mClientProgress + progress);
574         }
575     }
576 
577     @GuardedBy("mLock")
computeProgressLocked(boolean forcePublish)578     private void computeProgressLocked(boolean forcePublish) {
579         mProgress = MathUtils.constrain(mClientProgress * 0.8f, 0f, 0.8f)
580                 + MathUtils.constrain(mInternalProgress * 0.2f, 0f, 0.2f);
581 
582         // Only publish when meaningful change
583         if (forcePublish || Math.abs(mProgress - mReportedProgress) >= 0.01) {
584             mReportedProgress = mProgress;
585             mCallback.onSessionProgressChanged(this, mProgress);
586         }
587     }
588 
589     @Override
getNames()590     public String[] getNames() {
591         synchronized (mLock) {
592             assertCallerIsOwnerOrRootLocked();
593             assertPreparedAndNotCommittedOrDestroyedLocked("getNames");
594 
595             return getNamesLocked();
596         }
597     }
598 
599     @GuardedBy("mLock")
getNamesLocked()600     private String[] getNamesLocked() {
601         return stageDir.list();
602     }
603 
filterFiles(File parent, String[] names, FileFilter filter)604     private static File[] filterFiles(File parent, String[] names, FileFilter filter) {
605         return Arrays.stream(names).map(name -> new File(parent, name)).filter(
606                 file -> filter.accept(file)).toArray(File[]::new);
607     }
608 
609     @GuardedBy("mLock")
getAddedFilesLocked()610     private File[] getAddedFilesLocked() {
611         String[] names = getNamesLocked();
612         return filterFiles(stageDir, names, sAddedFilter);
613     }
614 
615     @GuardedBy("mLock")
getRemovedFilesLocked()616     private File[] getRemovedFilesLocked() {
617         String[] names = getNamesLocked();
618         return filterFiles(stageDir, names, sRemovedFilter);
619     }
620 
621     @Override
removeSplit(String splitName)622     public void removeSplit(String splitName) {
623         if (TextUtils.isEmpty(params.appPackageName)) {
624             throw new IllegalStateException("Must specify package name to remove a split");
625         }
626 
627         synchronized (mLock) {
628             assertCallerIsOwnerOrRootLocked();
629             assertPreparedAndNotCommittedOrDestroyedLocked("removeSplit");
630 
631             try {
632                 createRemoveSplitMarkerLocked(splitName);
633             } catch (IOException e) {
634                 throw ExceptionUtils.wrap(e);
635             }
636         }
637     }
638 
getRemoveMarkerName(String name)639     private static String getRemoveMarkerName(String name) {
640         final String markerName = name + REMOVE_MARKER_EXTENSION;
641         if (!FileUtils.isValidExtFilename(markerName)) {
642             throw new IllegalArgumentException("Invalid marker: " + markerName);
643         }
644         return markerName;
645     }
646 
createRemoveSplitMarkerLocked(String splitName)647     private void createRemoveSplitMarkerLocked(String splitName) throws IOException {
648         try {
649             final File target = new File(stageDir, getRemoveMarkerName(splitName));
650             target.createNewFile();
651             Os.chmod(target.getAbsolutePath(), 0 /*mode*/);
652         } catch (ErrnoException e) {
653             throw e.rethrowAsIOException();
654         }
655     }
656 
657     @Override
openWrite(String name, long offsetBytes, long lengthBytes)658     public ParcelFileDescriptor openWrite(String name, long offsetBytes, long lengthBytes) {
659         try {
660             return doWriteInternal(name, offsetBytes, lengthBytes, null);
661         } catch (IOException e) {
662             throw ExceptionUtils.wrap(e);
663         }
664     }
665 
666     @Override
write(String name, long offsetBytes, long lengthBytes, ParcelFileDescriptor fd)667     public void write(String name, long offsetBytes, long lengthBytes,
668             ParcelFileDescriptor fd) {
669         try {
670             doWriteInternal(name, offsetBytes, lengthBytes, fd);
671         } catch (IOException e) {
672             throw ExceptionUtils.wrap(e);
673         }
674     }
675 
doWriteInternal(String name, long offsetBytes, long lengthBytes, ParcelFileDescriptor incomingFd)676     private ParcelFileDescriptor doWriteInternal(String name, long offsetBytes, long lengthBytes,
677             ParcelFileDescriptor incomingFd) throws IOException {
678         // Quick sanity check of state, and allocate a pipe for ourselves. We
679         // then do heavy disk allocation outside the lock, but this open pipe
680         // will block any attempted install transitions.
681         final RevocableFileDescriptor fd;
682         final FileBridge bridge;
683         synchronized (mLock) {
684             assertCallerIsOwnerOrRootLocked();
685             assertPreparedAndNotSealedLocked("openWrite");
686 
687             if (PackageInstaller.ENABLE_REVOCABLE_FD) {
688                 fd = new RevocableFileDescriptor();
689                 bridge = null;
690                 mFds.add(fd);
691             } else {
692                 fd = null;
693                 bridge = new FileBridge();
694                 mBridges.add(bridge);
695             }
696         }
697 
698         try {
699             // Use installer provided name for now; we always rename later
700             if (!FileUtils.isValidExtFilename(name)) {
701                 throw new IllegalArgumentException("Invalid name: " + name);
702             }
703             final File target;
704             final long identity = Binder.clearCallingIdentity();
705             try {
706                 target = new File(stageDir, name);
707             } finally {
708                 Binder.restoreCallingIdentity(identity);
709             }
710 
711             // TODO: this should delegate to DCS so the system process avoids
712             // holding open FDs into containers.
713             final FileDescriptor targetFd = Os.open(target.getAbsolutePath(),
714                     O_CREAT | O_WRONLY, 0644);
715             Os.chmod(target.getAbsolutePath(), 0644);
716 
717             // If caller specified a total length, allocate it for them. Free up
718             // cache space to grow, if needed.
719             if (stageDir != null && lengthBytes > 0) {
720                 mContext.getSystemService(StorageManager.class).allocateBytes(targetFd, lengthBytes,
721                         PackageHelper.translateAllocateFlags(params.installFlags));
722             }
723 
724             if (offsetBytes > 0) {
725                 Os.lseek(targetFd, offsetBytes, OsConstants.SEEK_SET);
726             }
727 
728             if (incomingFd != null) {
729                 switch (Binder.getCallingUid()) {
730                     case android.os.Process.SHELL_UID:
731                     case android.os.Process.ROOT_UID:
732                     case android.os.Process.SYSTEM_UID:
733                         break;
734                     default:
735                         throw new SecurityException(
736                                 "Reverse mode only supported from shell or system");
737                 }
738 
739                 // In "reverse" mode, we're streaming data ourselves from the
740                 // incoming FD, which means we never have to hand out our
741                 // sensitive internal FD. We still rely on a "bridge" being
742                 // inserted above to hold the session active.
743                 try {
744                     final Int64Ref last = new Int64Ref(0);
745                     FileUtils.copy(incomingFd.getFileDescriptor(), targetFd, lengthBytes, null,
746                             Runnable::run, (long progress) -> {
747                                 if (params.sizeBytes > 0) {
748                                     final long delta = progress - last.value;
749                                     last.value = progress;
750                                     addClientProgress((float) delta / (float) params.sizeBytes);
751                                 }
752                             });
753                 } finally {
754                     IoUtils.closeQuietly(targetFd);
755                     IoUtils.closeQuietly(incomingFd);
756 
757                     // We're done here, so remove the "bridge" that was holding
758                     // the session active.
759                     synchronized (mLock) {
760                         if (PackageInstaller.ENABLE_REVOCABLE_FD) {
761                             mFds.remove(fd);
762                         } else {
763                             bridge.forceClose();
764                             mBridges.remove(bridge);
765                         }
766                     }
767                 }
768                 return null;
769             } else if (PackageInstaller.ENABLE_REVOCABLE_FD) {
770                 fd.init(mContext, targetFd);
771                 return fd.getRevocableFileDescriptor();
772             } else {
773                 bridge.setTargetFile(targetFd);
774                 bridge.start();
775                 return new ParcelFileDescriptor(bridge.getClientSocket());
776             }
777 
778         } catch (ErrnoException e) {
779             throw e.rethrowAsIOException();
780         }
781     }
782 
783     @Override
openRead(String name)784     public ParcelFileDescriptor openRead(String name) {
785         synchronized (mLock) {
786             assertCallerIsOwnerOrRootLocked();
787             assertPreparedAndNotCommittedOrDestroyedLocked("openRead");
788             try {
789                 return openReadInternalLocked(name);
790             } catch (IOException e) {
791                 throw ExceptionUtils.wrap(e);
792             }
793         }
794     }
795 
openReadInternalLocked(String name)796     private ParcelFileDescriptor openReadInternalLocked(String name) throws IOException {
797         try {
798             if (!FileUtils.isValidExtFilename(name)) {
799                 throw new IllegalArgumentException("Invalid name: " + name);
800             }
801             final File target = new File(stageDir, name);
802             final FileDescriptor targetFd = Os.open(target.getAbsolutePath(), O_RDONLY, 0);
803             return new ParcelFileDescriptor(targetFd);
804         } catch (ErrnoException e) {
805             throw e.rethrowAsIOException();
806         }
807     }
808 
809     /**
810      * Check if the caller is the owner of this session. Otherwise throw a
811      * {@link SecurityException}.
812      */
813     @GuardedBy("mLock")
assertCallerIsOwnerOrRootLocked()814     private void assertCallerIsOwnerOrRootLocked() {
815         final int callingUid = Binder.getCallingUid();
816         if (callingUid != Process.ROOT_UID && callingUid != mInstallerUid) {
817             throw new SecurityException("Session does not belong to uid " + callingUid);
818         }
819     }
820 
821     /**
822      * If anybody is reading or writing data of the session, throw an {@link SecurityException}.
823      */
824     @GuardedBy("mLock")
assertNoWriteFileTransfersOpenLocked()825     private void assertNoWriteFileTransfersOpenLocked() {
826         // Verify that all writers are hands-off
827         for (RevocableFileDescriptor fd : mFds) {
828             if (!fd.isRevoked()) {
829                 throw new SecurityException("Files still open");
830             }
831         }
832         for (FileBridge bridge : mBridges) {
833             if (!bridge.isClosed()) {
834                 throw new SecurityException("Files still open");
835             }
836         }
837     }
838 
839     @Override
commit(@onNull IntentSender statusReceiver, boolean forTransfer)840     public void commit(@NonNull IntentSender statusReceiver, boolean forTransfer) {
841         if (hasParentSessionId()) {
842             throw new IllegalStateException(
843                     "Session " + sessionId + " is a child of multi-package session "
844                             + mParentSessionId +  " and may not be committed directly.");
845         }
846         if (!markAsCommitted(statusReceiver, forTransfer)) {
847             return;
848         }
849         if (isMultiPackage()) {
850             final SparseIntArray remainingSessions = mChildSessionIds.clone();
851             final IntentSender childIntentSender =
852                     new ChildStatusIntentReceiver(remainingSessions, statusReceiver)
853                             .getIntentSender();
854             RuntimeException commitException = null;
855             boolean commitFailed = false;
856             for (int i = mChildSessionIds.size() - 1; i >= 0; --i) {
857                 final int childSessionId = mChildSessionIds.keyAt(i);
858                 try {
859                     // commit all children, regardless if any of them fail; we'll throw/return
860                     // as appropriate once all children have been processed
861                     if (!mSessionProvider.getSession(childSessionId)
862                             .markAsCommitted(childIntentSender, forTransfer)) {
863                         commitFailed = true;
864                     }
865                 } catch (RuntimeException e) {
866                     commitException = e;
867                 }
868             }
869             if (commitException != null) {
870                 throw commitException;
871             }
872             if (commitFailed) {
873                 return;
874             }
875         }
876         mHandler.obtainMessage(MSG_COMMIT).sendToTarget();
877     }
878 
879     private class ChildStatusIntentReceiver {
880         private final SparseIntArray mChildSessionsRemaining;
881         private final IntentSender mStatusReceiver;
882         private final IIntentSender.Stub mLocalSender = new IIntentSender.Stub() {
883             @Override
884             public void send(int code, Intent intent, String resolvedType, IBinder whitelistToken,
885                     IIntentReceiver finishedReceiver, String requiredPermission, Bundle options) {
886                 statusUpdate(intent);
887             }
888         };
889 
ChildStatusIntentReceiver(SparseIntArray remainingSessions, IntentSender statusReceiver)890         private ChildStatusIntentReceiver(SparseIntArray remainingSessions,
891                 IntentSender statusReceiver) {
892             this.mChildSessionsRemaining = remainingSessions;
893             this.mStatusReceiver = statusReceiver;
894         }
895 
getIntentSender()896         public IntentSender getIntentSender() {
897             return new IntentSender((IIntentSender) mLocalSender);
898         }
899 
statusUpdate(Intent intent)900         public void statusUpdate(Intent intent) {
901             mHandler.post(() -> {
902                 if (mChildSessionsRemaining.size() == 0) {
903                     return;
904                 }
905                 final int sessionId = intent.getIntExtra(
906                         PackageInstaller.EXTRA_SESSION_ID, 0);
907                 final int status = intent.getIntExtra(PackageInstaller.EXTRA_STATUS,
908                         PackageInstaller.STATUS_FAILURE);
909                 final int sessionIndex = mChildSessionsRemaining.indexOfKey(sessionId);
910                 if (PackageInstaller.STATUS_SUCCESS == status) {
911                     mChildSessionsRemaining.removeAt(sessionIndex);
912                     if (mChildSessionsRemaining.size() == 0) {
913                         try {
914                             intent.putExtra(PackageInstaller.EXTRA_SESSION_ID,
915                                     PackageInstallerSession.this.sessionId);
916                             mStatusReceiver.sendIntent(mContext, 0, intent, null, null);
917                         } catch (IntentSender.SendIntentException ignore) {
918                         }
919                     }
920                 } else if (PackageInstaller.STATUS_PENDING_USER_ACTION == status) {
921                     try {
922                         mStatusReceiver.sendIntent(mContext, 0, intent, null, null);
923                     } catch (IntentSender.SendIntentException ignore) {
924                     }
925                 } else {
926                     intent.putExtra(PackageInstaller.EXTRA_SESSION_ID,
927                             PackageInstallerSession.this.sessionId);
928                     mChildSessionsRemaining.clear(); // we're done. Don't send any more.
929                     try {
930                         mStatusReceiver.sendIntent(mContext, 0, intent, null, null);
931                     } catch (IntentSender.SendIntentException ignore) {
932                     }
933                 }
934             });
935         }
936     }
937 
938 
939     /**
940      * Do everything but actually commit the session. If this was not already called, the session
941      * will be sealed and marked as committed. The caller of this method is responsible for
942      * subsequently submitting this session for processing.
943      *
944      * This method may be called multiple times to update the status receiver validate caller
945      * permissions.
946      */
markAsCommitted( @onNull IntentSender statusReceiver, boolean forTransfer)947     private boolean markAsCommitted(
948             @NonNull IntentSender statusReceiver, boolean forTransfer) {
949         Preconditions.checkNotNull(statusReceiver);
950 
951         List<PackageInstallerSession> childSessions = getChildSessions();
952 
953         final boolean wasSealed;
954         synchronized (mLock) {
955             assertCallerIsOwnerOrRootLocked();
956             assertPreparedAndNotDestroyedLocked("commit");
957 
958             mRemoteStatusReceiver = statusReceiver;
959 
960             if (forTransfer) {
961                 mContext.enforceCallingOrSelfPermission(Manifest.permission.INSTALL_PACKAGES, null);
962 
963                 if (mInstallerUid == mOriginalInstallerUid) {
964                     throw new IllegalArgumentException("Session has not been transferred");
965                 }
966             } else {
967                 if (mInstallerUid != mOriginalInstallerUid) {
968                     throw new IllegalArgumentException("Session has been transferred");
969                 }
970             }
971 
972             // After validations and updating the observer, we can skip re-sealing, etc. because we
973             // have already marked ourselves as committed.
974             if (mCommitted) {
975                 return true;
976             }
977 
978             wasSealed = mSealed;
979             if (!mSealed) {
980                 try {
981                     sealAndValidateLocked(childSessions);
982                 } catch (PackageManagerException e) {
983                     return false;
984                 }
985             }
986 
987             // Client staging is fully done at this point
988             mClientProgress = 1f;
989             computeProgressLocked(true);
990 
991             // This ongoing commit should keep session active, even though client
992             // will probably close their end.
993             mActiveCount.incrementAndGet();
994 
995             mCommitted = true;
996         }
997 
998         if (!wasSealed) {
999             // Persist the fact that we've sealed ourselves to prevent
1000             // mutations of any hard links we create. We do this without holding
1001             // the session lock, since otherwise it's a lock inversion.
1002             mCallback.onSessionSealedBlocking(this);
1003         }
1004         return true;
1005     }
1006 
1007     /** Return a list of child sessions or null if the session is not multipackage
1008      *
1009      * <p> This method is handy to prevent potential deadlocks (b/123391593)
1010      */
getChildSessions()1011     private @Nullable List<PackageInstallerSession> getChildSessions() {
1012         List<PackageInstallerSession> childSessions = null;
1013         if (isMultiPackage()) {
1014             final int[] childSessionIds = getChildSessionIds();
1015             childSessions = new ArrayList<>(childSessionIds.length);
1016             for (int childSessionId : childSessionIds) {
1017                 childSessions.add(mSessionProvider.getSession(childSessionId));
1018             }
1019         }
1020         return childSessions;
1021     }
1022 
1023     /**
1024      * Assert multipackage install has consistent sessions.
1025      *
1026      * @throws PackageManagerException if child sessions don't match parent session
1027      *                                  in respect to staged and enable rollback parameters.
1028      */
1029     @GuardedBy("mLock")
assertMultiPackageConsistencyLocked( @onNull List<PackageInstallerSession> childSessions)1030     private void assertMultiPackageConsistencyLocked(
1031             @NonNull List<PackageInstallerSession> childSessions) throws PackageManagerException {
1032         for (PackageInstallerSession childSession : childSessions) {
1033             // It might be that the parent session is loaded before all of it's child sessions are,
1034             // e.g. when reading sessions from XML. Those sessions will be null here, and their
1035             // conformance with the multipackage params will be checked when they're loaded.
1036             if (childSession == null) {
1037                 continue;
1038             }
1039             assertConsistencyWithLocked(childSession);
1040         }
1041     }
1042 
1043     /**
1044      * Assert consistency with the given session.
1045      *
1046      * @throws PackageManagerException if other sessions doesn't match this session
1047      *                                  in respect to staged and enable rollback parameters.
1048      */
1049     @GuardedBy("mLock")
assertConsistencyWithLocked(PackageInstallerSession other)1050     private void assertConsistencyWithLocked(PackageInstallerSession other)
1051             throws PackageManagerException {
1052         // Session groups must be consistent wrt to isStaged parameter. Non-staging session
1053         // cannot be grouped with staging sessions.
1054         if (this.params.isStaged != other.params.isStaged) {
1055             throw new PackageManagerException(
1056                 PackageManager.INSTALL_FAILED_MULTIPACKAGE_INCONSISTENCY,
1057                 "Multipackage Inconsistency: session " + other.sessionId
1058                     + " and session " + sessionId
1059                     + " have inconsistent staged settings");
1060         }
1061         if (this.params.getEnableRollback() != other.params.getEnableRollback()) {
1062             throw new PackageManagerException(
1063                 PackageManager.INSTALL_FAILED_MULTIPACKAGE_INCONSISTENCY,
1064                 "Multipackage Inconsistency: session " + other.sessionId
1065                     + " and session " + sessionId
1066                     + " have inconsistent rollback settings");
1067         }
1068     }
1069 
1070     /**
1071      * Seal the session to prevent further modification and validate the contents of it.
1072      *
1073      * <p>The session will be sealed after calling this method even if it failed.
1074      *
1075      * @param childSessions the child sessions of a multipackage that will be checked for
1076      *                      consistency. Can be null if session is not multipackage.
1077      * @throws PackageManagerException if the session was sealed but something went wrong. If the
1078      *                                 session was sealed this is the only possible exception.
1079      */
1080     @GuardedBy("mLock")
sealAndValidateLocked(List<PackageInstallerSession> childSessions)1081     private void sealAndValidateLocked(List<PackageInstallerSession> childSessions)
1082             throws PackageManagerException {
1083         try {
1084             assertNoWriteFileTransfersOpenLocked();
1085             assertPreparedAndNotDestroyedLocked("sealing of session");
1086 
1087             mSealed = true;
1088 
1089             if (childSessions != null) {
1090                 assertMultiPackageConsistencyLocked(childSessions);
1091             }
1092 
1093             // Read transfers from the original owner stay open, but as the session's data
1094             // cannot be modified anymore, there is no leak of information. For staged sessions,
1095             // further validation is performed by the staging manager.
1096             if (!params.isMultiPackage) {
1097                 final PackageInfo pkgInfo = mPm.getPackageInfo(
1098                         params.appPackageName, PackageManager.GET_SIGNATURES
1099                                 | PackageManager.MATCH_STATIC_SHARED_LIBRARIES /*flags*/, userId);
1100 
1101                 try {
1102                     if ((params.installFlags & PackageManager.INSTALL_APEX) != 0) {
1103                         validateApexInstallLocked();
1104                     } else {
1105                         validateApkInstallLocked(pkgInfo);
1106                     }
1107                 } catch (PackageManagerException e) {
1108                     throw e;
1109                 } catch (Throwable e) {
1110                     // Convert all exceptions into package manager exceptions as only those are
1111                     // handled in the code above.
1112                     throw new PackageManagerException(e);
1113                 }
1114             }
1115 
1116             if (params.isStaged) {
1117                 mStagingManager.checkNonOverlappingWithStagedSessions(this);
1118             }
1119         } catch (PackageManagerException e) {
1120             // Session is sealed but could not be verified, we need to destroy it.
1121             destroyInternal();
1122             // Dispatch message to remove session from PackageInstallerService
1123             dispatchSessionFinished(
1124                     e.error, ExceptionUtils.getCompleteMessage(e), null);
1125             throw e;
1126         }
1127     }
1128 
1129     /**
1130      * If session should be sealed, then it's sealed to prevent further modification
1131      * and then it's validated.
1132      *
1133      * If the session was sealed but something went wrong then it's destroyed.
1134      *
1135      * <p> This is meant to be called after all of the sessions are loaded and added to
1136      * PackageInstallerService
1137      */
sealAndValidateIfNecessary()1138     void sealAndValidateIfNecessary() {
1139         synchronized (mLock) {
1140             if (!mShouldBeSealed || isStagedAndInTerminalState()) {
1141                 return;
1142             }
1143         }
1144         List<PackageInstallerSession> childSessions = getChildSessions();
1145         synchronized (mLock) {
1146             try {
1147                 sealAndValidateLocked(childSessions);
1148             } catch (PackageManagerException e) {
1149                 Slog.e(TAG, "Package not valid", e);
1150             }
1151         }
1152     }
1153 
1154     /** Update the timestamp of when the staged session last changed state */
markUpdated()1155     public void markUpdated() {
1156         synchronized (mLock) {
1157             this.updatedMillis = System.currentTimeMillis();
1158         }
1159     }
1160 
1161     @Override
transfer(String packageName)1162     public void transfer(String packageName) {
1163         Preconditions.checkNotNull(packageName);
1164 
1165         ApplicationInfo newOwnerAppInfo = mPm.getApplicationInfo(packageName, 0, userId);
1166         if (newOwnerAppInfo == null) {
1167             throw new ParcelableException(new PackageManager.NameNotFoundException(packageName));
1168         }
1169 
1170         if (PackageManager.PERMISSION_GRANTED != mPm.checkUidPermission(
1171                 Manifest.permission.INSTALL_PACKAGES, newOwnerAppInfo.uid)) {
1172             throw new SecurityException("Destination package " + packageName + " does not have "
1173                     + "the " + Manifest.permission.INSTALL_PACKAGES + " permission");
1174         }
1175 
1176         // Only install flags that can be verified by the app the session is transferred to are
1177         // allowed. The parameters can be read via PackageInstaller.SessionInfo.
1178         if (!params.areHiddenOptionsSet()) {
1179             throw new SecurityException("Can only transfer sessions that use public options");
1180         }
1181 
1182         List<PackageInstallerSession> childSessions = getChildSessions();
1183 
1184         synchronized (mLock) {
1185             assertCallerIsOwnerOrRootLocked();
1186             assertPreparedAndNotSealedLocked("transfer");
1187 
1188             try {
1189                 sealAndValidateLocked(childSessions);
1190             } catch (PackageManagerException e) {
1191                 throw new IllegalArgumentException("Package is not valid", e);
1192             }
1193 
1194             if (!mPackageName.equals(mInstallerPackageName)) {
1195                 throw new SecurityException("Can only transfer sessions that update the original "
1196                         + "installer");
1197             }
1198 
1199             mInstallerPackageName = packageName;
1200             mInstallerUid = newOwnerAppInfo.uid;
1201         }
1202 
1203         // Persist the fact that we've sealed ourselves to prevent
1204         // mutations of any hard links we create. We do this without holding
1205         // the session lock, since otherwise it's a lock inversion.
1206         mCallback.onSessionSealedBlocking(this);
1207     }
1208 
handleCommit()1209     private void handleCommit() {
1210         if (isInstallerDeviceOwnerOrAffiliatedProfileOwnerLocked()) {
1211             DevicePolicyEventLogger
1212                     .createEvent(DevicePolicyEnums.INSTALL_PACKAGE)
1213                     .setAdmin(mInstallerPackageName)
1214                     .write();
1215         }
1216         if (params.isStaged) {
1217             mStagingManager.commitSession(this);
1218             destroyInternal();
1219             dispatchSessionFinished(PackageManager.INSTALL_SUCCEEDED, "Session staged", null);
1220             return;
1221         }
1222 
1223         if ((params.installFlags & PackageManager.INSTALL_APEX) != 0) {
1224             destroyInternal();
1225             dispatchSessionFinished(PackageManager.INSTALL_FAILED_INTERNAL_ERROR,
1226                     "APEX packages can only be installed using staged sessions.", null);
1227             return;
1228         }
1229 
1230         // For a multiPackage session, read the child sessions
1231         // outside of the lock, because reading the child
1232         // sessions with the lock held could lead to deadlock
1233         // (b/123391593).
1234         List<PackageInstallerSession> childSessions = getChildSessions();
1235 
1236         try {
1237             synchronized (mLock) {
1238                 commitNonStagedLocked(childSessions);
1239             }
1240         } catch (PackageManagerException e) {
1241             final String completeMsg = ExceptionUtils.getCompleteMessage(e);
1242             Slog.e(TAG, "Commit of session " + sessionId + " failed: " + completeMsg);
1243             destroyInternal();
1244             dispatchSessionFinished(e.error, completeMsg, null);
1245         }
1246     }
1247 
1248     @GuardedBy("mLock")
commitNonStagedLocked(List<PackageInstallerSession> childSessions)1249     private void commitNonStagedLocked(List<PackageInstallerSession> childSessions)
1250             throws PackageManagerException {
1251         final PackageManagerService.ActiveInstallSession committingSession =
1252                 makeSessionActiveLocked();
1253         if (committingSession == null) {
1254             return;
1255         }
1256         if (isMultiPackage()) {
1257             List<PackageManagerService.ActiveInstallSession> activeChildSessions =
1258                     new ArrayList<>(childSessions.size());
1259             boolean success = true;
1260             PackageManagerException failure = null;
1261             for (int i = 0; i < childSessions.size(); ++i) {
1262                 final PackageInstallerSession session = childSessions.get(i);
1263                 try {
1264                     final PackageManagerService.ActiveInstallSession activeSession =
1265                             session.makeSessionActiveLocked();
1266                     if (activeSession != null) {
1267                         activeChildSessions.add(activeSession);
1268                     }
1269                 } catch (PackageManagerException e) {
1270                     failure = e;
1271                     success = false;
1272                 }
1273             }
1274             if (!success) {
1275                 PackageInstallerService.sendOnPackageInstalled(mContext,
1276                         mRemoteStatusReceiver, sessionId,
1277                         isInstallerDeviceOwnerOrAffiliatedProfileOwnerLocked(), userId, null,
1278                         failure.error, failure.getLocalizedMessage(), null);
1279                 return;
1280             }
1281             mPm.installStage(activeChildSessions);
1282         } else {
1283             mPm.installStage(committingSession);
1284         }
1285     }
1286 
1287     /**
1288      * Stages this session for install and returns a
1289      * {@link PackageManagerService.ActiveInstallSession} representing this new staged state or null
1290      * in case permissions need to be requested before install can proceed.
1291      */
1292     @GuardedBy("mLock")
makeSessionActiveLocked()1293     private PackageManagerService.ActiveInstallSession makeSessionActiveLocked()
1294             throws PackageManagerException {
1295         if (mRelinquished) {
1296             throw new PackageManagerException(INSTALL_FAILED_INTERNAL_ERROR,
1297                     "Session relinquished");
1298         }
1299         if (mDestroyed) {
1300             throw new PackageManagerException(INSTALL_FAILED_INTERNAL_ERROR, "Session destroyed");
1301         }
1302         if (!mSealed) {
1303             throw new PackageManagerException(INSTALL_FAILED_INTERNAL_ERROR, "Session not sealed");
1304         }
1305 
1306         final IPackageInstallObserver2 localObserver;
1307         if ((params.installFlags & PackageManager.INSTALL_APEX) != 0) {
1308             localObserver = null;
1309         } else {
1310             if (!params.isMultiPackage) {
1311                 Preconditions.checkNotNull(mPackageName);
1312                 Preconditions.checkNotNull(mSigningDetails);
1313                 Preconditions.checkNotNull(mResolvedBaseFile);
1314 
1315                 if (needToAskForPermissionsLocked()) {
1316                     // User needs to confirm installation;
1317                     // give installer an intent they can use to involve
1318                     // user.
1319                     final Intent intent = new Intent(PackageInstaller.ACTION_CONFIRM_INSTALL);
1320                     intent.setPackage(mPm.getPackageInstallerPackageName());
1321                     intent.putExtra(PackageInstaller.EXTRA_SESSION_ID, sessionId);
1322 
1323                     PackageInstallerService.sendOnUserActionRequired(mContext,
1324                             mRemoteStatusReceiver, sessionId, intent);
1325 
1326                     // Commit was keeping session marked as active until now; release
1327                     // that extra refcount so session appears idle.
1328                     closeInternal(false);
1329                     return null;
1330                 }
1331 
1332                 // Inherit any packages and native libraries from existing install that
1333                 // haven't been overridden.
1334                 if (params.mode == SessionParams.MODE_INHERIT_EXISTING) {
1335                     try {
1336                         final List<File> fromFiles = mResolvedInheritedFiles;
1337                         final File toDir = stageDir;
1338 
1339                         if (LOGD) Slog.d(TAG, "Inherited files: " + mResolvedInheritedFiles);
1340                         if (!mResolvedInheritedFiles.isEmpty() && mInheritedFilesBase == null) {
1341                             throw new IllegalStateException("mInheritedFilesBase == null");
1342                         }
1343 
1344                         if (isLinkPossible(fromFiles, toDir)) {
1345                             if (!mResolvedInstructionSets.isEmpty()) {
1346                                 final File oatDir = new File(toDir, "oat");
1347                                 createOatDirs(mResolvedInstructionSets, oatDir);
1348                             }
1349                             // pre-create lib dirs for linking if necessary
1350                             if (!mResolvedNativeLibPaths.isEmpty()) {
1351                                 for (String libPath : mResolvedNativeLibPaths) {
1352                                     // "/lib/arm64" -> ["lib", "arm64"]
1353                                     final int splitIndex = libPath.lastIndexOf('/');
1354                                     if (splitIndex < 0 || splitIndex >= libPath.length() - 1) {
1355                                         Slog.e(TAG,
1356                                                 "Skipping native library creation for linking due"
1357                                                         + " to invalid path: " + libPath);
1358                                         continue;
1359                                     }
1360                                     final String libDirPath = libPath.substring(1, splitIndex);
1361                                     final File libDir = new File(toDir, libDirPath);
1362                                     if (!libDir.exists()) {
1363                                         NativeLibraryHelper.createNativeLibrarySubdir(libDir);
1364                                     }
1365                                     final String archDirPath = libPath.substring(splitIndex + 1);
1366                                     NativeLibraryHelper.createNativeLibrarySubdir(
1367                                             new File(libDir, archDirPath));
1368                                 }
1369                             }
1370                             linkFiles(fromFiles, toDir, mInheritedFilesBase);
1371                         } else {
1372                             // TODO: this should delegate to DCS so the system process
1373                             // avoids holding open FDs into containers.
1374                             copyFiles(fromFiles, toDir);
1375                         }
1376                     } catch (IOException e) {
1377                         throw new PackageManagerException(INSTALL_FAILED_INSUFFICIENT_STORAGE,
1378                                 "Failed to inherit existing install", e);
1379                     }
1380                 }
1381 
1382                 // TODO: surface more granular state from dexopt
1383                 mInternalProgress = 0.5f;
1384                 computeProgressLocked(true);
1385 
1386                 // Unpack native libraries
1387                 extractNativeLibraries(stageDir, params.abiOverride, mayInheritNativeLibs());
1388             }
1389 
1390             // We've reached point of no return; call into PMS to install the stage.
1391             // Regardless of success or failure we always destroy session.
1392             localObserver = new IPackageInstallObserver2.Stub() {
1393                 @Override
1394                 public void onUserActionRequired(Intent intent) {
1395                     throw new IllegalStateException();
1396                 }
1397 
1398                 @Override
1399                 public void onPackageInstalled(String basePackageName, int returnCode, String msg,
1400                         Bundle extras) {
1401                     destroyInternal();
1402                     dispatchSessionFinished(returnCode, msg, extras);
1403                 }
1404             };
1405         }
1406 
1407         final UserHandle user;
1408         if ((params.installFlags & PackageManager.INSTALL_ALL_USERS) != 0) {
1409             user = UserHandle.ALL;
1410         } else {
1411             user = new UserHandle(userId);
1412         }
1413 
1414         mRelinquished = true;
1415         return new PackageManagerService.ActiveInstallSession(mPackageName, stageDir,
1416                 localObserver, params, mInstallerPackageName, mInstallerUid, user,
1417                 mSigningDetails);
1418     }
1419 
maybeRenameFile(File from, File to)1420     private static void maybeRenameFile(File from, File to) throws PackageManagerException {
1421         if (!from.equals(to)) {
1422             if (!from.renameTo(to)) {
1423                 throw new PackageManagerException(INSTALL_FAILED_INTERNAL_ERROR,
1424                         "Could not rename file " + from + " to " + to);
1425             }
1426         }
1427     }
1428 
1429     /**
1430      * Returns true if the session should attempt to inherit any existing native libraries already
1431      * extracted at the current install location. This is necessary to prevent double loading of
1432      * native libraries already loaded by the running app.
1433      */
mayInheritNativeLibs()1434     private boolean mayInheritNativeLibs() {
1435         return SystemProperties.getBoolean(PROPERTY_NAME_INHERIT_NATIVE, true) &&
1436                 params.mode == SessionParams.MODE_INHERIT_EXISTING &&
1437                 (params.installFlags & PackageManager.DONT_KILL_APP) != 0;
1438     }
1439 
1440     /**
1441      * Validate apex install.
1442      * <p>
1443      * Sets {@link #mResolvedBaseFile} for RollbackManager to use. Sets {@link #mPackageName} for
1444      * StagingManager to use.
1445      */
1446     @GuardedBy("mLock")
validateApexInstallLocked()1447     private void validateApexInstallLocked()
1448             throws PackageManagerException {
1449         final File[] addedFiles = getAddedFilesLocked();
1450         if (ArrayUtils.isEmpty(addedFiles)) {
1451             throw new PackageManagerException(INSTALL_FAILED_INVALID_APK, "No packages staged");
1452         }
1453 
1454         if (ArrayUtils.size(addedFiles) > 1) {
1455             throw new PackageManagerException(INSTALL_FAILED_INVALID_APK,
1456                     "Too many files for apex install");
1457         }
1458 
1459         File addedFile = addedFiles[0]; // there is only one file
1460 
1461         // Ensure file name has proper suffix
1462         final String sourceName = addedFile.getName();
1463         final String targetName = sourceName.endsWith(APEX_FILE_EXTENSION)
1464                 ? sourceName
1465                 : sourceName + APEX_FILE_EXTENSION;
1466         if (!FileUtils.isValidExtFilename(targetName)) {
1467             throw new PackageManagerException(INSTALL_FAILED_INVALID_APK,
1468                     "Invalid filename: " + targetName);
1469         }
1470 
1471         final File targetFile = new File(stageDir, targetName);
1472         resolveAndStageFile(addedFile, targetFile);
1473         mResolvedBaseFile = targetFile;
1474 
1475         // Populate package name of the apex session
1476         mPackageName = null;
1477         final ApkLite apk;
1478         try {
1479             apk = PackageParser.parseApkLite(
1480                     mResolvedBaseFile, PackageParser.PARSE_COLLECT_CERTIFICATES);
1481         } catch (PackageParserException e) {
1482             throw PackageManagerException.from(e);
1483         }
1484 
1485         if (mPackageName == null) {
1486             mPackageName = apk.packageName;
1487             mVersionCode = apk.getLongVersionCode();
1488         }
1489     }
1490 
1491     /**
1492      * Validate install by confirming that all application packages are have
1493      * consistent package name, version code, and signing certificates.
1494      * <p>
1495      * Clears and populates {@link #mResolvedBaseFile},
1496      * {@link #mResolvedStagedFiles}, and {@link #mResolvedInheritedFiles}.
1497      * <p>
1498      * Renames package files in stage to match split names defined inside.
1499      * <p>
1500      * Note that upgrade compatibility is still performed by
1501      * {@link PackageManagerService}.
1502      */
1503     @GuardedBy("mLock")
validateApkInstallLocked(@ullable PackageInfo pkgInfo)1504     private void validateApkInstallLocked(@Nullable PackageInfo pkgInfo)
1505             throws PackageManagerException {
1506         ApkLite baseApk = null;
1507         mPackageName = null;
1508         mVersionCode = -1;
1509         mSigningDetails = PackageParser.SigningDetails.UNKNOWN;
1510 
1511         mResolvedBaseFile = null;
1512         mResolvedStagedFiles.clear();
1513         mResolvedInheritedFiles.clear();
1514 
1515         // Partial installs must be consistent with existing install
1516         if (params.mode == SessionParams.MODE_INHERIT_EXISTING
1517                 && (pkgInfo == null || pkgInfo.applicationInfo == null)) {
1518             throw new PackageManagerException(INSTALL_FAILED_INVALID_APK,
1519                     "Missing existing base package");
1520         }
1521         // Default to require only if existing base has fs-verity.
1522         mVerityFound = PackageManagerServiceUtils.isApkVerityEnabled()
1523                 && params.mode == SessionParams.MODE_INHERIT_EXISTING
1524                 && VerityUtils.hasFsverity(pkgInfo.applicationInfo.getBaseCodePath());
1525 
1526         final File[] removedFiles = getRemovedFilesLocked();
1527         final List<String> removeSplitList = new ArrayList<>();
1528         if (!ArrayUtils.isEmpty(removedFiles)) {
1529             for (File removedFile : removedFiles) {
1530                 final String fileName = removedFile.getName();
1531                 final String splitName = fileName.substring(
1532                         0, fileName.length() - REMOVE_MARKER_EXTENSION.length());
1533                 removeSplitList.add(splitName);
1534             }
1535         }
1536 
1537         final File[] addedFiles = getAddedFilesLocked();
1538         if (ArrayUtils.isEmpty(addedFiles) && removeSplitList.size() == 0) {
1539             throw new PackageManagerException(INSTALL_FAILED_INVALID_APK, "No packages staged");
1540         }
1541 
1542         // Verify that all staged packages are internally consistent
1543         final ArraySet<String> stagedSplits = new ArraySet<>();
1544         for (File addedFile : addedFiles) {
1545             final ApkLite apk;
1546             try {
1547                 apk = PackageParser.parseApkLite(
1548                         addedFile, PackageParser.PARSE_COLLECT_CERTIFICATES);
1549             } catch (PackageParserException e) {
1550                 throw PackageManagerException.from(e);
1551             }
1552 
1553             if (!stagedSplits.add(apk.splitName)) {
1554                 throw new PackageManagerException(INSTALL_FAILED_INVALID_APK,
1555                         "Split " + apk.splitName + " was defined multiple times");
1556             }
1557 
1558             // Use first package to define unknown values
1559             if (mPackageName == null) {
1560                 mPackageName = apk.packageName;
1561                 mVersionCode = apk.getLongVersionCode();
1562             }
1563             if (mSigningDetails == PackageParser.SigningDetails.UNKNOWN) {
1564                 mSigningDetails = apk.signingDetails;
1565             }
1566 
1567             assertApkConsistentLocked(String.valueOf(addedFile), apk);
1568 
1569             // Take this opportunity to enforce uniform naming
1570             final String targetName;
1571             if (apk.splitName == null) {
1572                 targetName = "base" + APK_FILE_EXTENSION;
1573             } else {
1574                 targetName = "split_" + apk.splitName + APK_FILE_EXTENSION;
1575             }
1576             if (!FileUtils.isValidExtFilename(targetName)) {
1577                 throw new PackageManagerException(INSTALL_FAILED_INVALID_APK,
1578                         "Invalid filename: " + targetName);
1579             }
1580 
1581             final File targetFile = new File(stageDir, targetName);
1582             resolveAndStageFile(addedFile, targetFile);
1583 
1584             // Base is coming from session
1585             if (apk.splitName == null) {
1586                 mResolvedBaseFile = targetFile;
1587                 baseApk = apk;
1588             }
1589 
1590             final File dexMetadataFile = DexMetadataHelper.findDexMetadataForFile(addedFile);
1591             if (dexMetadataFile != null) {
1592                 if (!FileUtils.isValidExtFilename(dexMetadataFile.getName())) {
1593                     throw new PackageManagerException(INSTALL_FAILED_INVALID_APK,
1594                             "Invalid filename: " + dexMetadataFile);
1595                 }
1596                 final File targetDexMetadataFile = new File(stageDir,
1597                         DexMetadataHelper.buildDexMetadataPathForApk(targetName));
1598                 resolveAndStageFile(dexMetadataFile, targetDexMetadataFile);
1599             }
1600         }
1601 
1602         if (removeSplitList.size() > 0) {
1603             if (pkgInfo == null) {
1604                 throw new PackageManagerException(INSTALL_FAILED_INVALID_APK,
1605                         "Missing existing base package for " + mPackageName);
1606             }
1607 
1608             // validate split names marked for removal
1609             for (String splitName : removeSplitList) {
1610                 if (!ArrayUtils.contains(pkgInfo.splitNames, splitName)) {
1611                     throw new PackageManagerException(INSTALL_FAILED_INVALID_APK,
1612                             "Split not found: " + splitName);
1613                 }
1614             }
1615 
1616             // ensure we've got appropriate package name, version code and signatures
1617             if (mPackageName == null) {
1618                 mPackageName = pkgInfo.packageName;
1619                 mVersionCode = pkgInfo.getLongVersionCode();
1620             }
1621             if (mSigningDetails == PackageParser.SigningDetails.UNKNOWN) {
1622                 try {
1623                     mSigningDetails = ApkSignatureVerifier.unsafeGetCertsWithoutVerification(
1624                             pkgInfo.applicationInfo.sourceDir,
1625                             PackageParser.SigningDetails.SignatureSchemeVersion.JAR);
1626                 } catch (PackageParserException e) {
1627                     throw new PackageManagerException(INSTALL_FAILED_INVALID_APK,
1628                             "Couldn't obtain signatures from base APK");
1629                 }
1630             }
1631         }
1632 
1633         if (params.mode == SessionParams.MODE_FULL_INSTALL) {
1634             // Full installs must include a base package
1635             if (!stagedSplits.contains(null)) {
1636                 throw new PackageManagerException(INSTALL_FAILED_INVALID_APK,
1637                         "Full install must include a base package");
1638             }
1639 
1640         } else {
1641             final PackageLite existing;
1642             final ApkLite existingBase;
1643             ApplicationInfo appInfo = pkgInfo.applicationInfo;
1644             try {
1645                 existing = PackageParser.parsePackageLite(new File(appInfo.getCodePath()), 0);
1646                 existingBase = PackageParser.parseApkLite(new File(appInfo.getBaseCodePath()),
1647                         PackageParser.PARSE_COLLECT_CERTIFICATES);
1648             } catch (PackageParserException e) {
1649                 throw PackageManagerException.from(e);
1650             }
1651 
1652             assertApkConsistentLocked("Existing base", existingBase);
1653 
1654             // Inherit base if not overridden
1655             if (mResolvedBaseFile == null) {
1656                 mResolvedBaseFile = new File(appInfo.getBaseCodePath());
1657                 resolveInheritedFile(mResolvedBaseFile);
1658                 // Inherit the dex metadata if present.
1659                 final File baseDexMetadataFile =
1660                         DexMetadataHelper.findDexMetadataForFile(mResolvedBaseFile);
1661                 if (baseDexMetadataFile != null) {
1662                     resolveInheritedFile(baseDexMetadataFile);
1663                 }
1664                 baseApk = existingBase;
1665             }
1666 
1667             // Inherit splits if not overridden
1668             if (!ArrayUtils.isEmpty(existing.splitNames)) {
1669                 for (int i = 0; i < existing.splitNames.length; i++) {
1670                     final String splitName = existing.splitNames[i];
1671                     final File splitFile = new File(existing.splitCodePaths[i]);
1672                     final boolean splitRemoved = removeSplitList.contains(splitName);
1673                     if (!stagedSplits.contains(splitName) && !splitRemoved) {
1674                         resolveInheritedFile(splitFile);
1675                         // Inherit the dex metadata if present.
1676                         final File splitDexMetadataFile =
1677                                 DexMetadataHelper.findDexMetadataForFile(splitFile);
1678                         if (splitDexMetadataFile != null) {
1679                             resolveInheritedFile(splitDexMetadataFile);
1680                         }
1681                     }
1682                 }
1683             }
1684 
1685             // Inherit compiled oat directory.
1686             final File packageInstallDir = (new File(appInfo.getBaseCodePath())).getParentFile();
1687             mInheritedFilesBase = packageInstallDir;
1688             final File oatDir = new File(packageInstallDir, "oat");
1689             if (oatDir.exists()) {
1690                 final File[] archSubdirs = oatDir.listFiles();
1691 
1692                 // Keep track of all instruction sets we've seen compiled output for.
1693                 // If we're linking (and not copying) inherited files, we can recreate the
1694                 // instruction set hierarchy and link compiled output.
1695                 if (archSubdirs != null && archSubdirs.length > 0) {
1696                     final String[] instructionSets = InstructionSets.getAllDexCodeInstructionSets();
1697                     for (File archSubDir : archSubdirs) {
1698                         // Skip any directory that isn't an ISA subdir.
1699                         if (!ArrayUtils.contains(instructionSets, archSubDir.getName())) {
1700                             continue;
1701                         }
1702 
1703                         mResolvedInstructionSets.add(archSubDir.getName());
1704                         List<File> oatFiles = Arrays.asList(archSubDir.listFiles());
1705                         if (!oatFiles.isEmpty()) {
1706                             mResolvedInheritedFiles.addAll(oatFiles);
1707                         }
1708                     }
1709                 }
1710             }
1711 
1712             // Inherit native libraries for DONT_KILL sessions.
1713             if (mayInheritNativeLibs() && removeSplitList.isEmpty()) {
1714                 File[] libDirs = new File[]{
1715                         new File(packageInstallDir, NativeLibraryHelper.LIB_DIR_NAME),
1716                         new File(packageInstallDir, NativeLibraryHelper.LIB64_DIR_NAME)};
1717                 for (File libDir : libDirs) {
1718                     if (!libDir.exists() || !libDir.isDirectory()) {
1719                         continue;
1720                     }
1721                     final List<File> libDirsToInherit = new LinkedList<>();
1722                     for (File archSubDir : libDir.listFiles()) {
1723                         if (!archSubDir.isDirectory()) {
1724                             continue;
1725                         }
1726                         String relLibPath;
1727                         try {
1728                             relLibPath = getRelativePath(archSubDir, packageInstallDir);
1729                         } catch (IOException e) {
1730                             Slog.e(TAG, "Skipping linking of native library directory!", e);
1731                             // shouldn't be possible, but let's avoid inheriting these to be safe
1732                             libDirsToInherit.clear();
1733                             break;
1734                         }
1735                         if (!mResolvedNativeLibPaths.contains(relLibPath)) {
1736                             mResolvedNativeLibPaths.add(relLibPath);
1737                         }
1738                         libDirsToInherit.addAll(Arrays.asList(archSubDir.listFiles()));
1739                     }
1740                     mResolvedInheritedFiles.addAll(libDirsToInherit);
1741                 }
1742             }
1743         }
1744         if (baseApk.useEmbeddedDex) {
1745             for (File file : mResolvedStagedFiles) {
1746                 if (file.getName().endsWith(".apk")
1747                         && !DexManager.auditUncompressedDexInApk(file.getPath())) {
1748                     throw new PackageManagerException(INSTALL_FAILED_INVALID_APK,
1749                             "Some dex are not uncompressed and aligned correctly for "
1750                             + mPackageName);
1751                 }
1752             }
1753         }
1754         if (baseApk.isSplitRequired && stagedSplits.size() <= 1) {
1755             throw new PackageManagerException(INSTALL_FAILED_MISSING_SPLIT,
1756                     "Missing split for " + mPackageName);
1757         }
1758     }
1759 
resolveAndStageFile(File origFile, File targetFile)1760     private void resolveAndStageFile(File origFile, File targetFile)
1761             throws PackageManagerException {
1762         mResolvedStagedFiles.add(targetFile);
1763         maybeRenameFile(origFile, targetFile);
1764 
1765         final File originalSignature = new File(
1766                 VerityUtils.getFsveritySignatureFilePath(origFile.getPath()));
1767         // Make sure .fsv_sig exists when it should, then resolve and stage it.
1768         if (originalSignature.exists()) {
1769             // mVerityFound can only change from false to true here during the staging loop. Since
1770             // all or none of files should have .fsv_sig, this should only happen in the first time
1771             // (or never), otherwise bail out.
1772             if (!mVerityFound) {
1773                 mVerityFound = true;
1774                 if (mResolvedStagedFiles.size() > 1) {
1775                     throw new PackageManagerException(INSTALL_FAILED_BAD_SIGNATURE,
1776                             "Some file is missing fs-verity signature");
1777                 }
1778             }
1779         } else {
1780             if (!mVerityFound) {
1781                 return;
1782             }
1783             throw new PackageManagerException(INSTALL_FAILED_BAD_SIGNATURE,
1784                     "Missing corresponding fs-verity signature to " + origFile);
1785         }
1786 
1787         final File stagedSignature = new File(
1788                 VerityUtils.getFsveritySignatureFilePath(targetFile.getPath()));
1789         maybeRenameFile(originalSignature, stagedSignature);
1790         mResolvedStagedFiles.add(stagedSignature);
1791     }
1792 
resolveInheritedFile(File origFile)1793     private void resolveInheritedFile(File origFile) {
1794         mResolvedInheritedFiles.add(origFile);
1795 
1796         // Inherit the fsverity signature file if present.
1797         final File fsveritySignatureFile = new File(
1798                 VerityUtils.getFsveritySignatureFilePath(origFile.getPath()));
1799         if (fsveritySignatureFile.exists()) {
1800             mResolvedInheritedFiles.add(fsveritySignatureFile);
1801         }
1802     }
1803 
1804     @GuardedBy("mLock")
assertApkConsistentLocked(String tag, ApkLite apk)1805     private void assertApkConsistentLocked(String tag, ApkLite apk)
1806             throws PackageManagerException {
1807         if (!mPackageName.equals(apk.packageName)) {
1808             throw new PackageManagerException(INSTALL_FAILED_INVALID_APK, tag + " package "
1809                     + apk.packageName + " inconsistent with " + mPackageName);
1810         }
1811         if (params.appPackageName != null && !params.appPackageName.equals(apk.packageName)) {
1812             throw new PackageManagerException(INSTALL_FAILED_INVALID_APK, tag
1813                     + " specified package " + params.appPackageName
1814                     + " inconsistent with " + apk.packageName);
1815         }
1816         if (mVersionCode != apk.getLongVersionCode()) {
1817             throw new PackageManagerException(INSTALL_FAILED_INVALID_APK, tag
1818                     + " version code " + apk.versionCode + " inconsistent with "
1819                     + mVersionCode);
1820         }
1821         if (!mSigningDetails.signaturesMatchExactly(apk.signingDetails)) {
1822             throw new PackageManagerException(INSTALL_FAILED_INVALID_APK,
1823                     tag + " signatures are inconsistent");
1824         }
1825     }
1826 
1827     /**
1828      * Determine if creating hard links between source and destination is
1829      * possible. That is, do they all live on the same underlying device.
1830      */
isLinkPossible(List<File> fromFiles, File toDir)1831     private boolean isLinkPossible(List<File> fromFiles, File toDir) {
1832         try {
1833             final StructStat toStat = Os.stat(toDir.getAbsolutePath());
1834             for (File fromFile : fromFiles) {
1835                 final StructStat fromStat = Os.stat(fromFile.getAbsolutePath());
1836                 if (fromStat.st_dev != toStat.st_dev) {
1837                     return false;
1838                 }
1839             }
1840         } catch (ErrnoException e) {
1841             Slog.w(TAG, "Failed to detect if linking possible: " + e);
1842             return false;
1843         }
1844         return true;
1845     }
1846 
1847     /**
1848      * @return the uid of the owner this session
1849      */
getInstallerUid()1850     public int getInstallerUid() {
1851         synchronized (mLock) {
1852             return mInstallerUid;
1853         }
1854     }
1855 
1856     /**
1857      * @return the package name of this session
1858      */
getPackageName()1859     String getPackageName() {
1860         synchronized (mLock) {
1861             return mPackageName;
1862         }
1863     }
1864 
1865     /**
1866      * @return the timestamp of when this session last changed state
1867      */
getUpdatedMillis()1868     public long getUpdatedMillis() {
1869         synchronized (mLock) {
1870             return updatedMillis;
1871         }
1872     }
1873 
getInstallerPackageName()1874     String getInstallerPackageName() {
1875         synchronized (mLock) {
1876             return mInstallerPackageName;
1877         }
1878     }
1879 
getRelativePath(File file, File base)1880     private static String getRelativePath(File file, File base) throws IOException {
1881         final String pathStr = file.getAbsolutePath();
1882         final String baseStr = base.getAbsolutePath();
1883         // Don't allow relative paths.
1884         if (pathStr.contains("/.") ) {
1885             throw new IOException("Invalid path (was relative) : " + pathStr);
1886         }
1887 
1888         if (pathStr.startsWith(baseStr)) {
1889             return pathStr.substring(baseStr.length());
1890         }
1891 
1892         throw new IOException("File: " + pathStr + " outside base: " + baseStr);
1893     }
1894 
createOatDirs(List<String> instructionSets, File fromDir)1895     private void createOatDirs(List<String> instructionSets, File fromDir)
1896             throws PackageManagerException {
1897         for (String instructionSet : instructionSets) {
1898             try {
1899                 mPm.mInstaller.createOatDir(fromDir.getAbsolutePath(), instructionSet);
1900             } catch (InstallerException e) {
1901                 throw PackageManagerException.from(e);
1902             }
1903         }
1904     }
1905 
linkFiles(List<File> fromFiles, File toDir, File fromDir)1906     private void linkFiles(List<File> fromFiles, File toDir, File fromDir)
1907             throws IOException {
1908         for (File fromFile : fromFiles) {
1909             final String relativePath = getRelativePath(fromFile, fromDir);
1910             try {
1911                 mPm.mInstaller.linkFile(relativePath, fromDir.getAbsolutePath(),
1912                         toDir.getAbsolutePath());
1913             } catch (InstallerException e) {
1914                 throw new IOException("failed linkOrCreateDir(" + relativePath + ", "
1915                         + fromDir + ", " + toDir + ")", e);
1916             }
1917         }
1918 
1919         Slog.d(TAG, "Linked " + fromFiles.size() + " files into " + toDir);
1920     }
1921 
copyFiles(List<File> fromFiles, File toDir)1922     private static void copyFiles(List<File> fromFiles, File toDir) throws IOException {
1923         // Remove any partial files from previous attempt
1924         for (File file : toDir.listFiles()) {
1925             if (file.getName().endsWith(".tmp")) {
1926                 file.delete();
1927             }
1928         }
1929 
1930         for (File fromFile : fromFiles) {
1931             final File tmpFile = File.createTempFile("inherit", ".tmp", toDir);
1932             if (LOGD) Slog.d(TAG, "Copying " + fromFile + " to " + tmpFile);
1933             if (!FileUtils.copyFile(fromFile, tmpFile)) {
1934                 throw new IOException("Failed to copy " + fromFile + " to " + tmpFile);
1935             }
1936             try {
1937                 Os.chmod(tmpFile.getAbsolutePath(), 0644);
1938             } catch (ErrnoException e) {
1939                 throw new IOException("Failed to chmod " + tmpFile);
1940             }
1941             final File toFile = new File(toDir, fromFile.getName());
1942             if (LOGD) Slog.d(TAG, "Renaming " + tmpFile + " to " + toFile);
1943             if (!tmpFile.renameTo(toFile)) {
1944                 throw new IOException("Failed to rename " + tmpFile + " to " + toFile);
1945             }
1946         }
1947         Slog.d(TAG, "Copied " + fromFiles.size() + " files into " + toDir);
1948     }
1949 
extractNativeLibraries(File packageDir, String abiOverride, boolean inherit)1950     private static void extractNativeLibraries(File packageDir, String abiOverride, boolean inherit)
1951             throws PackageManagerException {
1952         final File libDir = new File(packageDir, NativeLibraryHelper.LIB_DIR_NAME);
1953         if (!inherit) {
1954             // Start from a clean slate
1955             NativeLibraryHelper.removeNativeBinariesFromDirLI(libDir, true);
1956         }
1957 
1958         NativeLibraryHelper.Handle handle = null;
1959         try {
1960             handle = NativeLibraryHelper.Handle.create(packageDir);
1961             final int res = NativeLibraryHelper.copyNativeBinariesWithOverride(handle, libDir,
1962                     abiOverride);
1963             if (res != PackageManager.INSTALL_SUCCEEDED) {
1964                 throw new PackageManagerException(res,
1965                         "Failed to extract native libraries, res=" + res);
1966             }
1967         } catch (IOException e) {
1968             throw new PackageManagerException(INSTALL_FAILED_INTERNAL_ERROR,
1969                     "Failed to extract native libraries", e);
1970         } finally {
1971             IoUtils.closeQuietly(handle);
1972         }
1973     }
1974 
setPermissionsResult(boolean accepted)1975     void setPermissionsResult(boolean accepted) {
1976         if (!mSealed) {
1977             throw new SecurityException("Must be sealed to accept permissions");
1978         }
1979 
1980         if (accepted) {
1981             // Mark and kick off another install pass
1982             synchronized (mLock) {
1983                 mPermissionsManuallyAccepted = true;
1984                 mHandler.obtainMessage(MSG_COMMIT).sendToTarget();
1985             }
1986         } else {
1987             destroyInternal();
1988             dispatchSessionFinished(INSTALL_FAILED_ABORTED, "User rejected permissions", null);
1989         }
1990     }
1991 
1992     /**
1993      * Adds a child session ID without any safety / sanity checks. This should only be used to
1994      * build a session from XML or similar.
1995      */
addChildSessionIdInternal(int sessionId)1996     void addChildSessionIdInternal(int sessionId) {
1997         mChildSessionIds.put(sessionId, 0);
1998     }
1999 
open()2000     public void open() throws IOException {
2001         if (mActiveCount.getAndIncrement() == 0) {
2002             mCallback.onSessionActiveChanged(this, true);
2003         }
2004 
2005         boolean wasPrepared;
2006         synchronized (mLock) {
2007             wasPrepared = mPrepared;
2008             if (!mPrepared) {
2009                 if (stageDir != null) {
2010                     prepareStageDir(stageDir);
2011                 } else if (params.isMultiPackage) {
2012                     // it's all ok
2013                 } else {
2014                     throw new IllegalArgumentException("stageDir must be set");
2015                 }
2016 
2017                 mPrepared = true;
2018             }
2019         }
2020 
2021         if (!wasPrepared) {
2022             mCallback.onSessionPrepared(this);
2023         }
2024     }
2025 
2026     @Override
close()2027     public void close() {
2028         closeInternal(true);
2029     }
2030 
closeInternal(boolean checkCaller)2031     private void closeInternal(boolean checkCaller) {
2032         int activeCount;
2033         synchronized (mLock) {
2034             if (checkCaller) {
2035                 assertCallerIsOwnerOrRootLocked();
2036             }
2037 
2038             activeCount = mActiveCount.decrementAndGet();
2039         }
2040 
2041         if (activeCount == 0) {
2042             mCallback.onSessionActiveChanged(this, false);
2043         }
2044     }
2045 
2046     @Override
abandon()2047     public void abandon() {
2048         if (hasParentSessionId()) {
2049             throw new IllegalStateException(
2050                     "Session " + sessionId + " is a child of multi-package session "
2051                             + mParentSessionId +  " and may not be abandoned directly.");
2052         }
2053         synchronized (mLock) {
2054             assertCallerIsOwnerOrRootLocked();
2055 
2056             if (isStagedAndInTerminalState()) {
2057                 // We keep the session in the database if it's in a finalized state. It will be
2058                 // removed by PackageInstallerService when the last update time is old enough.
2059                 // Also, in such cases cleanStageDir() has already been executed so no need to
2060                 // do it now.
2061                 return;
2062             }
2063             if (mCommitted && params.isStaged) {
2064                 synchronized (mLock) {
2065                     mDestroyed = true;
2066                 }
2067                 mStagingManager.abortCommittedSession(this);
2068 
2069                 cleanStageDir();
2070             }
2071 
2072             if (mRelinquished) {
2073                 Slog.d(TAG, "Ignoring abandon after commit relinquished control");
2074                 return;
2075             }
2076             destroyInternal();
2077         }
2078 
2079         dispatchSessionFinished(INSTALL_FAILED_ABORTED, "Session was abandoned", null);
2080     }
2081 
2082     @Override
isMultiPackage()2083     public boolean isMultiPackage() {
2084         return params.isMultiPackage;
2085     }
2086 
2087     @Override
isStaged()2088     public boolean isStaged() {
2089         return params.isStaged;
2090     }
2091 
2092     @Override
getChildSessionIds()2093     public int[] getChildSessionIds() {
2094         final int[] childSessionIds = mChildSessionIds.copyKeys();
2095         if (childSessionIds != null) {
2096             return childSessionIds;
2097         }
2098         return EMPTY_CHILD_SESSION_ARRAY;
2099     }
2100 
2101     @Override
addChildSessionId(int childSessionId)2102     public void addChildSessionId(int childSessionId) {
2103         final PackageInstallerSession childSession = mSessionProvider.getSession(childSessionId);
2104         if (childSession == null
2105                 || (childSession.hasParentSessionId() && childSession.mParentSessionId != sessionId)
2106                 || childSession.mCommitted
2107                 || childSession.mDestroyed) {
2108             throw new IllegalStateException("Unable to add child session " + childSessionId
2109                             + " as it does not exist or is in an invalid state.");
2110         }
2111         synchronized (mLock) {
2112             assertCallerIsOwnerOrRootLocked();
2113             assertPreparedAndNotSealedLocked("addChildSessionId");
2114 
2115             final int indexOfSession = mChildSessionIds.indexOfKey(childSessionId);
2116             if (indexOfSession >= 0) {
2117                 return;
2118             }
2119             childSession.setParentSessionId(this.sessionId);
2120             addChildSessionIdInternal(childSessionId);
2121         }
2122     }
2123 
2124     @Override
removeChildSessionId(int sessionId)2125     public void removeChildSessionId(int sessionId) {
2126         final PackageInstallerSession session = mSessionProvider.getSession(sessionId);
2127         synchronized (mLock) {
2128             final int indexOfSession = mChildSessionIds.indexOfKey(sessionId);
2129             if (session != null) {
2130                 session.setParentSessionId(SessionInfo.INVALID_ID);
2131             }
2132             if (indexOfSession < 0) {
2133                 // not added in the first place; no-op
2134                 return;
2135             }
2136             mChildSessionIds.removeAt(indexOfSession);
2137         }
2138     }
2139 
2140     /**
2141      * Sets the parent session ID if not already set.
2142      * If {@link SessionInfo#INVALID_ID} is passed, it will be unset.
2143      */
setParentSessionId(int parentSessionId)2144     void setParentSessionId(int parentSessionId) {
2145         synchronized (mLock) {
2146             if (parentSessionId != SessionInfo.INVALID_ID
2147                     && mParentSessionId != SessionInfo.INVALID_ID) {
2148                 throw new IllegalStateException("The parent of " + sessionId + " is" + " already"
2149                         + "set to " + mParentSessionId);
2150             }
2151             this.mParentSessionId = parentSessionId;
2152         }
2153     }
2154 
hasParentSessionId()2155     boolean hasParentSessionId() {
2156         return mParentSessionId != SessionInfo.INVALID_ID;
2157     }
2158 
2159     @Override
getParentSessionId()2160     public int getParentSessionId() {
2161         return mParentSessionId;
2162     }
2163 
dispatchSessionFinished(int returnCode, String msg, Bundle extras)2164     private void dispatchSessionFinished(int returnCode, String msg, Bundle extras) {
2165         final IntentSender statusReceiver;
2166         final String packageName;
2167         synchronized (mLock) {
2168             mFinalStatus = returnCode;
2169             mFinalMessage = msg;
2170 
2171             statusReceiver = mRemoteStatusReceiver;
2172             packageName = mPackageName;
2173         }
2174 
2175         if (statusReceiver != null) {
2176             // Execute observer.onPackageInstalled on different tread as we don't want callers
2177             // inside the system server have to worry about catching the callbacks while they are
2178             // calling into the session
2179             final SomeArgs args = SomeArgs.obtain();
2180             args.arg1 = packageName;
2181             args.arg2 = msg;
2182             args.arg3 = extras;
2183             args.arg4 = statusReceiver;
2184             args.argi1 = returnCode;
2185 
2186             mHandler.obtainMessage(MSG_ON_PACKAGE_INSTALLED, args).sendToTarget();
2187         }
2188 
2189         final boolean success = (returnCode == PackageManager.INSTALL_SUCCEEDED);
2190 
2191         // Send broadcast to default launcher only if it's a new install
2192         // TODO(b/144270665): Secure the usage of this broadcast.
2193         final boolean isNewInstall = extras == null || !extras.getBoolean(Intent.EXTRA_REPLACING);
2194         if (success && isNewInstall && mPm.mInstallerService.okToSendBroadcasts()
2195                 && (params.installFlags & PackageManager.INSTALL_DRY_RUN) == 0) {
2196             mPm.sendSessionCommitBroadcast(generateInfo(), userId);
2197         }
2198 
2199         mCallback.onSessionFinished(this, success);
2200     }
2201 
2202     /** {@hide} */
setStagedSessionReady()2203     void setStagedSessionReady() {
2204         synchronized (mLock) {
2205             mStagedSessionReady = true;
2206             mStagedSessionApplied = false;
2207             mStagedSessionFailed = false;
2208             mStagedSessionErrorCode = SessionInfo.STAGED_SESSION_NO_ERROR;
2209             mStagedSessionErrorMessage = "";
2210         }
2211         mCallback.onStagedSessionChanged(this);
2212     }
2213 
2214     /** {@hide} */
setStagedSessionFailed(@tagedSessionErrorCode int errorCode, String errorMessage)2215     void setStagedSessionFailed(@StagedSessionErrorCode int errorCode,
2216                                 String errorMessage) {
2217         synchronized (mLock) {
2218             mStagedSessionReady = false;
2219             mStagedSessionApplied = false;
2220             mStagedSessionFailed = true;
2221             mStagedSessionErrorCode = errorCode;
2222             mStagedSessionErrorMessage = errorMessage;
2223             Slog.d(TAG, "Marking session " + sessionId + " as failed: " + errorMessage);
2224         }
2225         cleanStageDir();
2226         mCallback.onStagedSessionChanged(this);
2227     }
2228 
2229     /** {@hide} */
setStagedSessionApplied()2230     void setStagedSessionApplied() {
2231         synchronized (mLock) {
2232             mStagedSessionReady = false;
2233             mStagedSessionApplied = true;
2234             mStagedSessionFailed = false;
2235             mStagedSessionErrorCode = SessionInfo.STAGED_SESSION_NO_ERROR;
2236             mStagedSessionErrorMessage = "";
2237             Slog.d(TAG, "Marking session " + sessionId + " as applied");
2238         }
2239         cleanStageDir();
2240         mCallback.onStagedSessionChanged(this);
2241     }
2242 
2243     /** {@hide} */
isStagedSessionReady()2244     boolean isStagedSessionReady() {
2245         return mStagedSessionReady;
2246     }
2247 
2248     /** {@hide} */
isStagedSessionApplied()2249     boolean isStagedSessionApplied() {
2250         return mStagedSessionApplied;
2251     }
2252 
2253     /** {@hide} */
isStagedSessionFailed()2254     boolean isStagedSessionFailed() {
2255         return mStagedSessionFailed;
2256     }
2257 
2258     /** {@hide} */
getStagedSessionErrorCode()2259     @StagedSessionErrorCode int getStagedSessionErrorCode() {
2260         return mStagedSessionErrorCode;
2261     }
2262 
2263     /** {@hide} */
getStagedSessionErrorMessage()2264     String getStagedSessionErrorMessage() {
2265         return mStagedSessionErrorMessage;
2266     }
2267 
destroyInternal()2268     private void destroyInternal() {
2269         synchronized (mLock) {
2270             mSealed = true;
2271             if (!params.isStaged || isStagedAndInTerminalState()) {
2272                 mDestroyed = true;
2273             }
2274             // Force shut down all bridges
2275             for (RevocableFileDescriptor fd : mFds) {
2276                 fd.revoke();
2277             }
2278             for (FileBridge bridge : mBridges) {
2279                 bridge.forceClose();
2280             }
2281         }
2282         // For staged sessions, we don't delete the directory where the packages have been copied,
2283         // since these packages are supposed to be read on reboot.
2284         // Those dirs are deleted when the staged session has reached a final state.
2285         if (stageDir != null && !params.isStaged) {
2286             try {
2287                 mPm.mInstaller.rmPackageDir(stageDir.getAbsolutePath());
2288             } catch (InstallerException ignored) {
2289             }
2290         }
2291     }
2292 
cleanStageDir()2293     private void cleanStageDir() {
2294         if (isMultiPackage()) {
2295             for (int childSessionId : getChildSessionIds()) {
2296                 mSessionProvider.getSession(childSessionId).cleanStageDir();
2297             }
2298         } else {
2299             try {
2300                 mPm.mInstaller.rmPackageDir(stageDir.getAbsolutePath());
2301             } catch (InstallerException ignored) {
2302             }
2303         }
2304     }
2305 
dump(IndentingPrintWriter pw)2306     void dump(IndentingPrintWriter pw) {
2307         synchronized (mLock) {
2308             dumpLocked(pw);
2309         }
2310     }
2311 
2312     @GuardedBy("mLock")
dumpLocked(IndentingPrintWriter pw)2313     private void dumpLocked(IndentingPrintWriter pw) {
2314         pw.println("Session " + sessionId + ":");
2315         pw.increaseIndent();
2316 
2317         pw.printPair("userId", userId);
2318         pw.printPair("mOriginalInstallerUid", mOriginalInstallerUid);
2319         pw.printPair("mInstallerPackageName", mInstallerPackageName);
2320         pw.printPair("mInstallerUid", mInstallerUid);
2321         pw.printPair("createdMillis", createdMillis);
2322         pw.printPair("stageDir", stageDir);
2323         pw.printPair("stageCid", stageCid);
2324         pw.println();
2325 
2326         params.dump(pw);
2327 
2328         pw.printPair("mClientProgress", mClientProgress);
2329         pw.printPair("mProgress", mProgress);
2330         pw.printPair("mCommitted", mCommitted);
2331         pw.printPair("mSealed", mSealed);
2332         pw.printPair("mPermissionsManuallyAccepted", mPermissionsManuallyAccepted);
2333         pw.printPair("mRelinquished", mRelinquished);
2334         pw.printPair("mDestroyed", mDestroyed);
2335         pw.printPair("mFds", mFds.size());
2336         pw.printPair("mBridges", mBridges.size());
2337         pw.printPair("mFinalStatus", mFinalStatus);
2338         pw.printPair("mFinalMessage", mFinalMessage);
2339         pw.printPair("params.isMultiPackage", params.isMultiPackage);
2340         pw.printPair("params.isStaged", params.isStaged);
2341         pw.println();
2342 
2343         pw.decreaseIndent();
2344     }
2345 
writeGrantedRuntimePermissionsLocked(XmlSerializer out, String[] grantedRuntimePermissions)2346     private static void writeGrantedRuntimePermissionsLocked(XmlSerializer out,
2347             String[] grantedRuntimePermissions) throws IOException {
2348         if (grantedRuntimePermissions != null) {
2349             for (String permission : grantedRuntimePermissions) {
2350                 out.startTag(null, TAG_GRANTED_RUNTIME_PERMISSION);
2351                 writeStringAttribute(out, ATTR_NAME, permission);
2352                 out.endTag(null, TAG_GRANTED_RUNTIME_PERMISSION);
2353             }
2354         }
2355     }
2356 
writeWhitelistedRestrictedPermissionsLocked(@onNull XmlSerializer out, @Nullable List<String> whitelistedRestrictedPermissions)2357     private static void writeWhitelistedRestrictedPermissionsLocked(@NonNull XmlSerializer out,
2358             @Nullable List<String> whitelistedRestrictedPermissions) throws IOException {
2359         if (whitelistedRestrictedPermissions != null) {
2360             final int permissionCount = whitelistedRestrictedPermissions.size();
2361             for (int i = 0; i < permissionCount; i++) {
2362                 out.startTag(null, TAG_WHITELISTED_RESTRICTED_PERMISSION);
2363                 writeStringAttribute(out, ATTR_NAME, whitelistedRestrictedPermissions.get(i));
2364                 out.endTag(null, TAG_WHITELISTED_RESTRICTED_PERMISSION);
2365             }
2366         }
2367     }
2368 
2369 
buildAppIconFile(int sessionId, @NonNull File sessionsDir)2370     private static File buildAppIconFile(int sessionId, @NonNull File sessionsDir) {
2371         return new File(sessionsDir, "app_icon." + sessionId + ".png");
2372     }
2373 
2374     /**
2375      * Write this session to a {@link XmlSerializer}.
2376      *
2377      * @param out Where to write the session to
2378      * @param sessionsDir The directory containing the sessions
2379      */
write(@onNull XmlSerializer out, @NonNull File sessionsDir)2380     void write(@NonNull XmlSerializer out, @NonNull File sessionsDir) throws IOException {
2381         synchronized (mLock) {
2382             if (mDestroyed) {
2383                 return;
2384             }
2385 
2386             out.startTag(null, TAG_SESSION);
2387 
2388             writeIntAttribute(out, ATTR_SESSION_ID, sessionId);
2389             writeIntAttribute(out, ATTR_USER_ID, userId);
2390             writeStringAttribute(out, ATTR_INSTALLER_PACKAGE_NAME,
2391                     mInstallerPackageName);
2392             writeIntAttribute(out, ATTR_INSTALLER_UID, mInstallerUid);
2393             writeLongAttribute(out, ATTR_CREATED_MILLIS, createdMillis);
2394             writeLongAttribute(out, ATTR_UPDATED_MILLIS, updatedMillis);
2395             if (stageDir != null) {
2396                 writeStringAttribute(out, ATTR_SESSION_STAGE_DIR,
2397                         stageDir.getAbsolutePath());
2398             }
2399             if (stageCid != null) {
2400                 writeStringAttribute(out, ATTR_SESSION_STAGE_CID, stageCid);
2401             }
2402             writeBooleanAttribute(out, ATTR_PREPARED, isPrepared());
2403             writeBooleanAttribute(out, ATTR_COMMITTED, isCommitted());
2404             writeBooleanAttribute(out, ATTR_SEALED, isSealed());
2405 
2406             writeBooleanAttribute(out, ATTR_MULTI_PACKAGE, params.isMultiPackage);
2407             writeBooleanAttribute(out, ATTR_STAGED_SESSION, params.isStaged);
2408             writeBooleanAttribute(out, ATTR_IS_READY, mStagedSessionReady);
2409             writeBooleanAttribute(out, ATTR_IS_FAILED, mStagedSessionFailed);
2410             writeBooleanAttribute(out, ATTR_IS_APPLIED, mStagedSessionApplied);
2411             writeIntAttribute(out, ATTR_STAGED_SESSION_ERROR_CODE, mStagedSessionErrorCode);
2412             writeStringAttribute(out, ATTR_STAGED_SESSION_ERROR_MESSAGE,
2413                     mStagedSessionErrorMessage);
2414             // TODO(patb,109941548): avoid writing to xml and instead infer / validate this after
2415             //                       we've read all sessions.
2416             writeIntAttribute(out, ATTR_PARENT_SESSION_ID, mParentSessionId);
2417             writeIntAttribute(out, ATTR_MODE, params.mode);
2418             writeIntAttribute(out, ATTR_INSTALL_FLAGS, params.installFlags);
2419             writeIntAttribute(out, ATTR_INSTALL_LOCATION, params.installLocation);
2420             writeLongAttribute(out, ATTR_SIZE_BYTES, params.sizeBytes);
2421             writeStringAttribute(out, ATTR_APP_PACKAGE_NAME, params.appPackageName);
2422             writeStringAttribute(out, ATTR_APP_LABEL, params.appLabel);
2423             writeUriAttribute(out, ATTR_ORIGINATING_URI, params.originatingUri);
2424             writeIntAttribute(out, ATTR_ORIGINATING_UID, params.originatingUid);
2425             writeUriAttribute(out, ATTR_REFERRER_URI, params.referrerUri);
2426             writeStringAttribute(out, ATTR_ABI_OVERRIDE, params.abiOverride);
2427             writeStringAttribute(out, ATTR_VOLUME_UUID, params.volumeUuid);
2428             writeIntAttribute(out, ATTR_INSTALL_REASON, params.installReason);
2429 
2430             writeGrantedRuntimePermissionsLocked(out, params.grantedRuntimePermissions);
2431             writeWhitelistedRestrictedPermissionsLocked(out,
2432                     params.whitelistedRestrictedPermissions);
2433 
2434             // Persist app icon if changed since last written
2435             File appIconFile = buildAppIconFile(sessionId, sessionsDir);
2436             if (params.appIcon == null && appIconFile.exists()) {
2437                 appIconFile.delete();
2438             } else if (params.appIcon != null
2439                     && appIconFile.lastModified() != params.appIconLastModified) {
2440                 if (LOGD) Slog.w(TAG, "Writing changed icon " + appIconFile);
2441                 FileOutputStream os = null;
2442                 try {
2443                     os = new FileOutputStream(appIconFile);
2444                     params.appIcon.compress(Bitmap.CompressFormat.PNG, 90, os);
2445                 } catch (IOException e) {
2446                     Slog.w(TAG, "Failed to write icon " + appIconFile + ": " + e.getMessage());
2447                 } finally {
2448                     IoUtils.closeQuietly(os);
2449                 }
2450 
2451                 params.appIconLastModified = appIconFile.lastModified();
2452             }
2453             final int[] childSessionIds = getChildSessionIds();
2454             for (int childSessionId : childSessionIds) {
2455                 out.startTag(null, TAG_CHILD_SESSION);
2456                 writeIntAttribute(out, ATTR_SESSION_ID, childSessionId);
2457                 out.endTag(null, TAG_CHILD_SESSION);
2458             }
2459         }
2460 
2461         out.endTag(null, TAG_SESSION);
2462     }
2463 
2464     // Sanity check to be performed when the session is restored from an external file. Only one
2465     // of the session states should be true, or none of them.
isStagedSessionStateValid(boolean isReady, boolean isApplied, boolean isFailed)2466     private static boolean isStagedSessionStateValid(boolean isReady, boolean isApplied,
2467                                                      boolean isFailed) {
2468         return (!isReady && !isApplied && !isFailed)
2469                 || (isReady && !isApplied && !isFailed)
2470                 || (!isReady && isApplied && !isFailed)
2471                 || (!isReady && !isApplied && isFailed);
2472     }
2473 
2474     /**
2475      * Read new session from a {@link XmlPullParser xml description} and create it.
2476      *
2477      * @param in The source of the description
2478      * @param callback Callback the session uses to notify about changes of it's state
2479      * @param context Context to be used by the session
2480      * @param pm PackageManager to use by the session
2481      * @param installerThread Thread to be used for callbacks of this session
2482      * @param sessionsDir The directory the sessions are stored in
2483      *
2484      * @param sessionProvider
2485      * @return The newly created session
2486      */
readFromXml(@onNull XmlPullParser in, @NonNull PackageInstallerService.InternalCallback callback, @NonNull Context context, @NonNull PackageManagerService pm, Looper installerThread, @NonNull StagingManager stagingManager, @NonNull File sessionsDir, @NonNull PackageSessionProvider sessionProvider)2487     public static PackageInstallerSession readFromXml(@NonNull XmlPullParser in,
2488             @NonNull PackageInstallerService.InternalCallback callback, @NonNull Context context,
2489             @NonNull PackageManagerService pm, Looper installerThread,
2490             @NonNull StagingManager stagingManager, @NonNull File sessionsDir,
2491             @NonNull PackageSessionProvider sessionProvider)
2492             throws IOException, XmlPullParserException {
2493         final int sessionId = readIntAttribute(in, ATTR_SESSION_ID);
2494         final int userId = readIntAttribute(in, ATTR_USER_ID);
2495         final String installerPackageName = readStringAttribute(in, ATTR_INSTALLER_PACKAGE_NAME);
2496         final int installerUid = readIntAttribute(in, ATTR_INSTALLER_UID, pm.getPackageUid(
2497                 installerPackageName, PackageManager.MATCH_UNINSTALLED_PACKAGES, userId));
2498         final long createdMillis = readLongAttribute(in, ATTR_CREATED_MILLIS);
2499         long updatedMillis = readLongAttribute(in, ATTR_UPDATED_MILLIS);
2500         final String stageDirRaw = readStringAttribute(in, ATTR_SESSION_STAGE_DIR);
2501         final File stageDir = (stageDirRaw != null) ? new File(stageDirRaw) : null;
2502         final String stageCid = readStringAttribute(in, ATTR_SESSION_STAGE_CID);
2503         final boolean prepared = readBooleanAttribute(in, ATTR_PREPARED, true);
2504         final boolean committed = readBooleanAttribute(in, ATTR_COMMITTED);
2505         final boolean sealed = readBooleanAttribute(in, ATTR_SEALED);
2506         final int parentSessionId = readIntAttribute(in, ATTR_PARENT_SESSION_ID,
2507                 SessionInfo.INVALID_ID);
2508 
2509         final SessionParams params = new SessionParams(
2510                 SessionParams.MODE_INVALID);
2511         params.isMultiPackage = readBooleanAttribute(in, ATTR_MULTI_PACKAGE, false);
2512         params.isStaged = readBooleanAttribute(in, ATTR_STAGED_SESSION, false);
2513         params.mode = readIntAttribute(in, ATTR_MODE);
2514         params.installFlags = readIntAttribute(in, ATTR_INSTALL_FLAGS);
2515         params.installLocation = readIntAttribute(in, ATTR_INSTALL_LOCATION);
2516         params.sizeBytes = readLongAttribute(in, ATTR_SIZE_BYTES);
2517         params.appPackageName = readStringAttribute(in, ATTR_APP_PACKAGE_NAME);
2518         params.appIcon = readBitmapAttribute(in, ATTR_APP_ICON);
2519         params.appLabel = readStringAttribute(in, ATTR_APP_LABEL);
2520         params.originatingUri = readUriAttribute(in, ATTR_ORIGINATING_URI);
2521         params.originatingUid =
2522                 readIntAttribute(in, ATTR_ORIGINATING_UID, SessionParams.UID_UNKNOWN);
2523         params.referrerUri = readUriAttribute(in, ATTR_REFERRER_URI);
2524         params.abiOverride = readStringAttribute(in, ATTR_ABI_OVERRIDE);
2525         params.volumeUuid = readStringAttribute(in, ATTR_VOLUME_UUID);
2526         params.installReason = readIntAttribute(in, ATTR_INSTALL_REASON);
2527 
2528         final File appIconFile = buildAppIconFile(sessionId, sessionsDir);
2529         if (appIconFile.exists()) {
2530             params.appIcon = BitmapFactory.decodeFile(appIconFile.getAbsolutePath());
2531             params.appIconLastModified = appIconFile.lastModified();
2532         }
2533         final boolean isReady = readBooleanAttribute(in, ATTR_IS_READY);
2534         final boolean isFailed = readBooleanAttribute(in, ATTR_IS_FAILED);
2535         final boolean isApplied = readBooleanAttribute(in, ATTR_IS_APPLIED);
2536         final int stagedSessionErrorCode = readIntAttribute(in, ATTR_STAGED_SESSION_ERROR_CODE,
2537                 SessionInfo.STAGED_SESSION_NO_ERROR);
2538         final String stagedSessionErrorMessage = readStringAttribute(in,
2539                 ATTR_STAGED_SESSION_ERROR_MESSAGE);
2540 
2541         if (!isStagedSessionStateValid(isReady, isApplied, isFailed)) {
2542             throw new IllegalArgumentException("Can't restore staged session with invalid state.");
2543         }
2544 
2545         // Parse sub tags of this session, typically used for repeated values / arrays.
2546         // Sub tags can come in any order, therefore we need to keep track of what we find while
2547         // parsing and only set the right values at the end.
2548 
2549         // Store the current depth. We should stop parsing when we reach an end tag at the same
2550         // depth.
2551         List<String> grantedRuntimePermissions = new ArrayList<>();
2552         List<String> whitelistedRestrictedPermissions = new ArrayList<>();
2553         List<Integer> childSessionIds = new ArrayList<>();
2554         int outerDepth = in.getDepth();
2555         int type;
2556         while ((type = in.next()) != XmlPullParser.END_DOCUMENT
2557                 && (type != XmlPullParser.END_TAG || in.getDepth() > outerDepth)) {
2558             if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
2559                 continue;
2560             }
2561             if (TAG_GRANTED_RUNTIME_PERMISSION.equals(in.getName())) {
2562                 grantedRuntimePermissions.add(readStringAttribute(in, ATTR_NAME));
2563             }
2564             if (TAG_WHITELISTED_RESTRICTED_PERMISSION.equals(in.getName())) {
2565                 whitelistedRestrictedPermissions.add(readStringAttribute(in, ATTR_NAME));
2566 
2567             }
2568             if (TAG_CHILD_SESSION.equals(in.getName())) {
2569                 childSessionIds.add(readIntAttribute(in, ATTR_SESSION_ID, SessionInfo.INVALID_ID));
2570             }
2571         }
2572 
2573         if (grantedRuntimePermissions.size() > 0) {
2574             params.grantedRuntimePermissions = grantedRuntimePermissions
2575                     .stream().toArray(String[]::new);
2576         }
2577 
2578         if (whitelistedRestrictedPermissions.size() > 0) {
2579             params.whitelistedRestrictedPermissions = whitelistedRestrictedPermissions;
2580         }
2581 
2582         int[] childSessionIdsArray;
2583         if (childSessionIds.size() > 0) {
2584             childSessionIdsArray = childSessionIds.stream().mapToInt(i -> i).toArray();
2585         } else {
2586             childSessionIdsArray = EMPTY_CHILD_SESSION_ARRAY;
2587         }
2588 
2589         return new PackageInstallerSession(callback, context, pm, sessionProvider,
2590                 installerThread, stagingManager, sessionId, userId, installerPackageName,
2591                 installerUid, params, createdMillis, stageDir, stageCid, prepared, committed,
2592                 sealed, childSessionIdsArray, parentSessionId, isReady, isFailed, isApplied,
2593                 stagedSessionErrorCode, stagedSessionErrorMessage);
2594     }
2595 
2596     /**
2597      * Reads the session ID from a child session tag stored in the provided {@link XmlPullParser}
2598      */
readChildSessionIdFromXml(@onNull XmlPullParser in)2599     static int readChildSessionIdFromXml(@NonNull XmlPullParser in) {
2600         return readIntAttribute(in, ATTR_SESSION_ID, SessionInfo.INVALID_ID);
2601     }
2602 }
2603