From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: Dmitry Muhomor 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 259701166147..6044961e8639 100644 --- a/services/core/java/com/android/server/pm/InstallPackageHelper.java +++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java @@ -3824,6 +3824,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 packages = new ArrayMap<>(); + private static final ArrayMap 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 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; + } +}