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