summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Victor Hsieh <victorhsieh@google.com> 2018-01-20 10:30:12 -0800
committer Victor Hsieh <victorhsieh@google.com> 2018-01-24 16:30:55 -0800
commit5f76124551aa6582bb82034f8423b9d84f633d70 (patch)
tree308775d5b408e67c1405f238868faa6d66f316ce
parentd93a795945076ef407542bb6945832e3024ce3d7 (diff)
Skip priv app full apk verification if has verify
When ro.apk_verity.mode is on, full apk verification is only skipped if the apk already has verity enabled in the file system, and if the apk contains the Merkle tree root hash we need. Since the configuration in the file system is duplicated from the apk (including the offset and size of Signing Block and the Merkle tree), in order to prevent offline attacker from changing it, we need to measure the observed configuration and make sure it matches the kernel's view. Test: observed package manager's requeset to installd (only) for updated priv apps. Bug: 30972906 Change-Id: I33531a3f6148232b777ea8bfd02f13700649e317
-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 67c9584b01f7..6c399153ddeb 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -4740,7 +4740,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 5b5ccf547b54..582a91bc5ead 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -1504,9 +1504,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);
@@ -1514,17 +1514,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 {
@@ -1532,7 +1532,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();
@@ -1542,7 +1542,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);
@@ -1622,9 +1622,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 20f79824c42f..750b087e8db6 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -339,6 +339,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;
@@ -8295,11 +8296,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
@@ -8325,7 +8328,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 {
@@ -8419,6 +8422,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;
/**
@@ -8630,10 +8675,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
@@ -16708,7 +16754,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.
*/