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