Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 9f36df99 authored by Alex Buynytskyy's avatar Alex Buynytskyy Committed by Android (Google) Code Review
Browse files

Merge "Wait for APK to be fully downloaded for full APK digests."

parents 284c825c bc0a7e6c
Loading
Loading
Loading
Loading
+8 −0
Original line number Diff line number Diff line
@@ -89,6 +89,14 @@ interface IIncrementalService {
     */
    int unlink(int storageId, in @utf8InCpp String path);

    /**
     * Checks if a file is fully loaded. File is specified by its path.
     * 0 - fully loaded
     * >0 - certain pages missing
     * <0 - -errcode
     */
    int isFileFullyLoaded(int storageId, in @utf8InCpp String path);

    /**
     * Returns overall loading progress of all the files on a storage, progress value between [0,1].
     * Returns a negative value on error.
+19 −0
Original line number Diff line number Diff line
@@ -303,6 +303,25 @@ public final class IncrementalStorage {
        }
    }

    /**
     * Checks whether a file under the current storage directory is fully loaded.
     *
     * @param path The relative path of the file.
     * @return True if the file is fully loaded.
     */
    public boolean isFileFullyLoaded(@NonNull String path) throws IOException {
        try {
            int res = mService.isFileFullyLoaded(mId, path);
            if (res < 0) {
                throw new IOException("isFileFullyLoaded() failed, errno " + -res);
            }
            return res == 0;
        } catch (RemoteException e) {
            e.rethrowFromSystemServer();
            return false;
        }
    }

    /**
     * Returns the loading progress of a storage
     *
+220 −20
Original line number Diff line number Diff line
@@ -16,6 +16,7 @@

package com.android.server.pm;

import static android.content.pm.PackageManager.EXTRA_CHECKSUMS;
import static android.content.pm.PackageManager.PARTIAL_MERKLE_ROOT_1M_SHA256;
import static android.content.pm.PackageManager.PARTIAL_MERKLE_ROOT_1M_SHA512;
import static android.content.pm.PackageManager.WHOLE_MD5;
@@ -27,11 +28,20 @@ import static android.util.apk.ApkSigningBlockUtils.CONTENT_DIGEST_CHUNKED_SHA25
import static android.util.apk.ApkSigningBlockUtils.CONTENT_DIGEST_CHUNKED_SHA512;
import static android.util.apk.ApkSigningBlockUtils.CONTENT_DIGEST_VERITY_CHUNKED_SHA256;

import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
import android.content.Intent;
import android.content.IntentSender;
import android.content.pm.FileChecksum;
import android.content.pm.PackageManager;
import android.content.pm.PackageParser;
import android.os.Handler;
import android.os.SystemClock;
import android.os.incremental.IncrementalManager;
import android.os.incremental.IncrementalStorage;
import android.util.ArrayMap;
import android.util.Pair;
import android.util.Slog;
import android.util.apk.ApkSignatureSchemeV2Verifier;
import android.util.apk.ApkSignatureSchemeV3Verifier;
@@ -43,6 +53,7 @@ import android.util.apk.SignatureInfo;
import android.util.apk.SignatureNotFoundException;
import android.util.apk.VerityBuilder;

import com.android.internal.annotations.VisibleForTesting;
import com.android.server.security.VerityUtils;

import java.io.BufferedInputStream;
@@ -72,32 +83,163 @@ public class ApkChecksums {
    static final String ALGO_SHA512 = "SHA512";

    /**
     * Fetch or calculate checksums for the specific file.
     * Check back in 1 second after we detected we needed to wait for the APK to be fully available.
     */
    private static final long PROCESS_REQUIRED_CHECKSUMS_DELAY_MILLIS = 1000;

    /**
     * 24 hours timeout to wait till all files are loaded.
     */
    private static final long PROCESS_REQUIRED_CHECKSUMS_TIMEOUT_MILLIS = 1000 * 3600 * 24;

    /**
     * Unit tests will instantiate, extend and/or mock to mock dependencies / behaviors.
     *
     * @param split             split name, null for base
     * @param file              to fetch checksums for
     * NOTE: All getters should return the same instance for every call.
     */
    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
    static class Injector {

        @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
        interface Producer<T> {
            /** Produce an instance of type {@link T} */
            T produce();
        }

        private final Producer<Context> mContext;
        private final Producer<Handler> mHandlerProducer;
        private final Producer<IncrementalManager> mIncrementalManagerProducer;

        Injector(Producer<Context> context, Producer<Handler> handlerProducer,
                Producer<IncrementalManager> incrementalManagerProducer) {
            mContext = context;
            mHandlerProducer = handlerProducer;
            mIncrementalManagerProducer = incrementalManagerProducer;
        }

        public Context getContext() {
            return mContext.produce();
        }

        public Handler getHandler() {
            return mHandlerProducer.produce();
        }

        public IncrementalManager getIncrementalManager() {
            return mIncrementalManagerProducer.produce();
        }
    }

    /**
     * Fetch or calculate checksums for the collection of files.
     *
     * @param filesToChecksum   split name, null for base and File to fetch checksums for
     * @param optional          mask to fetch readily available checksums
     * @param required          mask to forcefully calculate if not available
     * @param trustedInstallers array of certificate to trust, two specific cases:
     *                          null - trust anybody,
     *                          [] - trust nobody.
     * @param statusReceiver    to receive the resulting checksums
     */
    public static List<FileChecksum> getFileChecksums(String split, File file,
    public static void getChecksums(List<Pair<String, File>> filesToChecksum,
            @PackageManager.FileChecksumKind int optional,
            @PackageManager.FileChecksumKind int required,
            @Nullable Certificate[] trustedInstallers) {
        final String filePath = file.getAbsolutePath();
            @Nullable Certificate[] trustedInstallers,
            @NonNull IntentSender statusReceiver,
            @NonNull Injector injector) {
        List<Map<Integer, FileChecksum>> result = new ArrayList<>(filesToChecksum.size());
        for (int i = 0, size = filesToChecksum.size(); i < size; ++i) {
            final String split = filesToChecksum.get(i).first;
            final File file = filesToChecksum.get(i).second;
            Map<Integer, FileChecksum> checksums = new ArrayMap<>();
        final int kinds = (optional | required);
        // System enforced: FSI or v2/v3/v4 signatures.
        if ((kinds & WHOLE_MERKLE_ROOT_4K_SHA256) != 0) {
            result.add(checksums);

            try {
                getAvailableFileChecksums(split, file, optional | required, trustedInstallers,
                        checksums);
            } catch (Throwable e) {
                Slog.e(TAG, "Preferred checksum calculation error", e);
            }
        }

        long startTime = SystemClock.uptimeMillis();
        processRequiredChecksums(filesToChecksum, result, required, statusReceiver, injector,
                startTime);
    }

    private static void processRequiredChecksums(List<Pair<String, File>> filesToChecksum,
            List<Map<Integer, FileChecksum>> result,
            @PackageManager.FileChecksumKind int required,
            @NonNull IntentSender statusReceiver,
            @NonNull Injector injector,
            long startTime) {
        final boolean timeout =
                SystemClock.uptimeMillis() - startTime >= PROCESS_REQUIRED_CHECKSUMS_TIMEOUT_MILLIS;
        List<FileChecksum> allChecksums = new ArrayList<>();
        for (int i = 0, size = filesToChecksum.size(); i < size; ++i) {
            final String split = filesToChecksum.get(i).first;
            final File file = filesToChecksum.get(i).second;
            Map<Integer, FileChecksum> checksums = result.get(i);

            try {
                if (!timeout || required != 0) {
                    if (needToWait(file, required, checksums, injector)) {
                        // Not ready, come back later.
                        injector.getHandler().postDelayed(() -> {
                            processRequiredChecksums(filesToChecksum, result, required,
                                    statusReceiver, injector, startTime);
                        }, PROCESS_REQUIRED_CHECKSUMS_DELAY_MILLIS);
                        return;
                    }

                    getRequiredFileChecksums(split, file, required, checksums);
                }
                allChecksums.addAll(checksums.values());
            } catch (Throwable e) {
                Slog.e(TAG, "Required checksum calculation error", e);
            }
        }

        final Intent intent = new Intent();
        intent.putExtra(EXTRA_CHECKSUMS,
                allChecksums.toArray(new FileChecksum[allChecksums.size()]));

        try {
            statusReceiver.sendIntent(injector.getContext(), 1, intent, null, null);
        } catch (IntentSender.SendIntentException e) {
            Slog.w(TAG, e);
        }
    }

    /**
     * Fetch readily available checksums - enforced by kernel or provided by Installer.
     *
     * @param split             split name, null for base
     * @param file              to fetch checksums for
     * @param kinds             mask to fetch checksums
     * @param trustedInstallers array of certificate to trust, two specific cases:
     *                          null - trust anybody,
     *                          [] - trust nobody.
     * @param checksums         resulting checksums
     */
    private static void getAvailableFileChecksums(String split, File file,
            @PackageManager.FileChecksumKind int kinds,
            @Nullable Certificate[] trustedInstallers,
            Map<Integer, FileChecksum> checksums) {
        final String filePath = file.getAbsolutePath();

        // Always available: FSI or IncFs.
        if (isRequired(WHOLE_MERKLE_ROOT_4K_SHA256, kinds, checksums)) {
            // Hashes in fs-verity and IncFS are always verified.
            FileChecksum checksum = extractHashFromFS(split, filePath);
            if (checksum != null) {
                checksums.put(checksum.getKind(), checksum);
            }
        }
        if ((kinds & (PARTIAL_MERKLE_ROOT_1M_SHA256 | PARTIAL_MERKLE_ROOT_1M_SHA512)) != 0) {

        // System enforced: v2/v3.
        if (isRequired(PARTIAL_MERKLE_ROOT_1M_SHA256, kinds, checksums) || isRequired(
                PARTIAL_MERKLE_ROOT_1M_SHA512, kinds, checksums)) {
            Map<Integer, FileChecksum> v2v3checksums = extractHashFromV2V3Signature(
                    split, filePath, kinds);
            if (v2v3checksums != null) {
@@ -106,11 +248,58 @@ public class ApkChecksums {
        }

        // TODO(b/160605420): Installer provided.
        // TODO(b/160605420): Wait for Incremental to be fully loaded.
    }

    /**
     * Whether the file is available for checksumming or we need to wait.
     */
    private static boolean needToWait(File file,
            @PackageManager.FileChecksumKind int kinds,
            Map<Integer, FileChecksum> checksums,
            @NonNull Injector injector) throws IOException {
        if (!isRequired(WHOLE_MERKLE_ROOT_4K_SHA256, kinds, checksums)
                && !isRequired(WHOLE_MD5, kinds, checksums)
                && !isRequired(WHOLE_SHA1, kinds, checksums)
                && !isRequired(WHOLE_SHA256, kinds, checksums)
                && !isRequired(WHOLE_SHA512, kinds, checksums)
                && !isRequired(PARTIAL_MERKLE_ROOT_1M_SHA256, kinds, checksums)
                && !isRequired(PARTIAL_MERKLE_ROOT_1M_SHA512, kinds, checksums)) {
            return false;
        }

        final String filePath = file.getAbsolutePath();
        if (!IncrementalManager.isIncrementalPath(filePath)) {
            return false;
        }

        IncrementalManager manager = injector.getIncrementalManager();
        if (manager == null) {
            throw new IllegalStateException("IncrementalManager is missing.");
        }
        IncrementalStorage storage = manager.openStorage(filePath);
        if (storage == null) {
            throw new IllegalStateException(
                    "IncrementalStorage is missing for a path on IncFs: " + filePath);
        }

        return !storage.isFileFullyLoaded(filePath);
    }

    /**
     * Fetch or calculate checksums for the specific file.
     *
     * @param split     split name, null for base
     * @param file      to fetch checksums for
     * @param kinds     mask to forcefully calculate if not available
     * @param checksums resulting checksums
     */
    private static void getRequiredFileChecksums(String split, File file,
            @PackageManager.FileChecksumKind int kinds,
            Map<Integer, FileChecksum> checksums) {
        final String filePath = file.getAbsolutePath();

        // Manually calculating required checksums if not readily available.
        if ((required & WHOLE_MERKLE_ROOT_4K_SHA256) != 0 && !checksums.containsKey(
                WHOLE_MERKLE_ROOT_4K_SHA256)) {
        if (isRequired(WHOLE_MERKLE_ROOT_4K_SHA256, kinds, checksums)) {
            try {
                byte[] generatedRootHash = VerityBuilder.generateFsVerityRootHash(
                        filePath, /*salt=*/null,
@@ -127,14 +316,23 @@ public class ApkChecksums {
            }
        }

        calculateChecksumIfRequested(checksums, split, file, required, WHOLE_MD5);
        calculateChecksumIfRequested(checksums, split, file, required, WHOLE_SHA1);
        calculateChecksumIfRequested(checksums, split, file, required, WHOLE_SHA256);
        calculateChecksumIfRequested(checksums, split, file, required, WHOLE_SHA512);
        calculateChecksumIfRequested(checksums, split, file, kinds, WHOLE_MD5);
        calculateChecksumIfRequested(checksums, split, file, kinds, WHOLE_SHA1);
        calculateChecksumIfRequested(checksums, split, file, kinds, WHOLE_SHA256);
        calculateChecksumIfRequested(checksums, split, file, kinds, WHOLE_SHA512);

        calculatePartialChecksumsIfRequested(checksums, split, file, required);
        calculatePartialChecksumsIfRequested(checksums, split, file, kinds);
    }

        return new ArrayList<>(checksums.values());
    private static boolean isRequired(@PackageManager.FileChecksumKind int kind,
            @PackageManager.FileChecksumKind int kinds, Map<Integer, FileChecksum> checksums) {
        if ((kinds & kind) == 0) {
            return false;
        }
        if (checksums.containsKey(kind)) {
            return false;
        }
        return true;
    }

    private static FileChecksum extractHashFromFS(String split, String filePath) {
@@ -170,8 +368,10 @@ public class ApkChecksums {
                    PackageParser.SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V2,
                    false).contentDigests;
        } catch (PackageParser.PackageParserException e) {
            if (!(e.getCause() instanceof SignatureNotFoundException)) {
                Slog.e(TAG, "Signature verification error", e);
            }
        }

        if (contentDigests == null) {
            return null;
+15 −26
Original line number Diff line number Diff line
@@ -40,7 +40,6 @@ import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED
import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED;
import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER;
import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_ENABLED;
import static android.content.pm.PackageManager.EXTRA_CHECKSUMS;
import static android.content.pm.PackageManager.EXTRA_VERIFICATION_ID;
import static android.content.pm.PackageManager.FLAG_PERMISSION_GRANTED_BY_DEFAULT;
import static android.content.pm.PackageManager.FLAG_PERMISSION_POLICY_FIXED;
@@ -169,7 +168,6 @@ import android.content.pm.ComponentInfo;
import android.content.pm.DataLoaderType;
import android.content.pm.FallbackCategoryProvider;
import android.content.pm.FeatureInfo;
import android.content.pm.FileChecksum;
import android.content.pm.IDexModuleRegisterCallback;
import android.content.pm.IPackageChangeObserver;
import android.content.pm.IPackageDataObserver;
@@ -933,6 +931,7 @@ public class PackageManagerService extends IPackageManager.Stub
        private final Object mLock;
        private final Installer mInstaller;
        private final Object mInstallLock;
        private final Handler mBackgroundHandler;
        private final Executor mBackgroundExecutor;
        // ----- producers -----
@@ -955,7 +954,7 @@ public class PackageManagerService extends IPackageManager.Stub
        Injector(Context context, Object lock, Installer installer,
                Object installLock, PackageAbiHelper abiHelper,
                Executor backgroundExecutor,
                Handler backgroundHandler,
                Producer<ComponentResolver> componentResolverProducer,
                Producer<PermissionManagerServiceInternal> permissionManagerProducer,
                Producer<UserManagerService> userManagerProducer,
@@ -977,7 +976,8 @@ public class PackageManagerService extends IPackageManager.Stub
            mInstaller = installer;
            mAbiHelper = abiHelper;
            mInstallLock = installLock;
            mBackgroundExecutor = backgroundExecutor;
            mBackgroundHandler = backgroundHandler;
            mBackgroundExecutor = new HandlerExecutor(backgroundHandler);
            mComponentResolverProducer = new Singleton<>(componentResolverProducer);
            mPermissionManagerProducer = new Singleton<>(permissionManagerProducer);
            mUserManagerProducer = new Singleton<>(userManagerProducer);
@@ -1092,6 +1092,10 @@ public class PackageManagerService extends IPackageManager.Stub
            return mPlatformCompatProducer.get(this, mPackageManager);
        }
        public Handler getBackgroundHandler() {
            return mBackgroundHandler;
        }
        public Executor getBackgroundExecutor() {
            return mBackgroundExecutor;
        }
@@ -2489,29 +2493,14 @@ public class PackageManagerService extends IPackageManager.Stub
        final Certificate[] trustedCerts = (trustedInstallers != null) ? decodeCertificates(
                trustedInstallers) : null;
        final Context context = mContext;
        mInjector.getBackgroundExecutor().execute(() -> {
            final Intent intent = new Intent();
            List<FileChecksum> result = new ArrayList<>();
            for (int i = 0, size = filesToChecksum.size(); i < size; ++i) {
                final String split = filesToChecksum.get(i).first;
                final File file = filesToChecksum.get(i).second;
                try {
                    result.addAll(ApkChecksums.getFileChecksums(split, file, optional, required,
                            trustedCerts));
                } catch (Throwable e) {
                    Slog.e(TAG, "Checksum calculation error", e);
                }
            }
            intent.putExtra(EXTRA_CHECKSUMS,
                    result.toArray(new FileChecksum[result.size()]));
            try {
                statusReceiver.sendIntent(context, 1, intent, null, null);
            } catch (SendIntentException e) {
                Slog.w(TAG, e);
            }
            ApkChecksums.Injector injector = new ApkChecksums.Injector(
                    () -> mContext,
                    () -> mInjector.getBackgroundHandler(),
                    () -> mContext.getSystemService(IncrementalManager.class));
            ApkChecksums.getChecksums(filesToChecksum, optional, required, trustedCerts,
                    statusReceiver, injector);
        });
    }
@@ -2684,7 +2673,7 @@ public class PackageManagerService extends IPackageManager.Stub
        Injector injector = new Injector(
                context, lock, installer, installLock, new PackageAbiHelperImpl(),
                new HandlerExecutor(backgroundHandler),
                backgroundHandler,
                (i, pm) ->
                        new ComponentResolver(i.getUserManagerService(), pm.mPmInternal, lock),
                (i, pm) ->
+3 −0
Original line number Diff line number Diff line
@@ -32,6 +32,9 @@
    {
      "name": "CtsContentTestCases",
      "options": [
        {
          "include-filter": "android.content.pm.cts.ChecksumsTest"
        },
        {
          "include-filter": "android.content.pm.cts.PackageManagerShellCommandTest"
        },
Loading