summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/java/android/content/pm/PackageManager.java2
-rw-r--r--core/java/android/content/pm/PackageParser.java17
-rw-r--r--core/java/android/util/apk/ApkSignatureSchemeV2Verifier.java14
-rw-r--r--core/java/android/util/apk/ApkSignatureSchemeV3Verifier.java14
-rw-r--r--core/java/android/util/apk/ApkSignatureVerifier.java21
-rw-r--r--services/core/java/com/android/server/pm/Installer.java11
-rw-r--r--services/core/java/com/android/server/pm/PackageManagerService.java60
-rw-r--r--services/core/java/com/android/server/pm/PackageManagerServiceUtils.java3
-rw-r--r--services/core/java/com/android/server/security/VerityUtils.java8
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.
*/