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