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