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