diff options
9 files changed, 132 insertions, 18 deletions
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java index 3709a9045aaa..5a894c78af5f 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -4758,7 +4758,7 @@ public abstract class PackageManager { PackageParser.Package pkg = parser.parseMonolithicPackage(apkFile, 0); if ((flags & GET_SIGNATURES) != 0) { - PackageParser.collectCertificates(pkg, 0); + PackageParser.collectCertificates(pkg, false /* skipVerify */); } PackageUserState state = new PackageUserState(); return PackageParser.generatePackageInfo(pkg, null, flags, 0, 0, null, state); diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java index c705ef52f6b5..6cd299613115 100644 --- a/core/java/android/content/pm/PackageParser.java +++ b/core/java/android/content/pm/PackageParser.java @@ -1484,9 +1484,9 @@ public class PackageParser { * populating {@link Package#mSigningDetails}. Also asserts that all APK * contents are signed correctly and consistently. */ - public static void collectCertificates(Package pkg, @ParseFlags int parseFlags) + public static void collectCertificates(Package pkg, boolean skipVerify) throws PackageParserException { - collectCertificatesInternal(pkg, parseFlags); + collectCertificatesInternal(pkg, skipVerify); final int childCount = (pkg.childPackages != null) ? pkg.childPackages.size() : 0; for (int i = 0; i < childCount; i++) { Package childPkg = pkg.childPackages.get(i); @@ -1494,17 +1494,17 @@ public class PackageParser { } } - private static void collectCertificatesInternal(Package pkg, @ParseFlags int parseFlags) + private static void collectCertificatesInternal(Package pkg, boolean skipVerify) throws PackageParserException { pkg.mSigningDetails = SigningDetails.UNKNOWN; Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "collectCertificates"); try { - collectCertificates(pkg, new File(pkg.baseCodePath), parseFlags); + collectCertificates(pkg, new File(pkg.baseCodePath), skipVerify); if (!ArrayUtils.isEmpty(pkg.splitCodePaths)) { for (int i = 0; i < pkg.splitCodePaths.length; i++) { - collectCertificates(pkg, new File(pkg.splitCodePaths[i]), parseFlags); + collectCertificates(pkg, new File(pkg.splitCodePaths[i]), skipVerify); } } } finally { @@ -1512,7 +1512,7 @@ public class PackageParser { } } - private static void collectCertificates(Package pkg, File apkFile, @ParseFlags int parseFlags) + private static void collectCertificates(Package pkg, File apkFile, boolean skipVerify) throws PackageParserException { final String apkPath = apkFile.getAbsolutePath(); @@ -1522,7 +1522,7 @@ public class PackageParser { minSignatureScheme = SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V2; } SigningDetails verified; - if ((parseFlags & PARSE_IS_SYSTEM_DIR) != 0) { + if (skipVerify) { // systemDir APKs are already trusted, save time by not verifying verified = ApkSignatureVerifier.plsCertsNoVerifyOnlyCerts( apkPath, minSignatureScheme); @@ -1600,9 +1600,10 @@ public class PackageParser { if ((flags & PARSE_COLLECT_CERTIFICATES) != 0) { // TODO: factor signature related items out of Package object final Package tempPkg = new Package((String) null); + final boolean skipVerify = (flags & PARSE_IS_SYSTEM_DIR) != 0; Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "collectCertificates"); try { - collectCertificates(tempPkg, apkFile, flags); + collectCertificates(tempPkg, apkFile, skipVerify); } finally { Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); } diff --git a/core/java/android/util/apk/ApkSignatureSchemeV2Verifier.java b/core/java/android/util/apk/ApkSignatureSchemeV2Verifier.java index 5a09dab552e3..62222b5764e7 100644 --- a/core/java/android/util/apk/ApkSignatureSchemeV2Verifier.java +++ b/core/java/android/util/apk/ApkSignatureSchemeV2Verifier.java @@ -412,6 +412,20 @@ public class ApkSignatureSchemeV2Verifier { } } + static byte[] generateFsverityRootHash(String apkPath) + throws IOException, SignatureNotFoundException, DigestException, + NoSuchAlgorithmException { + try (RandomAccessFile apk = new RandomAccessFile(apkPath, "r")) { + SignatureInfo signatureInfo = findSignature(apk); + VerifiedSigner vSigner = verify(apk, false); + if (vSigner.verityRootHash == null) { + return null; + } + return ApkVerityBuilder.generateFsverityRootHash( + apk, ByteBuffer.wrap(vSigner.verityRootHash), signatureInfo); + } + } + private static boolean isSupportedSignatureAlgorithm(int sigAlgorithm) { switch (sigAlgorithm) { case SIGNATURE_RSA_PSS_WITH_SHA256: diff --git a/core/java/android/util/apk/ApkSignatureSchemeV3Verifier.java b/core/java/android/util/apk/ApkSignatureSchemeV3Verifier.java index 1b04eb2f7d44..ee6fc072765f 100644 --- a/core/java/android/util/apk/ApkSignatureSchemeV3Verifier.java +++ b/core/java/android/util/apk/ApkSignatureSchemeV3Verifier.java @@ -523,6 +523,20 @@ public class ApkSignatureSchemeV3Verifier { } } + static byte[] generateFsverityRootHash(String apkPath) + throws NoSuchAlgorithmException, DigestException, IOException, + SignatureNotFoundException { + try (RandomAccessFile apk = new RandomAccessFile(apkPath, "r")) { + SignatureInfo signatureInfo = findSignature(apk); + VerifiedSigner vSigner = verify(apk, false); + if (vSigner.verityRootHash == null) { + return null; + } + return ApkVerityBuilder.generateFsverityRootHash( + apk, ByteBuffer.wrap(vSigner.verityRootHash), signatureInfo); + } + } + private static boolean isSupportedSignatureAlgorithm(int sigAlgorithm) { switch (sigAlgorithm) { case SIGNATURE_RSA_PSS_WITH_SHA256: diff --git a/core/java/android/util/apk/ApkSignatureVerifier.java b/core/java/android/util/apk/ApkSignatureVerifier.java index 87943725ba21..de9f55b09200 100644 --- a/core/java/android/util/apk/ApkSignatureVerifier.java +++ b/core/java/android/util/apk/ApkSignatureVerifier.java @@ -427,6 +427,27 @@ public class ApkSignatureVerifier { } /** + * Generates the FSVerity root hash from FSVerity header, extensions and Merkle tree root hash + * in Signing Block. + * + * @return FSverity root hash + */ + public static byte[] generateFsverityRootHash(String apkPath) + throws NoSuchAlgorithmException, DigestException, IOException { + // first try v3 + try { + return ApkSignatureSchemeV3Verifier.generateFsverityRootHash(apkPath); + } catch (SignatureNotFoundException e) { + // try older version + } + try { + return ApkSignatureSchemeV2Verifier.generateFsverityRootHash(apkPath); + } catch (SignatureNotFoundException e) { + return null; + } + } + + /** * Result of a successful APK verification operation. */ public static class Result { diff --git a/services/core/java/com/android/server/pm/Installer.java b/services/core/java/com/android/server/pm/Installer.java index c04cdf66bf30..4b6c40669cb8 100644 --- a/services/core/java/com/android/server/pm/Installer.java +++ b/services/core/java/com/android/server/pm/Installer.java @@ -17,6 +17,7 @@ package com.android.server.pm; import android.annotation.AppIdInt; +import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.UserIdInt; import android.content.Context; @@ -490,6 +491,16 @@ public class Installer extends SystemService { } } + public void assertFsverityRootHashMatches(String filePath, @NonNull byte[] expectedHash) + throws InstallerException { + if (!checkBeforeRemote()) return; + try { + mInstalld.assertFsverityRootHashMatches(filePath, expectedHash); + } catch (Exception e) { + throw InstallerException.from(e); + } + } + public boolean reconcileSecondaryDexFile(String apkPath, String packageName, int uid, String[] isas, @Nullable String volumeUuid, int flags) throws InstallerException { for (int i = 0; i < isas.length; i++) { diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 5dfd3ae4b8e8..91a8317ff33d 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -337,6 +337,7 @@ import java.io.PrintWriter; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.nio.charset.StandardCharsets; +import java.security.DigestException; import java.security.DigestInputStream; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; @@ -8291,11 +8292,13 @@ Slog.e("TODD", } private void collectCertificatesLI(PackageSetting ps, PackageParser.Package pkg, - final @ParseFlags int parseFlags, boolean forceCollect) throws PackageManagerException { + boolean forceCollect) throws PackageManagerException { // When upgrading from pre-N MR1, verify the package time stamp using the package // directory and not the APK file. final long lastModifiedTime = mIsPreNMR1Upgrade ? new File(pkg.codePath).lastModified() : getLastModifiedTime(pkg); + // Note that currently skipVerify skips verification on both base and splits for simplicity. + final boolean skipVerify = forceCollect && canSkipFullPackageVerification(pkg); if (ps != null && !forceCollect && ps.codePathString.equals(pkg.codePath) && ps.timeStamp == lastModifiedTime @@ -8321,7 +8324,7 @@ Slog.e("TODD", try { Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "collectCertificates"); - PackageParser.collectCertificates(pkg, parseFlags); + PackageParser.collectCertificates(pkg, skipVerify); } catch (PackageParserException e) { throw PackageManagerException.from(e); } finally { @@ -8415,6 +8418,48 @@ Slog.e("TODD", return scannedPkg; } + /** + * Returns if full apk verification can be skipped for the whole package, including the splits. + */ + private boolean canSkipFullPackageVerification(PackageParser.Package pkg) { + if (!canSkipFullApkVerification(pkg.baseCodePath)) { + return false; + } + // TODO: Allow base and splits to be verified individually. + if (!ArrayUtils.isEmpty(pkg.splitCodePaths)) { + for (int i = 0; i < pkg.splitCodePaths.length; i++) { + if (!canSkipFullApkVerification(pkg.splitCodePaths[i])) { + return false; + } + } + } + return true; + } + + /** + * Returns if full apk verification can be skipped, depending on current FSVerity setup and + * whether the apk contains signed root hash. Note that the signer's certificate still needs to + * match one in a trusted source, and should be done separately. + */ + private boolean canSkipFullApkVerification(String apkPath) { + byte[] rootHashObserved = null; + try { + rootHashObserved = VerityUtils.generateFsverityRootHash(apkPath); + if (rootHashObserved == null) { + return false; // APK does not contain Merkle tree root hash. + } + synchronized (mInstallLock) { + // Returns whether the observed root hash matches what kernel has. + mInstaller.assertFsverityRootHashMatches(apkPath, rootHashObserved); + return true; + } + } catch (InstallerException | IOException | DigestException | + NoSuchAlgorithmException e) { + Slog.w(TAG, "Error in fsverity check. Fallback to full apk verification.", e); + } + return false; + } + // Temporary to catch potential issues with refactoring private static boolean REFACTOR_DEBUG = true; /** @@ -8626,10 +8671,11 @@ Slog.e("TODD", } // Verify certificates against what was last scanned. If it is an updated priv app, we will - // force the verification. Full apk verification will happen unless apk verity is set up for - // the file. In that case, only small part of the apk is verified upfront. - collectCertificatesLI(pkgSetting, pkg, parseFlags, - PackageManagerServiceUtils.isApkVerificationForced(disabledPkgSetting)); + // force re-collecting certificate. Full apk verification will happen unless apk verity is + // set up for the file. In that case, only small part of the apk is verified upfront. + final boolean forceCollect = PackageManagerServiceUtils.isApkVerificationForced( + disabledPkgSetting); + collectCertificatesLI(pkgSetting, pkg, forceCollect); boolean shouldHideSystemApp = false; // A new application appeared on /system, but, we already have a copy of @@ -16755,7 +16801,7 @@ Slog.e("TODD", if (args.signingDetails != PackageParser.SigningDetails.UNKNOWN) { pkg.setSigningDetails(args.signingDetails); } else { - PackageParser.collectCertificates(pkg, parseFlags); + PackageParser.collectCertificates(pkg, false /* skipVerify */); } } catch (PackageParserException e) { res.setError("Failed collect during installPackageLI", e); diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java index bf3eb8eaae1f..76c199b88a5a 100644 --- a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java +++ b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java @@ -562,8 +562,7 @@ public class PackageManagerServiceUtils { private static boolean matchSignatureInSystem(PackageSetting pkgSetting, PackageSetting disabledPkgSetting) { try { - PackageParser.collectCertificates(disabledPkgSetting.pkg, - PackageParser.PARSE_IS_SYSTEM_DIR); + PackageParser.collectCertificates(disabledPkgSetting.pkg, true /* skipVerify */); if (compareSignatures(pkgSetting.signatures.mSigningDetails.signatures, disabledPkgSetting.signatures.mSigningDetails.signatures) != PackageManager.SIGNATURE_MATCH) { diff --git a/services/core/java/com/android/server/security/VerityUtils.java b/services/core/java/com/android/server/security/VerityUtils.java index 3908df46d3a4..d2d0e60dc742 100644 --- a/services/core/java/com/android/server/security/VerityUtils.java +++ b/services/core/java/com/android/server/security/VerityUtils.java @@ -77,6 +77,14 @@ abstract public class VerityUtils { } /** + * {@see ApkSignatureVerifier#generateFsverityRootHash(String)}. + */ + public static byte[] generateFsverityRootHash(@NonNull String apkPath) + throws NoSuchAlgorithmException, DigestException, IOException { + return ApkSignatureVerifier.generateFsverityRootHash(apkPath); + } + + /** * Returns a {@code SharedMemory} that contains Merkle tree and fsverity headers for the given * apk, in the form that can immediately be used for fsverity setup. */ |