1 /* 2 * Copyright (C) 2016 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License 15 */ 16 17 package com.android.server.pm.dex; 18 19 import static com.android.server.pm.InstructionSets.getAppDexInstructionSets; 20 import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME; 21 import static com.android.server.pm.dex.PackageDexUsage.DexUseInfo; 22 import static com.android.server.pm.dex.PackageDexUsage.PackageUseInfo; 23 24 import static java.util.function.Function.identity; 25 26 import android.content.Context; 27 import android.content.pm.ApplicationInfo; 28 import android.content.pm.IPackageManager; 29 import android.content.pm.PackageInfo; 30 import android.content.pm.PackagePartitions; 31 import android.os.FileUtils; 32 import android.os.RemoteException; 33 import android.os.SystemProperties; 34 import android.os.UserHandle; 35 import android.os.storage.StorageManager; 36 import android.util.Log; 37 import android.util.Slog; 38 import android.util.jar.StrictJarFile; 39 40 import com.android.internal.annotations.GuardedBy; 41 import com.android.internal.annotations.VisibleForTesting; 42 import com.android.server.pm.Installer; 43 import com.android.server.pm.Installer.InstallerException; 44 import com.android.server.pm.PackageDexOptimizer; 45 import com.android.server.pm.PackageManagerService; 46 import com.android.server.pm.PackageManagerServiceUtils; 47 48 import dalvik.system.VMRuntime; 49 50 import java.io.File; 51 import java.io.IOException; 52 import java.nio.file.Files; 53 import java.nio.file.Paths; 54 import java.util.ArrayList; 55 import java.util.Arrays; 56 import java.util.Collection; 57 import java.util.Collections; 58 import java.util.HashMap; 59 import java.util.HashSet; 60 import java.util.Iterator; 61 import java.util.List; 62 import java.util.Map; 63 import java.util.Set; 64 import java.util.zip.ZipEntry; 65 66 /** 67 * This class keeps track of how dex files are used. 68 * Every time it gets a notification about a dex file being loaded it tracks 69 * its owning package and records it in PackageDexUsage (package-dex-usage.list). 70 * 71 * TODO(calin): Extract related dexopt functionality from PackageManagerService 72 * into this class. 73 */ 74 public class DexManager { 75 private static final String TAG = "DexManager"; 76 private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); 77 78 private static final String PROPERTY_NAME_PM_DEXOPT_PRIV_APPS_OOB = "pm.dexopt.priv-apps-oob"; 79 private static final String PROPERTY_NAME_PM_DEXOPT_PRIV_APPS_OOB_LIST = 80 "pm.dexopt.priv-apps-oob-list"; 81 82 // System server cannot load executable code outside system partitions. 83 // However it can load verification data - thus we pick the "verify" compiler filter. 84 private static final String SYSTEM_SERVER_COMPILER_FILTER = "verify"; 85 86 private final Context mContext; 87 88 // Maps package name to code locations. 89 // It caches the code locations for the installed packages. This allows for 90 // faster lookups (no locks) when finding what package owns the dex file. 91 @GuardedBy("mPackageCodeLocationsCache") 92 private final Map<String, PackageCodeLocations> mPackageCodeLocationsCache; 93 94 // PackageDexUsage handles the actual I/O operations. It is responsible to 95 // encode and save the dex usage data. 96 private final PackageDexUsage mPackageDexUsage; 97 98 // DynamicCodeLogger handles recording of dynamic code loading - which is similar to 99 // PackageDexUsage but records a different aspect of the data. 100 // (It additionally includes DEX files loaded with unsupported class loaders, and doesn't 101 // record class loaders or ISAs.) 102 private final DynamicCodeLogger mDynamicCodeLogger; 103 104 private final IPackageManager mPackageManager; 105 private final PackageDexOptimizer mPackageDexOptimizer; 106 private final Object mInstallLock; 107 @GuardedBy("mInstallLock") 108 private final Installer mInstaller; 109 110 // Possible outcomes of a dex search. 111 private static int DEX_SEARCH_NOT_FOUND = 0; // dex file not found 112 private static int DEX_SEARCH_FOUND_PRIMARY = 1; // dex file is the primary/base apk 113 private static int DEX_SEARCH_FOUND_SPLIT = 2; // dex file is a split apk 114 private static int DEX_SEARCH_FOUND_SECONDARY = 3; // dex file is a secondary dex 115 DexManager(Context context, IPackageManager pms, PackageDexOptimizer pdo, Installer installer, Object installLock)116 public DexManager(Context context, IPackageManager pms, PackageDexOptimizer pdo, 117 Installer installer, Object installLock) { 118 mContext = context; 119 mPackageCodeLocationsCache = new HashMap<>(); 120 mPackageDexUsage = new PackageDexUsage(); 121 mPackageManager = pms; 122 mPackageDexOptimizer = pdo; 123 mInstaller = installer; 124 mInstallLock = installLock; 125 mDynamicCodeLogger = new DynamicCodeLogger(pms, installer); 126 } 127 getDynamicCodeLogger()128 public DynamicCodeLogger getDynamicCodeLogger() { 129 return mDynamicCodeLogger; 130 } 131 132 /** 133 * Notify about dex files loads. 134 * Note that this method is invoked when apps load dex files and it should 135 * return as fast as possible. 136 * 137 * @param loadingAppInfo the package performing the load 138 * @param classLoaderContextMap a map from file paths to dex files that have been loaded to 139 * the class loader context that was used to load them. 140 * @param loaderIsa the ISA of the app loading the dex files 141 * @param loaderUserId the user id which runs the code loading the dex files 142 */ notifyDexLoad(ApplicationInfo loadingAppInfo, Map<String, String> classLoaderContextMap, String loaderIsa, int loaderUserId)143 public void notifyDexLoad(ApplicationInfo loadingAppInfo, 144 Map<String, String> classLoaderContextMap, String loaderIsa, int loaderUserId) { 145 try { 146 notifyDexLoadInternal(loadingAppInfo, classLoaderContextMap, loaderIsa, 147 loaderUserId); 148 } catch (Exception e) { 149 Slog.w(TAG, "Exception while notifying dex load for package " + 150 loadingAppInfo.packageName, e); 151 } 152 } 153 154 @VisibleForTesting notifyDexLoadInternal(ApplicationInfo loadingAppInfo, Map<String, String> classLoaderContextMap, String loaderIsa, int loaderUserId)155 /*package*/ void notifyDexLoadInternal(ApplicationInfo loadingAppInfo, 156 Map<String, String> classLoaderContextMap, String loaderIsa, 157 int loaderUserId) { 158 if (classLoaderContextMap == null) { 159 return; 160 } 161 if (classLoaderContextMap.isEmpty()) { 162 Slog.wtf(TAG, "Bad call to notifyDexLoad: class loaders list is empty"); 163 return; 164 } 165 if (!PackageManagerServiceUtils.checkISA(loaderIsa)) { 166 Slog.w(TAG, "Loading dex files " + classLoaderContextMap.keySet() 167 + " in unsupported ISA: " + loaderIsa + "?"); 168 return; 169 } 170 171 for (Map.Entry<String, String> mapping : classLoaderContextMap.entrySet()) { 172 String dexPath = mapping.getKey(); 173 // Find the owning package name. 174 DexSearchResult searchResult = getDexPackage(loadingAppInfo, dexPath, loaderUserId); 175 176 if (DEBUG) { 177 Slog.i(TAG, loadingAppInfo.packageName 178 + " loads from " + searchResult + " : " + loaderUserId + " : " + dexPath); 179 } 180 181 if (searchResult.mOutcome != DEX_SEARCH_NOT_FOUND) { 182 // TODO(calin): extend isUsedByOtherApps check to detect the cases where 183 // different apps share the same runtime. In that case we should not mark the dex 184 // file as isUsedByOtherApps. Currently this is a safe approximation. 185 boolean isUsedByOtherApps = !loadingAppInfo.packageName.equals( 186 searchResult.mOwningPackageName); 187 boolean primaryOrSplit = searchResult.mOutcome == DEX_SEARCH_FOUND_PRIMARY || 188 searchResult.mOutcome == DEX_SEARCH_FOUND_SPLIT; 189 190 if (primaryOrSplit && !isUsedByOtherApps 191 && !PLATFORM_PACKAGE_NAME.equals(searchResult.mOwningPackageName)) { 192 // If the dex file is the primary apk (or a split) and not isUsedByOtherApps 193 // do not record it. This case does not bring any new usable information 194 // and can be safely skipped. 195 // Note this is just an optimization that makes things easier to read in the 196 // package-dex-use file since we don't need to pollute it with redundant info. 197 // However, we always record system server packages. 198 continue; 199 } 200 201 if (!primaryOrSplit) { 202 // Record loading of a DEX file from an app data directory. 203 mDynamicCodeLogger.recordDex(loaderUserId, dexPath, 204 searchResult.mOwningPackageName, loadingAppInfo.packageName); 205 } 206 207 String classLoaderContext = mapping.getValue(); 208 if (classLoaderContext != null 209 && VMRuntime.isValidClassLoaderContext(classLoaderContext)) { 210 // Record dex file usage. If the current usage is a new pattern (e.g. new 211 // secondary, or UsedByOtherApps), record will return true and we trigger an 212 // async write to disk to make sure we don't loose the data in case of a reboot. 213 214 if (mPackageDexUsage.record(searchResult.mOwningPackageName, 215 dexPath, loaderUserId, loaderIsa, primaryOrSplit, 216 loadingAppInfo.packageName, classLoaderContext)) { 217 mPackageDexUsage.maybeWriteAsync(); 218 } 219 } 220 } else { 221 // If we can't find the owner of the dex we simply do not track it. The impact is 222 // that the dex file will not be considered for offline optimizations. 223 if (DEBUG) { 224 Slog.i(TAG, "Could not find owning package for dex file: " + dexPath); 225 } 226 } 227 } 228 } 229 230 /** 231 * Check if the dexPath belongs to system server. 232 * System server can load code from different location, so we cast a wide-net here, and 233 * assume that if the paths is on any of the registered system partitions then it can be loaded 234 * by system server. 235 */ isSystemServerDexPathSupportedForOdex(String dexPath)236 private boolean isSystemServerDexPathSupportedForOdex(String dexPath) { 237 ArrayList<PackagePartitions.SystemPartition> partitions = 238 PackagePartitions.getOrderedPartitions(identity()); 239 // First check the apex partition as it's not part of the SystemPartitions. 240 if (dexPath.startsWith("/apex/")) { 241 return true; 242 } 243 for (int i = 0; i < partitions.size(); i++) { 244 if (partitions.get(i).containsPath(dexPath)) { 245 return true; 246 } 247 } 248 return false; 249 } 250 251 /** 252 * Read the dex usage from disk and populate the code cache locations. 253 * @param existingPackages a map containing information about what packages 254 * are available to what users. Only packages in this list will be 255 * recognized during notifyDexLoad(). 256 */ load(Map<Integer, List<PackageInfo>> existingPackages)257 public void load(Map<Integer, List<PackageInfo>> existingPackages) { 258 try { 259 loadInternal(existingPackages); 260 } catch (Exception e) { 261 mPackageDexUsage.clear(); 262 mDynamicCodeLogger.clear(); 263 Slog.w(TAG, "Exception while loading. Starting with a fresh state.", e); 264 } 265 } 266 267 /** 268 * Notifies that a new package was installed for {@code userId}. 269 * {@code userId} must not be {@code UserHandle.USER_ALL}. 270 * 271 * @throws IllegalArgumentException if {@code userId} is {@code UserHandle.USER_ALL}. 272 */ notifyPackageInstalled(PackageInfo pi, int userId)273 public void notifyPackageInstalled(PackageInfo pi, int userId) { 274 if (userId == UserHandle.USER_ALL) { 275 throw new IllegalArgumentException( 276 "notifyPackageInstalled called with USER_ALL"); 277 } 278 cachePackageInfo(pi, userId); 279 } 280 281 /** 282 * Notifies that package {@code packageName} was updated. 283 * This will clear the UsedByOtherApps mark if it exists. 284 */ notifyPackageUpdated(String packageName, String baseCodePath, String[] splitCodePaths)285 public void notifyPackageUpdated(String packageName, String baseCodePath, 286 String[] splitCodePaths) { 287 cachePackageCodeLocation(packageName, baseCodePath, splitCodePaths, null, /*userId*/ -1); 288 // In case there was an update, write the package use info to disk async. 289 // Note that we do the writing here and not in PackageDexUsage in order to be 290 // consistent with other methods in DexManager (e.g. reconcileSecondaryDexFiles performs 291 // multiple updates in PackageDexUsage before writing it). 292 if (mPackageDexUsage.clearUsedByOtherApps(packageName)) { 293 mPackageDexUsage.maybeWriteAsync(); 294 } 295 } 296 297 /** 298 * Notifies that the user {@code userId} data for package {@code packageName} 299 * was destroyed. This will remove all usage info associated with the package 300 * for the given user. 301 * {@code userId} is allowed to be {@code UserHandle.USER_ALL} in which case 302 * all usage information for the package will be removed. 303 */ notifyPackageDataDestroyed(String packageName, int userId)304 public void notifyPackageDataDestroyed(String packageName, int userId) { 305 // In case there was an update, write the package use info to disk async. 306 // Note that we do the writing here and not in the lower level classes in order to be 307 // consistent with other methods in DexManager (e.g. reconcileSecondaryDexFiles performs 308 // multiple updates in PackageDexUsage before writing it). 309 if (userId == UserHandle.USER_ALL) { 310 if (mPackageDexUsage.removePackage(packageName)) { 311 mPackageDexUsage.maybeWriteAsync(); 312 } 313 mDynamicCodeLogger.removePackage(packageName); 314 } else { 315 if (mPackageDexUsage.removeUserPackage(packageName, userId)) { 316 mPackageDexUsage.maybeWriteAsync(); 317 } 318 mDynamicCodeLogger.removeUserPackage(packageName, userId); 319 } 320 } 321 322 /** 323 * Caches the code location from the given package info. 324 */ cachePackageInfo(PackageInfo pi, int userId)325 private void cachePackageInfo(PackageInfo pi, int userId) { 326 ApplicationInfo ai = pi.applicationInfo; 327 String[] dataDirs = new String[] {ai.dataDir, ai.deviceProtectedDataDir, 328 ai.credentialProtectedDataDir}; 329 cachePackageCodeLocation(pi.packageName, ai.sourceDir, ai.splitSourceDirs, 330 dataDirs, userId); 331 } 332 cachePackageCodeLocation(String packageName, String baseCodePath, String[] splitCodePaths, String[] dataDirs, int userId)333 private void cachePackageCodeLocation(String packageName, String baseCodePath, 334 String[] splitCodePaths, String[] dataDirs, int userId) { 335 synchronized (mPackageCodeLocationsCache) { 336 PackageCodeLocations pcl = putIfAbsent(mPackageCodeLocationsCache, packageName, 337 new PackageCodeLocations(packageName, baseCodePath, splitCodePaths)); 338 // TODO(calin): We are forced to extend the scope of this synchronization because 339 // the values of the cache (PackageCodeLocations) are updated in place. 340 // Make PackageCodeLocations immutable to simplify the synchronization reasoning. 341 pcl.updateCodeLocation(baseCodePath, splitCodePaths); 342 if (dataDirs != null) { 343 for (String dataDir : dataDirs) { 344 // The set of data dirs includes deviceProtectedDataDir and 345 // credentialProtectedDataDir which might be null for shared 346 // libraries. Currently we don't track these but be lenient 347 // and check in case we ever decide to store their usage data. 348 if (dataDir != null) { 349 pcl.mergeAppDataDirs(dataDir, userId); 350 } 351 } 352 } 353 } 354 } 355 loadInternal(Map<Integer, List<PackageInfo>> existingPackages)356 private void loadInternal(Map<Integer, List<PackageInfo>> existingPackages) { 357 Map<String, Set<Integer>> packageToUsersMap = new HashMap<>(); 358 Map<String, Set<String>> packageToCodePaths = new HashMap<>(); 359 360 // Cache the code locations for the installed packages. This allows for 361 // faster lookups (no locks) when finding what package owns the dex file. 362 for (Map.Entry<Integer, List<PackageInfo>> entry : existingPackages.entrySet()) { 363 List<PackageInfo> packageInfoList = entry.getValue(); 364 int userId = entry.getKey(); 365 for (PackageInfo pi : packageInfoList) { 366 // Cache the code locations. 367 cachePackageInfo(pi, userId); 368 369 // Cache two maps: 370 // - from package name to the set of user ids who installed the package. 371 // - from package name to the set of code paths. 372 // We will use it to sync the data and remove obsolete entries from 373 // mPackageDexUsage. 374 Set<Integer> users = putIfAbsent( 375 packageToUsersMap, pi.packageName, new HashSet<>()); 376 users.add(userId); 377 378 Set<String> codePaths = putIfAbsent( 379 packageToCodePaths, pi.packageName, new HashSet<>()); 380 codePaths.add(pi.applicationInfo.sourceDir); 381 if (pi.applicationInfo.splitSourceDirs != null) { 382 Collections.addAll(codePaths, pi.applicationInfo.splitSourceDirs); 383 } 384 } 385 } 386 387 try { 388 mPackageDexUsage.read(); 389 List<String> packagesToKeepDataAbout = new ArrayList<>(); 390 mPackageDexUsage.syncData( 391 packageToUsersMap, packageToCodePaths, packagesToKeepDataAbout); 392 } catch (Exception e) { 393 mPackageDexUsage.clear(); 394 Slog.w(TAG, "Exception while loading package dex usage. " 395 + "Starting with a fresh state.", e); 396 } 397 398 try { 399 mDynamicCodeLogger.readAndSync(packageToUsersMap); 400 } catch (Exception e) { 401 mDynamicCodeLogger.clear(); 402 Slog.w(TAG, "Exception while loading package dynamic code usage. " 403 + "Starting with a fresh state.", e); 404 } 405 } 406 407 /** 408 * Get the package dex usage for the given package name. 409 * If there is no usage info the method will return a default {@code PackageUseInfo} with 410 * no data about secondary dex files and marked as not being used by other apps. 411 * 412 * Note that no use info means the package was not used or it was used but not by other apps. 413 * Also, note that right now we might prune packages which are not used by other apps. 414 * TODO(calin): maybe we should not (prune) so we can have an accurate view when we try 415 * to access the package use. 416 */ getPackageUseInfoOrDefault(String packageName)417 public PackageUseInfo getPackageUseInfoOrDefault(String packageName) { 418 // We do not record packages that have no secondary dex files or that are not used by other 419 // apps. This is an optimization to reduce the amount of data that needs to be written to 420 // disk (apps will not usually be shared so this trims quite a bit the number we record). 421 // 422 // To make this behaviour transparent to the callers which need use information on packages, 423 // DexManager will return this DEFAULT instance from 424 // {@link DexManager#getPackageUseInfoOrDefault}. It has no data about secondary dex files 425 // and is marked as not being used by other apps. This reflects the intended behaviour when 426 // we don't find the package in the underlying data file. 427 PackageUseInfo useInfo = mPackageDexUsage.getPackageUseInfo(packageName); 428 return useInfo == null ? new PackageUseInfo(packageName) : useInfo; 429 } 430 431 /** 432 * Return whether or not the manager has usage information on the give package. 433 * 434 * Note that no use info means the package was not used or it was used but not by other apps. 435 * Also, note that right now we might prune packages which are not used by other apps. 436 * TODO(calin): maybe we should not (prune) so we can have an accurate view when we try 437 * to access the package use. 438 */ 439 @VisibleForTesting hasInfoOnPackage(String packageName)440 /*package*/ boolean hasInfoOnPackage(String packageName) { 441 return mPackageDexUsage.getPackageUseInfo(packageName) != null; 442 } 443 444 /** 445 * Perform dexopt on with the given {@code options} on the secondary dex files. 446 * @return true if all secondary dex files were processed successfully (compiled or skipped 447 * because they don't need to be compiled).. 448 */ dexoptSecondaryDex(DexoptOptions options)449 public boolean dexoptSecondaryDex(DexoptOptions options) { 450 if (PLATFORM_PACKAGE_NAME.equals(options.getPackageName())) { 451 // We could easily redirect to #dexoptSystemServer in this case. But there should be 452 // no-one calling this method directly for system server. 453 // As such we prefer to abort in this case. 454 Slog.wtf(TAG, "System server jars should be optimized with dexoptSystemServer"); 455 return false; 456 } 457 458 PackageDexOptimizer pdo = getPackageDexOptimizer(options); 459 String packageName = options.getPackageName(); 460 PackageUseInfo useInfo = getPackageUseInfoOrDefault(packageName); 461 if (useInfo.getDexUseInfoMap().isEmpty()) { 462 if (DEBUG) { 463 Slog.d(TAG, "No secondary dex use for package:" + packageName); 464 } 465 // Nothing to compile, return true. 466 return true; 467 } 468 boolean success = true; 469 for (Map.Entry<String, DexUseInfo> entry : useInfo.getDexUseInfoMap().entrySet()) { 470 String dexPath = entry.getKey(); 471 DexUseInfo dexUseInfo = entry.getValue(); 472 473 PackageInfo pkg; 474 try { 475 pkg = mPackageManager.getPackageInfo(packageName, /*flags*/0, 476 dexUseInfo.getOwnerUserId()); 477 } catch (RemoteException e) { 478 throw new AssertionError(e); 479 } 480 // It may be that the package gets uninstalled while we try to compile its 481 // secondary dex files. If that's the case, just ignore. 482 // Note that we don't break the entire loop because the package might still be 483 // installed for other users. 484 if (pkg == null) { 485 Slog.d(TAG, "Could not find package when compiling secondary dex " + packageName 486 + " for user " + dexUseInfo.getOwnerUserId()); 487 mPackageDexUsage.removeUserPackage(packageName, dexUseInfo.getOwnerUserId()); 488 continue; 489 } 490 491 int result = pdo.dexOptSecondaryDexPath(pkg.applicationInfo, dexPath, 492 dexUseInfo, options); 493 success = success && (result != PackageDexOptimizer.DEX_OPT_FAILED); 494 } 495 return success; 496 } 497 498 /** 499 * Performs dexopt on system server dex files. 500 * 501 * <p>Verfifies that the package name is {@link PackageManagerService#PLATFORM_PACKAGE_NAME}. 502 * 503 * @return 504 * <p>PackageDexOptimizer.DEX_OPT_SKIPPED if dexopt was skipped because no system server 505 * files were recorded or if no dexopt was needed. 506 * <p>PackageDexOptimizer.DEX_OPT_FAILED if any dexopt operation failed. 507 * <p>PackageDexOptimizer.DEX_OPT_PERFORMED if all dexopt operations succeeded. 508 */ dexoptSystemServer(DexoptOptions options)509 public int dexoptSystemServer(DexoptOptions options) { 510 if (!PLATFORM_PACKAGE_NAME.equals(options.getPackageName())) { 511 Slog.wtf(TAG, "Non system server package used when trying to dexopt system server:" 512 + options.getPackageName()); 513 return PackageDexOptimizer.DEX_OPT_FAILED; 514 } 515 516 // Override compiler filter for system server to the expected one. 517 // 518 // We could let the caller do this every time the invoke PackageManagerServer#dexopt. 519 // However, there are a few places were this will need to be done which creates 520 // redundancy and the danger of overlooking the config (and thus generating code that will 521 // waste storage and time). 522 DexoptOptions overriddenOptions = options.overrideCompilerFilter( 523 SYSTEM_SERVER_COMPILER_FILTER); 524 525 PackageDexOptimizer pdo = getPackageDexOptimizer(overriddenOptions); 526 String packageName = overriddenOptions.getPackageName(); 527 PackageUseInfo useInfo = getPackageUseInfoOrDefault(packageName); 528 if (useInfo.getDexUseInfoMap().isEmpty()) { 529 if (DEBUG) { 530 Slog.d(TAG, "No dex files recorded for system server"); 531 } 532 // Nothing to compile, return true. 533 return PackageDexOptimizer.DEX_OPT_SKIPPED; 534 } 535 536 boolean usageUpdated = false; 537 int result = PackageDexOptimizer.DEX_OPT_SKIPPED; 538 for (Map.Entry<String, DexUseInfo> entry : useInfo.getDexUseInfoMap().entrySet()) { 539 String dexPath = entry.getKey(); 540 DexUseInfo dexUseInfo = entry.getValue(); 541 if (!Files.exists(Paths.get(dexPath))) { 542 if (DEBUG) { 543 Slog.w(TAG, "A dex file previously loaded by System Server does not exist " 544 + " anymore: " + dexPath); 545 } 546 usageUpdated = mPackageDexUsage.removeDexFile( 547 packageName, dexPath, dexUseInfo.getOwnerUserId()) || usageUpdated; 548 continue; 549 } 550 551 if (dexUseInfo.isUnsupportedClassLoaderContext() 552 || dexUseInfo.isVariableClassLoaderContext()) { 553 String debugMsg = dexUseInfo.isUnsupportedClassLoaderContext() 554 ? "unsupported" 555 : "variable"; 556 Slog.w(TAG, "Skipping dexopt for system server path loaded with " + debugMsg 557 + " class loader context: " + dexPath); 558 continue; 559 } 560 561 int newResult = pdo.dexoptSystemServerPath(dexPath, dexUseInfo, overriddenOptions); 562 563 // The end result is: 564 // - FAILED if any path failed, 565 // - PERFORMED if at least one path needed compilation, 566 // - SKIPPED when all paths are up to date 567 if ((result != PackageDexOptimizer.DEX_OPT_FAILED) 568 && (newResult != PackageDexOptimizer.DEX_OPT_SKIPPED)) { 569 result = newResult; 570 } 571 } 572 573 if (usageUpdated) { 574 mPackageDexUsage.maybeWriteAsync(); 575 } 576 577 return result; 578 } 579 580 /** 581 * Select the dex optimizer based on the force parameter. 582 * Forced compilation is done through ForcedUpdatePackageDexOptimizer which will adjust 583 * the necessary dexopt flags to make sure that compilation is not skipped. This avoid 584 * passing the force flag through the multitude of layers. 585 * Note: The force option is rarely used (cmdline input for testing, mostly), so it's OK to 586 * allocate an object here. 587 */ getPackageDexOptimizer(DexoptOptions options)588 private PackageDexOptimizer getPackageDexOptimizer(DexoptOptions options) { 589 return options.isForce() 590 ? new PackageDexOptimizer.ForcedUpdatePackageDexOptimizer(mPackageDexOptimizer) 591 : mPackageDexOptimizer; 592 } 593 594 /** 595 * Reconcile the information we have about the secondary dex files belonging to 596 * {@code packagName} and the actual dex files. For all dex files that were 597 * deleted, update the internal records and delete any generated oat files. 598 */ reconcileSecondaryDexFiles(String packageName)599 public void reconcileSecondaryDexFiles(String packageName) { 600 PackageUseInfo useInfo = getPackageUseInfoOrDefault(packageName); 601 if (useInfo.getDexUseInfoMap().isEmpty()) { 602 if (DEBUG) { 603 Slog.d(TAG, "No secondary dex use for package:" + packageName); 604 } 605 // Nothing to reconcile. 606 return; 607 } 608 609 boolean updated = false; 610 for (Map.Entry<String, DexUseInfo> entry : useInfo.getDexUseInfoMap().entrySet()) { 611 String dexPath = entry.getKey(); 612 DexUseInfo dexUseInfo = entry.getValue(); 613 PackageInfo pkg = null; 614 try { 615 // Note that we look for the package in the PackageManager just to be able 616 // to get back the real app uid and its storage kind. These are only used 617 // to perform extra validation in installd. 618 // TODO(calin): maybe a bit overkill. 619 pkg = mPackageManager.getPackageInfo(packageName, /*flags*/0, 620 dexUseInfo.getOwnerUserId()); 621 } catch (RemoteException ignore) { 622 // Can't happen, DexManager is local. 623 } 624 if (pkg == null) { 625 // It may be that the package was uninstalled while we process the secondary 626 // dex files. 627 Slog.d(TAG, "Could not find package when compiling secondary dex " + packageName 628 + " for user " + dexUseInfo.getOwnerUserId()); 629 // Update the usage and continue, another user might still have the package. 630 updated = mPackageDexUsage.removeUserPackage( 631 packageName, dexUseInfo.getOwnerUserId()) || updated; 632 continue; 633 } 634 635 // Special handle system server files. 636 // We don't need an installd call because we have permissions to check if the file 637 // exists. 638 if (PLATFORM_PACKAGE_NAME.equals(packageName)) { 639 if (!Files.exists(Paths.get(dexPath))) { 640 if (DEBUG) { 641 Slog.w(TAG, "A dex file previously loaded by System Server does not exist " 642 + " anymore: " + dexPath); 643 } 644 updated = mPackageDexUsage.removeUserPackage( 645 packageName, dexUseInfo.getOwnerUserId()) || updated; 646 } 647 continue; 648 } 649 650 // This is a regular application. 651 ApplicationInfo info = pkg.applicationInfo; 652 int flags = 0; 653 if (info.deviceProtectedDataDir != null && 654 FileUtils.contains(info.deviceProtectedDataDir, dexPath)) { 655 flags |= StorageManager.FLAG_STORAGE_DE; 656 } else if (info.credentialProtectedDataDir!= null && 657 FileUtils.contains(info.credentialProtectedDataDir, dexPath)) { 658 flags |= StorageManager.FLAG_STORAGE_CE; 659 } else { 660 Slog.e(TAG, "Could not infer CE/DE storage for path " + dexPath); 661 updated = mPackageDexUsage.removeDexFile( 662 packageName, dexPath, dexUseInfo.getOwnerUserId()) || updated; 663 continue; 664 } 665 666 boolean dexStillExists = true; 667 synchronized(mInstallLock) { 668 try { 669 String[] isas = dexUseInfo.getLoaderIsas().toArray(new String[0]); 670 dexStillExists = mInstaller.reconcileSecondaryDexFile(dexPath, packageName, 671 info.uid, isas, info.volumeUuid, flags); 672 } catch (InstallerException e) { 673 Slog.e(TAG, "Got InstallerException when reconciling dex " + dexPath + 674 " : " + e.getMessage()); 675 } 676 } 677 if (!dexStillExists) { 678 updated = mPackageDexUsage.removeDexFile( 679 packageName, dexPath, dexUseInfo.getOwnerUserId()) || updated; 680 } 681 682 } 683 if (updated) { 684 mPackageDexUsage.maybeWriteAsync(); 685 } 686 } 687 688 // TODO(calin): questionable API in the presence of class loaders context. Needs amends as the 689 // compilation happening here will use a pessimistic context. registerDexModule(ApplicationInfo info, String dexPath, boolean isSharedModule, int userId)690 public RegisterDexModuleResult registerDexModule(ApplicationInfo info, String dexPath, 691 boolean isSharedModule, int userId) { 692 // Find the owning package record. 693 DexSearchResult searchResult = getDexPackage(info, dexPath, userId); 694 695 if (searchResult.mOutcome == DEX_SEARCH_NOT_FOUND) { 696 return new RegisterDexModuleResult(false, "Package not found"); 697 } 698 if (!info.packageName.equals(searchResult.mOwningPackageName)) { 699 return new RegisterDexModuleResult(false, "Dex path does not belong to package"); 700 } 701 if (searchResult.mOutcome == DEX_SEARCH_FOUND_PRIMARY || 702 searchResult.mOutcome == DEX_SEARCH_FOUND_SPLIT) { 703 return new RegisterDexModuleResult(false, "Main apks cannot be registered"); 704 } 705 706 // We found the package. Now record the usage for all declared ISAs. 707 boolean update = false; 708 // If this is a shared module set the loading package to an arbitrary package name 709 // so that we can mark that module as usedByOthers. 710 String loadingPackage = isSharedModule ? ".shared.module" : searchResult.mOwningPackageName; 711 for (String isa : getAppDexInstructionSets(info.primaryCpuAbi, info.secondaryCpuAbi)) { 712 boolean newUpdate = mPackageDexUsage.record(searchResult.mOwningPackageName, 713 dexPath, userId, isa, /*primaryOrSplit*/ false, 714 loadingPackage, 715 PackageDexUsage.VARIABLE_CLASS_LOADER_CONTEXT); 716 update |= newUpdate; 717 } 718 if (update) { 719 mPackageDexUsage.maybeWriteAsync(); 720 } 721 722 DexUseInfo dexUseInfo = mPackageDexUsage.getPackageUseInfo(searchResult.mOwningPackageName) 723 .getDexUseInfoMap().get(dexPath); 724 725 // Try to optimize the package according to the install reason. 726 DexoptOptions options = new DexoptOptions(info.packageName, 727 PackageManagerService.REASON_INSTALL, /*flags*/0); 728 729 int result = mPackageDexOptimizer.dexOptSecondaryDexPath(info, dexPath, dexUseInfo, 730 options); 731 732 // If we fail to optimize the package log an error but don't propagate the error 733 // back to the app. The app cannot do much about it and the background job 734 // will rety again when it executes. 735 // TODO(calin): there might be some value to return the error here but it may 736 // cause red herrings since that doesn't mean the app cannot use the module. 737 if (result != PackageDexOptimizer.DEX_OPT_FAILED) { 738 Slog.e(TAG, "Failed to optimize dex module " + dexPath); 739 } 740 return new RegisterDexModuleResult(true, "Dex module registered successfully"); 741 } 742 743 /** 744 * Return all packages that contain records of secondary dex files. 745 */ getAllPackagesWithSecondaryDexFiles()746 public Set<String> getAllPackagesWithSecondaryDexFiles() { 747 return mPackageDexUsage.getAllPackagesWithSecondaryDexFiles(); 748 } 749 750 /** 751 * Retrieves the package which owns the given dexPath. 752 */ getDexPackage( ApplicationInfo loadingAppInfo, String dexPath, int userId)753 private DexSearchResult getDexPackage( 754 ApplicationInfo loadingAppInfo, String dexPath, int userId) { 755 // First, check if the package which loads the dex file actually owns it. 756 // Most of the time this will be true and we can return early. 757 PackageCodeLocations loadingPackageCodeLocations = 758 new PackageCodeLocations(loadingAppInfo, userId); 759 int outcome = loadingPackageCodeLocations.searchDex(dexPath, userId); 760 if (outcome != DEX_SEARCH_NOT_FOUND) { 761 // TODO(calin): evaluate if we bother to detect symlinks at the dexPath level. 762 return new DexSearchResult(loadingPackageCodeLocations.mPackageName, outcome); 763 } 764 765 // The loadingPackage does not own the dex file. 766 // Perform a reverse look-up in the cache to detect if any package has ownership. 767 // Note that we can have false negatives if the cache falls out of date. 768 synchronized (mPackageCodeLocationsCache) { 769 for (PackageCodeLocations pcl : mPackageCodeLocationsCache.values()) { 770 outcome = pcl.searchDex(dexPath, userId); 771 if (outcome != DEX_SEARCH_NOT_FOUND) { 772 return new DexSearchResult(pcl.mPackageName, outcome); 773 } 774 } 775 } 776 777 // We could not find the owning package amongst regular apps. 778 // If the loading package is system server, see if the dex file resides 779 // on any of the potentially system server owning location and if so, 780 // assuming system server ownership. 781 // 782 // Note: We don't have any way to detect which code paths are actually 783 // owned by system server. We can only assume that such paths are on 784 // system partitions. 785 if (PLATFORM_PACKAGE_NAME.equals(loadingAppInfo.packageName)) { 786 if (isSystemServerDexPathSupportedForOdex(dexPath)) { 787 // We record system server dex files as secondary dex files. 788 // The reason is that we only record the class loader context for secondary dex 789 // files and we expect that all primary apks are loaded with an empty class loader. 790 // System server dex files may be loaded in non-empty class loader so we need to 791 // keep track of their context. 792 return new DexSearchResult(PLATFORM_PACKAGE_NAME, DEX_SEARCH_FOUND_SECONDARY); 793 } else { 794 Slog.wtf(TAG, "System server loads dex files outside paths supported for odex: " 795 + dexPath); 796 } 797 } 798 799 if (DEBUG) { 800 // TODO(calin): Consider checking for /data/data symlink. 801 // /data/data/ symlinks /data/user/0/ and there's nothing stopping apps 802 // to load dex files through it. 803 try { 804 String dexPathReal = PackageManagerServiceUtils.realpath(new File(dexPath)); 805 if (!dexPath.equals(dexPathReal)) { 806 Slog.d(TAG, "Dex loaded with symlink. dexPath=" + 807 dexPath + " dexPathReal=" + dexPathReal); 808 } 809 } catch (IOException e) { 810 // Ignore 811 } 812 } 813 // Cache miss. The cache is updated during installs and uninstalls, 814 // so if we get here we're pretty sure the dex path does not exist. 815 return new DexSearchResult(null, DEX_SEARCH_NOT_FOUND); 816 } 817 putIfAbsent(Map<K,V> map, K key, V newValue)818 private static <K,V> V putIfAbsent(Map<K,V> map, K key, V newValue) { 819 V existingValue = map.putIfAbsent(key, newValue); 820 return existingValue == null ? newValue : existingValue; 821 } 822 823 /** 824 * Writes the in-memory package dex usage to disk right away. 825 */ writePackageDexUsageNow()826 public void writePackageDexUsageNow() { 827 mPackageDexUsage.writeNow(); 828 mDynamicCodeLogger.writeNow(); 829 } 830 831 /** 832 * Returns whether the given package is in the list of privilaged apps that should run out of 833 * box. This only makes sense if the feature is enabled. Note that when the the OOB list is 834 * empty, all priv apps will run in OOB mode. 835 */ isPackageSelectedToRunOob(String packageName)836 public static boolean isPackageSelectedToRunOob(String packageName) { 837 return isPackageSelectedToRunOob(Arrays.asList(packageName)); 838 } 839 840 /** 841 * Returns whether any of the given packages are in the list of privilaged apps that should run 842 * out of box. This only makes sense if the feature is enabled. Note that when the the OOB list 843 * is empty, all priv apps will run in OOB mode. 844 */ isPackageSelectedToRunOob(Collection<String> packageNamesInSameProcess)845 public static boolean isPackageSelectedToRunOob(Collection<String> packageNamesInSameProcess) { 846 return isPackageSelectedToRunOobInternal( 847 SystemProperties.getBoolean(PROPERTY_NAME_PM_DEXOPT_PRIV_APPS_OOB, false), 848 SystemProperties.get(PROPERTY_NAME_PM_DEXOPT_PRIV_APPS_OOB_LIST, "ALL"), 849 packageNamesInSameProcess); 850 } 851 852 @VisibleForTesting isPackageSelectedToRunOobInternal(boolean isEnabled, String whitelist, Collection<String> packageNamesInSameProcess)853 /* package */ static boolean isPackageSelectedToRunOobInternal(boolean isEnabled, 854 String whitelist, Collection<String> packageNamesInSameProcess) { 855 if (!isEnabled) { 856 return false; 857 } 858 859 if ("ALL".equals(whitelist)) { 860 return true; 861 } 862 for (String oobPkgName : whitelist.split(",")) { 863 if (packageNamesInSameProcess.contains(oobPkgName)) { 864 return true; 865 } 866 } 867 return false; 868 } 869 870 /** 871 * Generates log if the archive located at {@code fileName} has uncompressed dex file that can 872 * be direclty mapped. 873 */ auditUncompressedDexInApk(String fileName)874 public static boolean auditUncompressedDexInApk(String fileName) { 875 StrictJarFile jarFile = null; 876 try { 877 jarFile = new StrictJarFile(fileName, 878 false /*verify*/, false /*signatureSchemeRollbackProtectionsEnforced*/); 879 Iterator<ZipEntry> it = jarFile.iterator(); 880 boolean allCorrect = true; 881 while (it.hasNext()) { 882 ZipEntry entry = it.next(); 883 if (entry.getName().endsWith(".dex")) { 884 if (entry.getMethod() != ZipEntry.STORED) { 885 allCorrect = false; 886 Slog.w(TAG, "APK " + fileName + " has compressed dex code " + 887 entry.getName()); 888 } else if ((entry.getDataOffset() & 0x3) != 0) { 889 allCorrect = false; 890 Slog.w(TAG, "APK " + fileName + " has unaligned dex code " + 891 entry.getName()); 892 } 893 } 894 } 895 return allCorrect; 896 } catch (IOException ignore) { 897 Slog.wtf(TAG, "Error when parsing APK " + fileName); 898 return false; 899 } finally { 900 try { 901 if (jarFile != null) { 902 jarFile.close(); 903 } 904 } catch (IOException ignore) {} 905 } 906 } 907 908 public static class RegisterDexModuleResult { RegisterDexModuleResult()909 public RegisterDexModuleResult() { 910 this(false, null); 911 } 912 RegisterDexModuleResult(boolean success, String message)913 public RegisterDexModuleResult(boolean success, String message) { 914 this.success = success; 915 this.message = message; 916 } 917 918 public final boolean success; 919 public final String message; 920 } 921 922 /** 923 * Convenience class to store the different locations where a package might 924 * own code. 925 */ 926 private static class PackageCodeLocations { 927 private final String mPackageName; 928 private String mBaseCodePath; 929 private final Set<String> mSplitCodePaths; 930 // Maps user id to the application private directory. 931 private final Map<Integer, Set<String>> mAppDataDirs; 932 PackageCodeLocations(ApplicationInfo ai, int userId)933 public PackageCodeLocations(ApplicationInfo ai, int userId) { 934 this(ai.packageName, ai.sourceDir, ai.splitSourceDirs); 935 mergeAppDataDirs(ai.dataDir, userId); 936 } PackageCodeLocations(String packageName, String baseCodePath, String[] splitCodePaths)937 public PackageCodeLocations(String packageName, String baseCodePath, 938 String[] splitCodePaths) { 939 mPackageName = packageName; 940 mSplitCodePaths = new HashSet<>(); 941 mAppDataDirs = new HashMap<>(); 942 updateCodeLocation(baseCodePath, splitCodePaths); 943 } 944 updateCodeLocation(String baseCodePath, String[] splitCodePaths)945 public void updateCodeLocation(String baseCodePath, String[] splitCodePaths) { 946 mBaseCodePath = baseCodePath; 947 mSplitCodePaths.clear(); 948 if (splitCodePaths != null) { 949 for (String split : splitCodePaths) { 950 mSplitCodePaths.add(split); 951 } 952 } 953 } 954 mergeAppDataDirs(String dataDir, int userId)955 public void mergeAppDataDirs(String dataDir, int userId) { 956 Set<String> dataDirs = putIfAbsent(mAppDataDirs, userId, new HashSet<>()); 957 dataDirs.add(dataDir); 958 } 959 searchDex(String dexPath, int userId)960 public int searchDex(String dexPath, int userId) { 961 // First check that this package is installed or active for the given user. 962 // A missing data dir means the package is not installed. 963 Set<String> userDataDirs = mAppDataDirs.get(userId); 964 if (userDataDirs == null) { 965 return DEX_SEARCH_NOT_FOUND; 966 } 967 968 if (mBaseCodePath.equals(dexPath)) { 969 return DEX_SEARCH_FOUND_PRIMARY; 970 } 971 if (mSplitCodePaths.contains(dexPath)) { 972 return DEX_SEARCH_FOUND_SPLIT; 973 } 974 for (String dataDir : userDataDirs) { 975 if (dexPath.startsWith(dataDir)) { 976 return DEX_SEARCH_FOUND_SECONDARY; 977 } 978 } 979 980 return DEX_SEARCH_NOT_FOUND; 981 } 982 } 983 984 /** 985 * Convenience class to store ownership search results. 986 */ 987 private class DexSearchResult { 988 private String mOwningPackageName; 989 private int mOutcome; 990 DexSearchResult(String owningPackageName, int outcome)991 public DexSearchResult(String owningPackageName, int outcome) { 992 this.mOwningPackageName = owningPackageName; 993 this.mOutcome = outcome; 994 } 995 996 @Override toString()997 public String toString() { 998 return mOwningPackageName + "-" + mOutcome; 999 } 1000 } 1001 } 1002