diff options
4 files changed, 155 insertions, 19 deletions
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java index 9ca2db970eb7..d36d583559a0 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -3158,6 +3158,23 @@ public abstract class PackageManager { "android.content.pm.extra.VERIFICATION_LONG_VERSION_CODE"; /** + * Extra field name for the Merkle tree root hash of a package. + * <p>Passed to a package verifier both prior to verification and as a result + * of verification. + * <p>The value of the extra is a specially formatted list: + * {@code filename1:HASH_1;filename2:HASH_2;...;filenameN:HASH_N} + * <p>The extra must include an entry for every APK within an installation. If + * a hash is not physically present, a hash value of {@code 0} will be used. + * <p>The root hash is generated using SHA-256, no salt with a 4096 byte block + * size. See the description of the + * <a href="https://www.kernel.org/doc/html/latest/filesystems/fsverity.html#merkle-tree">fs-verity merkle-tree</a> + * for more details. + * @hide + */ + public static final String EXTRA_VERIFICATION_ROOT_HASH = + "android.content.pm.extra.EXTRA_VERIFICATION_ROOT_HASH"; + + /** * Extra field name for the ID of a intent filter pending verification. * Passed to an intent filter verifier and is used to call back to * {@link #verifyIntentFilter} diff --git a/core/java/android/os/incremental/V4Signature.java b/core/java/android/os/incremental/V4Signature.java index 5cc73caa4f60..d35ce5b2c3f8 100644 --- a/core/java/android/os/incremental/V4Signature.java +++ b/core/java/android/os/incremental/V4Signature.java @@ -16,6 +16,8 @@ package android.os.incremental; +import android.annotation.NonNull; +import android.annotation.Nullable; import android.os.ParcelFileDescriptor; import java.io.ByteArrayInputStream; @@ -45,8 +47,8 @@ public class V4Signature { public static class HashingInfo { public final int hashAlgorithm; // only 1 == SHA256 supported public final byte log2BlockSize; // only 12 (block size 4096) supported now - public final byte[] salt; // used exactly as in fs-verity, 32 bytes max - public final byte[] rawRootHash; // salted digest of the first Merkle tree page + @Nullable public final byte[] salt; // used exactly as in fs-verity, 32 bytes max + @Nullable public final byte[] rawRootHash; // salted digest of the first Merkle tree page HashingInfo(int hashAlgorithm, byte log2BlockSize, byte[] salt, byte[] rawRootHash) { this.hashAlgorithm = hashAlgorithm; @@ -58,7 +60,8 @@ public class V4Signature { /** * Constructs HashingInfo from byte array. */ - public static HashingInfo fromByteArray(byte[] bytes) throws IOException { + @NonNull + public static HashingInfo fromByteArray(@NonNull byte[] bytes) throws IOException { ByteBuffer buffer = ByteBuffer.wrap(bytes).order(ByteOrder.LITTLE_ENDIAN); final int hashAlgorithm = buffer.getInt(); final byte log2BlockSize = buffer.get(); @@ -106,8 +109,18 @@ public class V4Signature { } public final int version; // Always 2 for now. - public final byte[] hashingInfo; - public final byte[] signingInfo; // Passed as-is to the kernel. Can be retrieved later. + /** + * Raw byte array containing the IncFS hashing data. + * @see HashingInfo#fromByteArray(byte[]) + */ + @Nullable public final byte[] hashingInfo; + + /** + * Raw byte array containing the V4 signature data. + * <p>Passed as-is to the kernel. Can be retrieved later. + * @see SigningInfo#fromByteArray(byte[]) + */ + @Nullable public final byte[] signingInfo; /** * Construct a V4Signature from .idsig file. @@ -121,7 +134,8 @@ public class V4Signature { /** * Construct a V4Signature from a byte array. */ - public static V4Signature readFrom(byte[] bytes) throws IOException { + @NonNull + public static V4Signature readFrom(@NonNull byte[] bytes) throws IOException { try (InputStream stream = new ByteArrayInputStream(bytes)) { return readFrom(stream); } @@ -169,7 +183,7 @@ public class V4Signature { return this.version == SUPPORTED_VERSION; } - private V4Signature(int version, byte[] hashingInfo, byte[] signingInfo) { + private V4Signature(int version, @Nullable byte[] hashingInfo, @Nullable byte[] signingInfo) { this.version = version; this.hashingInfo = hashingInfo; this.signingInfo = signingInfo; diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 59ac603875e2..12ee2f55e87f 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -94,6 +94,7 @@ import static android.content.pm.PackageManager.PERMISSION_DENIED; import static android.content.pm.PackageManager.PERMISSION_GRANTED; import static android.content.pm.PackageManager.RESTRICTION_NONE; import static android.content.pm.PackageManager.UNINSTALL_REASON_UNKNOWN; +import static android.content.pm.PackageParser.SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V4; import static android.content.pm.PackageParser.isApkFile; import static android.os.Trace.TRACE_TAG_PACKAGE_MANAGER; import static android.os.incremental.IncrementalManager.isIncrementalPath; @@ -1820,10 +1821,12 @@ public class PackageManagerService extends IPackageManager.Stub state.setVerifierResponse(Binder.getCallingUid(), PackageManager.VERIFICATION_ALLOW_WITHOUT_SUFFICIENT); broadcastPackageVerified(verificationId, originUri, - PackageManager.VERIFICATION_ALLOW, user); + PackageManager.VERIFICATION_ALLOW, null, args.mDataLoaderType, + user); } else { broadcastPackageVerified(verificationId, originUri, - PackageManager.VERIFICATION_REJECT, user); + PackageManager.VERIFICATION_REJECT, null, args.mDataLoaderType, + user); params.setReturnCode( PackageManager.INSTALL_FAILED_VERIFICATION_FAILURE); state.setVerifierResponse(Binder.getCallingUid(), @@ -1899,7 +1902,7 @@ public class PackageManagerService extends IPackageManager.Stub if (state.isInstallAllowed()) { broadcastPackageVerified(verificationId, originUri, - response.code, args.getUser()); + response.code, null, args.mDataLoaderType, args.getUser()); } else { params.setReturnCode( PackageManager.INSTALL_FAILED_VERIFICATION_FAILURE); @@ -13575,12 +13578,17 @@ public class PackageManagerService extends IPackageManager.Stub } private void broadcastPackageVerified(int verificationId, Uri packageUri, - int verificationCode, UserHandle user) { + int verificationCode, @Nullable String rootHashString, int dataLoaderType, + UserHandle user) { final Intent intent = new Intent(Intent.ACTION_PACKAGE_VERIFIED); intent.setDataAndType(packageUri, PACKAGE_MIME_TYPE); intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); intent.putExtra(PackageManager.EXTRA_VERIFICATION_ID, verificationId); intent.putExtra(PackageManager.EXTRA_VERIFICATION_RESULT, verificationCode); + if (rootHashString != null) { + intent.putExtra(PackageManager.EXTRA_VERIFICATION_ROOT_HASH, rootHashString); + } + intent.putExtra(PackageInstaller.EXTRA_DATA_LOADER_TYPE, dataLoaderType); mContext.sendBroadcastAsUser(intent, user, android.Manifest.permission.PACKAGE_VERIFICATION_AGENT); @@ -14952,8 +14960,17 @@ public class PackageManagerService extends IPackageManager.Stub verificationState.setRequiredVerifierUid(requiredUid); final int installerUid = verificationInfo == null ? -1 : verificationInfo.installerUid; - if (!origin.existing && isVerificationEnabled(pkgLite, verifierUser.getIdentifier(), - installFlags, installerUid)) { + final boolean isVerificationEnabled = isVerificationEnabled( + pkgLite, verifierUser.getIdentifier(), installFlags, installerUid); + final boolean isV4Signed = + (mArgs.signingDetails.signatureSchemeVersion == SIGNING_BLOCK_V4); + final boolean isIncrementalInstall = + (mArgs.mDataLoaderType == DataLoaderType.INCREMENTAL); + // NOTE: We purposefully skip verification for only incremental installs when there's + // a v4 signature block. Otherwise, proceed with verification as usual. + if (!origin.existing + && isVerificationEnabled + && (!isIncrementalInstall || !isV4Signed)) { final Intent verification = new Intent( Intent.ACTION_PACKAGE_NEEDS_VERIFICATION); verification.addFlags(Intent.FLAG_RECEIVER_FOREGROUND); @@ -16569,7 +16586,29 @@ public class PackageManagerService extends IPackageManager.Stub } executePostCommitSteps(commitRequest); } finally { - if (!success) { + if (success) { + for (InstallRequest request : requests) { + final InstallArgs args = request.args; + if (args.mDataLoaderType != DataLoaderType.INCREMENTAL) { + continue; + } + if (args.signingDetails.signatureSchemeVersion != SIGNING_BLOCK_V4) { + continue; + } + // For incremental installs, we bypass the verifier prior to install. Now + // that we know the package is valid, send a notice to the verifier with + // the root hash of the base.apk. + final String baseCodePath = request.installResult.pkg.getBaseCodePath(); + final String[] splitCodePaths = request.installResult.pkg.getSplitCodePaths(); + final Uri originUri = Uri.fromFile(args.origin.resolvedFile); + final int verificationId = mPendingVerificationToken++; + final String rootHashString = PackageManagerServiceUtils + .buildVerificationRootHashString(baseCodePath, splitCodePaths); + broadcastPackageVerified(verificationId, originUri, + PackageManager.VERIFICATION_ALLOW, rootHashString, + args.mDataLoaderType, args.getUser()); + } + } else { for (ScanResult result : preparedScans.values()) { if (createdAppId.getOrDefault(result.request.parsedPackage.getPackageName(), false)) { @@ -16911,7 +16950,6 @@ public class PackageManagerService extends IPackageManager.Stub if (args.signingDetails != PackageParser.SigningDetails.UNKNOWN) { parsedPackage.setSigningDetails(args.signingDetails); } else { - // TODO(b/136132412): skip for Incremental installation parsedPackage.setSigningDetails( ParsingPackageUtils.collectCertificates(parsedPackage, false /* skipVerify */)); } diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java index 91afd846a9c3..5c175a6ef847 100644 --- a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java +++ b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java @@ -32,7 +32,6 @@ import android.annotation.Nullable; import android.app.AppGlobals; import android.content.Context; import android.content.Intent; -import android.content.pm.ApplicationInfo; import android.content.pm.PackageInfoLite; import android.content.pm.PackageManager; import android.content.pm.PackageManagerInternal; @@ -40,7 +39,6 @@ import android.content.pm.PackageParser; import android.content.pm.PackageParser.PackageParserException; import android.content.pm.ResolveInfo; import android.content.pm.Signature; -import android.content.pm.parsing.ParsingPackageUtils; import android.os.Build; import android.os.Debug; import android.os.Environment; @@ -50,6 +48,9 @@ import android.os.RemoteException; import android.os.SystemProperties; import android.os.UserHandle; import android.os.UserManagerInternal; +import android.os.incremental.IncrementalManager; +import android.os.incremental.V4Signature; +import android.os.incremental.V4Signature.HashingInfo; import android.service.pm.PackageServiceDumpProto; import android.system.ErrnoException; import android.system.Os; @@ -62,6 +63,7 @@ import com.android.internal.content.NativeLibraryHelper; import com.android.internal.content.PackageHelper; import com.android.internal.util.ArrayUtils; import com.android.internal.util.FastPrintWriter; +import com.android.internal.util.HexDump; import com.android.server.EventLogTags; import com.android.server.pm.dex.DexManager; import com.android.server.pm.dex.PackageDexUsage; @@ -94,8 +96,6 @@ import java.util.Collections; import java.util.Date; import java.util.LinkedList; import java.util.List; -import java.util.Map; -import java.util.Set; import java.util.function.Predicate; import java.util.zip.GZIPInputStream; @@ -943,4 +943,71 @@ public class PackageManagerServiceUtils { Os.chmod(currentDir.getAbsolutePath(), mode); } } + + /** + * Returns a string that's compatible with the verification root hash extra. + * @see PackageManager#EXTRA_VERIFICATION_ROOT_HASH + */ + @NonNull + public static String buildVerificationRootHashString(@NonNull String baseFilename, + @Nullable String[] splitFilenameArray) { + final StringBuilder sb = new StringBuilder(); + final String baseFilePath = + baseFilename.substring(baseFilename.lastIndexOf(File.separator) + 1); + sb.append(baseFilePath).append(":"); + final byte[] baseRootHash = getRootHash(baseFilename); + if (baseRootHash == null) { + sb.append("0"); + } else { + sb.append(HexDump.toHexString(baseRootHash)); + } + if (splitFilenameArray == null || splitFilenameArray.length == 0) { + return sb.toString(); + } + + for (int i = splitFilenameArray.length - 1; i >= 0; i--) { + final String splitFilename = splitFilenameArray[i]; + final String splitFilePath = + splitFilename.substring(splitFilename.lastIndexOf(File.separator) + 1); + final byte[] splitRootHash = getRootHash(splitFilename); + sb.append(";").append(splitFilePath).append(":"); + if (splitRootHash == null) { + sb.append("0"); + } else { + sb.append(HexDump.toHexString(splitRootHash)); + } + } + return sb.toString(); + } + + /** + * Returns the root has for the given file. + * <p>Otherwise, returns {@code null} if the root hash could not be found or calculated. + * <p>NOTE: This currently only works on files stored on the incremental file system. The + * eventual goal is that this hash [among others] can be retrieved for any file. + */ + @Nullable + private static byte[] getRootHash(String filename) { + try { + final byte[] baseFileSignature = + IncrementalManager.unsafeGetFileSignature(filename); + if (baseFileSignature == null) { + throw new IOException("File signature not present"); + } + final V4Signature signature = + V4Signature.readFrom(baseFileSignature); + if (signature.hashingInfo == null) { + throw new IOException("Hashing info not present"); + } + final HashingInfo hashInfo = + HashingInfo.fromByteArray(signature.hashingInfo); + if (ArrayUtils.isEmpty(hashInfo.rawRootHash)) { + throw new IOException("Root has not present"); + } + return hashInfo.rawRootHash; + } catch (IOException ignore) { + Slog.e(TAG, "ERROR: could not load root hash from incremental install"); + } + return null; + } } |