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