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