PackageInstallerSession.java revision 544b3835ddf97dfb982b532e957094ea806c8bc4
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 17package com.android.server.pm; 18 19import static android.content.pm.PackageManager.INSTALL_FAILED_ABORTED; 20import static android.content.pm.PackageManager.INSTALL_FAILED_CONTAINER_ERROR; 21import static android.content.pm.PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE; 22import static android.content.pm.PackageManager.INSTALL_FAILED_INTERNAL_ERROR; 23import static android.content.pm.PackageManager.INSTALL_FAILED_INVALID_APK; 24import static android.system.OsConstants.O_CREAT; 25import static android.system.OsConstants.O_RDONLY; 26import static android.system.OsConstants.O_WRONLY; 27 28import static com.android.internal.util.XmlUtils.readBitmapAttribute; 29import static com.android.internal.util.XmlUtils.readBooleanAttribute; 30import static com.android.internal.util.XmlUtils.readIntAttribute; 31import static com.android.internal.util.XmlUtils.readLongAttribute; 32import static com.android.internal.util.XmlUtils.readStringAttribute; 33import static com.android.internal.util.XmlUtils.readUriAttribute; 34import static com.android.internal.util.XmlUtils.writeBooleanAttribute; 35import static com.android.internal.util.XmlUtils.writeIntAttribute; 36import static com.android.internal.util.XmlUtils.writeLongAttribute; 37import static com.android.internal.util.XmlUtils.writeStringAttribute; 38import static com.android.internal.util.XmlUtils.writeUriAttribute; 39import static com.android.server.pm.PackageInstallerService.prepareExternalStageCid; 40import static com.android.server.pm.PackageInstallerService.prepareStageDir; 41 42import android.Manifest; 43import android.annotation.NonNull; 44import android.annotation.Nullable; 45import android.app.admin.DevicePolicyManager; 46import android.content.Context; 47import android.content.Intent; 48import android.content.IntentSender; 49import android.content.pm.ApplicationInfo; 50import android.content.pm.IPackageInstallObserver2; 51import android.content.pm.IPackageInstallerSession; 52import android.content.pm.PackageInfo; 53import android.content.pm.PackageInstaller; 54import android.content.pm.PackageInstaller.SessionInfo; 55import android.content.pm.PackageInstaller.SessionParams; 56import android.content.pm.PackageManager; 57import android.content.pm.PackageParser; 58import android.content.pm.PackageParser.ApkLite; 59import android.content.pm.PackageParser.PackageLite; 60import android.content.pm.PackageParser.PackageParserException; 61import android.content.pm.Signature; 62import android.graphics.Bitmap; 63import android.graphics.BitmapFactory; 64import android.os.Binder; 65import android.os.Bundle; 66import android.os.FileBridge; 67import android.os.FileUtils; 68import android.os.Handler; 69import android.os.Looper; 70import android.os.Message; 71import android.os.ParcelFileDescriptor; 72import android.os.ParcelableException; 73import android.os.Process; 74import android.os.RemoteException; 75import android.os.RevocableFileDescriptor; 76import android.os.UserHandle; 77import android.os.storage.StorageManager; 78import android.system.ErrnoException; 79import android.system.Os; 80import android.system.OsConstants; 81import android.system.StructStat; 82import android.text.TextUtils; 83import android.util.ArraySet; 84import android.util.ExceptionUtils; 85import android.util.MathUtils; 86import android.util.Slog; 87 88import com.android.internal.annotations.GuardedBy; 89import com.android.internal.content.NativeLibraryHelper; 90import com.android.internal.content.PackageHelper; 91import com.android.internal.util.ArrayUtils; 92import com.android.internal.util.IndentingPrintWriter; 93import com.android.internal.util.Preconditions; 94import com.android.server.pm.Installer.InstallerException; 95import com.android.server.pm.PackageInstallerService.PackageInstallObserverAdapter; 96 97import libcore.io.IoUtils; 98import libcore.io.Libcore; 99 100import org.xmlpull.v1.XmlPullParser; 101import org.xmlpull.v1.XmlPullParserException; 102import org.xmlpull.v1.XmlSerializer; 103 104import java.io.File; 105import java.io.FileDescriptor; 106import java.io.FileFilter; 107import java.io.FileOutputStream; 108import java.io.IOException; 109import java.security.cert.Certificate; 110import java.util.ArrayList; 111import java.util.Arrays; 112import java.util.List; 113import java.util.concurrent.atomic.AtomicInteger; 114 115public class PackageInstallerSession extends IPackageInstallerSession.Stub { 116 private static final String TAG = "PackageInstaller"; 117 private static final boolean LOGD = true; 118 private static final String REMOVE_SPLIT_MARKER_EXTENSION = ".removed"; 119 120 private static final int MSG_COMMIT = 0; 121 122 /** XML constants used for persisting a session */ 123 static final String TAG_SESSION = "session"; 124 private static final String TAG_GRANTED_RUNTIME_PERMISSION = "granted-runtime-permission"; 125 private static final String ATTR_SESSION_ID = "sessionId"; 126 private static final String ATTR_USER_ID = "userId"; 127 private static final String ATTR_INSTALLER_PACKAGE_NAME = "installerPackageName"; 128 private static final String ATTR_INSTALLER_UID = "installerUid"; 129 private static final String ATTR_CREATED_MILLIS = "createdMillis"; 130 private static final String ATTR_SESSION_STAGE_DIR = "sessionStageDir"; 131 private static final String ATTR_SESSION_STAGE_CID = "sessionStageCid"; 132 private static final String ATTR_PREPARED = "prepared"; 133 private static final String ATTR_SEALED = "sealed"; 134 private static final String ATTR_MODE = "mode"; 135 private static final String ATTR_INSTALL_FLAGS = "installFlags"; 136 private static final String ATTR_INSTALL_LOCATION = "installLocation"; 137 private static final String ATTR_SIZE_BYTES = "sizeBytes"; 138 private static final String ATTR_APP_PACKAGE_NAME = "appPackageName"; 139 @Deprecated 140 private static final String ATTR_APP_ICON = "appIcon"; 141 private static final String ATTR_APP_LABEL = "appLabel"; 142 private static final String ATTR_ORIGINATING_URI = "originatingUri"; 143 private static final String ATTR_ORIGINATING_UID = "originatingUid"; 144 private static final String ATTR_REFERRER_URI = "referrerUri"; 145 private static final String ATTR_ABI_OVERRIDE = "abiOverride"; 146 private static final String ATTR_VOLUME_UUID = "volumeUuid"; 147 private static final String ATTR_NAME = "name"; 148 private static final String ATTR_INSTALL_REASON = "installRason"; 149 150 // TODO: enforce INSTALL_ALLOW_TEST 151 // TODO: enforce INSTALL_ALLOW_DOWNGRADE 152 153 private final PackageInstallerService.InternalCallback mCallback; 154 private final Context mContext; 155 private final PackageManagerService mPm; 156 private final Handler mHandler; 157 158 final int sessionId; 159 final int userId; 160 final SessionParams params; 161 final long createdMillis; 162 final int defaultContainerGid; 163 164 /** Staging location where client data is written. */ 165 final File stageDir; 166 final String stageCid; 167 168 private final AtomicInteger mActiveCount = new AtomicInteger(); 169 170 private final Object mLock = new Object(); 171 172 /** Uid of the creator of this session. */ 173 private final int mOriginalInstallerUid; 174 175 /** Package of the owner of the installer session */ 176 @GuardedBy("mLock") 177 private String mInstallerPackageName; 178 179 /** Uid of the owner of the installer session */ 180 @GuardedBy("mLock") 181 private int mInstallerUid; 182 183 @GuardedBy("mLock") 184 private float mClientProgress = 0; 185 @GuardedBy("mLock") 186 private float mInternalProgress = 0; 187 188 @GuardedBy("mLock") 189 private float mProgress = 0; 190 @GuardedBy("mLock") 191 private float mReportedProgress = -1; 192 193 /** State of the session. */ 194 @GuardedBy("mLock") 195 private boolean mPrepared = false; 196 @GuardedBy("mLock") 197 private boolean mSealed = false; 198 @GuardedBy("mLock") 199 private boolean mCommitted = false; 200 @GuardedBy("mLock") 201 private boolean mRelinquished = false; 202 @GuardedBy("mLock") 203 private boolean mDestroyed = false; 204 205 /** Permissions have been accepted by the user (see {@link #setPermissionsResult}) */ 206 @GuardedBy("mLock") 207 private boolean mPermissionsManuallyAccepted = false; 208 209 @GuardedBy("mLock") 210 private int mFinalStatus; 211 @GuardedBy("mLock") 212 private String mFinalMessage; 213 214 @GuardedBy("mLock") 215 private final ArrayList<RevocableFileDescriptor> mFds = new ArrayList<>(); 216 @GuardedBy("mLock") 217 private final ArrayList<FileBridge> mBridges = new ArrayList<>(); 218 219 @GuardedBy("mLock") 220 private IPackageInstallObserver2 mRemoteObserver; 221 222 /** Fields derived from commit parsing */ 223 @GuardedBy("mLock") 224 private String mPackageName; 225 @GuardedBy("mLock") 226 private int mVersionCode; 227 @GuardedBy("mLock") 228 private Signature[] mSignatures; 229 @GuardedBy("mLock") 230 private Certificate[][] mCertificates; 231 232 /** 233 * Path to the validated base APK for this session, which may point at an 234 * APK inside the session (when the session defines the base), or it may 235 * point at the existing base APK (when adding splits to an existing app). 236 * <p> 237 * This is used when confirming permissions, since we can't fully stage the 238 * session inside an ASEC before confirming with user. 239 */ 240 @GuardedBy("mLock") 241 private File mResolvedBaseFile; 242 243 @GuardedBy("mLock") 244 private File mResolvedStageDir; 245 246 @GuardedBy("mLock") 247 private final List<File> mResolvedStagedFiles = new ArrayList<>(); 248 @GuardedBy("mLock") 249 private final List<File> mResolvedInheritedFiles = new ArrayList<>(); 250 @GuardedBy("mLock") 251 private final List<String> mResolvedInstructionSets = new ArrayList<>(); 252 @GuardedBy("mLock") 253 private File mInheritedFilesBase; 254 255 private static final FileFilter sAddedFilter = new FileFilter() { 256 @Override 257 public boolean accept(File file) { 258 // Installers can't stage directories, so it's fine to ignore 259 // entries like "lost+found". 260 if (file.isDirectory()) return false; 261 if (file.getName().endsWith(REMOVE_SPLIT_MARKER_EXTENSION)) return false; 262 return true; 263 } 264 }; 265 private static final FileFilter sRemovedFilter = new FileFilter() { 266 @Override 267 public boolean accept(File file) { 268 if (file.isDirectory()) return false; 269 if (!file.getName().endsWith(REMOVE_SPLIT_MARKER_EXTENSION)) return false; 270 return true; 271 } 272 }; 273 274 private final Handler.Callback mHandlerCallback = new Handler.Callback() { 275 @Override 276 public boolean handleMessage(Message msg) { 277 synchronized (mLock) { 278 if (msg.obj != null) { 279 mRemoteObserver = (IPackageInstallObserver2) msg.obj; 280 } 281 try { 282 commitLocked(); 283 } catch (PackageManagerException e) { 284 final String completeMsg = ExceptionUtils.getCompleteMessage(e); 285 Slog.e(TAG, "Commit of session " + sessionId + " failed: " + completeMsg); 286 destroyInternal(); 287 dispatchSessionFinished(e.error, completeMsg, null); 288 } 289 } 290 291 return true; 292 } 293 }; 294 295 /** 296 * @return {@code true} iff the installing is app an device owner? 297 */ 298 private boolean isInstallerDeviceOwnerLocked() { 299 DevicePolicyManager dpm = (DevicePolicyManager) mContext.getSystemService( 300 Context.DEVICE_POLICY_SERVICE); 301 302 return (dpm != null) && dpm.isDeviceOwnerAppOnCallingUser( 303 mInstallerPackageName); 304 } 305 306 /** 307 * Checks if the permissions still need to be confirmed. 308 * 309 * <p>This is dependant on the identity of the installer, hence this cannot be cached if the 310 * installer might still {@link #transfer(String) change}. 311 * 312 * @return {@code true} iff we need to ask to confirm the permissions? 313 */ 314 private boolean needToAskForPermissionsLocked() { 315 if (mPermissionsManuallyAccepted) { 316 return false; 317 } 318 319 final boolean isPermissionGranted = 320 (mPm.checkUidPermission(android.Manifest.permission.INSTALL_PACKAGES, 321 mInstallerUid) == PackageManager.PERMISSION_GRANTED); 322 final boolean isInstallerRoot = (mInstallerUid == Process.ROOT_UID); 323 final boolean forcePermissionPrompt = 324 (params.installFlags & PackageManager.INSTALL_FORCE_PERMISSION_PROMPT) != 0; 325 326 // Device owners are allowed to silently install packages, so the permission check is 327 // waived if the installer is the device owner. 328 return forcePermissionPrompt || !(isPermissionGranted || isInstallerRoot 329 || isInstallerDeviceOwnerLocked()); 330 } 331 332 public PackageInstallerSession(PackageInstallerService.InternalCallback callback, 333 Context context, PackageManagerService pm, Looper looper, int sessionId, int userId, 334 String installerPackageName, int installerUid, SessionParams params, long createdMillis, 335 File stageDir, String stageCid, boolean prepared, boolean sealed) { 336 mCallback = callback; 337 mContext = context; 338 mPm = pm; 339 mHandler = new Handler(looper, mHandlerCallback); 340 341 this.sessionId = sessionId; 342 this.userId = userId; 343 mOriginalInstallerUid = installerUid; 344 mInstallerPackageName = installerPackageName; 345 mInstallerUid = installerUid; 346 this.params = params; 347 this.createdMillis = createdMillis; 348 this.stageDir = stageDir; 349 this.stageCid = stageCid; 350 351 if ((stageDir == null) == (stageCid == null)) { 352 throw new IllegalArgumentException( 353 "Exactly one of stageDir or stageCid stage must be set"); 354 } 355 356 mPrepared = prepared; 357 mSealed = sealed; 358 359 final long identity = Binder.clearCallingIdentity(); 360 try { 361 final int uid = mPm.getPackageUid(PackageManagerService.DEFAULT_CONTAINER_PACKAGE, 362 PackageManager.MATCH_SYSTEM_ONLY, UserHandle.USER_SYSTEM); 363 defaultContainerGid = UserHandle.getSharedAppGid(uid); 364 } finally { 365 Binder.restoreCallingIdentity(identity); 366 } 367 } 368 369 public SessionInfo generateInfo() { 370 return generateInfo(true); 371 } 372 373 public SessionInfo generateInfo(boolean includeIcon) { 374 final SessionInfo info = new SessionInfo(); 375 synchronized (mLock) { 376 info.sessionId = sessionId; 377 info.installerPackageName = mInstallerPackageName; 378 info.resolvedBaseCodePath = (mResolvedBaseFile != null) ? 379 mResolvedBaseFile.getAbsolutePath() : null; 380 info.progress = mProgress; 381 info.sealed = mSealed; 382 info.active = mActiveCount.get() > 0; 383 384 info.mode = params.mode; 385 info.installReason = params.installReason; 386 info.sizeBytes = params.sizeBytes; 387 info.appPackageName = params.appPackageName; 388 if (includeIcon) { 389 info.appIcon = params.appIcon; 390 } 391 info.appLabel = params.appLabel; 392 393 info.installLocation = params.installLocation; 394 info.originatingUri = params.originatingUri; 395 info.originatingUid = params.originatingUid; 396 info.referrerUri = params.referrerUri; 397 info.grantedRuntimePermissions = params.grantedRuntimePermissions; 398 info.installFlags = params.installFlags; 399 } 400 return info; 401 } 402 403 public boolean isPrepared() { 404 synchronized (mLock) { 405 return mPrepared; 406 } 407 } 408 409 public boolean isSealed() { 410 synchronized (mLock) { 411 return mSealed; 412 } 413 } 414 415 private void assertPreparedAndNotSealedLocked(String cookie) { 416 assertPreparedAndNotCommittedOrDestroyedLocked(cookie); 417 if (mSealed) { 418 throw new SecurityException(cookie + " not allowed after sealing"); 419 } 420 } 421 422 private void assertPreparedAndNotCommittedOrDestroyedLocked(String cookie) { 423 assertPreparedAndNotDestroyedLocked(cookie); 424 if (mCommitted) { 425 throw new SecurityException(cookie + " not allowed after commit"); 426 } 427 } 428 429 private void assertPreparedAndNotDestroyedLocked(String cookie) { 430 if (!mPrepared) { 431 throw new IllegalStateException(cookie + " before prepared"); 432 } 433 if (mDestroyed) { 434 throw new SecurityException(cookie + " not allowed after destruction"); 435 } 436 } 437 438 /** 439 * Resolve the actual location where staged data should be written. This 440 * might point at an ASEC mount point, which is why we delay path resolution 441 * until someone actively works with the session. 442 */ 443 private File resolveStageDirLocked() throws IOException { 444 if (mResolvedStageDir == null) { 445 if (stageDir != null) { 446 mResolvedStageDir = stageDir; 447 } else { 448 final String path = PackageHelper.getSdDir(stageCid); 449 if (path != null) { 450 mResolvedStageDir = new File(path); 451 } else { 452 throw new IOException("Failed to resolve path to container " + stageCid); 453 } 454 } 455 } 456 return mResolvedStageDir; 457 } 458 459 @Override 460 public void setClientProgress(float progress) { 461 synchronized (mLock) { 462 assertCallerIsOwnerOrRootLocked(); 463 464 // Always publish first staging movement 465 final boolean forcePublish = (mClientProgress == 0); 466 mClientProgress = progress; 467 computeProgressLocked(forcePublish); 468 } 469 } 470 471 @Override 472 public void addClientProgress(float progress) { 473 synchronized (mLock) { 474 assertCallerIsOwnerOrRootLocked(); 475 476 setClientProgress(mClientProgress + progress); 477 } 478 } 479 480 private void computeProgressLocked(boolean forcePublish) { 481 mProgress = MathUtils.constrain(mClientProgress * 0.8f, 0f, 0.8f) 482 + MathUtils.constrain(mInternalProgress * 0.2f, 0f, 0.2f); 483 484 // Only publish when meaningful change 485 if (forcePublish || Math.abs(mProgress - mReportedProgress) >= 0.01) { 486 mReportedProgress = mProgress; 487 mCallback.onSessionProgressChanged(this, mProgress); 488 } 489 } 490 491 @Override 492 public String[] getNames() { 493 synchronized (mLock) { 494 assertCallerIsOwnerOrRootLocked(); 495 assertPreparedAndNotCommittedOrDestroyedLocked("getNames"); 496 497 try { 498 return resolveStageDirLocked().list(); 499 } catch (IOException e) { 500 throw ExceptionUtils.wrap(e); 501 } 502 } 503 } 504 505 @Override 506 public void removeSplit(String splitName) { 507 if (TextUtils.isEmpty(params.appPackageName)) { 508 throw new IllegalStateException("Must specify package name to remove a split"); 509 } 510 511 synchronized (mLock) { 512 assertCallerIsOwnerOrRootLocked(); 513 assertPreparedAndNotCommittedOrDestroyedLocked("removeSplit"); 514 515 try { 516 createRemoveSplitMarkerLocked(splitName); 517 } catch (IOException e) { 518 throw ExceptionUtils.wrap(e); 519 } 520 } 521 } 522 523 private void createRemoveSplitMarkerLocked(String splitName) throws IOException { 524 try { 525 final String markerName = splitName + REMOVE_SPLIT_MARKER_EXTENSION; 526 if (!FileUtils.isValidExtFilename(markerName)) { 527 throw new IllegalArgumentException("Invalid marker: " + markerName); 528 } 529 final File target = new File(resolveStageDirLocked(), markerName); 530 target.createNewFile(); 531 Os.chmod(target.getAbsolutePath(), 0 /*mode*/); 532 } catch (ErrnoException e) { 533 throw e.rethrowAsIOException(); 534 } 535 } 536 537 @Override 538 public ParcelFileDescriptor openWrite(String name, long offsetBytes, long lengthBytes) { 539 try { 540 return openWriteInternal(name, offsetBytes, lengthBytes); 541 } catch (IOException e) { 542 throw ExceptionUtils.wrap(e); 543 } 544 } 545 546 private ParcelFileDescriptor openWriteInternal(String name, long offsetBytes, long lengthBytes) 547 throws IOException { 548 // Quick sanity check of state, and allocate a pipe for ourselves. We 549 // then do heavy disk allocation outside the lock, but this open pipe 550 // will block any attempted install transitions. 551 final RevocableFileDescriptor fd; 552 final FileBridge bridge; 553 final File stageDir; 554 synchronized (mLock) { 555 assertCallerIsOwnerOrRootLocked(); 556 assertPreparedAndNotSealedLocked("openWrite"); 557 558 if (PackageInstaller.ENABLE_REVOCABLE_FD) { 559 fd = new RevocableFileDescriptor(); 560 bridge = null; 561 mFds.add(fd); 562 } else { 563 fd = null; 564 bridge = new FileBridge(); 565 mBridges.add(bridge); 566 } 567 568 stageDir = resolveStageDirLocked(); 569 } 570 571 try { 572 // Use installer provided name for now; we always rename later 573 if (!FileUtils.isValidExtFilename(name)) { 574 throw new IllegalArgumentException("Invalid name: " + name); 575 } 576 final File target; 577 final long identity = Binder.clearCallingIdentity(); 578 try { 579 target = new File(stageDir, name); 580 } finally { 581 Binder.restoreCallingIdentity(identity); 582 } 583 584 // TODO: this should delegate to DCS so the system process avoids 585 // holding open FDs into containers. 586 final FileDescriptor targetFd = Libcore.os.open(target.getAbsolutePath(), 587 O_CREAT | O_WRONLY, 0644); 588 Os.chmod(target.getAbsolutePath(), 0644); 589 590 // If caller specified a total length, allocate it for them. Free up 591 // cache space to grow, if needed. 592 if (stageDir != null && lengthBytes > 0) { 593 mContext.getSystemService(StorageManager.class).allocateBytes(targetFd, lengthBytes, 594 PackageHelper.translateAllocateFlags(params.installFlags)); 595 } 596 597 if (offsetBytes > 0) { 598 Libcore.os.lseek(targetFd, offsetBytes, OsConstants.SEEK_SET); 599 } 600 601 if (PackageInstaller.ENABLE_REVOCABLE_FD) { 602 fd.init(mContext, targetFd); 603 return fd.getRevocableFileDescriptor(); 604 } else { 605 bridge.setTargetFile(targetFd); 606 bridge.start(); 607 return new ParcelFileDescriptor(bridge.getClientSocket()); 608 } 609 610 } catch (ErrnoException e) { 611 throw e.rethrowAsIOException(); 612 } 613 } 614 615 @Override 616 public ParcelFileDescriptor openRead(String name) { 617 synchronized (mLock) { 618 assertCallerIsOwnerOrRootLocked(); 619 assertPreparedAndNotCommittedOrDestroyedLocked("openRead"); 620 try { 621 return openReadInternalLocked(name); 622 } catch (IOException e) { 623 throw ExceptionUtils.wrap(e); 624 } 625 } 626 } 627 628 private ParcelFileDescriptor openReadInternalLocked(String name) throws IOException { 629 try { 630 if (!FileUtils.isValidExtFilename(name)) { 631 throw new IllegalArgumentException("Invalid name: " + name); 632 } 633 final File target = new File(resolveStageDirLocked(), name); 634 final FileDescriptor targetFd = Libcore.os.open(target.getAbsolutePath(), O_RDONLY, 0); 635 return new ParcelFileDescriptor(targetFd); 636 } catch (ErrnoException e) { 637 throw e.rethrowAsIOException(); 638 } 639 } 640 641 /** 642 * Check if the caller is the owner of this session. Otherwise throw a 643 * {@link SecurityException}. 644 */ 645 private void assertCallerIsOwnerOrRootLocked() { 646 final int callingUid = Binder.getCallingUid(); 647 if (callingUid != Process.ROOT_UID && callingUid != mInstallerUid) { 648 throw new SecurityException("Session does not belong to uid " + callingUid); 649 } 650 } 651 652 /** 653 * If anybody is reading or writing data of the session, throw an {@link SecurityException}. 654 */ 655 private void assertNoWriteFileTransfersOpenLocked() { 656 // Verify that all writers are hands-off 657 for (RevocableFileDescriptor fd : mFds) { 658 if (!fd.isRevoked()) { 659 throw new SecurityException("Files still open"); 660 } 661 } 662 for (FileBridge bridge : mBridges) { 663 if (!bridge.isClosed()) { 664 throw new SecurityException("Files still open"); 665 } 666 } 667 } 668 669 @Override 670 public void commit(IntentSender statusReceiver, boolean forTransfer) { 671 Preconditions.checkNotNull(statusReceiver); 672 673 // Cache package manager data without the lock held 674 final PackageInfo installedPkgInfo = mPm.getPackageInfo( 675 params.appPackageName, PackageManager.GET_SIGNATURES 676 | PackageManager.MATCH_STATIC_SHARED_LIBRARIES /*flags*/, userId); 677 678 final boolean wasSealed; 679 synchronized (mLock) { 680 assertCallerIsOwnerOrRootLocked(); 681 assertPreparedAndNotDestroyedLocked("commit"); 682 683 if (forTransfer) { 684 mContext.enforceCallingOrSelfPermission(Manifest.permission.INSTALL_PACKAGES, null); 685 686 if (mInstallerUid == mOriginalInstallerUid) { 687 throw new IllegalArgumentException("Session has not been transferred"); 688 } 689 } else { 690 if (mInstallerUid != mOriginalInstallerUid) { 691 throw new IllegalArgumentException("Session has been transferred"); 692 } 693 } 694 695 wasSealed = mSealed; 696 if (!mSealed) { 697 try { 698 sealAndValidateLocked(installedPkgInfo); 699 } catch (PackageManagerException e) { 700 // Do now throw an exception here to stay compatible with O and older 701 destroyInternal(); 702 dispatchSessionFinished(e.error, ExceptionUtils.getCompleteMessage(e), null); 703 return; 704 } 705 } 706 707 // Client staging is fully done at this point 708 mClientProgress = 1f; 709 computeProgressLocked(true); 710 711 // This ongoing commit should keep session active, even though client 712 // will probably close their end. 713 mActiveCount.incrementAndGet(); 714 715 mCommitted = true; 716 final PackageInstallObserverAdapter adapter = new PackageInstallObserverAdapter( 717 mContext, statusReceiver, sessionId, isInstallerDeviceOwnerLocked(), userId); 718 mHandler.obtainMessage(MSG_COMMIT, adapter.getBinder()).sendToTarget(); 719 } 720 721 if (!wasSealed) { 722 // Persist the fact that we've sealed ourselves to prevent 723 // mutations of any hard links we create. We do this without holding 724 // the session lock, since otherwise it's a lock inversion. 725 mCallback.onSessionSealedBlocking(this); 726 } 727 } 728 729 /** 730 * Seal the session to prevent further modification and validate the contents of it. 731 * 732 * <p>The session will be sealed after calling this method even if it failed. 733 * 734 * @param pkgInfo The package info for {@link #params}.packagename 735 */ 736 private void sealAndValidateLocked(@Nullable PackageInfo pkgInfo) 737 throws PackageManagerException { 738 assertNoWriteFileTransfersOpenLocked(); 739 740 mSealed = true; 741 742 // Verify that stage looks sane with respect to existing application. 743 // This currently only ensures packageName, versionCode, and certificate 744 // consistency. 745 validateInstallLocked(pkgInfo); 746 747 // Read transfers from the original owner stay open, but as the session's data 748 // cannot be modified anymore, there is no leak of information. 749 } 750 751 @Override 752 public void transfer(String packageName) { 753 Preconditions.checkNotNull(packageName); 754 755 ApplicationInfo newOwnerAppInfo = mPm.getApplicationInfo(packageName, 0, userId); 756 if (newOwnerAppInfo == null) { 757 throw new ParcelableException(new PackageManager.NameNotFoundException(packageName)); 758 } 759 760 if (PackageManager.PERMISSION_GRANTED != mPm.checkUidPermission( 761 Manifest.permission.INSTALL_PACKAGES, newOwnerAppInfo.uid)) { 762 throw new SecurityException("Destination package " + packageName + " does not have " 763 + "the " + Manifest.permission.INSTALL_PACKAGES + " permission"); 764 } 765 766 // Cache package manager data without the lock held 767 final PackageInfo installedPkgInfo = mPm.getPackageInfo( 768 params.appPackageName, PackageManager.GET_SIGNATURES 769 | PackageManager.MATCH_STATIC_SHARED_LIBRARIES /*flags*/, userId); 770 771 // Only install flags that can be verified by the app the session is transferred to are 772 // allowed. The parameters can be read via PackageInstaller.SessionInfo. 773 if (!params.areHiddenOptionsSet()) { 774 throw new SecurityException("Can only transfer sessions that use public options"); 775 } 776 777 synchronized (mLock) { 778 assertCallerIsOwnerOrRootLocked(); 779 assertPreparedAndNotSealedLocked("transfer"); 780 781 try { 782 sealAndValidateLocked(installedPkgInfo); 783 } catch (PackageManagerException e) { 784 throw new IllegalArgumentException("Package is not valid", e); 785 } 786 787 if (!mPackageName.equals(mInstallerPackageName)) { 788 throw new SecurityException("Can only transfer sessions that update the original " 789 + "installer"); 790 } 791 792 mInstallerPackageName = packageName; 793 mInstallerUid = newOwnerAppInfo.uid; 794 } 795 796 // Persist the fact that we've sealed ourselves to prevent 797 // mutations of any hard links we create. We do this without holding 798 // the session lock, since otherwise it's a lock inversion. 799 mCallback.onSessionSealedBlocking(this); 800 } 801 802 private void commitLocked() 803 throws PackageManagerException { 804 if (mDestroyed) { 805 throw new PackageManagerException(INSTALL_FAILED_INTERNAL_ERROR, "Session destroyed"); 806 } 807 if (!mSealed) { 808 throw new PackageManagerException(INSTALL_FAILED_INTERNAL_ERROR, "Session not sealed"); 809 } 810 811 try { 812 resolveStageDirLocked(); 813 } catch (IOException e) { 814 throw new PackageManagerException(INSTALL_FAILED_CONTAINER_ERROR, 815 "Failed to resolve stage location", e); 816 } 817 818 Preconditions.checkNotNull(mPackageName); 819 Preconditions.checkNotNull(mSignatures); 820 Preconditions.checkNotNull(mResolvedBaseFile); 821 822 if (needToAskForPermissionsLocked()) { 823 // User needs to accept permissions; give installer an intent they 824 // can use to involve user. 825 final Intent intent = new Intent(PackageInstaller.ACTION_CONFIRM_PERMISSIONS); 826 intent.setPackage(mContext.getPackageManager().getPermissionControllerPackageName()); 827 intent.putExtra(PackageInstaller.EXTRA_SESSION_ID, sessionId); 828 try { 829 mRemoteObserver.onUserActionRequired(intent); 830 } catch (RemoteException ignored) { 831 } 832 833 // Commit was keeping session marked as active until now; release 834 // that extra refcount so session appears idle. 835 closeInternal(false); 836 return; 837 } 838 839 if (stageCid != null) { 840 // Figure out the final installed size and resize the container once 841 // and for all. Internally the parser handles straddling between two 842 // locations when inheriting. 843 final long finalSize = calculateInstalledSize(); 844 resizeContainer(stageCid, finalSize); 845 } 846 847 // Inherit any packages and native libraries from existing install that 848 // haven't been overridden. 849 if (params.mode == SessionParams.MODE_INHERIT_EXISTING) { 850 try { 851 final List<File> fromFiles = mResolvedInheritedFiles; 852 final File toDir = resolveStageDirLocked(); 853 854 if (LOGD) Slog.d(TAG, "Inherited files: " + mResolvedInheritedFiles); 855 if (!mResolvedInheritedFiles.isEmpty() && mInheritedFilesBase == null) { 856 throw new IllegalStateException("mInheritedFilesBase == null"); 857 } 858 859 if (isLinkPossible(fromFiles, toDir)) { 860 if (!mResolvedInstructionSets.isEmpty()) { 861 final File oatDir = new File(toDir, "oat"); 862 createOatDirs(mResolvedInstructionSets, oatDir); 863 } 864 linkFiles(fromFiles, toDir, mInheritedFilesBase); 865 } else { 866 // TODO: this should delegate to DCS so the system process 867 // avoids holding open FDs into containers. 868 copyFiles(fromFiles, toDir); 869 } 870 } catch (IOException e) { 871 throw new PackageManagerException(INSTALL_FAILED_INSUFFICIENT_STORAGE, 872 "Failed to inherit existing install", e); 873 } 874 } 875 876 // TODO: surface more granular state from dexopt 877 mInternalProgress = 0.5f; 878 computeProgressLocked(true); 879 880 // Unpack native libraries 881 extractNativeLibraries(mResolvedStageDir, params.abiOverride); 882 883 // Container is ready to go, let's seal it up! 884 if (stageCid != null) { 885 finalizeAndFixContainer(stageCid); 886 } 887 888 // We've reached point of no return; call into PMS to install the stage. 889 // Regardless of success or failure we always destroy session. 890 final IPackageInstallObserver2 localObserver = new IPackageInstallObserver2.Stub() { 891 @Override 892 public void onUserActionRequired(Intent intent) { 893 throw new IllegalStateException(); 894 } 895 896 @Override 897 public void onPackageInstalled(String basePackageName, int returnCode, String msg, 898 Bundle extras) { 899 destroyInternal(); 900 dispatchSessionFinished(returnCode, msg, extras); 901 } 902 }; 903 904 final UserHandle user; 905 if ((params.installFlags & PackageManager.INSTALL_ALL_USERS) != 0) { 906 user = UserHandle.ALL; 907 } else { 908 user = new UserHandle(userId); 909 } 910 911 mRelinquished = true; 912 mPm.installStage(mPackageName, stageDir, stageCid, localObserver, params, 913 mInstallerPackageName, mInstallerUid, user, mCertificates); 914 } 915 916 /** 917 * Validate install by confirming that all application packages are have 918 * consistent package name, version code, and signing certificates. 919 * <p> 920 * Clears and populates {@link #mResolvedBaseFile}, 921 * {@link #mResolvedStagedFiles}, and {@link #mResolvedInheritedFiles}. 922 * <p> 923 * Renames package files in stage to match split names defined inside. 924 * <p> 925 * Note that upgrade compatibility is still performed by 926 * {@link PackageManagerService}. 927 */ 928 private void validateInstallLocked(@Nullable PackageInfo pkgInfo) 929 throws PackageManagerException { 930 mPackageName = null; 931 mVersionCode = -1; 932 mSignatures = null; 933 934 mResolvedBaseFile = null; 935 mResolvedStagedFiles.clear(); 936 mResolvedInheritedFiles.clear(); 937 938 final File[] removedFiles = mResolvedStageDir.listFiles(sRemovedFilter); 939 final List<String> removeSplitList = new ArrayList<>(); 940 if (!ArrayUtils.isEmpty(removedFiles)) { 941 for (File removedFile : removedFiles) { 942 final String fileName = removedFile.getName(); 943 final String splitName = fileName.substring( 944 0, fileName.length() - REMOVE_SPLIT_MARKER_EXTENSION.length()); 945 removeSplitList.add(splitName); 946 } 947 } 948 949 final File[] addedFiles = mResolvedStageDir.listFiles(sAddedFilter); 950 if (ArrayUtils.isEmpty(addedFiles) && removeSplitList.size() == 0) { 951 throw new PackageManagerException(INSTALL_FAILED_INVALID_APK, "No packages staged"); 952 } 953 // Verify that all staged packages are internally consistent 954 final ArraySet<String> stagedSplits = new ArraySet<>(); 955 for (File addedFile : addedFiles) { 956 final ApkLite apk; 957 try { 958 int flags = PackageParser.PARSE_COLLECT_CERTIFICATES; 959 if ((params.installFlags & PackageManager.INSTALL_INSTANT_APP) != 0) { 960 flags |= PackageParser.PARSE_IS_EPHEMERAL; 961 } 962 apk = PackageParser.parseApkLite(addedFile, flags); 963 } catch (PackageParserException e) { 964 throw PackageManagerException.from(e); 965 } 966 967 if (!stagedSplits.add(apk.splitName)) { 968 throw new PackageManagerException(INSTALL_FAILED_INVALID_APK, 969 "Split " + apk.splitName + " was defined multiple times"); 970 } 971 972 // Use first package to define unknown values 973 if (mPackageName == null) { 974 mPackageName = apk.packageName; 975 mVersionCode = apk.versionCode; 976 } 977 if (mSignatures == null) { 978 mSignatures = apk.signatures; 979 mCertificates = apk.certificates; 980 } 981 982 assertApkConsistentLocked(String.valueOf(addedFile), apk); 983 984 // Take this opportunity to enforce uniform naming 985 final String targetName; 986 if (apk.splitName == null) { 987 targetName = "base.apk"; 988 } else { 989 targetName = "split_" + apk.splitName + ".apk"; 990 } 991 if (!FileUtils.isValidExtFilename(targetName)) { 992 throw new PackageManagerException(INSTALL_FAILED_INVALID_APK, 993 "Invalid filename: " + targetName); 994 } 995 996 final File targetFile = new File(mResolvedStageDir, targetName); 997 if (!addedFile.equals(targetFile)) { 998 addedFile.renameTo(targetFile); 999 } 1000 1001 // Base is coming from session 1002 if (apk.splitName == null) { 1003 mResolvedBaseFile = targetFile; 1004 } 1005 1006 mResolvedStagedFiles.add(targetFile); 1007 } 1008 1009 if (removeSplitList.size() > 0) { 1010 if (pkgInfo == null) { 1011 throw new PackageManagerException(INSTALL_FAILED_INVALID_APK, 1012 "Missing existing base package for " + mPackageName); 1013 } 1014 1015 // validate split names marked for removal 1016 for (String splitName : removeSplitList) { 1017 if (!ArrayUtils.contains(pkgInfo.splitNames, splitName)) { 1018 throw new PackageManagerException(INSTALL_FAILED_INVALID_APK, 1019 "Split not found: " + splitName); 1020 } 1021 } 1022 1023 // ensure we've got appropriate package name, version code and signatures 1024 if (mPackageName == null) { 1025 mPackageName = pkgInfo.packageName; 1026 mVersionCode = pkgInfo.versionCode; 1027 } 1028 if (mSignatures == null) { 1029 mSignatures = pkgInfo.signatures; 1030 } 1031 } 1032 1033 if (params.mode == SessionParams.MODE_FULL_INSTALL) { 1034 // Full installs must include a base package 1035 if (!stagedSplits.contains(null)) { 1036 throw new PackageManagerException(INSTALL_FAILED_INVALID_APK, 1037 "Full install must include a base package"); 1038 } 1039 1040 } else { 1041 // Partial installs must be consistent with existing install 1042 if (pkgInfo == null || pkgInfo.applicationInfo == null) { 1043 throw new PackageManagerException(INSTALL_FAILED_INVALID_APK, 1044 "Missing existing base package for " + mPackageName); 1045 } 1046 1047 final PackageLite existing; 1048 final ApkLite existingBase; 1049 ApplicationInfo appInfo = pkgInfo.applicationInfo; 1050 try { 1051 existing = PackageParser.parsePackageLite(new File(appInfo.getCodePath()), 0); 1052 existingBase = PackageParser.parseApkLite(new File(appInfo.getBaseCodePath()), 1053 PackageParser.PARSE_COLLECT_CERTIFICATES); 1054 } catch (PackageParserException e) { 1055 throw PackageManagerException.from(e); 1056 } 1057 1058 assertApkConsistentLocked("Existing base", existingBase); 1059 1060 // Inherit base if not overridden 1061 if (mResolvedBaseFile == null) { 1062 mResolvedBaseFile = new File(appInfo.getBaseCodePath()); 1063 mResolvedInheritedFiles.add(mResolvedBaseFile); 1064 } 1065 1066 // Inherit splits if not overridden 1067 if (!ArrayUtils.isEmpty(existing.splitNames)) { 1068 for (int i = 0; i < existing.splitNames.length; i++) { 1069 final String splitName = existing.splitNames[i]; 1070 final File splitFile = new File(existing.splitCodePaths[i]); 1071 final boolean splitRemoved = removeSplitList.contains(splitName); 1072 if (!stagedSplits.contains(splitName) && !splitRemoved) { 1073 mResolvedInheritedFiles.add(splitFile); 1074 } 1075 } 1076 } 1077 1078 // Inherit compiled oat directory. 1079 final File packageInstallDir = (new File(appInfo.getBaseCodePath())).getParentFile(); 1080 mInheritedFilesBase = packageInstallDir; 1081 final File oatDir = new File(packageInstallDir, "oat"); 1082 if (oatDir.exists()) { 1083 final File[] archSubdirs = oatDir.listFiles(); 1084 1085 // Keep track of all instruction sets we've seen compiled output for. 1086 // If we're linking (and not copying) inherited files, we can recreate the 1087 // instruction set hierarchy and link compiled output. 1088 if (archSubdirs != null && archSubdirs.length > 0) { 1089 final String[] instructionSets = InstructionSets.getAllDexCodeInstructionSets(); 1090 for (File archSubDir : archSubdirs) { 1091 // Skip any directory that isn't an ISA subdir. 1092 if (!ArrayUtils.contains(instructionSets, archSubDir.getName())) { 1093 continue; 1094 } 1095 1096 mResolvedInstructionSets.add(archSubDir.getName()); 1097 List<File> oatFiles = Arrays.asList(archSubDir.listFiles()); 1098 1099 // Only add compiled files associated with the base. 1100 // Once b/62269291 is resolved, we can add all compiled files again. 1101 for (File oatFile : oatFiles) { 1102 if (oatFile.getName().equals("base.art") 1103 || oatFile.getName().equals("base.odex") 1104 || oatFile.getName().equals("base.vdex")) { 1105 mResolvedInheritedFiles.add(oatFile); 1106 } 1107 } 1108 } 1109 } 1110 } 1111 } 1112 } 1113 1114 private void assertApkConsistentLocked(String tag, ApkLite apk) 1115 throws PackageManagerException { 1116 if (!mPackageName.equals(apk.packageName)) { 1117 throw new PackageManagerException(INSTALL_FAILED_INVALID_APK, tag + " package " 1118 + apk.packageName + " inconsistent with " + mPackageName); 1119 } 1120 if (params.appPackageName != null && !params.appPackageName.equals(apk.packageName)) { 1121 throw new PackageManagerException(INSTALL_FAILED_INVALID_APK, tag 1122 + " specified package " + params.appPackageName 1123 + " inconsistent with " + apk.packageName); 1124 } 1125 if (mVersionCode != apk.versionCode) { 1126 throw new PackageManagerException(INSTALL_FAILED_INVALID_APK, tag 1127 + " version code " + apk.versionCode + " inconsistent with " 1128 + mVersionCode); 1129 } 1130 if (!Signature.areExactMatch(mSignatures, apk.signatures)) { 1131 throw new PackageManagerException(INSTALL_FAILED_INVALID_APK, 1132 tag + " signatures are inconsistent"); 1133 } 1134 } 1135 1136 /** 1137 * Calculate the final install footprint size, combining both staged and 1138 * existing APKs together and including unpacked native code from both. 1139 */ 1140 private long calculateInstalledSize() throws PackageManagerException { 1141 Preconditions.checkNotNull(mResolvedBaseFile); 1142 1143 final ApkLite baseApk; 1144 try { 1145 baseApk = PackageParser.parseApkLite(mResolvedBaseFile, 0); 1146 } catch (PackageParserException e) { 1147 throw PackageManagerException.from(e); 1148 } 1149 1150 final List<String> splitPaths = new ArrayList<>(); 1151 for (File file : mResolvedStagedFiles) { 1152 if (mResolvedBaseFile.equals(file)) continue; 1153 splitPaths.add(file.getAbsolutePath()); 1154 } 1155 for (File file : mResolvedInheritedFiles) { 1156 if (mResolvedBaseFile.equals(file)) continue; 1157 splitPaths.add(file.getAbsolutePath()); 1158 } 1159 1160 // This is kind of hacky; we're creating a half-parsed package that is 1161 // straddled between the inherited and staged APKs. 1162 final PackageLite pkg = new PackageLite(null, baseApk, null, null, null, null, 1163 splitPaths.toArray(new String[splitPaths.size()]), null); 1164 final boolean isForwardLocked = 1165 (params.installFlags & PackageManager.INSTALL_FORWARD_LOCK) != 0; 1166 1167 try { 1168 return PackageHelper.calculateInstalledSize(pkg, isForwardLocked, params.abiOverride); 1169 } catch (IOException e) { 1170 throw new PackageManagerException(INSTALL_FAILED_INVALID_APK, 1171 "Failed to calculate install size", e); 1172 } 1173 } 1174 1175 /** 1176 * Determine if creating hard links between source and destination is 1177 * possible. That is, do they all live on the same underlying device. 1178 */ 1179 private boolean isLinkPossible(List<File> fromFiles, File toDir) { 1180 try { 1181 final StructStat toStat = Os.stat(toDir.getAbsolutePath()); 1182 for (File fromFile : fromFiles) { 1183 final StructStat fromStat = Os.stat(fromFile.getAbsolutePath()); 1184 if (fromStat.st_dev != toStat.st_dev) { 1185 return false; 1186 } 1187 } 1188 } catch (ErrnoException e) { 1189 Slog.w(TAG, "Failed to detect if linking possible: " + e); 1190 return false; 1191 } 1192 return true; 1193 } 1194 1195 /** 1196 * @return the uid of the owner this session 1197 */ 1198 public int getInstallerUid() { 1199 synchronized (mLock) { 1200 return mInstallerUid; 1201 } 1202 } 1203 1204 private static String getRelativePath(File file, File base) throws IOException { 1205 final String pathStr = file.getAbsolutePath(); 1206 final String baseStr = base.getAbsolutePath(); 1207 // Don't allow relative paths. 1208 if (pathStr.contains("/.") ) { 1209 throw new IOException("Invalid path (was relative) : " + pathStr); 1210 } 1211 1212 if (pathStr.startsWith(baseStr)) { 1213 return pathStr.substring(baseStr.length()); 1214 } 1215 1216 throw new IOException("File: " + pathStr + " outside base: " + baseStr); 1217 } 1218 1219 private void createOatDirs(List<String> instructionSets, File fromDir) 1220 throws PackageManagerException { 1221 for (String instructionSet : instructionSets) { 1222 try { 1223 mPm.mInstaller.createOatDir(fromDir.getAbsolutePath(), instructionSet); 1224 } catch (InstallerException e) { 1225 throw PackageManagerException.from(e); 1226 } 1227 } 1228 } 1229 1230 private void linkFiles(List<File> fromFiles, File toDir, File fromDir) 1231 throws IOException { 1232 for (File fromFile : fromFiles) { 1233 final String relativePath = getRelativePath(fromFile, fromDir); 1234 try { 1235 mPm.mInstaller.linkFile(relativePath, fromDir.getAbsolutePath(), 1236 toDir.getAbsolutePath()); 1237 } catch (InstallerException e) { 1238 throw new IOException("failed linkOrCreateDir(" + relativePath + ", " 1239 + fromDir + ", " + toDir + ")", e); 1240 } 1241 } 1242 1243 Slog.d(TAG, "Linked " + fromFiles.size() + " files into " + toDir); 1244 } 1245 1246 private static void copyFiles(List<File> fromFiles, File toDir) throws IOException { 1247 // Remove any partial files from previous attempt 1248 for (File file : toDir.listFiles()) { 1249 if (file.getName().endsWith(".tmp")) { 1250 file.delete(); 1251 } 1252 } 1253 1254 for (File fromFile : fromFiles) { 1255 final File tmpFile = File.createTempFile("inherit", ".tmp", toDir); 1256 if (LOGD) Slog.d(TAG, "Copying " + fromFile + " to " + tmpFile); 1257 if (!FileUtils.copyFile(fromFile, tmpFile)) { 1258 throw new IOException("Failed to copy " + fromFile + " to " + tmpFile); 1259 } 1260 try { 1261 Os.chmod(tmpFile.getAbsolutePath(), 0644); 1262 } catch (ErrnoException e) { 1263 throw new IOException("Failed to chmod " + tmpFile); 1264 } 1265 final File toFile = new File(toDir, fromFile.getName()); 1266 if (LOGD) Slog.d(TAG, "Renaming " + tmpFile + " to " + toFile); 1267 if (!tmpFile.renameTo(toFile)) { 1268 throw new IOException("Failed to rename " + tmpFile + " to " + toFile); 1269 } 1270 } 1271 Slog.d(TAG, "Copied " + fromFiles.size() + " files into " + toDir); 1272 } 1273 1274 private static void extractNativeLibraries(File packageDir, String abiOverride) 1275 throws PackageManagerException { 1276 // Always start from a clean slate 1277 final File libDir = new File(packageDir, NativeLibraryHelper.LIB_DIR_NAME); 1278 NativeLibraryHelper.removeNativeBinariesFromDirLI(libDir, true); 1279 1280 NativeLibraryHelper.Handle handle = null; 1281 try { 1282 handle = NativeLibraryHelper.Handle.create(packageDir); 1283 final int res = NativeLibraryHelper.copyNativeBinariesWithOverride(handle, libDir, 1284 abiOverride); 1285 if (res != PackageManager.INSTALL_SUCCEEDED) { 1286 throw new PackageManagerException(res, 1287 "Failed to extract native libraries, res=" + res); 1288 } 1289 } catch (IOException e) { 1290 throw new PackageManagerException(INSTALL_FAILED_INTERNAL_ERROR, 1291 "Failed to extract native libraries", e); 1292 } finally { 1293 IoUtils.closeQuietly(handle); 1294 } 1295 } 1296 1297 private static void resizeContainer(String cid, long targetSize) 1298 throws PackageManagerException { 1299 String path = PackageHelper.getSdDir(cid); 1300 if (path == null) { 1301 throw new PackageManagerException(INSTALL_FAILED_CONTAINER_ERROR, 1302 "Failed to find mounted " + cid); 1303 } 1304 1305 final long currentSize = new File(path).getTotalSpace(); 1306 if (currentSize > targetSize) { 1307 Slog.w(TAG, "Current size " + currentSize + " is larger than target size " 1308 + targetSize + "; skipping resize"); 1309 return; 1310 } 1311 1312 if (!PackageHelper.unMountSdDir(cid)) { 1313 throw new PackageManagerException(INSTALL_FAILED_CONTAINER_ERROR, 1314 "Failed to unmount " + cid + " before resize"); 1315 } 1316 1317 if (!PackageHelper.resizeSdDir(targetSize, cid, 1318 PackageManagerService.getEncryptKey())) { 1319 throw new PackageManagerException(INSTALL_FAILED_CONTAINER_ERROR, 1320 "Failed to resize " + cid + " to " + targetSize + " bytes"); 1321 } 1322 1323 path = PackageHelper.mountSdDir(cid, PackageManagerService.getEncryptKey(), 1324 Process.SYSTEM_UID, false); 1325 if (path == null) { 1326 throw new PackageManagerException(INSTALL_FAILED_CONTAINER_ERROR, 1327 "Failed to mount " + cid + " after resize"); 1328 } 1329 } 1330 1331 private void finalizeAndFixContainer(String cid) throws PackageManagerException { 1332 if (!PackageHelper.finalizeSdDir(cid)) { 1333 throw new PackageManagerException(INSTALL_FAILED_CONTAINER_ERROR, 1334 "Failed to finalize container " + cid); 1335 } 1336 1337 if (!PackageHelper.fixSdPermissions(cid, defaultContainerGid, null)) { 1338 throw new PackageManagerException(INSTALL_FAILED_CONTAINER_ERROR, 1339 "Failed to fix permissions on container " + cid); 1340 } 1341 } 1342 1343 void setPermissionsResult(boolean accepted) { 1344 if (!mSealed) { 1345 throw new SecurityException("Must be sealed to accept permissions"); 1346 } 1347 1348 if (accepted) { 1349 // Mark and kick off another install pass 1350 synchronized (mLock) { 1351 mPermissionsManuallyAccepted = true; 1352 mHandler.obtainMessage(MSG_COMMIT).sendToTarget(); 1353 } 1354 } else { 1355 destroyInternal(); 1356 dispatchSessionFinished(INSTALL_FAILED_ABORTED, "User rejected permissions", null); 1357 } 1358 } 1359 1360 public void open() throws IOException { 1361 if (mActiveCount.getAndIncrement() == 0) { 1362 mCallback.onSessionActiveChanged(this, true); 1363 } 1364 1365 boolean wasPrepared; 1366 synchronized (mLock) { 1367 wasPrepared = mPrepared; 1368 if (!mPrepared) { 1369 if (stageDir != null) { 1370 prepareStageDir(stageDir); 1371 } else if (stageCid != null) { 1372 final long identity = Binder.clearCallingIdentity(); 1373 try { 1374 prepareExternalStageCid(stageCid, params.sizeBytes); 1375 } finally { 1376 Binder.restoreCallingIdentity(identity); 1377 } 1378 1379 // TODO: deliver more granular progress for ASEC allocation 1380 mInternalProgress = 0.25f; 1381 computeProgressLocked(true); 1382 } else { 1383 throw new IllegalArgumentException( 1384 "Exactly one of stageDir or stageCid stage must be set"); 1385 } 1386 1387 mPrepared = true; 1388 } 1389 } 1390 1391 if (!wasPrepared) { 1392 mCallback.onSessionPrepared(this); 1393 } 1394 } 1395 1396 @Override 1397 public void close() { 1398 closeInternal(true); 1399 } 1400 1401 private void closeInternal(boolean checkCaller) { 1402 int activeCount; 1403 synchronized (mLock) { 1404 if (checkCaller) { 1405 assertCallerIsOwnerOrRootLocked(); 1406 } 1407 1408 activeCount = mActiveCount.decrementAndGet(); 1409 } 1410 1411 if (activeCount == 0) { 1412 mCallback.onSessionActiveChanged(this, false); 1413 } 1414 } 1415 1416 @Override 1417 public void abandon() { 1418 synchronized (mLock) { 1419 assertCallerIsOwnerOrRootLocked(); 1420 1421 if (mRelinquished) { 1422 Slog.d(TAG, "Ignoring abandon after commit relinquished control"); 1423 return; 1424 } 1425 destroyInternal(); 1426 } 1427 1428 dispatchSessionFinished(INSTALL_FAILED_ABORTED, "Session was abandoned", null); 1429 } 1430 1431 private void dispatchSessionFinished(int returnCode, String msg, Bundle extras) { 1432 IPackageInstallObserver2 observer; 1433 String packageName; 1434 synchronized (mLock) { 1435 mFinalStatus = returnCode; 1436 mFinalMessage = msg; 1437 1438 observer = mRemoteObserver; 1439 packageName = mPackageName; 1440 } 1441 1442 if (observer != null) { 1443 try { 1444 observer.onPackageInstalled(packageName, returnCode, msg, extras); 1445 } catch (RemoteException ignored) { 1446 } 1447 } 1448 1449 final boolean success = (returnCode == PackageManager.INSTALL_SUCCEEDED); 1450 1451 // Send broadcast to default launcher only if it's a new install 1452 final boolean isNewInstall = extras == null || !extras.getBoolean(Intent.EXTRA_REPLACING); 1453 if (success && isNewInstall) { 1454 mPm.sendSessionCommitBroadcast(generateInfo(), userId); 1455 } 1456 1457 mCallback.onSessionFinished(this, success); 1458 } 1459 1460 private void destroyInternal() { 1461 synchronized (mLock) { 1462 mSealed = true; 1463 mDestroyed = true; 1464 1465 // Force shut down all bridges 1466 for (RevocableFileDescriptor fd : mFds) { 1467 fd.revoke(); 1468 } 1469 for (FileBridge bridge : mBridges) { 1470 bridge.forceClose(); 1471 } 1472 } 1473 if (stageDir != null) { 1474 try { 1475 mPm.mInstaller.rmPackageDir(stageDir.getAbsolutePath()); 1476 } catch (InstallerException ignored) { 1477 } 1478 } 1479 if (stageCid != null) { 1480 PackageHelper.destroySdDir(stageCid); 1481 } 1482 } 1483 1484 void dump(IndentingPrintWriter pw) { 1485 synchronized (mLock) { 1486 dumpLocked(pw); 1487 } 1488 } 1489 1490 private void dumpLocked(IndentingPrintWriter pw) { 1491 pw.println("Session " + sessionId + ":"); 1492 pw.increaseIndent(); 1493 1494 pw.printPair("userId", userId); 1495 pw.printPair("mOriginalInstallerUid", mOriginalInstallerUid); 1496 pw.printPair("mInstallerPackageName", mInstallerPackageName); 1497 pw.printPair("mInstallerUid", mInstallerUid); 1498 pw.printPair("createdMillis", createdMillis); 1499 pw.printPair("stageDir", stageDir); 1500 pw.printPair("stageCid", stageCid); 1501 pw.println(); 1502 1503 params.dump(pw); 1504 1505 pw.printPair("mClientProgress", mClientProgress); 1506 pw.printPair("mProgress", mProgress); 1507 pw.printPair("mSealed", mSealed); 1508 pw.printPair("mPermissionsManuallyAccepted", mPermissionsManuallyAccepted); 1509 pw.printPair("mRelinquished", mRelinquished); 1510 pw.printPair("mDestroyed", mDestroyed); 1511 pw.printPair("mFds", mFds.size()); 1512 pw.printPair("mBridges", mBridges.size()); 1513 pw.printPair("mFinalStatus", mFinalStatus); 1514 pw.printPair("mFinalMessage", mFinalMessage); 1515 pw.println(); 1516 1517 pw.decreaseIndent(); 1518 } 1519 1520 private static void writeGrantedRuntimePermissionsLocked(XmlSerializer out, 1521 String[] grantedRuntimePermissions) throws IOException { 1522 if (grantedRuntimePermissions != null) { 1523 for (String permission : grantedRuntimePermissions) { 1524 out.startTag(null, TAG_GRANTED_RUNTIME_PERMISSION); 1525 writeStringAttribute(out, ATTR_NAME, permission); 1526 out.endTag(null, TAG_GRANTED_RUNTIME_PERMISSION); 1527 } 1528 } 1529 } 1530 1531 private static File buildAppIconFile(int sessionId, @NonNull File sessionsDir) { 1532 return new File(sessionsDir, "app_icon." + sessionId + ".png"); 1533 } 1534 1535 /** 1536 * Write this session to a {@link XmlSerializer}. 1537 * 1538 * @param out Where to write the session to 1539 * @param sessionsDir The directory containing the sessions 1540 */ 1541 void write(@NonNull XmlSerializer out, @NonNull File sessionsDir) throws IOException { 1542 synchronized (mLock) { 1543 out.startTag(null, TAG_SESSION); 1544 1545 writeIntAttribute(out, ATTR_SESSION_ID, sessionId); 1546 writeIntAttribute(out, ATTR_USER_ID, userId); 1547 writeStringAttribute(out, ATTR_INSTALLER_PACKAGE_NAME, 1548 mInstallerPackageName); 1549 writeIntAttribute(out, ATTR_INSTALLER_UID, mInstallerUid); 1550 writeLongAttribute(out, ATTR_CREATED_MILLIS, createdMillis); 1551 if (stageDir != null) { 1552 writeStringAttribute(out, ATTR_SESSION_STAGE_DIR, 1553 stageDir.getAbsolutePath()); 1554 } 1555 if (stageCid != null) { 1556 writeStringAttribute(out, ATTR_SESSION_STAGE_CID, stageCid); 1557 } 1558 writeBooleanAttribute(out, ATTR_PREPARED, isPrepared()); 1559 writeBooleanAttribute(out, ATTR_SEALED, isSealed()); 1560 1561 writeIntAttribute(out, ATTR_MODE, params.mode); 1562 writeIntAttribute(out, ATTR_INSTALL_FLAGS, params.installFlags); 1563 writeIntAttribute(out, ATTR_INSTALL_LOCATION, params.installLocation); 1564 writeLongAttribute(out, ATTR_SIZE_BYTES, params.sizeBytes); 1565 writeStringAttribute(out, ATTR_APP_PACKAGE_NAME, params.appPackageName); 1566 writeStringAttribute(out, ATTR_APP_LABEL, params.appLabel); 1567 writeUriAttribute(out, ATTR_ORIGINATING_URI, params.originatingUri); 1568 writeIntAttribute(out, ATTR_ORIGINATING_UID, params.originatingUid); 1569 writeUriAttribute(out, ATTR_REFERRER_URI, params.referrerUri); 1570 writeStringAttribute(out, ATTR_ABI_OVERRIDE, params.abiOverride); 1571 writeStringAttribute(out, ATTR_VOLUME_UUID, params.volumeUuid); 1572 writeIntAttribute(out, ATTR_INSTALL_REASON, params.installReason); 1573 1574 // Persist app icon if changed since last written 1575 File appIconFile = buildAppIconFile(sessionId, sessionsDir); 1576 if (params.appIcon == null && appIconFile.exists()) { 1577 appIconFile.delete(); 1578 } else if (params.appIcon != null 1579 && appIconFile.lastModified() != params.appIconLastModified) { 1580 if (LOGD) Slog.w(TAG, "Writing changed icon " + appIconFile); 1581 FileOutputStream os = null; 1582 try { 1583 os = new FileOutputStream(appIconFile); 1584 params.appIcon.compress(Bitmap.CompressFormat.PNG, 90, os); 1585 } catch (IOException e) { 1586 Slog.w(TAG, "Failed to write icon " + appIconFile + ": " + e.getMessage()); 1587 } finally { 1588 IoUtils.closeQuietly(os); 1589 } 1590 1591 params.appIconLastModified = appIconFile.lastModified(); 1592 } 1593 1594 writeGrantedRuntimePermissionsLocked(out, params.grantedRuntimePermissions); 1595 } 1596 1597 out.endTag(null, TAG_SESSION); 1598 } 1599 1600 private static String[] readGrantedRuntimePermissions(XmlPullParser in) 1601 throws IOException, XmlPullParserException { 1602 List<String> permissions = null; 1603 1604 final int outerDepth = in.getDepth(); 1605 int type; 1606 while ((type = in.next()) != XmlPullParser.END_DOCUMENT 1607 && (type != XmlPullParser.END_TAG || in.getDepth() > outerDepth)) { 1608 if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) { 1609 continue; 1610 } 1611 if (TAG_GRANTED_RUNTIME_PERMISSION.equals(in.getName())) { 1612 String permission = readStringAttribute(in, ATTR_NAME); 1613 if (permissions == null) { 1614 permissions = new ArrayList<>(); 1615 } 1616 permissions.add(permission); 1617 } 1618 } 1619 1620 if (permissions == null) { 1621 return null; 1622 } 1623 1624 String[] permissionsArray = new String[permissions.size()]; 1625 permissions.toArray(permissionsArray); 1626 return permissionsArray; 1627 } 1628 1629 /** 1630 * Read new session from a {@link XmlPullParser xml description} and create it. 1631 * 1632 * @param in The source of the description 1633 * @param callback Callback the session uses to notify about changes of it's state 1634 * @param context Context to be used by the session 1635 * @param pm PackageManager to use by the session 1636 * @param installerThread Thread to be used for callbacks of this session 1637 * @param sessionsDir The directory the sessions are stored in 1638 * 1639 * @return The newly created session 1640 */ 1641 public static PackageInstallerSession readFromXml(@NonNull XmlPullParser in, 1642 @NonNull PackageInstallerService.InternalCallback callback, @NonNull Context context, 1643 @NonNull PackageManagerService pm, Looper installerThread, @NonNull File sessionsDir) 1644 throws IOException, XmlPullParserException { 1645 final int sessionId = readIntAttribute(in, ATTR_SESSION_ID); 1646 final int userId = readIntAttribute(in, ATTR_USER_ID); 1647 final String installerPackageName = readStringAttribute(in, ATTR_INSTALLER_PACKAGE_NAME); 1648 final int installerUid = readIntAttribute(in, ATTR_INSTALLER_UID, pm.getPackageUid( 1649 installerPackageName, PackageManager.MATCH_UNINSTALLED_PACKAGES, userId)); 1650 final long createdMillis = readLongAttribute(in, ATTR_CREATED_MILLIS); 1651 final String stageDirRaw = readStringAttribute(in, ATTR_SESSION_STAGE_DIR); 1652 final File stageDir = (stageDirRaw != null) ? new File(stageDirRaw) : null; 1653 final String stageCid = readStringAttribute(in, ATTR_SESSION_STAGE_CID); 1654 final boolean prepared = readBooleanAttribute(in, ATTR_PREPARED, true); 1655 final boolean sealed = readBooleanAttribute(in, ATTR_SEALED); 1656 1657 final SessionParams params = new SessionParams( 1658 SessionParams.MODE_INVALID); 1659 params.mode = readIntAttribute(in, ATTR_MODE); 1660 params.installFlags = readIntAttribute(in, ATTR_INSTALL_FLAGS); 1661 params.installLocation = readIntAttribute(in, ATTR_INSTALL_LOCATION); 1662 params.sizeBytes = readLongAttribute(in, ATTR_SIZE_BYTES); 1663 params.appPackageName = readStringAttribute(in, ATTR_APP_PACKAGE_NAME); 1664 params.appIcon = readBitmapAttribute(in, ATTR_APP_ICON); 1665 params.appLabel = readStringAttribute(in, ATTR_APP_LABEL); 1666 params.originatingUri = readUriAttribute(in, ATTR_ORIGINATING_URI); 1667 params.originatingUid = 1668 readIntAttribute(in, ATTR_ORIGINATING_UID, SessionParams.UID_UNKNOWN); 1669 params.referrerUri = readUriAttribute(in, ATTR_REFERRER_URI); 1670 params.abiOverride = readStringAttribute(in, ATTR_ABI_OVERRIDE); 1671 params.volumeUuid = readStringAttribute(in, ATTR_VOLUME_UUID); 1672 params.grantedRuntimePermissions = readGrantedRuntimePermissions(in); 1673 params.installReason = readIntAttribute(in, ATTR_INSTALL_REASON); 1674 1675 final File appIconFile = buildAppIconFile(sessionId, sessionsDir); 1676 if (appIconFile.exists()) { 1677 params.appIcon = BitmapFactory.decodeFile(appIconFile.getAbsolutePath()); 1678 params.appIconLastModified = appIconFile.lastModified(); 1679 } 1680 1681 return new PackageInstallerSession(callback, context, pm, 1682 installerThread, sessionId, userId, installerPackageName, installerUid, 1683 params, createdMillis, stageDir, stageCid, prepared, sealed); 1684 } 1685} 1686