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