PackageInstallerSession.java revision cef0b39b9211882f59b6bfe1148e2cd247056693
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_ALREADY_EXISTS; 20import static android.content.pm.PackageManager.INSTALL_FAILED_INTERNAL_ERROR; 21import static android.content.pm.PackageManager.INSTALL_FAILED_INVALID_APK; 22import static android.content.pm.PackageManager.INSTALL_FAILED_PACKAGE_CHANGED; 23import static android.content.pm.PackageManager.INSTALL_SUCCEEDED; 24 25import android.content.pm.ApplicationInfo; 26import android.content.pm.IPackageInstallObserver2; 27import android.content.pm.IPackageInstallerSession; 28import android.content.pm.PackageInstallerParams; 29import android.content.pm.PackageManager; 30import android.content.pm.PackageParser; 31import android.content.pm.PackageParser.ApkLite; 32import android.content.pm.PackageParser.PackageParserException; 33import android.content.pm.Signature; 34import android.os.Build; 35import android.os.Bundle; 36import android.os.FileBridge; 37import android.os.FileUtils; 38import android.os.Handler; 39import android.os.Looper; 40import android.os.Message; 41import android.os.ParcelFileDescriptor; 42import android.os.RemoteException; 43import android.os.SELinux; 44import android.system.ErrnoException; 45import android.system.OsConstants; 46import android.system.StructStat; 47import android.util.ArraySet; 48import android.util.Slog; 49 50import com.android.internal.content.NativeLibraryHelper; 51import com.android.internal.util.ArrayUtils; 52import com.android.internal.util.Preconditions; 53 54import libcore.io.IoUtils; 55import libcore.io.Libcore; 56 57import java.io.File; 58import java.io.FileDescriptor; 59import java.io.IOException; 60import java.util.ArrayList; 61 62public class PackageInstallerSession extends IPackageInstallerSession.Stub { 63 private static final String TAG = "PackageInstaller"; 64 65 private final PackageInstallerService.Callback mCallback; 66 private final PackageManagerService mPm; 67 private final Handler mHandler; 68 69 public final int sessionId; 70 public final int userId; 71 public final String installerPackageName; 72 /** UID not persisted */ 73 public final int installerUid; 74 public final PackageInstallerParams params; 75 public final long createdMillis; 76 public final File sessionDir; 77 78 private static final int MSG_INSTALL = 0; 79 80 private Handler.Callback mHandlerCallback = new Handler.Callback() { 81 @Override 82 public boolean handleMessage(Message msg) { 83 synchronized (mLock) { 84 if (msg.obj != null) { 85 mRemoteObserver = (IPackageInstallObserver2) msg.obj; 86 } 87 88 try { 89 installLocked(); 90 } catch (InstallFailedException e) { 91 Slog.e(TAG, "Install failed: " + e); 92 try { 93 mRemoteObserver.packageInstalled(mPackageName, null, e.error); 94 } catch (RemoteException ignored) { 95 } 96 } 97 98 return true; 99 } 100 } 101 }; 102 103 private final Object mLock = new Object(); 104 105 private int mProgress; 106 107 private String mPackageName; 108 private int mVersionCode; 109 private Signature[] mSignatures; 110 111 private boolean mMutationsAllowed; 112 private boolean mVerifierConfirmed; 113 private boolean mPermissionsConfirmed; 114 private boolean mInvalid; 115 116 private ArrayList<FileBridge> mBridges = new ArrayList<>(); 117 118 private IPackageInstallObserver2 mRemoteObserver; 119 120 public PackageInstallerSession(PackageInstallerService.Callback callback, 121 PackageManagerService pm, int sessionId, int userId, String installerPackageName, 122 int installerUid, PackageInstallerParams params, long createdMillis, File sessionDir, 123 Looper looper) { 124 mCallback = callback; 125 mPm = pm; 126 mHandler = new Handler(looper, mHandlerCallback); 127 128 this.sessionId = sessionId; 129 this.userId = userId; 130 this.installerPackageName = installerPackageName; 131 this.installerUid = installerUid; 132 this.params = params; 133 this.createdMillis = createdMillis; 134 this.sessionDir = sessionDir; 135 136 // Check against any explicitly provided signatures 137 mSignatures = params.signatures; 138 139 // TODO: splice in flag when restoring persisted session 140 mMutationsAllowed = true; 141 142 if (pm.checkPermission(android.Manifest.permission.INSTALL_PACKAGES, installerPackageName) 143 == PackageManager.PERMISSION_GRANTED) { 144 mPermissionsConfirmed = true; 145 } 146 } 147 148 @Override 149 public void updateProgress(int progress) { 150 mProgress = progress; 151 mCallback.onProgressChanged(this); 152 } 153 154 @Override 155 public ParcelFileDescriptor openWrite(String name, long offsetBytes, long lengthBytes) { 156 // TODO: relay over to DCS when installing to ASEC 157 158 // Quick sanity check of state, and allocate a pipe for ourselves. We 159 // then do heavy disk allocation outside the lock, but this open pipe 160 // will block any attempted install transitions. 161 final FileBridge bridge; 162 synchronized (mLock) { 163 if (!mMutationsAllowed) { 164 throw new IllegalStateException("Mutations not allowed"); 165 } 166 167 bridge = new FileBridge(); 168 mBridges.add(bridge); 169 } 170 171 try { 172 // Use installer provided name for now; we always rename later 173 if (!FileUtils.isValidExtFilename(name)) { 174 throw new IllegalArgumentException("Invalid name: " + name); 175 } 176 final File target = new File(sessionDir, name); 177 178 final FileDescriptor targetFd = Libcore.os.open(target.getAbsolutePath(), 179 OsConstants.O_CREAT | OsConstants.O_WRONLY, 00700); 180 181 // If caller specified a total length, allocate it for them. Free up 182 // cache space to grow, if needed. 183 if (lengthBytes > 0) { 184 final StructStat stat = Libcore.os.fstat(targetFd); 185 final long deltaBytes = lengthBytes - stat.st_size; 186 if (deltaBytes > 0) { 187 mPm.freeStorage(deltaBytes); 188 } 189 Libcore.os.posix_fallocate(targetFd, 0, lengthBytes); 190 } 191 192 if (offsetBytes > 0) { 193 Libcore.os.lseek(targetFd, offsetBytes, OsConstants.SEEK_SET); 194 } 195 196 bridge.setTargetFile(targetFd); 197 bridge.start(); 198 return new ParcelFileDescriptor(bridge.getClientSocket()); 199 200 } catch (ErrnoException e) { 201 throw new IllegalStateException("Failed to write", e); 202 } catch (IOException e) { 203 throw new IllegalStateException("Failed to write", e); 204 } 205 } 206 207 @Override 208 public void install(IPackageInstallObserver2 observer) { 209 Preconditions.checkNotNull(observer); 210 mHandler.obtainMessage(MSG_INSTALL, observer).sendToTarget(); 211 } 212 213 private void installLocked() throws InstallFailedException { 214 if (mInvalid) { 215 throw new InstallFailedException(INSTALL_FAILED_ALREADY_EXISTS, "Invalid session"); 216 } 217 218 // Verify that all writers are hands-off 219 if (mMutationsAllowed) { 220 for (FileBridge bridge : mBridges) { 221 if (!bridge.isClosed()) { 222 throw new InstallFailedException(INSTALL_FAILED_PACKAGE_CHANGED, 223 "Files still open"); 224 } 225 } 226 mMutationsAllowed = false; 227 228 // TODO: persist disabled mutations before going forward, since 229 // beyond this point we may have hardlinks to the valid install 230 } 231 232 // Verify that stage looks sane with respect to existing application. 233 // This currently only ensures packageName, versionCode, and certificate 234 // consistency. 235 validateInstallLocked(); 236 237 Preconditions.checkNotNull(mPackageName); 238 Preconditions.checkNotNull(mSignatures); 239 240 if (!mVerifierConfirmed) { 241 // TODO: async communication with verifier 242 // when they confirm, we'll kick off another install() pass 243 mVerifierConfirmed = true; 244 } 245 246 if (!mPermissionsConfirmed) { 247 // TODO: async confirm permissions with user 248 // when they confirm, we'll kick off another install() pass 249 mPermissionsConfirmed = true; 250 } 251 252 // Unpack any native libraries contained in this session 253 unpackNativeLibraries(); 254 255 // Inherit any packages and native libraries from existing install that 256 // haven't been overridden. 257 if (!params.fullInstall) { 258 spliceExistingFilesIntoStage(); 259 } 260 261 // TODO: for ASEC based applications, grow and stream in packages 262 263 // We've reached point of no return; call into PMS to install the stage. 264 // Regardless of success or failure we always destroy session. 265 final IPackageInstallObserver2 remoteObserver = mRemoteObserver; 266 final IPackageInstallObserver2 localObserver = new IPackageInstallObserver2.Stub() { 267 @Override 268 public void packageInstalled(String basePackageName, Bundle extras, int returnCode) 269 throws RemoteException { 270 destroy(); 271 remoteObserver.packageInstalled(basePackageName, extras, returnCode); 272 } 273 }; 274 275 mPm.installStage(mPackageName, this.sessionDir, localObserver, params.installFlags); 276 } 277 278 /** 279 * Validate install by confirming that all application packages are have 280 * consistent package name, version code, and signing certificates. 281 * <p> 282 * Renames package files in stage to match split names defined inside. 283 */ 284 private void validateInstallLocked() throws InstallFailedException { 285 mPackageName = null; 286 mVersionCode = -1; 287 mSignatures = null; 288 289 final File[] files = sessionDir.listFiles(); 290 if (ArrayUtils.isEmpty(files)) { 291 throw new InstallFailedException(INSTALL_FAILED_INVALID_APK, "No packages staged"); 292 } 293 294 final ArraySet<String> seenSplits = new ArraySet<>(); 295 296 // Verify that all staged packages are internally consistent 297 for (File file : files) { 298 final ApkLite info; 299 try { 300 info = PackageParser.parseApkLite(file, PackageParser.PARSE_GET_SIGNATURES); 301 } catch (PackageParserException e) { 302 throw new InstallFailedException(INSTALL_FAILED_INVALID_APK, 303 "Failed to parse " + file + ": " + e); 304 } 305 306 if (!seenSplits.add(info.splitName)) { 307 throw new InstallFailedException(INSTALL_FAILED_INVALID_APK, 308 "Split " + info.splitName + " was defined multiple times"); 309 } 310 311 // Use first package to define unknown values 312 if (mPackageName != null) { 313 mPackageName = info.packageName; 314 mVersionCode = info.versionCode; 315 } 316 if (mSignatures != null) { 317 mSignatures = info.signatures; 318 } 319 320 assertPackageConsistent(String.valueOf(file), info.packageName, info.versionCode, 321 info.signatures); 322 323 // Take this opportunity to enforce uniform naming 324 final String name; 325 if (info.splitName == null) { 326 name = info.packageName + ".apk"; 327 } else { 328 name = info.packageName + "-" + info.splitName + ".apk"; 329 } 330 if (!FileUtils.isValidExtFilename(name)) { 331 throw new InstallFailedException(INSTALL_FAILED_INVALID_APK, 332 "Invalid filename: " + name); 333 } 334 if (!file.getName().equals(name)) { 335 file.renameTo(new File(file.getParentFile(), name)); 336 } 337 } 338 339 // TODO: shift package signature verification to installer; we're 340 // currently relying on PMS to do this. 341 // TODO: teach about compatible upgrade keysets. 342 343 if (params.fullInstall) { 344 // Full installs must include a base package 345 if (!seenSplits.contains(null)) { 346 throw new InstallFailedException(INSTALL_FAILED_INVALID_APK, 347 "Full install must include a base package"); 348 } 349 350 } else { 351 // Partial installs must be consistent with existing install. 352 final ApplicationInfo app = mPm.getApplicationInfo(mPackageName, 0, userId); 353 if (app == null) { 354 throw new InstallFailedException(INSTALL_FAILED_INVALID_APK, 355 "Missing existing base package for " + mPackageName); 356 } 357 358 final ApkLite info; 359 try { 360 info = PackageParser.parseApkLite(new File(app.sourceDir), 361 PackageParser.PARSE_GET_SIGNATURES); 362 } catch (PackageParserException e) { 363 throw new InstallFailedException(INSTALL_FAILED_INVALID_APK, 364 "Failed to parse existing base " + app.sourceDir + ": " + e); 365 } 366 367 assertPackageConsistent("Existing base", info.packageName, info.versionCode, 368 info.signatures); 369 } 370 } 371 372 private void assertPackageConsistent(String tag, String packageName, int versionCode, 373 Signature[] signatures) throws InstallFailedException { 374 if (!mPackageName.equals(packageName)) { 375 throw new InstallFailedException(INSTALL_FAILED_INVALID_APK, tag + " package " 376 + packageName + " inconsistent with " + mPackageName); 377 } 378 if (mVersionCode != versionCode) { 379 throw new InstallFailedException(INSTALL_FAILED_INVALID_APK, tag 380 + " version code " + versionCode + " inconsistent with " 381 + mVersionCode); 382 } 383 if (!Signature.areExactMatch(mSignatures, signatures)) { 384 throw new InstallFailedException(INSTALL_FAILED_INVALID_APK, 385 tag + " signatures are inconsistent"); 386 } 387 } 388 389 /** 390 * Application is already installed; splice existing files that haven't been 391 * overridden into our stage. 392 */ 393 private void spliceExistingFilesIntoStage() throws InstallFailedException { 394 final ApplicationInfo app = mPm.getApplicationInfo(mPackageName, 0, userId); 395 final File existingDir = new File(app.sourceDir).getParentFile(); 396 397 try { 398 linkTreeIgnoringExisting(existingDir, sessionDir); 399 } catch (ErrnoException e) { 400 throw new InstallFailedException(INSTALL_FAILED_INTERNAL_ERROR, 401 "Failed to splice into stage"); 402 } 403 } 404 405 /** 406 * Recursively hard link all files from source directory tree to target. 407 * When a file already exists in the target tree, it leaves that file 408 * intact. 409 */ 410 private void linkTreeIgnoringExisting(File sourceDir, File targetDir) throws ErrnoException { 411 final File[] sourceContents = sourceDir.listFiles(); 412 if (ArrayUtils.isEmpty(sourceContents)) return; 413 414 for (File sourceFile : sourceContents) { 415 final File targetFile = new File(targetDir, sourceFile.getName()); 416 417 if (sourceFile.isDirectory()) { 418 targetFile.mkdir(); 419 linkTreeIgnoringExisting(sourceFile, targetFile); 420 } else { 421 Libcore.os.link(sourceFile.getAbsolutePath(), targetFile.getAbsolutePath()); 422 } 423 } 424 } 425 426 private void unpackNativeLibraries() throws InstallFailedException { 427 final File libDir = new File(sessionDir, "lib"); 428 429 if (!libDir.mkdir()) { 430 throw new InstallFailedException(INSTALL_FAILED_INTERNAL_ERROR, 431 "Failed to create " + libDir); 432 } 433 434 try { 435 Libcore.os.chmod(libDir.getAbsolutePath(), 0755); 436 } catch (ErrnoException e) { 437 throw new InstallFailedException(INSTALL_FAILED_INTERNAL_ERROR, 438 "Failed to prepare " + libDir + ": " + e); 439 } 440 441 if (!SELinux.restorecon(libDir)) { 442 throw new InstallFailedException(INSTALL_FAILED_INTERNAL_ERROR, 443 "Failed to set context on " + libDir); 444 } 445 446 // Unpack all native libraries under stage 447 final File[] files = sessionDir.listFiles(); 448 if (ArrayUtils.isEmpty(files)) { 449 throw new InstallFailedException(INSTALL_FAILED_INVALID_APK, "No packages staged"); 450 } 451 452 for (File file : files) { 453 NativeLibraryHelper.ApkHandle handle = null; 454 try { 455 handle = NativeLibraryHelper.ApkHandle.create(file); 456 final int abiIndex = NativeLibraryHelper.findSupportedAbi(handle, 457 Build.SUPPORTED_ABIS); 458 if (abiIndex >= 0) { 459 int copyRet = NativeLibraryHelper.copyNativeBinariesIfNeededLI(handle, libDir, 460 Build.SUPPORTED_ABIS[abiIndex]); 461 if (copyRet != INSTALL_SUCCEEDED) { 462 throw new InstallFailedException(copyRet, 463 "Failed to copy native libraries for " + file); 464 } 465 } else if (abiIndex != PackageManager.NO_NATIVE_LIBRARIES) { 466 throw new InstallFailedException(abiIndex, 467 "Failed to copy native libraries for " + file); 468 } 469 } catch (IOException ioe) { 470 throw new InstallFailedException(INSTALL_FAILED_INTERNAL_ERROR, 471 "Failed to create handle for " + file); 472 } finally { 473 IoUtils.closeQuietly(handle); 474 } 475 } 476 } 477 478 @Override 479 public void destroy() { 480 try { 481 synchronized (mLock) { 482 mInvalid = true; 483 } 484 FileUtils.deleteContents(sessionDir); 485 sessionDir.delete(); 486 } finally { 487 mCallback.onSessionInvalid(this); 488 } 489 } 490 491 private class InstallFailedException extends Exception { 492 private final int error; 493 494 public InstallFailedException(int error, String detailMessage) { 495 super(detailMessage); 496 this.error = error; 497 } 498 } 499} 500