diff options
6 files changed, 147 insertions, 15 deletions
diff --git a/core/java/android/os/incremental/V4Signature.java b/core/java/android/os/incremental/V4Signature.java index 2044502b10e8..38d885f503c5 100644 --- a/core/java/android/os/incremental/V4Signature.java +++ b/core/java/android/os/incremental/V4Signature.java @@ -254,7 +254,10 @@ public class V4Signature { this.signingInfos = signingInfos; } - private static V4Signature readFrom(InputStream stream) throws IOException { + /** + * Constructs a V4Signature from an InputStream. + */ + public static V4Signature readFrom(InputStream stream) throws IOException { final int version = readIntLE(stream); int maxSize = INCFS_MAX_SIGNATURE_SIZE; final byte[] hashingInfo = readBytes(stream, maxSize); diff --git a/core/java/android/util/apk/ApkSignatureSchemeV4Verifier.java b/core/java/android/util/apk/ApkSignatureSchemeV4Verifier.java index 6b26155eced4..52102714eb5f 100644 --- a/core/java/android/util/apk/ApkSignatureSchemeV4Verifier.java +++ b/core/java/android/util/apk/ApkSignatureSchemeV4Verifier.java @@ -27,9 +27,14 @@ import android.os.incremental.V4Signature; import android.util.ArrayMap; import android.util.Pair; +import com.android.internal.security.VerityUtils; + import java.io.ByteArrayInputStream; +import java.io.EOFException; import java.io.File; +import java.io.FileInputStream; import java.io.IOException; +import java.security.DigestException; import java.security.InvalidAlgorithmParameterException; import java.security.InvalidKeyException; import java.security.KeyFactory; @@ -60,7 +65,7 @@ public class ApkSignatureSchemeV4Verifier { * certificates associated with each signer. */ public static VerifiedSigner extractCertificates(String apkFile) - throws SignatureNotFoundException, SecurityException { + throws SignatureNotFoundException, SignatureException, SecurityException { Pair<V4Signature.HashingInfo, V4Signature.SigningInfos> pair = extractSignature(apkFile); return verify(apkFile, pair.first, pair.second, APK_SIGNATURE_SCHEME_DEFAULT); } @@ -69,15 +74,37 @@ public class ApkSignatureSchemeV4Verifier { * Extracts APK Signature Scheme v4 signature of the provided APK. */ public static Pair<V4Signature.HashingInfo, V4Signature.SigningInfos> extractSignature( - String apkFile) throws SignatureNotFoundException { - final File apk = new File(apkFile); - final byte[] signatureBytes = IncrementalManager.unsafeGetFileSignature( - apk.getAbsolutePath()); - if (signatureBytes == null || signatureBytes.length == 0) { - throw new SignatureNotFoundException("Failed to obtain signature bytes from IncFS."); - } + String apkFile) throws SignatureNotFoundException, SignatureException { try { - final V4Signature signature = V4Signature.readFrom(signatureBytes); + final File apk = new File(apkFile); + boolean needsConsistencyCheck; + + // 1. Try IncFS first. IncFS verifies the file according to the integrity metadata + // (including the root hash of Merkle tree) it keeps track of with signature check. No + // further consistentcy check is needed. + byte[] signatureBytes = IncrementalManager.unsafeGetFileSignature( + apk.getAbsolutePath()); + V4Signature signature; + if (signatureBytes != null && signatureBytes.length > 0) { + needsConsistencyCheck = false; + signature = V4Signature.readFrom(signatureBytes); + } else if (android.security.Flags.extendVbChainToUpdatedApk()) { + // 2. Try fs-verity next. fs-verity checks against the Merkle tree, but the + // v4 signature file (including a raw root hash) is managed separately. We need to + // ensure the signed data from the file is consistent with the actual file. + needsConsistencyCheck = true; + + final File idsig = new File(apk.getAbsolutePath() + V4Signature.EXT); + try (var fis = new FileInputStream(idsig.getAbsolutePath())) { + signature = V4Signature.readFrom(fis); + } catch (IOException e) { + throw new SignatureNotFoundException( + "Failed to obtain signature bytes from .idsig"); + } + } else { + throw new SignatureNotFoundException( + "Failed to obtain signature bytes from IncFS."); + } if (!signature.isVersionSupported()) { throw new SecurityException( "v4 signature version " + signature.version + " is not supported"); @@ -86,9 +113,26 @@ public class ApkSignatureSchemeV4Verifier { signature.hashingInfo); final V4Signature.SigningInfos signingInfos = V4Signature.SigningInfos.fromByteArray( signature.signingInfos); + + if (needsConsistencyCheck) { + final byte[] actualDigest = VerityUtils.getFsverityDigest(apk.getAbsolutePath()); + if (actualDigest == null) { + throw new SecurityException("The APK does not have fs-verity"); + } + final byte[] computedDigest = + VerityUtils.generateFsVerityDigest(apk.length(), hashingInfo); + if (!Arrays.equals(computedDigest, actualDigest)) { + throw new SignatureException("Actual digest does not match the v4 signature"); + } + } + return Pair.create(hashingInfo, signingInfos); + } catch (EOFException e) { + throw new SignatureException("V4 signature is invalid.", e); } catch (IOException e) { throw new SignatureNotFoundException("Failed to read V4 signature.", e); + } catch (DigestException | NoSuchAlgorithmException e) { + throw new SecurityException("Failed to calculate the digest", e); } } @@ -107,7 +151,7 @@ public class ApkSignatureSchemeV4Verifier { signingInfo); final Pair<Certificate, byte[]> result = verifySigner(signingInfo, signedData); - // Populate digests enforced by IncFS driver. + // Populate digests enforced by IncFS driver and fs-verity. Map<Integer, byte[]> contentDigests = new ArrayMap<>(); contentDigests.put(convertToContentDigestType(hashingInfo.hashAlgorithm), hashingInfo.rawRootHash); @@ -217,7 +261,7 @@ public class ApkSignatureSchemeV4Verifier { public final byte[] apkDigest; // Algorithm -> digest map of signed digests in the signature. - // These are continuously enforced by the IncFS driver. + // These are continuously enforced by the IncFS driver and fs-verity. public final Map<Integer, byte[]> contentDigests; public VerifiedSigner(Certificate[] certs, byte[] apkDigest, diff --git a/core/java/com/android/internal/security/VerityUtils.java b/core/java/com/android/internal/security/VerityUtils.java index 74a9d16c890d..7f7ea8b28546 100644 --- a/core/java/com/android/internal/security/VerityUtils.java +++ b/core/java/com/android/internal/security/VerityUtils.java @@ -17,8 +17,10 @@ package com.android.internal.security; import android.annotation.NonNull; +import android.annotation.Nullable; import android.os.Build; import android.os.SystemProperties; +import android.os.incremental.V4Signature; import android.system.Os; import android.system.OsConstants; import android.util.Slog; @@ -40,6 +42,9 @@ import java.io.InputStream; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio.charset.StandardCharsets; +import java.security.DigestException; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; import java.security.cert.CertificateException; import java.security.cert.CertificateFactory; import java.security.cert.X509Certificate; @@ -192,9 +197,9 @@ public abstract class VerityUtils { * * @see <a href="https://www.kernel.org/doc/html/latest/filesystems/fsverity.html#file-digest-computation"> * File digest computation in Linux kernel documentation</a> - * @return Bytes of fs-verity digest + * @return Bytes of fs-verity digest, or null if the file does not have fs-verity enabled */ - public static byte[] getFsverityDigest(@NonNull String filePath) { + public static @Nullable byte[] getFsverityDigest(@NonNull String filePath) { byte[] result = new byte[HASH_SIZE_BYTES]; int retval = measureFsverityNative(filePath, result); if (retval < 0) { @@ -206,6 +211,34 @@ public abstract class VerityUtils { return result; } + /** + * Generates an fs-verity digest from a V4Signature.HashingInfo and the file's size. + */ + public static @NonNull byte[] generateFsVerityDigest(long fileSize, + @NonNull V4Signature.HashingInfo hashingInfo) + throws DigestException, NoSuchAlgorithmException { + if (hashingInfo.rawRootHash == null || hashingInfo.rawRootHash.length != 32) { + throw new IllegalArgumentException("Expect a 32-byte rootHash for SHA256"); + } + if (hashingInfo.log2BlockSize != 12) { + throw new IllegalArgumentException( + "Unsupported log2BlockSize: " + hashingInfo.log2BlockSize); + } + + var buffer = ByteBuffer.allocate(256); // sizeof(fsverity_descriptor) + buffer.order(ByteOrder.LITTLE_ENDIAN); + buffer.put((byte) 1); // version + buffer.put((byte) 1); // Merkle tree hash algorithm, 1 for SHA256 + buffer.put(hashingInfo.log2BlockSize); // log2(block-size), only log2(4096) is supported + buffer.put((byte) 0); // size of salt in bytes; 0 if none + buffer.putInt(0); // reserved, must be 0 + buffer.putLong(fileSize); // size of file the Merkle tree is built over + buffer.put(hashingInfo.rawRootHash); // Merkle tree root hash + // The rest are zeros, including the latter half of root hash unused for SHA256. + + return MessageDigest.getInstance("SHA-256").digest(buffer.array()); + } + /** @hide */ @VisibleForTesting public static byte[] toFormattedDigest(byte[] digest) { diff --git a/services/core/java/com/android/server/integrity/AppIntegrityManagerServiceImpl.java b/services/core/java/com/android/server/integrity/AppIntegrityManagerServiceImpl.java index a7c986d04fa4..ec858ee296e1 100644 --- a/services/core/java/com/android/server/integrity/AppIntegrityManagerServiceImpl.java +++ b/services/core/java/com/android/server/integrity/AppIntegrityManagerServiceImpl.java @@ -513,6 +513,7 @@ public class AppIntegrityManagerServiceImpl extends IAppIntegrityManager.Stub { List<String> apkFiles = filesList .map(path -> path.toAbsolutePath().toString()) + .filter(str -> str.endsWith(".apk")) .collect(Collectors.toList()); sourceStampVerificationResult = SourceStampVerifier.verify(apkFiles); } catch (IOException e) { diff --git a/services/core/java/com/android/server/pm/ApkChecksums.java b/services/core/java/com/android/server/pm/ApkChecksums.java index 5b93244c1710..50ed3b1859df 100644 --- a/services/core/java/com/android/server/pm/ApkChecksums.java +++ b/services/core/java/com/android/server/pm/ApkChecksums.java @@ -655,7 +655,7 @@ public class ApkChecksums { } } catch (SignatureNotFoundException e) { // Nothing - } catch (SecurityException e) { + } catch (SignatureException | SecurityException e) { Slog.e(TAG, "V4 signature error", e); } return null; diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java index d699baa0d9ab..f554773becad 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerSession.java +++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java @@ -138,6 +138,7 @@ import android.os.incremental.IncrementalFileStorages; import android.os.incremental.IncrementalManager; import android.os.incremental.PerUidReadTimeouts; import android.os.incremental.StorageHealthCheckParams; +import android.os.incremental.V4Signature; import android.os.storage.StorageManager; import android.provider.DeviceConfig; import android.provider.Settings.Global; @@ -802,6 +803,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { // entries like "lost+found". if (file.isDirectory()) return false; if (file.getName().endsWith(REMOVE_MARKER_EXTENSION)) return false; + if (file.getName().endsWith(V4Signature.EXT)) return false; if (isAppMetadata(file)) return false; if (DexMetadataHelper.isDexMetadataFile(file)) return false; if (VerityUtils.isFsveritySignatureFile(file)) return false; @@ -1502,6 +1504,21 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { } @GuardedBy("mLock") + private void enableFsVerityToAddedApksWithIdsig() throws PackageManagerException { + try { + List<File> files = getAddedApksLocked(); + for (var file : files) { + if (new File(file.getPath() + V4Signature.EXT).exists()) { + VerityUtils.setUpFsverity(file.getPath()); + } + } + } catch (IOException e) { + throw new PrepareFailure(PackageManager.INSTALL_FAILED_BAD_SIGNATURE, + "Failed to enable fs-verity to verify with idsig: " + e); + } + } + + @GuardedBy("mLock") private List<ApkLite> getAddedApkLitesLocked() throws PackageManagerException { if (!isArchivedInstallation()) { List<File> files = getAddedApksLocked(); @@ -3294,6 +3311,14 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { } } + // Needs to happen before the first v4 signature verification, which happens in + // getAddedApkLitesLocked. + if (android.security.Flags.extendVbChainToUpdatedApk()) { + if (!isIncrementalInstallation()) { + enableFsVerityToAddedApksWithIdsig(); + } + } + final List<ApkLite> addedFiles = getAddedApkLitesLocked(); if (addedFiles.isEmpty() && (removeSplitList.size() == 0 || getStagedAppMetadataFile() != null)) { @@ -3656,6 +3681,16 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { } @GuardedBy("mLock") + private void maybeStageV4SignatureLocked(File origFile, File targetFile) + throws PackageManagerException { + final File originalSignature = new File(origFile.getPath() + V4Signature.EXT); + if (originalSignature.exists()) { + final File stagedSignature = new File(targetFile.getPath() + V4Signature.EXT); + stageFileLocked(originalSignature, stagedSignature); + } + } + + @GuardedBy("mLock") private void maybeStageDexMetadataLocked(File origFile, File targetFile) throws PackageManagerException { final File dexMetadataFile = DexMetadataHelper.findDexMetadataForFile(origFile); @@ -3782,6 +3817,10 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { // Stage APK's fs-verity signature if present. maybeStageFsveritySignatureLocked(origFile, targetFile, isFsVerityRequiredForApk(origFile, targetFile)); + // Stage APK's v4 signature if present. + if (android.security.Flags.extendVbChainToUpdatedApk()) { + maybeStageV4SignatureLocked(origFile, targetFile); + } // Stage dex metadata (.dm) and corresponding fs-verity signature if present. maybeStageDexMetadataLocked(origFile, targetFile); // Stage checksums (.digests) if present. @@ -3799,10 +3838,22 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { } @GuardedBy("mLock") + private void maybeInheritV4SignatureLocked(File origFile) { + // Inherit the v4 signature file if present. + final File v4SignatureFile = new File(origFile.getPath() + V4Signature.EXT); + if (v4SignatureFile.exists()) { + mResolvedInheritedFiles.add(v4SignatureFile); + } + } + + @GuardedBy("mLock") private void inheritFileLocked(File origFile) { mResolvedInheritedFiles.add(origFile); maybeInheritFsveritySignatureLocked(origFile); + if (android.security.Flags.extendVbChainToUpdatedApk()) { + maybeInheritV4SignatureLocked(origFile); + } // Inherit the dex metadata if present. final File dexMetadataFile = |