• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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;
18 
19 import static android.content.pm.PackageManager.INSTALL_FAILED_SHARED_USER_INCOMPATIBLE;
20 import static android.content.pm.PackageManager.INSTALL_FAILED_UPDATE_INCOMPATIBLE;
21 import static android.system.OsConstants.O_CREAT;
22 import static android.system.OsConstants.O_RDWR;
23 
24 import static com.android.server.pm.PackageManagerService.COMPRESSED_EXTENSION;
25 import static com.android.server.pm.PackageManagerService.DEBUG_COMPRESSION;
26 import static com.android.server.pm.PackageManagerService.DEBUG_DEXOPT;
27 import static com.android.server.pm.PackageManagerService.STUB_SUFFIX;
28 import static com.android.server.pm.PackageManagerService.TAG;
29 
30 import android.annotation.NonNull;
31 import android.annotation.Nullable;
32 import android.app.AppGlobals;
33 import android.content.Context;
34 import android.content.Intent;
35 import android.content.pm.PackageInfoLite;
36 import android.content.pm.PackageManager;
37 import android.content.pm.PackageManagerInternal;
38 import android.content.pm.PackageParser;
39 import android.content.pm.PackageParser.PackageParserException;
40 import android.content.pm.ResolveInfo;
41 import android.content.pm.Signature;
42 import android.os.Build;
43 import android.os.Debug;
44 import android.os.Environment;
45 import android.os.FileUtils;
46 import android.os.Process;
47 import android.os.RemoteException;
48 import android.os.SystemProperties;
49 import android.os.UserHandle;
50 import android.os.UserManagerInternal;
51 import android.os.incremental.IncrementalManager;
52 import android.os.incremental.V4Signature;
53 import android.os.incremental.V4Signature.HashingInfo;
54 import android.service.pm.PackageServiceDumpProto;
55 import android.system.ErrnoException;
56 import android.system.Os;
57 import android.util.ArraySet;
58 import android.util.Log;
59 import android.util.Slog;
60 import android.util.proto.ProtoOutputStream;
61 
62 import com.android.internal.content.NativeLibraryHelper;
63 import com.android.internal.content.PackageHelper;
64 import com.android.internal.util.ArrayUtils;
65 import com.android.internal.util.FastPrintWriter;
66 import com.android.internal.util.HexDump;
67 import com.android.server.EventLogTags;
68 import com.android.server.pm.dex.DexManager;
69 import com.android.server.pm.dex.PackageDexUsage;
70 import com.android.server.pm.parsing.pkg.AndroidPackage;
71 import com.android.server.pm.permission.PermissionsState;
72 
73 import dalvik.system.VMRuntime;
74 
75 import libcore.io.IoUtils;
76 
77 import java.io.BufferedReader;
78 import java.io.File;
79 import java.io.FileDescriptor;
80 import java.io.FileInputStream;
81 import java.io.FileOutputStream;
82 import java.io.FileReader;
83 import java.io.FilenameFilter;
84 import java.io.IOException;
85 import java.io.InputStream;
86 import java.io.OutputStream;
87 import java.io.PrintWriter;
88 import java.nio.file.Path;
89 import java.security.cert.CertificateEncodingException;
90 import java.security.cert.CertificateException;
91 import java.text.SimpleDateFormat;
92 import java.util.ArrayList;
93 import java.util.Arrays;
94 import java.util.Collection;
95 import java.util.Collections;
96 import java.util.Date;
97 import java.util.LinkedList;
98 import java.util.List;
99 import java.util.function.Predicate;
100 import java.util.zip.GZIPInputStream;
101 
102 /**
103  * Class containing helper methods for the PackageManagerService.
104  *
105  * {@hide}
106  */
107 public class PackageManagerServiceUtils {
108     private final static long SEVEN_DAYS_IN_MILLISECONDS = 7 * 24 * 60 * 60 * 1000;
109 
110     public final static Predicate<PackageSetting> REMOVE_IF_NULL_PKG =
111             pkgSetting -> pkgSetting.pkg == null;
112 
getPackageNamesForIntent(Intent intent, int userId)113     private static ArraySet<String> getPackageNamesForIntent(Intent intent, int userId) {
114         List<ResolveInfo> ris = null;
115         try {
116             ris = AppGlobals.getPackageManager().queryIntentReceivers(intent, null, 0, userId)
117                     .getList();
118         } catch (RemoteException e) {
119         }
120         ArraySet<String> pkgNames = new ArraySet<String>();
121         if (ris != null) {
122             for (ResolveInfo ri : ris) {
123                 pkgNames.add(ri.activityInfo.packageName);
124             }
125         }
126         return pkgNames;
127     }
128 
129     // Sort a list of apps by their last usage, most recently used apps first. The order of
130     // packages without usage data is undefined (but they will be sorted after the packages
131     // that do have usage data).
sortPackagesByUsageDate(List<PackageSetting> pkgSettings, PackageManagerService packageManagerService)132     public static void sortPackagesByUsageDate(List<PackageSetting> pkgSettings,
133             PackageManagerService packageManagerService) {
134         if (!packageManagerService.isHistoricalPackageUsageAvailable()) {
135             return;
136         }
137 
138         Collections.sort(pkgSettings, (pkgSetting1, pkgSetting2) ->
139                 Long.compare(
140                         pkgSetting2.getPkgState().getLatestForegroundPackageUseTimeInMills(),
141                         pkgSetting1.getPkgState().getLatestForegroundPackageUseTimeInMills())
142         );
143     }
144 
145     // Apply the given {@code filter} to all packages in {@code packages}. If tested positive, the
146     // package will be removed from {@code packages} and added to {@code result} with its
147     // dependencies. If usage data is available, the positive packages will be sorted by usage
148     // data (with {@code sortTemp} as temporary storage).
applyPackageFilter( Predicate<PackageSetting> filter, Collection<PackageSetting> result, Collection<PackageSetting> packages, @NonNull List<PackageSetting> sortTemp, PackageManagerService packageManagerService)149     private static void applyPackageFilter(
150             Predicate<PackageSetting> filter,
151             Collection<PackageSetting> result,
152             Collection<PackageSetting> packages,
153             @NonNull List<PackageSetting> sortTemp,
154             PackageManagerService packageManagerService) {
155         for (PackageSetting pkgSetting : packages) {
156             if (filter.test(pkgSetting)) {
157                 sortTemp.add(pkgSetting);
158             }
159         }
160 
161         sortPackagesByUsageDate(sortTemp, packageManagerService);
162         packages.removeAll(sortTemp);
163 
164         for (PackageSetting pkgSetting : sortTemp) {
165             result.add(pkgSetting);
166 
167             List<PackageSetting> deps =
168                     packageManagerService.findSharedNonSystemLibraries(pkgSetting);
169             if (!deps.isEmpty()) {
170                 deps.removeAll(result);
171                 result.addAll(deps);
172                 packages.removeAll(deps);
173             }
174         }
175 
176         sortTemp.clear();
177     }
178 
179     // Sort apps by importance for dexopt ordering. Important apps are given
180     // more priority in case the device runs out of space.
getPackagesForDexopt( Collection<PackageSetting> packages, PackageManagerService packageManagerService)181     public static List<PackageSetting> getPackagesForDexopt(
182             Collection<PackageSetting> packages,
183             PackageManagerService packageManagerService) {
184         return getPackagesForDexopt(packages, packageManagerService, DEBUG_DEXOPT);
185     }
186 
getPackagesForDexopt( Collection<PackageSetting> pkgSettings, PackageManagerService packageManagerService, boolean debug)187     public static List<PackageSetting> getPackagesForDexopt(
188             Collection<PackageSetting> pkgSettings,
189             PackageManagerService packageManagerService,
190             boolean debug) {
191         List<PackageSetting> result = new LinkedList<>();
192         ArrayList<PackageSetting> remainingPkgSettings = new ArrayList<>(pkgSettings);
193 
194         // First, remove all settings without available packages
195         remainingPkgSettings.removeIf(REMOVE_IF_NULL_PKG);
196 
197         ArrayList<PackageSetting> sortTemp = new ArrayList<>(remainingPkgSettings.size());
198 
199         // Give priority to core apps.
200         applyPackageFilter(pkgSetting -> pkgSetting.pkg.isCoreApp(), result, remainingPkgSettings, sortTemp,
201                 packageManagerService);
202 
203         // Give priority to system apps that listen for pre boot complete.
204         Intent intent = new Intent(Intent.ACTION_PRE_BOOT_COMPLETED);
205         final ArraySet<String> pkgNames = getPackageNamesForIntent(intent, UserHandle.USER_SYSTEM);
206         applyPackageFilter(pkgSetting -> pkgNames.contains(pkgSetting.name), result,
207                 remainingPkgSettings, sortTemp, packageManagerService);
208 
209         // Give priority to apps used by other apps.
210         DexManager dexManager = packageManagerService.getDexManager();
211         applyPackageFilter(pkgSetting ->
212                 dexManager.getPackageUseInfoOrDefault(pkgSetting.name)
213                         .isAnyCodePathUsedByOtherApps(),
214                 result, remainingPkgSettings, sortTemp, packageManagerService);
215 
216         // Filter out packages that aren't recently used, add all remaining apps.
217         // TODO: add a property to control this?
218         Predicate<PackageSetting> remainingPredicate;
219         if (!remainingPkgSettings.isEmpty() && packageManagerService.isHistoricalPackageUsageAvailable()) {
220             if (debug) {
221                 Log.i(TAG, "Looking at historical package use");
222             }
223             // Get the package that was used last.
224             PackageSetting lastUsed = Collections.max(remainingPkgSettings,
225                     (pkgSetting1, pkgSetting2) -> Long.compare(
226                             pkgSetting1.getPkgState().getLatestForegroundPackageUseTimeInMills(),
227                             pkgSetting2.getPkgState().getLatestForegroundPackageUseTimeInMills()));
228             if (debug) {
229                 Log.i(TAG, "Taking package " + lastUsed.name
230                         + " as reference in time use");
231             }
232             long estimatedPreviousSystemUseTime = lastUsed.getPkgState()
233                     .getLatestForegroundPackageUseTimeInMills();
234             // Be defensive if for some reason package usage has bogus data.
235             if (estimatedPreviousSystemUseTime != 0) {
236                 final long cutoffTime = estimatedPreviousSystemUseTime - SEVEN_DAYS_IN_MILLISECONDS;
237                 remainingPredicate = pkgSetting -> pkgSetting.getPkgState()
238                         .getLatestForegroundPackageUseTimeInMills() >= cutoffTime;
239             } else {
240                 // No meaningful historical info. Take all.
241                 remainingPredicate = pkgSetting -> true;
242             }
243             sortPackagesByUsageDate(remainingPkgSettings, packageManagerService);
244         } else {
245             // No historical info. Take all.
246             remainingPredicate = pkgSetting -> true;
247         }
248         applyPackageFilter(remainingPredicate, result, remainingPkgSettings, sortTemp,
249                 packageManagerService);
250 
251         if (debug) {
252             Log.i(TAG, "Packages to be dexopted: " + packagesToString(result));
253             Log.i(TAG, "Packages skipped from dexopt: " + packagesToString(remainingPkgSettings));
254         }
255 
256         return result;
257     }
258 
259     /**
260      * Checks if the package was inactive during since <code>thresholdTimeinMillis</code>.
261      * Package is considered active, if:
262      * 1) It was active in foreground.
263      * 2) It was active in background and also used by other apps.
264      *
265      * If it doesn't have sufficient information about the package, it return <code>false</code>.
266      */
isUnusedSinceTimeInMillis(long firstInstallTime, long currentTimeInMillis, long thresholdTimeinMillis, PackageDexUsage.PackageUseInfo packageUseInfo, long latestPackageUseTimeInMillis, long latestForegroundPackageUseTimeInMillis)267     public static boolean isUnusedSinceTimeInMillis(long firstInstallTime, long currentTimeInMillis,
268             long thresholdTimeinMillis, PackageDexUsage.PackageUseInfo packageUseInfo,
269             long latestPackageUseTimeInMillis, long latestForegroundPackageUseTimeInMillis) {
270 
271         if (currentTimeInMillis - firstInstallTime < thresholdTimeinMillis) {
272             return false;
273         }
274 
275         // If the app was active in foreground during the threshold period.
276         boolean isActiveInForeground = (currentTimeInMillis
277                 - latestForegroundPackageUseTimeInMillis)
278                 < thresholdTimeinMillis;
279 
280         if (isActiveInForeground) {
281             return false;
282         }
283 
284         // If the app was active in background during the threshold period and was used
285         // by other packages.
286         boolean isActiveInBackgroundAndUsedByOtherPackages = ((currentTimeInMillis
287                 - latestPackageUseTimeInMillis)
288                 < thresholdTimeinMillis)
289                 && packageUseInfo.isAnyCodePathUsedByOtherApps();
290 
291         return !isActiveInBackgroundAndUsedByOtherPackages;
292     }
293 
294     /**
295      * Returns the canonicalized path of {@code path} as per {@code realpath(3)}
296      * semantics.
297      */
298     public static String realpath(File path) throws IOException {
299         try {
300             return Os.realpath(path.getAbsolutePath());
301         } catch (ErrnoException ee) {
302             throw ee.rethrowAsIOException();
303         }
304     }
305 
306     public static String packagesToString(List<PackageSetting> pkgSettings) {
307         StringBuilder sb = new StringBuilder();
308         for (int index = 0; index < pkgSettings.size(); index++) {
309             if (sb.length() > 0) {
310                 sb.append(", ");
311             }
312             sb.append(pkgSettings.get(index).name);
313         }
314         return sb.toString();
315     }
316 
317     /**
318      * Verifies that the given string {@code isa} is a valid supported isa on
319      * the running device.
320      */
321     public static boolean checkISA(String isa) {
322         for (String abi : Build.SUPPORTED_ABIS) {
323             if (VMRuntime.getInstructionSet(abi).equals(isa)) {
324                 return true;
325             }
326         }
327         return false;
328     }
329 
330     public static long getLastModifiedTime(AndroidPackage pkg) {
331         final File srcFile = new File(pkg.getCodePath());
332         if (!srcFile.isDirectory()) {
333             return srcFile.lastModified();
334         }
335         final File baseFile = new File(pkg.getBaseCodePath());
336         long maxModifiedTime = baseFile.lastModified();
337         if (pkg.getSplitCodePaths() != null) {
338             for (int i = pkg.getSplitCodePaths().length - 1; i >=0; --i) {
339                 final File splitFile = new File(pkg.getSplitCodePaths()[i]);
340                 maxModifiedTime = Math.max(maxModifiedTime, splitFile.lastModified());
341             }
342         }
343         return maxModifiedTime;
344     }
345 
346     private static File getSettingsProblemFile() {
347         File dataDir = Environment.getDataDirectory();
348         File systemDir = new File(dataDir, "system");
349         File fname = new File(systemDir, "uiderrors.txt");
350         return fname;
351     }
352 
353     public static void dumpCriticalInfo(ProtoOutputStream proto) {
354         try (BufferedReader in = new BufferedReader(new FileReader(getSettingsProblemFile()))) {
355             String line = null;
356             while ((line = in.readLine()) != null) {
357                 if (line.contains("ignored: updated version")) continue;
358                 proto.write(PackageServiceDumpProto.MESSAGES, line);
359             }
360         } catch (IOException ignored) {
361         }
362     }
363 
364     public static void dumpCriticalInfo(PrintWriter pw, String msg) {
365         try (BufferedReader in = new BufferedReader(new FileReader(getSettingsProblemFile()))) {
366             String line = null;
367             while ((line = in.readLine()) != null) {
368                 if (line.contains("ignored: updated version")) continue;
369                 if (msg != null) {
370                     pw.print(msg);
371                 }
372                 pw.println(line);
373             }
374         } catch (IOException ignored) {
375         }
376     }
377 
378     public static void logCriticalInfo(int priority, String msg) {
379         Slog.println(priority, TAG, msg);
380         EventLogTags.writePmCriticalInfo(msg);
381         try {
382             File fname = getSettingsProblemFile();
383             FileOutputStream out = new FileOutputStream(fname, true);
384             PrintWriter pw = new FastPrintWriter(out);
385             SimpleDateFormat formatter = new SimpleDateFormat();
386             String dateString = formatter.format(new Date(System.currentTimeMillis()));
387             pw.println(dateString + ": " + msg);
388             pw.close();
389             FileUtils.setPermissions(
390                     fname.toString(),
391                     FileUtils.S_IRWXU|FileUtils.S_IRWXG|FileUtils.S_IROTH,
392                     -1, -1);
393         } catch (java.io.IOException e) {
394         }
395     }
396 
397     /** Enforces that if the caller is shell, it does not have the provided user restriction. */
398     public static void enforceShellRestriction(
399             UserManagerInternal userManager, String restriction, int callingUid, int userHandle) {
400         if (callingUid == Process.SHELL_UID) {
401             if (userHandle >= 0
402                     && userManager.hasUserRestriction(
403                             restriction, userHandle)) {
404                 throw new SecurityException("Shell does not have permission to access user "
405                         + userHandle);
406             } else if (userHandle < 0) {
407                 Slog.e(PackageManagerService.TAG, "Unable to check shell permission for user "
408                         + userHandle + "\n\t" + Debug.getCallers(3));
409             }
410         }
411     }
412 
413     /**
414      * Enforces that the caller must be either the system process or the phone process.
415      * If not, throws a {@link SecurityException}.
416      */
417     public static void enforceSystemOrPhoneCaller(String methodName, int callingUid) {
418         if (callingUid != Process.PHONE_UID && callingUid != Process.SYSTEM_UID) {
419             throw new SecurityException(
420                     "Cannot call " + methodName + " from UID " + callingUid);
421         }
422     }
423 
424     /**
425      * Derive the value of the {@code cpuAbiOverride} based on the provided
426      * value and an optional stored value from the package settings.
427      */
428     public static String deriveAbiOverride(String abiOverride, PackageSetting settings) {
429         String cpuAbiOverride = null;
430         if (NativeLibraryHelper.CLEAR_ABI_OVERRIDE.equals(abiOverride)) {
431             cpuAbiOverride = null;
432         } else if (abiOverride != null) {
433             cpuAbiOverride = abiOverride;
434         } else if (settings != null) {
435             cpuAbiOverride = settings.cpuAbiOverrideString;
436         }
437         return cpuAbiOverride;
438     }
439 
440     /**
441      * Compares two sets of signatures. Returns:
442      * <br />
443      * {@link PackageManager#SIGNATURE_NEITHER_SIGNED}: if both signature sets are null,
444      * <br />
445      * {@link PackageManager#SIGNATURE_FIRST_NOT_SIGNED}: if the first signature set is null,
446      * <br />
447      * {@link PackageManager#SIGNATURE_SECOND_NOT_SIGNED}: if the second signature set is null,
448      * <br />
449      * {@link PackageManager#SIGNATURE_MATCH}: if the two signature sets are identical,
450      * <br />
451      * {@link PackageManager#SIGNATURE_NO_MATCH}: if the two signature sets differ.
452      */
453     public static int compareSignatures(Signature[] s1, Signature[] s2) {
454         if (s1 == null) {
455             return s2 == null
456                     ? PackageManager.SIGNATURE_NEITHER_SIGNED
457                     : PackageManager.SIGNATURE_FIRST_NOT_SIGNED;
458         }
459 
460         if (s2 == null) {
461             return PackageManager.SIGNATURE_SECOND_NOT_SIGNED;
462         }
463 
464         if (s1.length != s2.length) {
465             return PackageManager.SIGNATURE_NO_MATCH;
466         }
467 
468         // Since both signature sets are of size 1, we can compare without HashSets.
469         if (s1.length == 1) {
470             return s1[0].equals(s2[0]) ?
471                     PackageManager.SIGNATURE_MATCH :
472                     PackageManager.SIGNATURE_NO_MATCH;
473         }
474 
475         ArraySet<Signature> set1 = new ArraySet<Signature>();
476         for (Signature sig : s1) {
477             set1.add(sig);
478         }
479         ArraySet<Signature> set2 = new ArraySet<Signature>();
480         for (Signature sig : s2) {
481             set2.add(sig);
482         }
483         // Make sure s2 contains all signatures in s1.
484         if (set1.equals(set2)) {
485             return PackageManager.SIGNATURE_MATCH;
486         }
487         return PackageManager.SIGNATURE_NO_MATCH;
488     }
489 
490     /**
491      * Used for backward compatibility to make sure any packages with
492      * certificate chains get upgraded to the new style. {@code existingSigs}
493      * will be in the old format (since they were stored on disk from before the
494      * system upgrade) and {@code scannedSigs} will be in the newer format.
495      */
496     private static boolean matchSignaturesCompat(String packageName,
497             PackageSignatures packageSignatures, PackageParser.SigningDetails parsedSignatures) {
498         ArraySet<Signature> existingSet = new ArraySet<Signature>();
499         for (Signature sig : packageSignatures.mSigningDetails.signatures) {
500             existingSet.add(sig);
501         }
502         ArraySet<Signature> scannedCompatSet = new ArraySet<Signature>();
503         for (Signature sig : parsedSignatures.signatures) {
504             try {
505                 Signature[] chainSignatures = sig.getChainSignatures();
506                 for (Signature chainSig : chainSignatures) {
507                     scannedCompatSet.add(chainSig);
508                 }
509             } catch (CertificateEncodingException e) {
510                 scannedCompatSet.add(sig);
511             }
512         }
513         // make sure the expanded scanned set contains all signatures in the existing one
514         if (scannedCompatSet.equals(existingSet)) {
515             // migrate the old signatures to the new scheme
516             packageSignatures.mSigningDetails = parsedSignatures;
517             return true;
518         } else if (parsedSignatures.hasPastSigningCertificates()) {
519 
520             // well this sucks: the parsed package has probably rotated signing certificates, but
521             // we don't have enough information to determine if the new signing certificate was
522             // blessed by the old one
523             logCriticalInfo(Log.INFO, "Existing package " + packageName + " has flattened signing "
524                     + "certificate chain. Unable to install newer version with rotated signing "
525                     + "certificate.");
526         }
527         return false;
528     }
529 
530     private static boolean matchSignaturesRecover(
531             String packageName,
532             PackageParser.SigningDetails existingSignatures,
533             PackageParser.SigningDetails parsedSignatures,
534             @PackageParser.SigningDetails.CertCapabilities int flags) {
535         String msg = null;
536         try {
537             if (parsedSignatures.checkCapabilityRecover(existingSignatures, flags)) {
538                 logCriticalInfo(Log.INFO, "Recovered effectively matching certificates for "
539                         + packageName);
540                     return true;
541             }
542         } catch (CertificateException e) {
543             msg = e.getMessage();
544         }
545         logCriticalInfo(Log.INFO,
546                 "Failed to recover certificates for " + packageName + ": " + msg);
547         return false;
548     }
549 
550     /**
551      * Make sure the updated priv app is signed with the same key as the original APK file on the
552      * /system partition.
553      *
554      * <p>The rationale is that {@code disabledPkg} is a PackageSetting backed by xml files in /data
555      * and is not tamperproof.
556      */
557     private static boolean matchSignatureInSystem(PackageSetting pkgSetting,
558             PackageSetting disabledPkgSetting) {
559         if (pkgSetting.signatures.mSigningDetails.checkCapability(
560                 disabledPkgSetting.signatures.mSigningDetails,
561                 PackageParser.SigningDetails.CertCapabilities.INSTALLED_DATA)
562                 || disabledPkgSetting.signatures.mSigningDetails.checkCapability(
563                 pkgSetting.signatures.mSigningDetails,
564                 PackageParser.SigningDetails.CertCapabilities.ROLLBACK)) {
565             return true;
566         } else {
567             logCriticalInfo(Log.ERROR, "Updated system app mismatches cert on /system: " +
568                     pkgSetting.name);
569             return false;
570         }
571     }
572 
573     /** Default is to not use fs-verity since it depends on kernel support. */
574     private static final int FSVERITY_DISABLED = 0;
575 
576     /**
577      * Experimental implementation targeting priv apps, with Android specific kernel patches to
578      * extend fs-verity.
579      */
580     private static final int FSVERITY_LEGACY = 1;
581 
582     /** Standard fs-verity. */
583     private static final int FSVERITY_ENABLED = 2;
584 
585     /** Returns true if standard APK Verity is enabled. */
isApkVerityEnabled()586     static boolean isApkVerityEnabled() {
587         return Build.VERSION.FIRST_SDK_INT >= Build.VERSION_CODES.R
588                 || SystemProperties.getInt("ro.apk_verity.mode", FSVERITY_DISABLED)
589                         == FSVERITY_ENABLED;
590     }
591 
isLegacyApkVerityEnabled()592     static boolean isLegacyApkVerityEnabled() {
593         return SystemProperties.getInt("ro.apk_verity.mode", FSVERITY_DISABLED) == FSVERITY_LEGACY;
594     }
595 
596     /** Returns true to force apk verification if the package is considered privileged. */
isApkVerificationForced(@ullable PackageSetting ps)597     static boolean isApkVerificationForced(@Nullable PackageSetting ps) {
598         // TODO(b/154310064): re-enable.
599         return false;
600     }
601 
602     /**
603      * Verifies that signatures match.
604      * @returns {@code true} if the compat signatures were matched; otherwise, {@code false}.
605      * @throws PackageManagerException if the signatures did not match.
606      */
verifySignatures(PackageSetting pkgSetting, PackageSetting disabledPkgSetting, PackageParser.SigningDetails parsedSignatures, boolean compareCompat, boolean compareRecover)607     public static boolean verifySignatures(PackageSetting pkgSetting,
608             PackageSetting disabledPkgSetting, PackageParser.SigningDetails parsedSignatures,
609             boolean compareCompat, boolean compareRecover)
610             throws PackageManagerException {
611         final String packageName = pkgSetting.name;
612         boolean compatMatch = false;
613         if (pkgSetting.signatures.mSigningDetails.signatures != null) {
614             // Already existing package. Make sure signatures match
615             boolean match = parsedSignatures.checkCapability(
616                     pkgSetting.signatures.mSigningDetails,
617                     PackageParser.SigningDetails.CertCapabilities.INSTALLED_DATA)
618                             || pkgSetting.signatures.mSigningDetails.checkCapability(
619                                     parsedSignatures,
620                                     PackageParser.SigningDetails.CertCapabilities.ROLLBACK);
621             if (!match && compareCompat) {
622                 match = matchSignaturesCompat(packageName, pkgSetting.signatures,
623                         parsedSignatures);
624                 compatMatch = match;
625             }
626             if (!match && compareRecover) {
627                 match = matchSignaturesRecover(
628                         packageName,
629                         pkgSetting.signatures.mSigningDetails,
630                         parsedSignatures,
631                         PackageParser.SigningDetails.CertCapabilities.INSTALLED_DATA)
632                                 || matchSignaturesRecover(
633                                         packageName,
634                                         parsedSignatures,
635                                         pkgSetting.signatures.mSigningDetails,
636                                         PackageParser.SigningDetails.CertCapabilities.ROLLBACK);
637             }
638 
639             if (!match && isApkVerificationForced(disabledPkgSetting)) {
640                 match = matchSignatureInSystem(pkgSetting, disabledPkgSetting);
641             }
642 
643             if (!match) {
644                 throw new PackageManagerException(INSTALL_FAILED_UPDATE_INCOMPATIBLE,
645                         "Package " + packageName +
646                         " signatures do not match previously installed version; ignoring!");
647             }
648         }
649         // Check for shared user signatures
650         if (pkgSetting.getSharedUser() != null
651                 && pkgSetting.getSharedUser().signatures.mSigningDetails
652                         != PackageParser.SigningDetails.UNKNOWN) {
653 
654             // Already existing package. Make sure signatures match.  In case of signing certificate
655             // rotation, the packages with newer certs need to be ok with being sharedUserId with
656             // the older ones.  We check to see if either the new package is signed by an older cert
657             // with which the current sharedUser is ok, or if it is signed by a newer one, and is ok
658             // with being sharedUser with the existing signing cert.
659             boolean match =
660                     parsedSignatures.checkCapability(
661                             pkgSetting.getSharedUser().signatures.mSigningDetails,
662                             PackageParser.SigningDetails.CertCapabilities.SHARED_USER_ID)
663                     || pkgSetting.getSharedUser().signatures.mSigningDetails.checkCapability(
664                             parsedSignatures,
665                             PackageParser.SigningDetails.CertCapabilities.SHARED_USER_ID);
666             // Special case: if the sharedUserId capability check failed it could be due to this
667             // being the only package in the sharedUserId so far and the lineage being updated to
668             // deny the sharedUserId capability of the previous key in the lineage.
669             if (!match && pkgSetting.getSharedUser().packages.size() == 1
670                     && pkgSetting.getSharedUser().packages.valueAt(0).name.equals(packageName)) {
671                 match = true;
672             }
673             if (!match && compareCompat) {
674                 match = matchSignaturesCompat(
675                         packageName, pkgSetting.getSharedUser().signatures, parsedSignatures);
676             }
677             if (!match && compareRecover) {
678                 match =
679                         matchSignaturesRecover(packageName,
680                                 pkgSetting.getSharedUser().signatures.mSigningDetails,
681                                 parsedSignatures,
682                                 PackageParser.SigningDetails.CertCapabilities.SHARED_USER_ID)
683                         || matchSignaturesRecover(packageName,
684                                 parsedSignatures,
685                                 pkgSetting.getSharedUser().signatures.mSigningDetails,
686                                 PackageParser.SigningDetails.CertCapabilities.SHARED_USER_ID);
687                 compatMatch |= match;
688             }
689             if (!match) {
690                 throw new PackageManagerException(INSTALL_FAILED_SHARED_USER_INCOMPATIBLE,
691                         "Package " + packageName
692                         + " has no signatures that match those in shared user "
693                         + pkgSetting.getSharedUser().name + "; ignoring!");
694             }
695             // It is possible that this package contains a lineage that blocks sharedUserId access
696             // to an already installed package in the sharedUserId signed with a previous key.
697             // Iterate over all of the packages in the sharedUserId and ensure any that are signed
698             // with a key in this package's lineage have the SHARED_USER_ID capability granted.
699             if (parsedSignatures.hasPastSigningCertificates()) {
700                 for (PackageSetting shUidPkgSetting : pkgSetting.getSharedUser().packages) {
701                     // if the current package in the sharedUserId is the package being updated then
702                     // skip this check as the update may revoke the sharedUserId capability from
703                     // the key with which this app was previously signed.
704                     if (packageName.equals(shUidPkgSetting.name)) {
705                         continue;
706                     }
707                     PackageParser.SigningDetails shUidSigningDetails =
708                             shUidPkgSetting.getSigningDetails();
709                     // The capability check only needs to be performed against the package if it is
710                     // signed with a key that is in the lineage of the package being installed.
711                     if (parsedSignatures.hasAncestor(shUidSigningDetails)) {
712                         if (!parsedSignatures.checkCapability(shUidSigningDetails,
713                                 PackageParser.SigningDetails.CertCapabilities.SHARED_USER_ID)) {
714                             throw new PackageManagerException(
715                                     INSTALL_FAILED_SHARED_USER_INCOMPATIBLE,
716                                     "Package " + packageName
717                                             + " revoked the sharedUserId capability from the "
718                                             + "signing key used to sign " + shUidPkgSetting.name);
719                         }
720                     }
721                 }
722             }
723             // If the lineage of this package diverges from the lineage of the sharedUserId then
724             // do not allow the installation to proceed.
725             if (!parsedSignatures.hasCommonAncestor(
726                     pkgSetting.getSharedUser().signatures.mSigningDetails)) {
727                 throw new PackageManagerException(INSTALL_FAILED_SHARED_USER_INCOMPATIBLE,
728                         "Package " + packageName + " has a signing lineage "
729                                 + "that diverges from the lineage of the sharedUserId");
730             }
731         }
732         return compatMatch;
733     }
734 
decompressFile(File srcFile, File dstFile)735     public static int decompressFile(File srcFile, File dstFile) throws ErrnoException {
736         if (DEBUG_COMPRESSION) {
737             Slog.i(TAG, "Decompress file"
738                     + "; src: " + srcFile.getAbsolutePath()
739                     + ", dst: " + dstFile.getAbsolutePath());
740         }
741         try (
742                 InputStream fileIn = new GZIPInputStream(new FileInputStream(srcFile));
743                 OutputStream fileOut = new FileOutputStream(dstFile, false /*append*/);
744         ) {
745             FileUtils.copy(fileIn, fileOut);
746             Os.chmod(dstFile.getAbsolutePath(), 0644);
747             return PackageManager.INSTALL_SUCCEEDED;
748         } catch (IOException e) {
749             logCriticalInfo(Log.ERROR, "Failed to decompress file"
750                     + "; src: " + srcFile.getAbsolutePath()
751                     + ", dst: " + dstFile.getAbsolutePath());
752         }
753         return PackageManager.INSTALL_FAILED_INTERNAL_ERROR;
754     }
755 
getCompressedFiles(String codePath)756     public static File[] getCompressedFiles(String codePath) {
757         final File stubCodePath = new File(codePath);
758         final String stubName = stubCodePath.getName();
759 
760         // The layout of a compressed package on a given partition is as follows :
761         //
762         // Compressed artifacts:
763         //
764         // /partition/ModuleName/foo.gz
765         // /partation/ModuleName/bar.gz
766         //
767         // Stub artifact:
768         //
769         // /partition/ModuleName-Stub/ModuleName-Stub.apk
770         //
771         // In other words, stub is on the same partition as the compressed artifacts
772         // and in a directory that's suffixed with "-Stub".
773         int idx = stubName.lastIndexOf(STUB_SUFFIX);
774         if (idx < 0 || (stubName.length() != (idx + STUB_SUFFIX.length()))) {
775             return null;
776         }
777 
778         final File stubParentDir = stubCodePath.getParentFile();
779         if (stubParentDir == null) {
780             Slog.e(TAG, "Unable to determine stub parent dir for codePath: " + codePath);
781             return null;
782         }
783 
784         final File compressedPath = new File(stubParentDir, stubName.substring(0, idx));
785         final File[] files = compressedPath.listFiles(new FilenameFilter() {
786             @Override
787             public boolean accept(File dir, String name) {
788                 return name.toLowerCase().endsWith(COMPRESSED_EXTENSION);
789             }
790         });
791 
792         if (DEBUG_COMPRESSION && files != null && files.length > 0) {
793             Slog.i(TAG, "getCompressedFiles[" + codePath + "]: " + Arrays.toString(files));
794         }
795 
796         return files;
797     }
798 
compressedFileExists(String codePath)799     public static boolean compressedFileExists(String codePath) {
800         final File[] compressedFiles = getCompressedFiles(codePath);
801         return compressedFiles != null && compressedFiles.length > 0;
802     }
803 
804     /**
805      * Parse given package and return minimal details.
806      */
getMinimalPackageInfo(Context context, String packagePath, int flags, String abiOverride)807     public static PackageInfoLite getMinimalPackageInfo(Context context, String packagePath,
808             int flags, String abiOverride) {
809         final PackageInfoLite ret = new PackageInfoLite();
810         if (packagePath == null) {
811             Slog.i(TAG, "Invalid package file " + packagePath);
812             ret.recommendedInstallLocation = PackageHelper.RECOMMEND_FAILED_INVALID_APK;
813             return ret;
814         }
815 
816         final File packageFile = new File(packagePath);
817         final PackageParser.PackageLite pkg;
818         final long sizeBytes;
819         try {
820             pkg = PackageParser.parsePackageLite(packageFile, 0);
821             sizeBytes = PackageHelper.calculateInstalledSize(pkg, abiOverride);
822         } catch (PackageParserException | IOException e) {
823             Slog.w(TAG, "Failed to parse package at " + packagePath + ": " + e);
824 
825             if (!packageFile.exists()) {
826                 ret.recommendedInstallLocation = PackageHelper.RECOMMEND_FAILED_INVALID_URI;
827             } else {
828                 ret.recommendedInstallLocation = PackageHelper.RECOMMEND_FAILED_INVALID_APK;
829             }
830 
831             return ret;
832         }
833 
834         final int recommendedInstallLocation = PackageHelper.resolveInstallLocation(context,
835                 pkg.packageName, pkg.installLocation, sizeBytes, flags);
836 
837         ret.packageName = pkg.packageName;
838         ret.splitNames = pkg.splitNames;
839         ret.versionCode = pkg.versionCode;
840         ret.versionCodeMajor = pkg.versionCodeMajor;
841         ret.baseRevisionCode = pkg.baseRevisionCode;
842         ret.splitRevisionCodes = pkg.splitRevisionCodes;
843         ret.installLocation = pkg.installLocation;
844         ret.verifiers = pkg.verifiers;
845         ret.recommendedInstallLocation = recommendedInstallLocation;
846         ret.multiArch = pkg.multiArch;
847         ret.debuggable = pkg.debuggable;
848 
849         return ret;
850     }
851 
852     /**
853      * Calculate estimated footprint of given package post-installation.
854      *
855      * @return -1 if there's some error calculating the size, otherwise installed size of the
856      *         package.
857      */
calculateInstalledSize(String packagePath, String abiOverride)858     public static long calculateInstalledSize(String packagePath, String abiOverride) {
859         final File packageFile = new File(packagePath);
860         final PackageParser.PackageLite pkg;
861         try {
862             pkg = PackageParser.parsePackageLite(packageFile, 0);
863             return PackageHelper.calculateInstalledSize(pkg, abiOverride);
864         } catch (PackageParserException | IOException e) {
865             Slog.w(TAG, "Failed to calculate installed size: " + e);
866             return -1;
867         }
868     }
869 
870     /**
871      * Checks whenever downgrade of an app is permitted.
872      *
873      * @param installFlags flags of the current install.
874      * @param isAppDebuggable if the currently installed version of the app is debuggable.
875      * @return {@code true} if downgrade is permitted according to the {@code installFlags} and
876      *         {@code applicationFlags}.
877      */
isDowngradePermitted(int installFlags, boolean isAppDebuggable)878     public static boolean isDowngradePermitted(int installFlags, boolean isAppDebuggable) {
879         // If installed, the package will get access to data left on the device by its
880         // predecessor. As a security measure, this is permitted only if this is not a
881         // version downgrade or if the predecessor package is marked as debuggable and
882         // a downgrade is explicitly requested.
883         //
884         // On debuggable platform builds, downgrades are permitted even for
885         // non-debuggable packages to make testing easier. Debuggable platform builds do
886         // not offer security guarantees and thus it's OK to disable some security
887         // mechanisms to make debugging/testing easier on those builds. However, even on
888         // debuggable builds downgrades of packages are permitted only if requested via
889         // installFlags. This is because we aim to keep the behavior of debuggable
890         // platform builds as close as possible to the behavior of non-debuggable
891         // platform builds.
892         //
893         // In case of user builds, downgrade is permitted only for the system server initiated
894         // sessions. This is enforced by INSTALL_ALLOW_DOWNGRADE flag parameter.
895         final boolean downgradeRequested =
896                 (installFlags & PackageManager.INSTALL_REQUEST_DOWNGRADE) != 0;
897         if (!downgradeRequested) {
898             return false;
899         }
900         final boolean isDebuggable = Build.IS_DEBUGGABLE || isAppDebuggable;
901         if (isDebuggable) {
902             return true;
903         }
904         return (installFlags & PackageManager.INSTALL_ALLOW_DOWNGRADE) != 0;
905     }
906 
907     /**
908      * Copy package to the target location.
909      *
910      * @param packagePath absolute path to the package to be copied. Can be
911      *                    a single monolithic APK file or a cluster directory
912      *                    containing one or more APKs.
913      * @return returns status code according to those in
914      *         {@link PackageManager}
915      */
copyPackage(String packagePath, File targetDir)916     public static int copyPackage(String packagePath, File targetDir) {
917         if (packagePath == null) {
918             return PackageManager.INSTALL_FAILED_INVALID_URI;
919         }
920 
921         try {
922             final File packageFile = new File(packagePath);
923             final PackageParser.PackageLite pkg = PackageParser.parsePackageLite(packageFile, 0);
924             copyFile(pkg.baseCodePath, targetDir, "base.apk");
925             if (!ArrayUtils.isEmpty(pkg.splitNames)) {
926                 for (int i = 0; i < pkg.splitNames.length; i++) {
927                     copyFile(pkg.splitCodePaths[i], targetDir,
928                             "split_" + pkg.splitNames[i] + ".apk");
929                 }
930             }
931             return PackageManager.INSTALL_SUCCEEDED;
932         } catch (PackageParserException | IOException | ErrnoException e) {
933             Slog.w(TAG, "Failed to copy package at " + packagePath + ": " + e);
934             return PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE;
935         }
936     }
937 
copyFile(String sourcePath, File targetDir, String targetName)938     private static void copyFile(String sourcePath, File targetDir, String targetName)
939             throws ErrnoException, IOException {
940         if (!FileUtils.isValidExtFilename(targetName)) {
941             throw new IllegalArgumentException("Invalid filename: " + targetName);
942         }
943         Slog.d(TAG, "Copying " + sourcePath + " to " + targetName);
944 
945         final File targetFile = new File(targetDir, targetName);
946         final FileDescriptor targetFd = Os.open(targetFile.getAbsolutePath(),
947                 O_RDWR | O_CREAT, 0644);
948         Os.chmod(targetFile.getAbsolutePath(), 0644);
949         FileInputStream source = null;
950         try {
951             source = new FileInputStream(sourcePath);
952             FileUtils.copy(source.getFD(), targetFd);
953         } finally {
954             IoUtils.closeQuietly(source);
955         }
956     }
957 
958     /**
959      * Returns the {@link PermissionsState} for the given package. If the {@link PermissionsState}
960      * could not be found, {@code null} will be returned.
961      */
getPermissionsState( PackageManagerInternal packageManagerInternal, AndroidPackage pkg)962     public static PermissionsState getPermissionsState(
963             PackageManagerInternal packageManagerInternal, AndroidPackage pkg) {
964         final PackageSetting packageSetting = packageManagerInternal.getPackageSetting(
965                 pkg.getPackageName());
966         if (packageSetting == null) {
967             return null;
968         }
969         return packageSetting.getPermissionsState();
970     }
971 
972     /**
973      * Recursively create target directory
974      */
makeDirRecursive(File targetDir, int mode)975     public static void makeDirRecursive(File targetDir, int mode) throws ErrnoException {
976         final Path targetDirPath = targetDir.toPath();
977         final int directoriesCount = targetDirPath.getNameCount();
978         File currentDir;
979         for (int i = 1; i <= directoriesCount; i++) {
980             currentDir = targetDirPath.subpath(0, i).toFile();
981             if (currentDir.exists()) {
982                 continue;
983             }
984             Os.mkdir(currentDir.getAbsolutePath(), mode);
985             Os.chmod(currentDir.getAbsolutePath(), mode);
986         }
987     }
988 
989     /**
990      * Returns a string that's compatible with the verification root hash extra.
991      * @see PackageManager#EXTRA_VERIFICATION_ROOT_HASH
992      */
993     @NonNull
buildVerificationRootHashString(@onNull String baseFilename, @Nullable String[] splitFilenameArray)994     public static String buildVerificationRootHashString(@NonNull String baseFilename,
995             @Nullable String[] splitFilenameArray) {
996         final StringBuilder sb = new StringBuilder();
997         final String baseFilePath =
998                 baseFilename.substring(baseFilename.lastIndexOf(File.separator) + 1);
999         sb.append(baseFilePath).append(":");
1000         final byte[] baseRootHash = getRootHash(baseFilename);
1001         if (baseRootHash == null) {
1002             sb.append("0");
1003         } else {
1004             sb.append(HexDump.toHexString(baseRootHash));
1005         }
1006         if (splitFilenameArray == null || splitFilenameArray.length == 0) {
1007             return sb.toString();
1008         }
1009 
1010         for (int i = splitFilenameArray.length - 1; i >= 0; i--) {
1011             final String splitFilename = splitFilenameArray[i];
1012             final String splitFilePath =
1013                     splitFilename.substring(splitFilename.lastIndexOf(File.separator) + 1);
1014             final byte[] splitRootHash = getRootHash(splitFilename);
1015             sb.append(";").append(splitFilePath).append(":");
1016             if (splitRootHash == null) {
1017                 sb.append("0");
1018             } else {
1019                 sb.append(HexDump.toHexString(splitRootHash));
1020             }
1021         }
1022         return sb.toString();
1023     }
1024 
1025     /**
1026      * Returns the root has for the given file.
1027      * <p>Otherwise, returns {@code null} if the root hash could not be found or calculated.
1028      * <p>NOTE: This currently only works on files stored on the incremental file system. The
1029      * eventual goal is that this hash [among others] can be retrieved for any file.
1030      */
1031     @Nullable
getRootHash(String filename)1032     private static byte[] getRootHash(String filename) {
1033         try {
1034             final byte[] baseFileSignature =
1035                     IncrementalManager.unsafeGetFileSignature(filename);
1036             if (baseFileSignature == null) {
1037                 throw new IOException("File signature not present");
1038             }
1039             final V4Signature signature =
1040                     V4Signature.readFrom(baseFileSignature);
1041             if (signature.hashingInfo == null) {
1042                 throw new IOException("Hashing info not present");
1043             }
1044             final HashingInfo hashInfo =
1045                     HashingInfo.fromByteArray(signature.hashingInfo);
1046             if (ArrayUtils.isEmpty(hashInfo.rawRootHash)) {
1047                 throw new IOException("Root has not present");
1048             }
1049             return hashInfo.rawRootHash;
1050         } catch (IOException ignore) {
1051             Slog.e(TAG, "ERROR: could not load root hash from incremental install");
1052         }
1053         return null;
1054     }
1055 }
1056