From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Dmitry Muhomor <muhomor.dmitry@gmail.com>
Date: Tue, 31 Jan 2023 17:55:11 +0200
Subject: [PATCH] perform additional boot-time checks on system package updates

[tad@spotco.us]: disable verity checks
---
 .../server/pm/InstallPackageHelper.java       |   7 +
 .../android/server/pm/PackageVerityExt.java   | 162 ++++++++++++++++++
 2 files changed, 169 insertions(+)
 create mode 100644 services/core/java/com/android/server/pm/PackageVerityExt.java

diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index 0ec70238ff64..adf3e172d192 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -3827,6 +3827,13 @@ final class InstallPackageHelper {
             @Nullable UserHandle user) throws PackageManagerException {
         final boolean scanSystemPartition =
                 (parseFlags & ParsingPackageUtils.PARSE_IS_SYSTEM_DIR) != 0;
+        if ((scanFlags & SCAN_BOOTING) != 0) {
+            if (scanSystemPartition) {
+                PackageVerityExt.addSystemPackage(parsedPackage);
+            } else {
+                PackageVerityExt.checkSystemPackageUpdate(parsedPackage);
+            }
+        }
         final ScanRequest initialScanRequest = prepareInitialScanRequest(parsedPackage, parseFlags,
                 scanFlags, user, null);
         final PackageSetting installedPkgSetting = initialScanRequest.mPkgSetting;
diff --git a/services/core/java/com/android/server/pm/PackageVerityExt.java b/services/core/java/com/android/server/pm/PackageVerityExt.java
new file mode 100644
index 000000000000..cd0e213b3f4d
--- /dev/null
+++ b/services/core/java/com/android/server/pm/PackageVerityExt.java
@@ -0,0 +1,162 @@
+/*
+ * Copyright (C) 2022 GrapheneOS
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package com.android.server.pm;
+
+import android.annotation.Nullable;
+import android.content.pm.SigningDetails;
+import android.content.pm.parsing.result.ParseResult;
+import android.content.pm.parsing.result.ParseTypeImpl;
+import android.os.Build;
+import android.os.SystemProperties;
+import android.util.ArrayMap;
+import android.util.Slog;
+
+import com.android.internal.security.VerityUtils;
+import com.android.server.pm.parsing.pkg.AndroidPackage;
+import com.android.server.pm.pkg.parsing.ParsingPackageUtils;
+
+import static android.content.pm.PackageManager.INSTALL_FAILED_BAD_SIGNATURE;
+import static android.content.pm.PackageManager.INSTALL_FAILED_INVALID_APK;
+import static android.content.pm.PackageManager.INSTALL_FAILED_UPDATE_INCOMPATIBLE;
+
+// Performs additional checks on system package updates
+public class PackageVerityExt {
+    private static final String TAG = PackageVerityExt.class.getSimpleName();
+
+    // Parsed packages from immutable partitions. Static shared libraries are handled separately
+    // due to a different policy that OS uses for their replacement
+    private static final ArrayMap<String, AndroidPackage> packages = new ArrayMap<>();
+    private static final ArrayMap<String, AndroidPackage> staticSharedLibraries = new ArrayMap<>();
+
+    // Called when PackageManager scans a package from immutable system image partition during OS boot.
+    // All packages from immutable partitions are scanned before any packages from mutable partitions.
+    public static void addSystemPackage(AndroidPackage pkg) {
+        if (pkg.isStaticSharedLibrary()) {
+            String name = pkg.getStaticSharedLibName();
+            AndroidPackage prev;
+            synchronized (staticSharedLibraries) {
+                prev = staticSharedLibraries.put(name, pkg);
+            }
+            if (prev != null) {
+                Slog.w(TAG, "duplicate static shared lib " + name
+                        + ": prev " + prev.getPath() + " -> new " + pkg.getPath());
+            }
+        } else {
+            String name = pkg.getManifestPackageName();
+            AndroidPackage prev;
+            synchronized (packages) {
+                prev = packages.put(name, pkg);
+            }
+            if (prev != null) {
+                Slog.w(TAG, "duplicate system package " + name + ": prev " + prev.getPath() +
+                        " -> new " + pkg.getPath());
+            }
+        }
+    }
+
+    // If pkg is a system package update, returns its matching system image package
+    @Nullable public static AndroidPackage getSystemPackage(AndroidPackage pkg) {
+        if (pkg.isStaticSharedLibrary()) {
+            String name = pkg.getStaticSharedLibName();
+            synchronized (staticSharedLibraries) {
+                return staticSharedLibraries.get(name);
+            }
+        } else {
+            String name = pkg.getManifestPackageName();
+            synchronized (packages) {
+                return packages.get(name);
+            }
+        }
+    }
+
+    // Called when PackageManager scans a package from mutable partition (ie /data) during OS boot.
+    // PackageManagerException thrown from here will prevent this package from replacing its system
+    // image version.
+    public static void checkSystemPackageUpdate(AndroidPackage maybeSystemPackageUpdate) throws PackageManagerException {
+        final AndroidPackage systemPkg = getSystemPackage(maybeSystemPackageUpdate);
+
+        if (systemPkg == null) {
+            // not a system package update
+            return;
+        }
+
+        final AndroidPackage systemPkgUpdate = maybeSystemPackageUpdate;
+
+        Slog.d(TAG, "Performing verification of system package update "
+                + systemPkgUpdate.getManifestPackageName());
+
+        if (systemPkg.getLongVersionCode() >= systemPkgUpdate.getLongVersionCode()) {
+            throw new PackageManagerException(INSTALL_FAILED_UPDATE_INCOMPATIBLE,
+                    "versionCode of system image package (" + systemPkg.getLongVersionCode()
+                            + ") is >= versionCode of system package update ("
+                            + systemPkgUpdate.getLongVersionCode() + ")");
+        }
+
+        boolean checkFsVerity = false;
+        if (Build.IS_DEBUGGABLE) {
+            if (SystemProperties.getBoolean("persist.disable_boot_time_fsverity_check", false)) {
+                checkFsVerity = false;
+            }
+        }
+
+        if (checkFsVerity) {
+            checkFsVerity(systemPkgUpdate);
+        }
+
+        final SigningDetails updatePkgSigningDetails = parseSigningDetails(systemPkgUpdate,
+                // verify APK against its signature
+                false);
+
+        final SigningDetails systemPkgSigningDetails = parseSigningDetails(systemPkg,
+                // skip signature verification, system image APKs are protected by verified boot
+                true);
+
+        final boolean valid = updatePkgSigningDetails.checkCapability(systemPkgSigningDetails,
+                    SigningDetails.CertCapabilities.INSTALLED_DATA)
+                || systemPkgSigningDetails.checkCapability(updatePkgSigningDetails,
+                    SigningDetails.CertCapabilities.ROLLBACK);
+
+        if (!valid) {
+            String msg = "System package update " + systemPkgUpdate.getManifestPackageName()
+                    + " signature doesn't match the signature of system image package";
+            throw new PackageManagerException(INSTALL_FAILED_BAD_SIGNATURE, msg);
+        }
+    }
+
+    public static void checkFsVerity(AndroidPackage pkg) throws PackageManagerException {
+        final String baseApkPath = pkg.getBaseApkPath();
+        if (!VerityUtils.hasFsverity(baseApkPath)) {
+            throw new PackageManagerException(INSTALL_FAILED_BAD_SIGNATURE,
+                    "Base APK doesn't have fs-verity: " + baseApkPath);
+        }
+
+        for (String path : pkg.getSplitCodePaths()) {
+            if (!VerityUtils.hasFsverity(path)) {
+                throw new PackageManagerException(INSTALL_FAILED_BAD_SIGNATURE,
+                        "APK split doesn't have fs-verity: " + path);
+            }
+        }
+    }
+
+    private static SigningDetails parseSigningDetails(AndroidPackage pkg, boolean skipVerify) throws PackageManagerException {
+        final ParseTypeImpl input = ParseTypeImpl.forDefaultParsing();
+        final ParseResult<SigningDetails> result = ParsingPackageUtils.getSigningDetails(
+                input, pkg, skipVerify);
+
+        if (result.isError()) {
+            throw new PackageManagerException(
+                    result.getErrorCode(), result.getErrorMessage(), result.getException());
+        }
+
+        final SigningDetails sd = result.getResult();
+        if (sd == null) {
+            throw new PackageManagerException(INSTALL_FAILED_INVALID_APK,
+                        "Null signing details of package " + pkg.getManifestPackageName());
+        }
+
+        return sd;
+    }
+}
