PackageInstallerSession.java revision 6217e37d30042fd78d17a8d5145f578279d60808
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.server.pm.PackageInstallerService.prepareExternalStageCid; 29import static com.android.server.pm.PackageInstallerService.prepareStageDir; 30 31import android.app.admin.DevicePolicyManager; 32import android.content.Context; 33import android.content.Intent; 34import android.content.IntentSender; 35import android.content.pm.ApplicationInfo; 36import android.content.pm.IPackageInstallObserver2; 37import android.content.pm.IPackageInstallerSession; 38import android.content.pm.PackageInfo; 39import android.content.pm.PackageInstaller; 40import android.content.pm.PackageInstaller.SessionInfo; 41import android.content.pm.PackageInstaller.SessionParams; 42import android.content.pm.PackageManager; 43import android.content.pm.PackageParser; 44import android.content.pm.PackageParser.ApkLite; 45import android.content.pm.PackageParser.PackageLite; 46import android.content.pm.PackageParser.PackageParserException; 47import android.content.pm.Signature; 48import android.os.Binder; 49import android.os.Bundle; 50import android.os.FileBridge; 51import android.os.FileUtils; 52import android.os.Handler; 53import android.os.Looper; 54import android.os.Message; 55import android.os.ParcelFileDescriptor; 56import android.os.Process; 57import android.os.RemoteException; 58import android.os.RevocableFileDescriptor; 59import android.os.UserHandle; 60import android.os.storage.StorageManager; 61import android.system.ErrnoException; 62import android.system.Os; 63import android.system.OsConstants; 64import android.system.StructStat; 65import android.text.TextUtils; 66import android.util.ArraySet; 67import android.util.ExceptionUtils; 68import android.util.MathUtils; 69import android.util.Slog; 70 71import com.android.internal.annotations.GuardedBy; 72import com.android.internal.content.NativeLibraryHelper; 73import com.android.internal.content.PackageHelper; 74import com.android.internal.util.ArrayUtils; 75import com.android.internal.util.IndentingPrintWriter; 76import com.android.internal.util.Preconditions; 77import com.android.server.pm.Installer.InstallerException; 78import com.android.server.pm.PackageInstallerService.PackageInstallObserverAdapter; 79 80import libcore.io.IoUtils; 81 82import java.io.File; 83import java.io.FileDescriptor; 84import java.io.FileFilter; 85import java.io.IOException; 86import java.security.cert.Certificate; 87import java.util.ArrayList; 88import java.util.Arrays; 89import java.util.List; 90import java.util.concurrent.atomic.AtomicInteger; 91 92public class PackageInstallerSession extends IPackageInstallerSession.Stub { 93 private static final String TAG = "PackageInstaller"; 94 private static final boolean LOGD = true; 95 private static final String REMOVE_SPLIT_MARKER_EXTENSION = ".removed"; 96 97 private static final int MSG_COMMIT = 0; 98 99 // TODO: enforce INSTALL_ALLOW_TEST 100 // TODO: enforce INSTALL_ALLOW_DOWNGRADE 101 102 private final PackageInstallerService.InternalCallback mCallback; 103 private final Context mContext; 104 private final PackageManagerService mPm; 105 private final Handler mHandler; 106 private final boolean mIsInstallerDeviceOwner; 107 108 final int sessionId; 109 final int userId; 110 final String installerPackageName; 111 final int installerUid; 112 final SessionParams params; 113 final long createdMillis; 114 final int defaultContainerGid; 115 116 /** Staging location where client data is written. */ 117 final File stageDir; 118 final String stageCid; 119 120 private final AtomicInteger mActiveCount = new AtomicInteger(); 121 122 private final Object mLock = new Object(); 123 124 @GuardedBy("mLock") 125 private float mClientProgress = 0; 126 @GuardedBy("mLock") 127 private float mInternalProgress = 0; 128 129 @GuardedBy("mLock") 130 private float mProgress = 0; 131 @GuardedBy("mLock") 132 private float mReportedProgress = -1; 133 134 @GuardedBy("mLock") 135 private boolean mPrepared = false; 136 @GuardedBy("mLock") 137 private boolean mSealed = false; 138 @GuardedBy("mLock") 139 private boolean mPermissionsAccepted = false; 140 @GuardedBy("mLock") 141 private boolean mRelinquished = false; 142 @GuardedBy("mLock") 143 private boolean mDestroyed = false; 144 145 private int mFinalStatus; 146 private String mFinalMessage; 147 148 @GuardedBy("mLock") 149 private final ArrayList<RevocableFileDescriptor> mFds = new ArrayList<>(); 150 @GuardedBy("mLock") 151 private final ArrayList<FileBridge> mBridges = new ArrayList<>(); 152 153 @GuardedBy("mLock") 154 private IPackageInstallObserver2 mRemoteObserver; 155 156 /** Fields derived from commit parsing */ 157 private String mPackageName; 158 private int mVersionCode; 159 private Signature[] mSignatures; 160 private Certificate[][] mCertificates; 161 162 /** 163 * Path to the validated base APK for this session, which may point at an 164 * APK inside the session (when the session defines the base), or it may 165 * point at the existing base APK (when adding splits to an existing app). 166 * <p> 167 * This is used when confirming permissions, since we can't fully stage the 168 * session inside an ASEC before confirming with user. 169 */ 170 @GuardedBy("mLock") 171 private File mResolvedBaseFile; 172 173 @GuardedBy("mLock") 174 private File mResolvedStageDir; 175 176 @GuardedBy("mLock") 177 private final List<File> mResolvedStagedFiles = new ArrayList<>(); 178 @GuardedBy("mLock") 179 private final List<File> mResolvedInheritedFiles = new ArrayList<>(); 180 @GuardedBy("mLock") 181 private final List<String> mResolvedInstructionSets = new ArrayList<>(); 182 @GuardedBy("mLock") 183 private File mInheritedFilesBase; 184 185 private static final FileFilter sAddedFilter = new FileFilter() { 186 @Override 187 public boolean accept(File file) { 188 // Installers can't stage directories, so it's fine to ignore 189 // entries like "lost+found". 190 if (file.isDirectory()) return false; 191 if (file.getName().endsWith(REMOVE_SPLIT_MARKER_EXTENSION)) return false; 192 return true; 193 } 194 }; 195 private static final FileFilter sRemovedFilter = new FileFilter() { 196 @Override 197 public boolean accept(File file) { 198 if (file.isDirectory()) return false; 199 if (!file.getName().endsWith(REMOVE_SPLIT_MARKER_EXTENSION)) return false; 200 return true; 201 } 202 }; 203 204 private final Handler.Callback mHandlerCallback = new Handler.Callback() { 205 @Override 206 public boolean handleMessage(Message msg) { 207 // Cache package manager data without the lock held 208 final PackageInfo pkgInfo = mPm.getPackageInfo( 209 params.appPackageName, PackageManager.GET_SIGNATURES 210 | PackageManager.MATCH_STATIC_SHARED_LIBRARIES /*flags*/, userId); 211 final ApplicationInfo appInfo = mPm.getApplicationInfo( 212 params.appPackageName, 0, userId); 213 214 synchronized (mLock) { 215 if (msg.obj != null) { 216 mRemoteObserver = (IPackageInstallObserver2) msg.obj; 217 } 218 219 try { 220 commitLocked(pkgInfo, appInfo); 221 } catch (PackageManagerException e) { 222 final String completeMsg = ExceptionUtils.getCompleteMessage(e); 223 Slog.e(TAG, "Commit of session " + sessionId + " failed: " + completeMsg); 224 destroyInternal(); 225 dispatchSessionFinished(e.error, completeMsg, null); 226 } 227 228 return true; 229 } 230 } 231 }; 232 233 public PackageInstallerSession(PackageInstallerService.InternalCallback callback, 234 Context context, PackageManagerService pm, Looper looper, int sessionId, int userId, 235 String installerPackageName, int installerUid, SessionParams params, long createdMillis, 236 File stageDir, String stageCid, boolean prepared, boolean sealed) { 237 mCallback = callback; 238 mContext = context; 239 mPm = pm; 240 mHandler = new Handler(looper, mHandlerCallback); 241 242 this.sessionId = sessionId; 243 this.userId = userId; 244 this.installerPackageName = installerPackageName; 245 this.installerUid = installerUid; 246 this.params = params; 247 this.createdMillis = createdMillis; 248 this.stageDir = stageDir; 249 this.stageCid = stageCid; 250 251 if ((stageDir == null) == (stageCid == null)) { 252 throw new IllegalArgumentException( 253 "Exactly one of stageDir or stageCid stage must be set"); 254 } 255 256 mPrepared = prepared; 257 mSealed = sealed; 258 259 // Device owners are allowed to silently install packages, so the permission check is 260 // waived if the installer is the device owner. 261 DevicePolicyManager dpm = (DevicePolicyManager) mContext.getSystemService( 262 Context.DEVICE_POLICY_SERVICE); 263 final boolean isPermissionGranted = 264 (mPm.checkUidPermission(android.Manifest.permission.INSTALL_PACKAGES, installerUid) 265 == PackageManager.PERMISSION_GRANTED); 266 final boolean isInstallerRoot = (installerUid == Process.ROOT_UID); 267 final boolean forcePermissionPrompt = 268 (params.installFlags & PackageManager.INSTALL_FORCE_PERMISSION_PROMPT) != 0; 269 mIsInstallerDeviceOwner = (dpm != null) && dpm.isDeviceOwnerAppOnCallingUser( 270 installerPackageName); 271 if ((isPermissionGranted 272 || isInstallerRoot 273 || mIsInstallerDeviceOwner) 274 && !forcePermissionPrompt) { 275 mPermissionsAccepted = true; 276 } else { 277 mPermissionsAccepted = false; 278 } 279 final long identity = Binder.clearCallingIdentity(); 280 try { 281 final int uid = mPm.getPackageUid(PackageManagerService.DEFAULT_CONTAINER_PACKAGE, 282 PackageManager.MATCH_SYSTEM_ONLY, UserHandle.USER_SYSTEM); 283 defaultContainerGid = UserHandle.getSharedAppGid(uid); 284 } finally { 285 Binder.restoreCallingIdentity(identity); 286 } 287 } 288 289 public SessionInfo generateInfo() { 290 final SessionInfo info = new SessionInfo(); 291 synchronized (mLock) { 292 info.sessionId = sessionId; 293 info.installerPackageName = installerPackageName; 294 info.resolvedBaseCodePath = (mResolvedBaseFile != null) ? 295 mResolvedBaseFile.getAbsolutePath() : null; 296 info.progress = mProgress; 297 info.sealed = mSealed; 298 info.active = mActiveCount.get() > 0; 299 300 info.mode = params.mode; 301 info.installReason = params.installReason; 302 info.sizeBytes = params.sizeBytes; 303 info.appPackageName = params.appPackageName; 304 info.appIcon = params.appIcon; 305 info.appLabel = params.appLabel; 306 } 307 return info; 308 } 309 310 public boolean isPrepared() { 311 synchronized (mLock) { 312 return mPrepared; 313 } 314 } 315 316 public boolean isSealed() { 317 synchronized (mLock) { 318 return mSealed; 319 } 320 } 321 322 private void assertPreparedAndNotSealed(String cookie) { 323 synchronized (mLock) { 324 if (!mPrepared) { 325 throw new IllegalStateException(cookie + " before prepared"); 326 } 327 if (mSealed) { 328 throw new SecurityException(cookie + " not allowed after commit"); 329 } 330 } 331 } 332 333 /** 334 * Resolve the actual location where staged data should be written. This 335 * might point at an ASEC mount point, which is why we delay path resolution 336 * until someone actively works with the session. 337 */ 338 private File resolveStageDir() throws IOException { 339 synchronized (mLock) { 340 if (mResolvedStageDir == null) { 341 if (stageDir != null) { 342 mResolvedStageDir = stageDir; 343 } else { 344 final String path = PackageHelper.getSdDir(stageCid); 345 if (path != null) { 346 mResolvedStageDir = new File(path); 347 } else { 348 throw new IOException("Failed to resolve path to container " + stageCid); 349 } 350 } 351 } 352 return mResolvedStageDir; 353 } 354 } 355 356 @Override 357 public void setClientProgress(float progress) { 358 synchronized (mLock) { 359 // Always publish first staging movement 360 final boolean forcePublish = (mClientProgress == 0); 361 mClientProgress = progress; 362 computeProgressLocked(forcePublish); 363 } 364 } 365 366 @Override 367 public void addClientProgress(float progress) { 368 synchronized (mLock) { 369 setClientProgress(mClientProgress + progress); 370 } 371 } 372 373 private void computeProgressLocked(boolean forcePublish) { 374 mProgress = MathUtils.constrain(mClientProgress * 0.8f, 0f, 0.8f) 375 + MathUtils.constrain(mInternalProgress * 0.2f, 0f, 0.2f); 376 377 // Only publish when meaningful change 378 if (forcePublish || Math.abs(mProgress - mReportedProgress) >= 0.01) { 379 mReportedProgress = mProgress; 380 mCallback.onSessionProgressChanged(this, mProgress); 381 } 382 } 383 384 @Override 385 public String[] getNames() { 386 assertPreparedAndNotSealed("getNames"); 387 try { 388 return resolveStageDir().list(); 389 } catch (IOException e) { 390 throw ExceptionUtils.wrap(e); 391 } 392 } 393 394 @Override 395 public void removeSplit(String splitName) { 396 if (TextUtils.isEmpty(params.appPackageName)) { 397 throw new IllegalStateException("Must specify package name to remove a split"); 398 } 399 try { 400 createRemoveSplitMarker(splitName); 401 } catch (IOException e) { 402 throw ExceptionUtils.wrap(e); 403 } 404 } 405 406 private void createRemoveSplitMarker(String splitName) throws IOException { 407 try { 408 final String markerName = splitName + REMOVE_SPLIT_MARKER_EXTENSION; 409 if (!FileUtils.isValidExtFilename(markerName)) { 410 throw new IllegalArgumentException("Invalid marker: " + markerName); 411 } 412 final File target = new File(resolveStageDir(), markerName); 413 target.createNewFile(); 414 Os.chmod(target.getAbsolutePath(), 0 /*mode*/); 415 } catch (ErrnoException e) { 416 throw e.rethrowAsIOException(); 417 } 418 } 419 420 @Override 421 public ParcelFileDescriptor openWrite(String name, long offsetBytes, long lengthBytes) { 422 try { 423 return openWriteInternal(name, offsetBytes, lengthBytes); 424 } catch (IOException e) { 425 throw ExceptionUtils.wrap(e); 426 } 427 } 428 429 private ParcelFileDescriptor openWriteInternal(String name, long offsetBytes, long lengthBytes) 430 throws IOException { 431 // Quick sanity check of state, and allocate a pipe for ourselves. We 432 // then do heavy disk allocation outside the lock, but this open pipe 433 // will block any attempted install transitions. 434 final RevocableFileDescriptor fd; 435 final FileBridge bridge; 436 synchronized (mLock) { 437 assertPreparedAndNotSealed("openWrite"); 438 439 if (PackageInstaller.ENABLE_REVOCABLE_FD) { 440 fd = new RevocableFileDescriptor(); 441 bridge = null; 442 mFds.add(fd); 443 } else { 444 fd = null; 445 bridge = new FileBridge(); 446 mBridges.add(bridge); 447 } 448 } 449 450 try { 451 // Use installer provided name for now; we always rename later 452 if (!FileUtils.isValidExtFilename(name)) { 453 throw new IllegalArgumentException("Invalid name: " + name); 454 } 455 final File target; 456 final long identity = Binder.clearCallingIdentity(); 457 try { 458 target = new File(resolveStageDir(), name); 459 } finally { 460 Binder.restoreCallingIdentity(identity); 461 } 462 463 // TODO: this should delegate to DCS so the system process avoids 464 // holding open FDs into containers. 465 final FileDescriptor targetFd = Os.open(target.getAbsolutePath(), 466 O_CREAT | O_WRONLY, 0644); 467 Os.chmod(target.getAbsolutePath(), 0644); 468 469 // If caller specified a total length, allocate it for them. Free up 470 // cache space to grow, if needed. 471 if (stageDir != null && lengthBytes > 0) { 472 mContext.getSystemService(StorageManager.class).allocateBytes(targetFd, lengthBytes, 473 PackageHelper.translateAllocateFlags(params.installFlags)); 474 } 475 476 if (offsetBytes > 0) { 477 Os.lseek(targetFd, offsetBytes, OsConstants.SEEK_SET); 478 } 479 480 if (PackageInstaller.ENABLE_REVOCABLE_FD) { 481 fd.init(mContext, targetFd); 482 return fd.getRevocableFileDescriptor(); 483 } else { 484 bridge.setTargetFile(targetFd); 485 bridge.start(); 486 return new ParcelFileDescriptor(bridge.getClientSocket()); 487 } 488 489 } catch (ErrnoException e) { 490 throw e.rethrowAsIOException(); 491 } 492 } 493 494 @Override 495 public ParcelFileDescriptor openRead(String name) { 496 try { 497 return openReadInternal(name); 498 } catch (IOException e) { 499 throw ExceptionUtils.wrap(e); 500 } 501 } 502 503 private ParcelFileDescriptor openReadInternal(String name) throws IOException { 504 assertPreparedAndNotSealed("openRead"); 505 506 try { 507 if (!FileUtils.isValidExtFilename(name)) { 508 throw new IllegalArgumentException("Invalid name: " + name); 509 } 510 final File target = new File(resolveStageDir(), name); 511 512 final FileDescriptor targetFd = Os.open(target.getAbsolutePath(), O_RDONLY, 0); 513 return new ParcelFileDescriptor(targetFd); 514 515 } catch (ErrnoException e) { 516 throw e.rethrowAsIOException(); 517 } 518 } 519 520 @Override 521 public void commit(IntentSender statusReceiver) { 522 Preconditions.checkNotNull(statusReceiver); 523 524 final boolean wasSealed; 525 synchronized (mLock) { 526 wasSealed = mSealed; 527 if (!mSealed) { 528 // Verify that all writers are hands-off 529 for (RevocableFileDescriptor fd : mFds) { 530 if (!fd.isRevoked()) { 531 throw new SecurityException("Files still open"); 532 } 533 } 534 for (FileBridge bridge : mBridges) { 535 if (!bridge.isClosed()) { 536 throw new SecurityException("Files still open"); 537 } 538 } 539 mSealed = true; 540 } 541 542 // Client staging is fully done at this point 543 mClientProgress = 1f; 544 computeProgressLocked(true); 545 } 546 547 if (!wasSealed) { 548 // Persist the fact that we've sealed ourselves to prevent 549 // mutations of any hard links we create. We do this without holding 550 // the session lock, since otherwise it's a lock inversion. 551 mCallback.onSessionSealedBlocking(this); 552 } 553 554 // This ongoing commit should keep session active, even though client 555 // will probably close their end. 556 mActiveCount.incrementAndGet(); 557 558 final PackageInstallObserverAdapter adapter = new PackageInstallObserverAdapter(mContext, 559 statusReceiver, sessionId, mIsInstallerDeviceOwner, userId); 560 mHandler.obtainMessage(MSG_COMMIT, adapter.getBinder()).sendToTarget(); 561 } 562 563 private void commitLocked(PackageInfo pkgInfo, ApplicationInfo appInfo) 564 throws PackageManagerException { 565 if (mDestroyed) { 566 throw new PackageManagerException(INSTALL_FAILED_INTERNAL_ERROR, "Session destroyed"); 567 } 568 if (!mSealed) { 569 throw new PackageManagerException(INSTALL_FAILED_INTERNAL_ERROR, "Session not sealed"); 570 } 571 572 try { 573 resolveStageDir(); 574 } catch (IOException e) { 575 throw new PackageManagerException(INSTALL_FAILED_CONTAINER_ERROR, 576 "Failed to resolve stage location", e); 577 } 578 579 // Verify that stage looks sane with respect to existing application. 580 // This currently only ensures packageName, versionCode, and certificate 581 // consistency. 582 validateInstallLocked(pkgInfo, appInfo); 583 584 Preconditions.checkNotNull(mPackageName); 585 Preconditions.checkNotNull(mSignatures); 586 Preconditions.checkNotNull(mResolvedBaseFile); 587 588 if (!mPermissionsAccepted) { 589 // User needs to accept permissions; give installer an intent they 590 // can use to involve user. 591 final Intent intent = new Intent(PackageInstaller.ACTION_CONFIRM_PERMISSIONS); 592 intent.setPackage(mContext.getPackageManager().getPermissionControllerPackageName()); 593 intent.putExtra(PackageInstaller.EXTRA_SESSION_ID, sessionId); 594 try { 595 mRemoteObserver.onUserActionRequired(intent); 596 } catch (RemoteException ignored) { 597 } 598 599 // Commit was keeping session marked as active until now; release 600 // that extra refcount so session appears idle. 601 close(); 602 return; 603 } 604 605 if (stageCid != null) { 606 // Figure out the final installed size and resize the container once 607 // and for all. Internally the parser handles straddling between two 608 // locations when inheriting. 609 final long finalSize = calculateInstalledSize(); 610 resizeContainer(stageCid, finalSize); 611 } 612 613 // Inherit any packages and native libraries from existing install that 614 // haven't been overridden. 615 if (params.mode == SessionParams.MODE_INHERIT_EXISTING) { 616 try { 617 final List<File> fromFiles = mResolvedInheritedFiles; 618 final File toDir = resolveStageDir(); 619 620 if (LOGD) Slog.d(TAG, "Inherited files: " + mResolvedInheritedFiles); 621 if (!mResolvedInheritedFiles.isEmpty() && mInheritedFilesBase == null) { 622 throw new IllegalStateException("mInheritedFilesBase == null"); 623 } 624 625 if (isLinkPossible(fromFiles, toDir)) { 626 if (!mResolvedInstructionSets.isEmpty()) { 627 final File oatDir = new File(toDir, "oat"); 628 createOatDirs(mResolvedInstructionSets, oatDir); 629 } 630 linkFiles(fromFiles, toDir, mInheritedFilesBase); 631 } else { 632 // TODO: this should delegate to DCS so the system process 633 // avoids holding open FDs into containers. 634 copyFiles(fromFiles, toDir); 635 } 636 } catch (IOException e) { 637 throw new PackageManagerException(INSTALL_FAILED_INSUFFICIENT_STORAGE, 638 "Failed to inherit existing install", e); 639 } 640 } 641 642 // TODO: surface more granular state from dexopt 643 mInternalProgress = 0.5f; 644 computeProgressLocked(true); 645 646 // Unpack native libraries 647 extractNativeLibraries(mResolvedStageDir, params.abiOverride); 648 649 // Container is ready to go, let's seal it up! 650 if (stageCid != null) { 651 finalizeAndFixContainer(stageCid); 652 } 653 654 // We've reached point of no return; call into PMS to install the stage. 655 // Regardless of success or failure we always destroy session. 656 final IPackageInstallObserver2 localObserver = new IPackageInstallObserver2.Stub() { 657 @Override 658 public void onUserActionRequired(Intent intent) { 659 throw new IllegalStateException(); 660 } 661 662 @Override 663 public void onPackageInstalled(String basePackageName, int returnCode, String msg, 664 Bundle extras) { 665 destroyInternal(); 666 dispatchSessionFinished(returnCode, msg, extras); 667 } 668 }; 669 670 final UserHandle user; 671 if ((params.installFlags & PackageManager.INSTALL_ALL_USERS) != 0) { 672 user = UserHandle.ALL; 673 } else { 674 user = new UserHandle(userId); 675 } 676 677 mRelinquished = true; 678 mPm.installStage(mPackageName, stageDir, stageCid, localObserver, params, 679 installerPackageName, installerUid, user, mCertificates); 680 } 681 682 /** 683 * Validate install by confirming that all application packages are have 684 * consistent package name, version code, and signing certificates. 685 * <p> 686 * Clears and populates {@link #mResolvedBaseFile}, 687 * {@link #mResolvedStagedFiles}, and {@link #mResolvedInheritedFiles}. 688 * <p> 689 * Renames package files in stage to match split names defined inside. 690 * <p> 691 * Note that upgrade compatibility is still performed by 692 * {@link PackageManagerService}. 693 */ 694 private void validateInstallLocked(PackageInfo pkgInfo, ApplicationInfo appInfo) 695 throws PackageManagerException { 696 mPackageName = null; 697 mVersionCode = -1; 698 mSignatures = null; 699 700 mResolvedBaseFile = null; 701 mResolvedStagedFiles.clear(); 702 mResolvedInheritedFiles.clear(); 703 704 final File[] removedFiles = mResolvedStageDir.listFiles(sRemovedFilter); 705 final List<String> removeSplitList = new ArrayList<>(); 706 if (!ArrayUtils.isEmpty(removedFiles)) { 707 for (File removedFile : removedFiles) { 708 final String fileName = removedFile.getName(); 709 final String splitName = fileName.substring( 710 0, fileName.length() - REMOVE_SPLIT_MARKER_EXTENSION.length()); 711 removeSplitList.add(splitName); 712 } 713 } 714 715 final File[] addedFiles = mResolvedStageDir.listFiles(sAddedFilter); 716 if (ArrayUtils.isEmpty(addedFiles) && removeSplitList.size() == 0) { 717 throw new PackageManagerException(INSTALL_FAILED_INVALID_APK, "No packages staged"); 718 } 719 // Verify that all staged packages are internally consistent 720 final ArraySet<String> stagedSplits = new ArraySet<>(); 721 for (File addedFile : addedFiles) { 722 final ApkLite apk; 723 try { 724 int flags = PackageParser.PARSE_COLLECT_CERTIFICATES; 725 if ((params.installFlags & PackageManager.INSTALL_INSTANT_APP) != 0) { 726 flags |= PackageParser.PARSE_IS_EPHEMERAL; 727 } 728 apk = PackageParser.parseApkLite(addedFile, flags); 729 } catch (PackageParserException e) { 730 throw PackageManagerException.from(e); 731 } 732 733 if (!stagedSplits.add(apk.splitName)) { 734 throw new PackageManagerException(INSTALL_FAILED_INVALID_APK, 735 "Split " + apk.splitName + " was defined multiple times"); 736 } 737 738 // Use first package to define unknown values 739 if (mPackageName == null) { 740 mPackageName = apk.packageName; 741 mVersionCode = apk.versionCode; 742 } 743 if (mSignatures == null) { 744 mSignatures = apk.signatures; 745 mCertificates = apk.certificates; 746 } 747 748 assertApkConsistent(String.valueOf(addedFile), apk); 749 750 // Take this opportunity to enforce uniform naming 751 final String targetName; 752 if (apk.splitName == null) { 753 targetName = "base.apk"; 754 } else { 755 targetName = "split_" + apk.splitName + ".apk"; 756 } 757 if (!FileUtils.isValidExtFilename(targetName)) { 758 throw new PackageManagerException(INSTALL_FAILED_INVALID_APK, 759 "Invalid filename: " + targetName); 760 } 761 762 final File targetFile = new File(mResolvedStageDir, targetName); 763 if (!addedFile.equals(targetFile)) { 764 addedFile.renameTo(targetFile); 765 } 766 767 // Base is coming from session 768 if (apk.splitName == null) { 769 mResolvedBaseFile = targetFile; 770 } 771 772 mResolvedStagedFiles.add(targetFile); 773 } 774 775 if (removeSplitList.size() > 0) { 776 // validate split names marked for removal 777 for (String splitName : removeSplitList) { 778 if (!ArrayUtils.contains(pkgInfo.splitNames, splitName)) { 779 throw new PackageManagerException(INSTALL_FAILED_INVALID_APK, 780 "Split not found: " + splitName); 781 } 782 } 783 784 // ensure we've got appropriate package name, version code and signatures 785 if (mPackageName == null) { 786 mPackageName = pkgInfo.packageName; 787 mVersionCode = pkgInfo.versionCode; 788 } 789 if (mSignatures == null) { 790 mSignatures = pkgInfo.signatures; 791 } 792 } 793 794 if (params.mode == SessionParams.MODE_FULL_INSTALL) { 795 // Full installs must include a base package 796 if (!stagedSplits.contains(null)) { 797 throw new PackageManagerException(INSTALL_FAILED_INVALID_APK, 798 "Full install must include a base package"); 799 } 800 801 } else { 802 // Partial installs must be consistent with existing install 803 if (appInfo == null) { 804 throw new PackageManagerException(INSTALL_FAILED_INVALID_APK, 805 "Missing existing base package for " + mPackageName); 806 } 807 808 final PackageLite existing; 809 final ApkLite existingBase; 810 try { 811 existing = PackageParser.parsePackageLite(new File(appInfo.getCodePath()), 0); 812 existingBase = PackageParser.parseApkLite(new File(appInfo.getBaseCodePath()), 813 PackageParser.PARSE_COLLECT_CERTIFICATES); 814 } catch (PackageParserException e) { 815 throw PackageManagerException.from(e); 816 } 817 818 assertApkConsistent("Existing base", existingBase); 819 820 // Inherit base if not overridden 821 if (mResolvedBaseFile == null) { 822 mResolvedBaseFile = new File(appInfo.getBaseCodePath()); 823 mResolvedInheritedFiles.add(mResolvedBaseFile); 824 } 825 826 // Inherit splits if not overridden 827 if (!ArrayUtils.isEmpty(existing.splitNames)) { 828 for (int i = 0; i < existing.splitNames.length; i++) { 829 final String splitName = existing.splitNames[i]; 830 final File splitFile = new File(existing.splitCodePaths[i]); 831 final boolean splitRemoved = removeSplitList.contains(splitName); 832 if (!stagedSplits.contains(splitName) && !splitRemoved) { 833 mResolvedInheritedFiles.add(splitFile); 834 } 835 } 836 } 837 838 // Inherit compiled oat directory. 839 final File packageInstallDir = (new File(appInfo.getBaseCodePath())).getParentFile(); 840 mInheritedFilesBase = packageInstallDir; 841 final File oatDir = new File(packageInstallDir, "oat"); 842 if (oatDir.exists()) { 843 final File[] archSubdirs = oatDir.listFiles(); 844 845 // Keep track of all instruction sets we've seen compiled output for. 846 // If we're linking (and not copying) inherited files, we can recreate the 847 // instruction set hierarchy and link compiled output. 848 if (archSubdirs != null && archSubdirs.length > 0) { 849 final String[] instructionSets = InstructionSets.getAllDexCodeInstructionSets(); 850 for (File archSubDir : archSubdirs) { 851 // Skip any directory that isn't an ISA subdir. 852 if (!ArrayUtils.contains(instructionSets, archSubDir.getName())) { 853 continue; 854 } 855 856 mResolvedInstructionSets.add(archSubDir.getName()); 857 List<File> oatFiles = Arrays.asList(archSubDir.listFiles()); 858 if (!oatFiles.isEmpty()) { 859 mResolvedInheritedFiles.addAll(oatFiles); 860 } 861 } 862 } 863 } 864 } 865 } 866 867 private void assertApkConsistent(String tag, ApkLite apk) 868 throws PackageManagerException { 869 if (!mPackageName.equals(apk.packageName)) { 870 throw new PackageManagerException(INSTALL_FAILED_INVALID_APK, tag + " package " 871 + apk.packageName + " inconsistent with " + mPackageName); 872 } 873 if (params.appPackageName != null && !params.appPackageName.equals(apk.packageName)) { 874 throw new PackageManagerException(INSTALL_FAILED_INVALID_APK, tag 875 + " specified package " + params.appPackageName 876 + " inconsistent with " + apk.packageName); 877 } 878 if (mVersionCode != apk.versionCode) { 879 throw new PackageManagerException(INSTALL_FAILED_INVALID_APK, tag 880 + " version code " + apk.versionCode + " inconsistent with " 881 + mVersionCode); 882 } 883 if (!Signature.areExactMatch(mSignatures, apk.signatures)) { 884 throw new PackageManagerException(INSTALL_FAILED_INVALID_APK, 885 tag + " signatures are inconsistent"); 886 } 887 } 888 889 /** 890 * Calculate the final install footprint size, combining both staged and 891 * existing APKs together and including unpacked native code from both. 892 */ 893 private long calculateInstalledSize() throws PackageManagerException { 894 Preconditions.checkNotNull(mResolvedBaseFile); 895 896 final ApkLite baseApk; 897 try { 898 baseApk = PackageParser.parseApkLite(mResolvedBaseFile, 0); 899 } catch (PackageParserException e) { 900 throw PackageManagerException.from(e); 901 } 902 903 final List<String> splitPaths = new ArrayList<>(); 904 for (File file : mResolvedStagedFiles) { 905 if (mResolvedBaseFile.equals(file)) continue; 906 splitPaths.add(file.getAbsolutePath()); 907 } 908 for (File file : mResolvedInheritedFiles) { 909 if (mResolvedBaseFile.equals(file)) continue; 910 splitPaths.add(file.getAbsolutePath()); 911 } 912 913 // This is kind of hacky; we're creating a half-parsed package that is 914 // straddled between the inherited and staged APKs. 915 final PackageLite pkg = new PackageLite(null, baseApk, null, null, null, null, 916 splitPaths.toArray(new String[splitPaths.size()]), null); 917 final boolean isForwardLocked = 918 (params.installFlags & PackageManager.INSTALL_FORWARD_LOCK) != 0; 919 920 try { 921 return PackageHelper.calculateInstalledSize(pkg, isForwardLocked, params.abiOverride); 922 } catch (IOException e) { 923 throw new PackageManagerException(INSTALL_FAILED_INVALID_APK, 924 "Failed to calculate install size", e); 925 } 926 } 927 928 /** 929 * Determine if creating hard links between source and destination is 930 * possible. That is, do they all live on the same underlying device. 931 */ 932 private boolean isLinkPossible(List<File> fromFiles, File toDir) { 933 try { 934 final StructStat toStat = Os.stat(toDir.getAbsolutePath()); 935 for (File fromFile : fromFiles) { 936 final StructStat fromStat = Os.stat(fromFile.getAbsolutePath()); 937 if (fromStat.st_dev != toStat.st_dev) { 938 return false; 939 } 940 } 941 } catch (ErrnoException e) { 942 Slog.w(TAG, "Failed to detect if linking possible: " + e); 943 return false; 944 } 945 return true; 946 } 947 948 private static String getRelativePath(File file, File base) throws IOException { 949 final String pathStr = file.getAbsolutePath(); 950 final String baseStr = base.getAbsolutePath(); 951 // Don't allow relative paths. 952 if (pathStr.contains("/.") ) { 953 throw new IOException("Invalid path (was relative) : " + pathStr); 954 } 955 956 if (pathStr.startsWith(baseStr)) { 957 return pathStr.substring(baseStr.length()); 958 } 959 960 throw new IOException("File: " + pathStr + " outside base: " + baseStr); 961 } 962 963 private void createOatDirs(List<String> instructionSets, File fromDir) 964 throws PackageManagerException { 965 for (String instructionSet : instructionSets) { 966 try { 967 mPm.mInstaller.createOatDir(fromDir.getAbsolutePath(), instructionSet); 968 } catch (InstallerException e) { 969 throw PackageManagerException.from(e); 970 } 971 } 972 } 973 974 private void linkFiles(List<File> fromFiles, File toDir, File fromDir) 975 throws IOException { 976 for (File fromFile : fromFiles) { 977 final String relativePath = getRelativePath(fromFile, fromDir); 978 try { 979 mPm.mInstaller.linkFile(relativePath, fromDir.getAbsolutePath(), 980 toDir.getAbsolutePath()); 981 } catch (InstallerException e) { 982 throw new IOException("failed linkOrCreateDir(" + relativePath + ", " 983 + fromDir + ", " + toDir + ")", e); 984 } 985 } 986 987 Slog.d(TAG, "Linked " + fromFiles.size() + " files into " + toDir); 988 } 989 990 private static void copyFiles(List<File> fromFiles, File toDir) throws IOException { 991 // Remove any partial files from previous attempt 992 for (File file : toDir.listFiles()) { 993 if (file.getName().endsWith(".tmp")) { 994 file.delete(); 995 } 996 } 997 998 for (File fromFile : fromFiles) { 999 final File tmpFile = File.createTempFile("inherit", ".tmp", toDir); 1000 if (LOGD) Slog.d(TAG, "Copying " + fromFile + " to " + tmpFile); 1001 if (!FileUtils.copyFile(fromFile, tmpFile)) { 1002 throw new IOException("Failed to copy " + fromFile + " to " + tmpFile); 1003 } 1004 try { 1005 Os.chmod(tmpFile.getAbsolutePath(), 0644); 1006 } catch (ErrnoException e) { 1007 throw new IOException("Failed to chmod " + tmpFile); 1008 } 1009 final File toFile = new File(toDir, fromFile.getName()); 1010 if (LOGD) Slog.d(TAG, "Renaming " + tmpFile + " to " + toFile); 1011 if (!tmpFile.renameTo(toFile)) { 1012 throw new IOException("Failed to rename " + tmpFile + " to " + toFile); 1013 } 1014 } 1015 Slog.d(TAG, "Copied " + fromFiles.size() + " files into " + toDir); 1016 } 1017 1018 private static void extractNativeLibraries(File packageDir, String abiOverride) 1019 throws PackageManagerException { 1020 // Always start from a clean slate 1021 final File libDir = new File(packageDir, NativeLibraryHelper.LIB_DIR_NAME); 1022 NativeLibraryHelper.removeNativeBinariesFromDirLI(libDir, true); 1023 1024 NativeLibraryHelper.Handle handle = null; 1025 try { 1026 handle = NativeLibraryHelper.Handle.create(packageDir); 1027 final int res = NativeLibraryHelper.copyNativeBinariesWithOverride(handle, libDir, 1028 abiOverride); 1029 if (res != PackageManager.INSTALL_SUCCEEDED) { 1030 throw new PackageManagerException(res, 1031 "Failed to extract native libraries, res=" + res); 1032 } 1033 } catch (IOException e) { 1034 throw new PackageManagerException(INSTALL_FAILED_INTERNAL_ERROR, 1035 "Failed to extract native libraries", e); 1036 } finally { 1037 IoUtils.closeQuietly(handle); 1038 } 1039 } 1040 1041 private static void resizeContainer(String cid, long targetSize) 1042 throws PackageManagerException { 1043 String path = PackageHelper.getSdDir(cid); 1044 if (path == null) { 1045 throw new PackageManagerException(INSTALL_FAILED_CONTAINER_ERROR, 1046 "Failed to find mounted " + cid); 1047 } 1048 1049 final long currentSize = new File(path).getTotalSpace(); 1050 if (currentSize > targetSize) { 1051 Slog.w(TAG, "Current size " + currentSize + " is larger than target size " 1052 + targetSize + "; skipping resize"); 1053 return; 1054 } 1055 1056 if (!PackageHelper.unMountSdDir(cid)) { 1057 throw new PackageManagerException(INSTALL_FAILED_CONTAINER_ERROR, 1058 "Failed to unmount " + cid + " before resize"); 1059 } 1060 1061 if (!PackageHelper.resizeSdDir(targetSize, cid, 1062 PackageManagerService.getEncryptKey())) { 1063 throw new PackageManagerException(INSTALL_FAILED_CONTAINER_ERROR, 1064 "Failed to resize " + cid + " to " + targetSize + " bytes"); 1065 } 1066 1067 path = PackageHelper.mountSdDir(cid, PackageManagerService.getEncryptKey(), 1068 Process.SYSTEM_UID, false); 1069 if (path == null) { 1070 throw new PackageManagerException(INSTALL_FAILED_CONTAINER_ERROR, 1071 "Failed to mount " + cid + " after resize"); 1072 } 1073 } 1074 1075 private void finalizeAndFixContainer(String cid) throws PackageManagerException { 1076 if (!PackageHelper.finalizeSdDir(cid)) { 1077 throw new PackageManagerException(INSTALL_FAILED_CONTAINER_ERROR, 1078 "Failed to finalize container " + cid); 1079 } 1080 1081 if (!PackageHelper.fixSdPermissions(cid, defaultContainerGid, null)) { 1082 throw new PackageManagerException(INSTALL_FAILED_CONTAINER_ERROR, 1083 "Failed to fix permissions on container " + cid); 1084 } 1085 } 1086 1087 void setPermissionsResult(boolean accepted) { 1088 if (!mSealed) { 1089 throw new SecurityException("Must be sealed to accept permissions"); 1090 } 1091 1092 if (accepted) { 1093 // Mark and kick off another install pass 1094 synchronized (mLock) { 1095 mPermissionsAccepted = true; 1096 } 1097 mHandler.obtainMessage(MSG_COMMIT).sendToTarget(); 1098 } else { 1099 destroyInternal(); 1100 dispatchSessionFinished(INSTALL_FAILED_ABORTED, "User rejected permissions", null); 1101 } 1102 } 1103 1104 public void open() throws IOException { 1105 if (mActiveCount.getAndIncrement() == 0) { 1106 mCallback.onSessionActiveChanged(this, true); 1107 } 1108 1109 synchronized (mLock) { 1110 if (!mPrepared) { 1111 if (stageDir != null) { 1112 prepareStageDir(stageDir); 1113 } else if (stageCid != null) { 1114 final long identity = Binder.clearCallingIdentity(); 1115 try { 1116 prepareExternalStageCid(stageCid, params.sizeBytes); 1117 } finally { 1118 Binder.restoreCallingIdentity(identity); 1119 } 1120 1121 // TODO: deliver more granular progress for ASEC allocation 1122 mInternalProgress = 0.25f; 1123 computeProgressLocked(true); 1124 } else { 1125 throw new IllegalArgumentException( 1126 "Exactly one of stageDir or stageCid stage must be set"); 1127 } 1128 1129 mPrepared = true; 1130 mCallback.onSessionPrepared(this); 1131 } 1132 } 1133 } 1134 1135 @Override 1136 public void close() { 1137 if (mActiveCount.decrementAndGet() == 0) { 1138 mCallback.onSessionActiveChanged(this, false); 1139 } 1140 } 1141 1142 @Override 1143 public void abandon() { 1144 if (mRelinquished) { 1145 Slog.d(TAG, "Ignoring abandon after commit relinquished control"); 1146 return; 1147 } 1148 destroyInternal(); 1149 dispatchSessionFinished(INSTALL_FAILED_ABORTED, "Session was abandoned", null); 1150 } 1151 1152 private void dispatchSessionFinished(int returnCode, String msg, Bundle extras) { 1153 mFinalStatus = returnCode; 1154 mFinalMessage = msg; 1155 1156 if (mRemoteObserver != null) { 1157 try { 1158 mRemoteObserver.onPackageInstalled(mPackageName, returnCode, msg, extras); 1159 } catch (RemoteException ignored) { 1160 } 1161 } 1162 1163 final boolean success = (returnCode == PackageManager.INSTALL_SUCCEEDED); 1164 1165 // Send broadcast to default launcher only if it's a new install 1166 final boolean isNewInstall = extras == null || !extras.getBoolean(Intent.EXTRA_REPLACING); 1167 if (success && isNewInstall) { 1168 mPm.sendSessionCommitBroadcast(generateInfo(), userId); 1169 } 1170 1171 mCallback.onSessionFinished(this, success); 1172 } 1173 1174 private void destroyInternal() { 1175 synchronized (mLock) { 1176 mSealed = true; 1177 mDestroyed = true; 1178 1179 // Force shut down all bridges 1180 for (RevocableFileDescriptor fd : mFds) { 1181 fd.revoke(); 1182 } 1183 for (FileBridge bridge : mBridges) { 1184 bridge.forceClose(); 1185 } 1186 } 1187 if (stageDir != null) { 1188 try { 1189 mPm.mInstaller.rmPackageDir(stageDir.getAbsolutePath()); 1190 } catch (InstallerException ignored) { 1191 } 1192 } 1193 if (stageCid != null) { 1194 PackageHelper.destroySdDir(stageCid); 1195 } 1196 } 1197 1198 void dump(IndentingPrintWriter pw) { 1199 synchronized (mLock) { 1200 dumpLocked(pw); 1201 } 1202 } 1203 1204 private void dumpLocked(IndentingPrintWriter pw) { 1205 pw.println("Session " + sessionId + ":"); 1206 pw.increaseIndent(); 1207 1208 pw.printPair("userId", userId); 1209 pw.printPair("installerPackageName", installerPackageName); 1210 pw.printPair("installerUid", installerUid); 1211 pw.printPair("createdMillis", createdMillis); 1212 pw.printPair("stageDir", stageDir); 1213 pw.printPair("stageCid", stageCid); 1214 pw.println(); 1215 1216 params.dump(pw); 1217 1218 pw.printPair("mClientProgress", mClientProgress); 1219 pw.printPair("mProgress", mProgress); 1220 pw.printPair("mSealed", mSealed); 1221 pw.printPair("mPermissionsAccepted", mPermissionsAccepted); 1222 pw.printPair("mRelinquished", mRelinquished); 1223 pw.printPair("mDestroyed", mDestroyed); 1224 pw.printPair("mFds", mFds.size()); 1225 pw.printPair("mBridges", mBridges.size()); 1226 pw.printPair("mFinalStatus", mFinalStatus); 1227 pw.printPair("mFinalMessage", mFinalMessage); 1228 pw.println(); 1229 1230 pw.decreaseIndent(); 1231 } 1232} 1233