PackageManagerServiceUtils.java revision 5cdda3425ccf3c62e400a1646615f4479a8266af
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
17package com.android.server.pm;
18
19import static android.content.pm.PackageManager.INSTALL_FAILED_SHARED_USER_INCOMPATIBLE;
20import static android.content.pm.PackageManager.INSTALL_FAILED_UPDATE_INCOMPATIBLE;
21import static com.android.server.pm.PackageManagerService.COMPRESSED_EXTENSION;
22import static com.android.server.pm.PackageManagerService.DEBUG_COMPRESSION;
23import static com.android.server.pm.PackageManagerService.DEBUG_DEXOPT;
24import static com.android.server.pm.PackageManagerService.STUB_SUFFIX;
25import static com.android.server.pm.PackageManagerService.TAG;
26import static com.android.server.pm.PackageManagerServiceUtils.logCriticalInfo;
27
28import com.android.internal.content.NativeLibraryHelper;
29import com.android.internal.util.ArrayUtils;
30import com.android.internal.util.FastPrintWriter;
31import com.android.server.EventLogTags;
32import com.android.server.pm.dex.DexManager;
33import com.android.server.pm.dex.PackageDexUsage;
34
35import android.annotation.NonNull;
36import android.annotation.Nullable;
37import android.app.AppGlobals;
38import android.content.Intent;
39import android.content.pm.PackageManager;
40import android.content.pm.PackageParser;
41import android.content.pm.PackageParser.PackageParserException;
42import android.content.pm.ResolveInfo;
43import android.content.pm.Signature;
44import android.os.Build;
45import android.os.Debug;
46import android.os.Environment;
47import android.os.FileUtils;
48import android.os.Process;
49import android.os.RemoteException;
50import android.os.SystemProperties;
51import android.os.UserHandle;
52import android.service.pm.PackageServiceDumpProto;
53import android.system.ErrnoException;
54import android.system.Os;
55import android.util.ArraySet;
56import android.util.Log;
57import android.util.PackageUtils;
58import android.util.Slog;
59import android.util.jar.StrictJarFile;
60import android.util.proto.ProtoOutputStream;
61
62import dalvik.system.VMRuntime;
63
64import libcore.io.IoUtils;
65import libcore.io.Libcore;
66import libcore.io.Streams;
67
68import java.io.BufferedReader;
69import java.io.File;
70import java.io.FileInputStream;
71import java.io.FileOutputStream;
72import java.io.FileReader;
73import java.io.FilenameFilter;
74import java.io.IOException;
75import java.io.InputStream;
76import java.io.OutputStream;
77import java.io.PrintWriter;
78import java.security.MessageDigest;
79import java.security.NoSuchAlgorithmException;
80import java.security.cert.CertificateEncodingException;
81import java.security.cert.CertificateException;
82import java.text.SimpleDateFormat;
83import java.util.ArrayList;
84import java.util.Arrays;
85import java.util.Collection;
86import java.util.Collections;
87import java.util.Date;
88import java.util.Iterator;
89import java.util.LinkedList;
90import java.util.List;
91import java.util.function.Predicate;
92import java.util.zip.GZIPInputStream;
93import java.util.zip.ZipEntry;
94
95/**
96 * Class containing helper methods for the PackageManagerService.
97 *
98 * {@hide}
99 */
100public class PackageManagerServiceUtils {
101    private final static long SEVEN_DAYS_IN_MILLISECONDS = 7 * 24 * 60 * 60 * 1000;
102
103    private static ArraySet<String> getPackageNamesForIntent(Intent intent, int userId) {
104        List<ResolveInfo> ris = null;
105        try {
106            ris = AppGlobals.getPackageManager().queryIntentReceivers(intent, null, 0, userId)
107                    .getList();
108        } catch (RemoteException e) {
109        }
110        ArraySet<String> pkgNames = new ArraySet<String>();
111        if (ris != null) {
112            for (ResolveInfo ri : ris) {
113                pkgNames.add(ri.activityInfo.packageName);
114            }
115        }
116        return pkgNames;
117    }
118
119    // Sort a list of apps by their last usage, most recently used apps first. The order of
120    // packages without usage data is undefined (but they will be sorted after the packages
121    // that do have usage data).
122    public static void sortPackagesByUsageDate(List<PackageParser.Package> pkgs,
123            PackageManagerService packageManagerService) {
124        if (!packageManagerService.isHistoricalPackageUsageAvailable()) {
125            return;
126        }
127
128        Collections.sort(pkgs, (pkg1, pkg2) ->
129                Long.compare(pkg2.getLatestForegroundPackageUseTimeInMills(),
130                        pkg1.getLatestForegroundPackageUseTimeInMills()));
131    }
132
133    // Apply the given {@code filter} to all packages in {@code packages}. If tested positive, the
134    // package will be removed from {@code packages} and added to {@code result} with its
135    // dependencies. If usage data is available, the positive packages will be sorted by usage
136    // data (with {@code sortTemp} as temporary storage).
137    private static void applyPackageFilter(Predicate<PackageParser.Package> filter,
138            Collection<PackageParser.Package> result,
139            Collection<PackageParser.Package> packages,
140            @NonNull List<PackageParser.Package> sortTemp,
141            PackageManagerService packageManagerService) {
142        for (PackageParser.Package pkg : packages) {
143            if (filter.test(pkg)) {
144                sortTemp.add(pkg);
145            }
146        }
147
148        sortPackagesByUsageDate(sortTemp, packageManagerService);
149        packages.removeAll(sortTemp);
150
151        for (PackageParser.Package pkg : sortTemp) {
152            result.add(pkg);
153
154            Collection<PackageParser.Package> deps =
155                    packageManagerService.findSharedNonSystemLibraries(pkg);
156            if (!deps.isEmpty()) {
157                deps.removeAll(result);
158                result.addAll(deps);
159                packages.removeAll(deps);
160            }
161        }
162
163        sortTemp.clear();
164    }
165
166    // Sort apps by importance for dexopt ordering. Important apps are given
167    // more priority in case the device runs out of space.
168    public static List<PackageParser.Package> getPackagesForDexopt(
169            Collection<PackageParser.Package> packages,
170            PackageManagerService packageManagerService) {
171        ArrayList<PackageParser.Package> remainingPkgs = new ArrayList<>(packages);
172        LinkedList<PackageParser.Package> result = new LinkedList<>();
173        ArrayList<PackageParser.Package> sortTemp = new ArrayList<>(remainingPkgs.size());
174
175        // Give priority to core apps.
176        applyPackageFilter((pkg) -> pkg.coreApp, result, remainingPkgs, sortTemp,
177                packageManagerService);
178
179        // Give priority to system apps that listen for pre boot complete.
180        Intent intent = new Intent(Intent.ACTION_PRE_BOOT_COMPLETED);
181        final ArraySet<String> pkgNames = getPackageNamesForIntent(intent, UserHandle.USER_SYSTEM);
182        applyPackageFilter((pkg) -> pkgNames.contains(pkg.packageName), result, remainingPkgs,
183                sortTemp, packageManagerService);
184
185        // Give priority to apps used by other apps.
186        DexManager dexManager = packageManagerService.getDexManager();
187        applyPackageFilter((pkg) ->
188                dexManager.getPackageUseInfoOrDefault(pkg.packageName)
189                        .isAnyCodePathUsedByOtherApps(),
190                result, remainingPkgs, sortTemp, packageManagerService);
191
192        // Filter out packages that aren't recently used, add all remaining apps.
193        // TODO: add a property to control this?
194        Predicate<PackageParser.Package> remainingPredicate;
195        if (!remainingPkgs.isEmpty() && packageManagerService.isHistoricalPackageUsageAvailable()) {
196            if (DEBUG_DEXOPT) {
197                Log.i(TAG, "Looking at historical package use");
198            }
199            // Get the package that was used last.
200            PackageParser.Package lastUsed = Collections.max(remainingPkgs, (pkg1, pkg2) ->
201                    Long.compare(pkg1.getLatestForegroundPackageUseTimeInMills(),
202                            pkg2.getLatestForegroundPackageUseTimeInMills()));
203            if (DEBUG_DEXOPT) {
204                Log.i(TAG, "Taking package " + lastUsed.packageName + " as reference in time use");
205            }
206            long estimatedPreviousSystemUseTime =
207                    lastUsed.getLatestForegroundPackageUseTimeInMills();
208            // Be defensive if for some reason package usage has bogus data.
209            if (estimatedPreviousSystemUseTime != 0) {
210                final long cutoffTime = estimatedPreviousSystemUseTime - SEVEN_DAYS_IN_MILLISECONDS;
211                remainingPredicate =
212                        (pkg) -> pkg.getLatestForegroundPackageUseTimeInMills() >= cutoffTime;
213            } else {
214                // No meaningful historical info. Take all.
215                remainingPredicate = (pkg) -> true;
216            }
217            sortPackagesByUsageDate(remainingPkgs, packageManagerService);
218        } else {
219            // No historical info. Take all.
220            remainingPredicate = (pkg) -> true;
221        }
222        applyPackageFilter(remainingPredicate, result, remainingPkgs, sortTemp,
223                packageManagerService);
224
225        if (DEBUG_DEXOPT) {
226            Log.i(TAG, "Packages to be dexopted: " + packagesToString(result));
227            Log.i(TAG, "Packages skipped from dexopt: " + packagesToString(remainingPkgs));
228        }
229
230        return result;
231    }
232
233    /**
234     * Checks if the package was inactive during since <code>thresholdTimeinMillis</code>.
235     * Package is considered active, if:
236     * 1) It was active in foreground.
237     * 2) It was active in background and also used by other apps.
238     *
239     * If it doesn't have sufficient information about the package, it return <code>false</code>.
240     */
241    public static boolean isUnusedSinceTimeInMillis(long firstInstallTime, long currentTimeInMillis,
242            long thresholdTimeinMillis, PackageDexUsage.PackageUseInfo packageUseInfo,
243            long latestPackageUseTimeInMillis, long latestForegroundPackageUseTimeInMillis) {
244
245        if (currentTimeInMillis - firstInstallTime < thresholdTimeinMillis) {
246            return false;
247        }
248
249        // If the app was active in foreground during the threshold period.
250        boolean isActiveInForeground = (currentTimeInMillis
251                - latestForegroundPackageUseTimeInMillis)
252                < thresholdTimeinMillis;
253
254        if (isActiveInForeground) {
255            return false;
256        }
257
258        // If the app was active in background during the threshold period and was used
259        // by other packages.
260        boolean isActiveInBackgroundAndUsedByOtherPackages = ((currentTimeInMillis
261                - latestPackageUseTimeInMillis)
262                < thresholdTimeinMillis)
263                && packageUseInfo.isAnyCodePathUsedByOtherApps();
264
265        return !isActiveInBackgroundAndUsedByOtherPackages;
266    }
267
268    /**
269     * Returns the canonicalized path of {@code path} as per {@code realpath(3)}
270     * semantics.
271     */
272    public static String realpath(File path) throws IOException {
273        try {
274            return Os.realpath(path.getAbsolutePath());
275        } catch (ErrnoException ee) {
276            throw ee.rethrowAsIOException();
277        }
278    }
279
280    public static String packagesToString(Collection<PackageParser.Package> c) {
281        StringBuilder sb = new StringBuilder();
282        for (PackageParser.Package pkg : c) {
283            if (sb.length() > 0) {
284                sb.append(", ");
285            }
286            sb.append(pkg.packageName);
287        }
288        return sb.toString();
289    }
290
291    /**
292     * Verifies that the given string {@code isa} is a valid supported isa on
293     * the running device.
294     */
295    public static boolean checkISA(String isa) {
296        for (String abi : Build.SUPPORTED_ABIS) {
297            if (VMRuntime.getInstructionSet(abi).equals(isa)) {
298                return true;
299            }
300        }
301        return false;
302    }
303
304    public static long getLastModifiedTime(PackageParser.Package pkg) {
305        final File srcFile = new File(pkg.codePath);
306        if (!srcFile.isDirectory()) {
307            return srcFile.lastModified();
308        }
309        final File baseFile = new File(pkg.baseCodePath);
310        long maxModifiedTime = baseFile.lastModified();
311        if (pkg.splitCodePaths != null) {
312            for (int i = pkg.splitCodePaths.length - 1; i >=0; --i) {
313                final File splitFile = new File(pkg.splitCodePaths[i]);
314                maxModifiedTime = Math.max(maxModifiedTime, splitFile.lastModified());
315            }
316        }
317        return maxModifiedTime;
318    }
319
320    /**
321     * Checks that the archive located at {@code fileName} has uncompressed dex file and so
322     * files that can be direclty mapped.
323     */
324    public static void logApkHasUncompressedCode(String fileName) {
325        StrictJarFile jarFile = null;
326        try {
327            jarFile = new StrictJarFile(fileName,
328                    false /*verify*/, false /*signatureSchemeRollbackProtectionsEnforced*/);
329            Iterator<ZipEntry> it = jarFile.iterator();
330            while (it.hasNext()) {
331                ZipEntry entry = it.next();
332                if (entry.getName().endsWith(".dex")) {
333                    if (entry.getMethod() != ZipEntry.STORED) {
334                        Slog.wtf(TAG, "APK " + fileName + " has compressed dex code " +
335                                entry.getName());
336                    } else if ((entry.getDataOffset() & 0x3) != 0) {
337                        Slog.wtf(TAG, "APK " + fileName + " has unaligned dex code " +
338                                entry.getName());
339                    }
340                } else if (entry.getName().endsWith(".so")) {
341                    if (entry.getMethod() != ZipEntry.STORED) {
342                        Slog.wtf(TAG, "APK " + fileName + " has compressed native code " +
343                                entry.getName());
344                    } else if ((entry.getDataOffset() & (0x1000 - 1)) != 0) {
345                        Slog.wtf(TAG, "APK " + fileName + " has unaligned native code " +
346                                entry.getName());
347                    }
348                }
349            }
350        } catch (IOException ignore) {
351            Slog.wtf(TAG, "Error when parsing APK " + fileName);
352        } finally {
353            try {
354                if (jarFile != null) {
355                    jarFile.close();
356                }
357            } catch (IOException ignore) {}
358        }
359        return;
360    }
361
362    /**
363     * Checks that the APKs in the given package have uncompressed dex file and so
364     * files that can be direclty mapped.
365     */
366    public static void logPackageHasUncompressedCode(PackageParser.Package pkg) {
367        logApkHasUncompressedCode(pkg.baseCodePath);
368        if (!ArrayUtils.isEmpty(pkg.splitCodePaths)) {
369            for (int i = 0; i < pkg.splitCodePaths.length; i++) {
370                logApkHasUncompressedCode(pkg.splitCodePaths[i]);
371            }
372        }
373    }
374
375    private static File getSettingsProblemFile() {
376        File dataDir = Environment.getDataDirectory();
377        File systemDir = new File(dataDir, "system");
378        File fname = new File(systemDir, "uiderrors.txt");
379        return fname;
380    }
381
382    public static void dumpCriticalInfo(ProtoOutputStream proto) {
383        try (BufferedReader in = new BufferedReader(new FileReader(getSettingsProblemFile()))) {
384            String line = null;
385            while ((line = in.readLine()) != null) {
386                if (line.contains("ignored: updated version")) continue;
387                proto.write(PackageServiceDumpProto.MESSAGES, line);
388            }
389        } catch (IOException ignored) {
390        }
391    }
392
393    public static void dumpCriticalInfo(PrintWriter pw, String msg) {
394        try (BufferedReader in = new BufferedReader(new FileReader(getSettingsProblemFile()))) {
395            String line = null;
396            while ((line = in.readLine()) != null) {
397                if (line.contains("ignored: updated version")) continue;
398                if (msg != null) {
399                    pw.print(msg);
400                }
401                pw.println(line);
402            }
403        } catch (IOException ignored) {
404        }
405    }
406
407    public static void logCriticalInfo(int priority, String msg) {
408        Slog.println(priority, TAG, msg);
409        EventLogTags.writePmCriticalInfo(msg);
410        try {
411            File fname = getSettingsProblemFile();
412            FileOutputStream out = new FileOutputStream(fname, true);
413            PrintWriter pw = new FastPrintWriter(out);
414            SimpleDateFormat formatter = new SimpleDateFormat();
415            String dateString = formatter.format(new Date(System.currentTimeMillis()));
416            pw.println(dateString + ": " + msg);
417            pw.close();
418            FileUtils.setPermissions(
419                    fname.toString(),
420                    FileUtils.S_IRWXU|FileUtils.S_IRWXG|FileUtils.S_IROTH,
421                    -1, -1);
422        } catch (java.io.IOException e) {
423        }
424    }
425
426    public static void enforceShellRestriction(String restriction, int callingUid, int userHandle) {
427        if (callingUid == Process.SHELL_UID) {
428            if (userHandle >= 0
429                    && PackageManagerService.sUserManager.hasUserRestriction(
430                            restriction, userHandle)) {
431                throw new SecurityException("Shell does not have permission to access user "
432                        + userHandle);
433            } else if (userHandle < 0) {
434                Slog.e(PackageManagerService.TAG, "Unable to check shell permission for user "
435                        + userHandle + "\n\t" + Debug.getCallers(3));
436            }
437        }
438    }
439
440    /**
441     * Derive the value of the {@code cpuAbiOverride} based on the provided
442     * value and an optional stored value from the package settings.
443     */
444    public static String deriveAbiOverride(String abiOverride, PackageSetting settings) {
445        String cpuAbiOverride = null;
446        if (NativeLibraryHelper.CLEAR_ABI_OVERRIDE.equals(abiOverride)) {
447            cpuAbiOverride = null;
448        } else if (abiOverride != null) {
449            cpuAbiOverride = abiOverride;
450        } else if (settings != null) {
451            cpuAbiOverride = settings.cpuAbiOverrideString;
452        }
453        return cpuAbiOverride;
454    }
455
456    /**
457     * Compares two sets of signatures. Returns:
458     * <br />
459     * {@link PackageManager#SIGNATURE_NEITHER_SIGNED}: if both signature sets are null,
460     * <br />
461     * {@link PackageManager#SIGNATURE_FIRST_NOT_SIGNED}: if the first signature set is null,
462     * <br />
463     * {@link PackageManager#SIGNATURE_SECOND_NOT_SIGNED}: if the second signature set is null,
464     * <br />
465     * {@link PackageManager#SIGNATURE_MATCH}: if the two signature sets are identical,
466     * <br />
467     * {@link PackageManager#SIGNATURE_NO_MATCH}: if the two signature sets differ.
468     */
469    public static int compareSignatures(Signature[] s1, Signature[] s2) {
470        if (s1 == null) {
471            return s2 == null
472                    ? PackageManager.SIGNATURE_NEITHER_SIGNED
473                    : PackageManager.SIGNATURE_FIRST_NOT_SIGNED;
474        }
475
476        if (s2 == null) {
477            return PackageManager.SIGNATURE_SECOND_NOT_SIGNED;
478        }
479
480        if (s1.length != s2.length) {
481            return PackageManager.SIGNATURE_NO_MATCH;
482        }
483
484        // Since both signature sets are of size 1, we can compare without HashSets.
485        if (s1.length == 1) {
486            return s1[0].equals(s2[0]) ?
487                    PackageManager.SIGNATURE_MATCH :
488                    PackageManager.SIGNATURE_NO_MATCH;
489        }
490
491        ArraySet<Signature> set1 = new ArraySet<Signature>();
492        for (Signature sig : s1) {
493            set1.add(sig);
494        }
495        ArraySet<Signature> set2 = new ArraySet<Signature>();
496        for (Signature sig : s2) {
497            set2.add(sig);
498        }
499        // Make sure s2 contains all signatures in s1.
500        if (set1.equals(set2)) {
501            return PackageManager.SIGNATURE_MATCH;
502        }
503        return PackageManager.SIGNATURE_NO_MATCH;
504    }
505
506    /**
507     * Used for backward compatibility to make sure any packages with
508     * certificate chains get upgraded to the new style. {@code existingSigs}
509     * will be in the old format (since they were stored on disk from before the
510     * system upgrade) and {@code scannedSigs} will be in the newer format.
511     */
512    private static boolean matchSignaturesCompat(String packageName,
513            PackageSignatures packageSignatures, PackageParser.SigningDetails parsedSignatures) {
514        ArraySet<Signature> existingSet = new ArraySet<Signature>();
515        for (Signature sig : packageSignatures.mSigningDetails.signatures) {
516            existingSet.add(sig);
517        }
518        ArraySet<Signature> scannedCompatSet = new ArraySet<Signature>();
519        for (Signature sig : parsedSignatures.signatures) {
520            try {
521                Signature[] chainSignatures = sig.getChainSignatures();
522                for (Signature chainSig : chainSignatures) {
523                    scannedCompatSet.add(chainSig);
524                }
525            } catch (CertificateEncodingException e) {
526                scannedCompatSet.add(sig);
527            }
528        }
529        // make sure the expanded scanned set contains all signatures in the existing one
530        if (scannedCompatSet.equals(existingSet)) {
531            // migrate the old signatures to the new scheme
532            packageSignatures.mSigningDetails = parsedSignatures;
533            return true;
534        }
535        return false;
536    }
537
538    private static boolean matchSignaturesRecover(String packageName,
539            Signature[] existingSignatures, Signature[] parsedSignatures) {
540        String msg = null;
541        try {
542            if (Signature.areEffectiveMatch(existingSignatures, parsedSignatures)) {
543                logCriticalInfo(Log.INFO,
544                        "Recovered effectively matching certificates for " + packageName);
545                return true;
546            }
547        } catch (CertificateException e) {
548            msg = e.getMessage();
549        }
550        logCriticalInfo(Log.INFO,
551                "Failed to recover certificates for " + packageName + ": " + msg);
552        return false;
553    }
554
555    /**
556     * Make sure the updated priv app is signed with the same key as the original APK file on the
557     * /system partition.
558     *
559     * <p>The rationale is that {@code disabledPkg} is a PackageSetting backed by xml files in /data
560     * and is not tamperproof.
561     */
562    private static boolean matchSignatureInSystem(PackageSetting pkgSetting,
563            PackageSetting disabledPkgSetting) {
564        try {
565            PackageParser.collectCertificates(disabledPkgSetting.pkg,
566                    PackageParser.PARSE_IS_SYSTEM_DIR);
567            if (compareSignatures(pkgSetting.signatures.mSigningDetails.signatures,
568                        disabledPkgSetting.signatures.mSigningDetails.signatures)
569                    != PackageManager.SIGNATURE_MATCH) {
570                logCriticalInfo(Log.ERROR, "Updated system app mismatches cert on /system: " +
571                        pkgSetting.name);
572                return false;
573            }
574        } catch (PackageParserException e) {
575            logCriticalInfo(Log.ERROR, "Failed to collect cert for " + pkgSetting.name + ": " +
576                    e.getMessage());
577            return false;
578        }
579        return true;
580    }
581
582
583    /**
584     * Checks the signing certificates to see if the provided certificate is a member.  Invalid for
585     * {@code SigningDetails} with multiple signing certificates.
586     * @param certificate certificate to check for membership
587     * @param signingDetails signing certificates record whose members are to be searched
588     * @return true if {@code certificate} is in {@code signingDetails}
589     */
590    public static boolean signingDetailsHasCertificate(
591            byte[] certificate, PackageParser.SigningDetails signingDetails) {
592        if (signingDetails == PackageParser.SigningDetails.UNKNOWN) {
593            return false;
594        }
595        Signature signature = new Signature(certificate);
596        if (signingDetails.hasPastSigningCertificates()) {
597            for (int i = 0; i < signingDetails.pastSigningCertificates.length; i++) {
598                if (signingDetails.pastSigningCertificates[i].equals(signature)) {
599                    return true;
600                }
601            }
602        } else {
603            // no signing history, just check the current signer
604            if (signingDetails.signatures.length == 1
605                    && signingDetails.signatures[0].equals(signature)) {
606                return true;
607            }
608        }
609        return false;
610    }
611
612    /**
613     * Checks the signing certificates to see if the provided certificate is a member.  Invalid for
614     * {@code SigningDetails} with multiple signing certificaes.
615     * @param sha256Certificate certificate to check for membership
616     * @param signingDetails signing certificates record whose members are to be searched
617     * @return true if {@code certificate} is in {@code signingDetails}
618     */
619    public static boolean signingDetailsHasSha256Certificate(
620            byte[] sha256Certificate, PackageParser.SigningDetails signingDetails ) {
621        if (signingDetails == PackageParser.SigningDetails.UNKNOWN) {
622            return false;
623        }
624        if (signingDetails.hasPastSigningCertificates()) {
625            for (int i = 0; i < signingDetails.pastSigningCertificates.length; i++) {
626                byte[] digest = PackageUtils.computeSha256DigestBytes(
627                        signingDetails.pastSigningCertificates[i].toByteArray());
628                if (Arrays.equals(sha256Certificate, digest)) {
629                    return true;
630                }
631            }
632        } else {
633            // no signing history, just check the current signer
634            if (signingDetails.signatures.length == 1) {
635                byte[] digest = PackageUtils.computeSha256DigestBytes(
636                        signingDetails.signatures[0].toByteArray());
637                if (Arrays.equals(sha256Certificate, digest)) {
638                    return true;
639                }
640            }
641        }
642        return false;
643    }
644
645    /** Returns true to force apk verification if the updated package (in /data) is a priv app. */
646    static boolean isApkVerificationForced(@Nullable PackageSetting disabledPs) {
647        return disabledPs != null && disabledPs.isPrivileged() &&
648                SystemProperties.getInt("ro.apk_verity.mode", 0) != 0;
649    }
650
651    /**
652     * Verifies that signatures match.
653     * @returns {@code true} if the compat signatures were matched; otherwise, {@code false}.
654     * @throws PackageManagerException if the signatures did not match.
655     */
656    public static boolean verifySignatures(PackageSetting pkgSetting,
657            PackageSetting disabledPkgSetting, PackageParser.SigningDetails parsedSignatures,
658            boolean compareCompat, boolean compareRecover)
659            throws PackageManagerException {
660        final String packageName = pkgSetting.name;
661        boolean compatMatch = false;
662        if (pkgSetting.signatures.mSigningDetails.signatures != null) {
663            // Already existing package. Make sure signatures match
664            boolean match = compareSignatures(pkgSetting.signatures.mSigningDetails.signatures,
665                    parsedSignatures.signatures)
666                    == PackageManager.SIGNATURE_MATCH;
667            if (!match && compareCompat) {
668                match = matchSignaturesCompat(packageName, pkgSetting.signatures,
669                        parsedSignatures);
670                compatMatch = match;
671            }
672            if (!match && compareRecover) {
673                match = matchSignaturesRecover(
674                        packageName, pkgSetting.signatures.mSigningDetails.signatures,
675                        parsedSignatures.signatures);
676            }
677
678            if (!match && isApkVerificationForced(disabledPkgSetting)) {
679                match = matchSignatureInSystem(pkgSetting, disabledPkgSetting);
680            }
681
682            if (!match) {
683                throw new PackageManagerException(INSTALL_FAILED_UPDATE_INCOMPATIBLE,
684                        "Package " + packageName +
685                        " signatures do not match previously installed version; ignoring!");
686            }
687        }
688        // Check for shared user signatures
689        if (pkgSetting.sharedUser != null
690                && pkgSetting.sharedUser.signatures.mSigningDetails.signatures != null) {
691            // Already existing package. Make sure signatures match
692            boolean match =
693                    compareSignatures(
694                            pkgSetting.sharedUser.signatures.mSigningDetails.signatures,
695                            parsedSignatures.signatures) == PackageManager.SIGNATURE_MATCH;
696            if (!match && compareCompat) {
697                match = matchSignaturesCompat(
698                        packageName, pkgSetting.sharedUser.signatures, parsedSignatures);
699            }
700            if (!match && compareRecover) {
701                match = matchSignaturesRecover(packageName,
702                        pkgSetting.sharedUser.signatures.mSigningDetails.signatures,
703                        parsedSignatures.signatures);
704                compatMatch |= match;
705            }
706            if (!match) {
707                throw new PackageManagerException(INSTALL_FAILED_SHARED_USER_INCOMPATIBLE,
708                        "Package " + packageName
709                        + " has no signatures that match those in shared user "
710                        + pkgSetting.sharedUser.name + "; ignoring!");
711            }
712        }
713        return compatMatch;
714    }
715
716    public static int decompressFile(File srcFile, File dstFile) throws ErrnoException {
717        if (DEBUG_COMPRESSION) {
718            Slog.i(TAG, "Decompress file"
719                    + "; src: " + srcFile.getAbsolutePath()
720                    + ", dst: " + dstFile.getAbsolutePath());
721        }
722        try (
723                InputStream fileIn = new GZIPInputStream(new FileInputStream(srcFile));
724                OutputStream fileOut = new FileOutputStream(dstFile, false /*append*/);
725        ) {
726            Streams.copy(fileIn, fileOut);
727            Os.chmod(dstFile.getAbsolutePath(), 0644);
728            return PackageManager.INSTALL_SUCCEEDED;
729        } catch (IOException e) {
730            logCriticalInfo(Log.ERROR, "Failed to decompress file"
731                    + "; src: " + srcFile.getAbsolutePath()
732                    + ", dst: " + dstFile.getAbsolutePath());
733        }
734        return PackageManager.INSTALL_FAILED_INTERNAL_ERROR;
735    }
736
737    public static File[] getCompressedFiles(String codePath) {
738        final File stubCodePath = new File(codePath);
739        final String stubName = stubCodePath.getName();
740
741        // The layout of a compressed package on a given partition is as follows :
742        //
743        // Compressed artifacts:
744        //
745        // /partition/ModuleName/foo.gz
746        // /partation/ModuleName/bar.gz
747        //
748        // Stub artifact:
749        //
750        // /partition/ModuleName-Stub/ModuleName-Stub.apk
751        //
752        // In other words, stub is on the same partition as the compressed artifacts
753        // and in a directory that's suffixed with "-Stub".
754        int idx = stubName.lastIndexOf(STUB_SUFFIX);
755        if (idx < 0 || (stubName.length() != (idx + STUB_SUFFIX.length()))) {
756            return null;
757        }
758
759        final File stubParentDir = stubCodePath.getParentFile();
760        if (stubParentDir == null) {
761            Slog.e(TAG, "Unable to determine stub parent dir for codePath: " + codePath);
762            return null;
763        }
764
765        final File compressedPath = new File(stubParentDir, stubName.substring(0, idx));
766        final File[] files = compressedPath.listFiles(new FilenameFilter() {
767            @Override
768            public boolean accept(File dir, String name) {
769                return name.toLowerCase().endsWith(COMPRESSED_EXTENSION);
770            }
771        });
772
773        if (DEBUG_COMPRESSION && files != null && files.length > 0) {
774            Slog.i(TAG, "getCompressedFiles[" + codePath + "]: " + Arrays.toString(files));
775        }
776
777        return files;
778    }
779
780    public static boolean compressedFileExists(String codePath) {
781        final File[] compressedFiles = getCompressedFiles(codePath);
782        return compressedFiles != null && compressedFiles.length > 0;
783    }
784}
785