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