Loading services/core/java/com/android/server/pm/PackageManagerService.java +1 −2 Original line number Diff line number Diff line Loading @@ -17474,8 +17474,7 @@ public class PackageManagerService extends IPackageManager.Stub if (new File(signaturePath).exists() && !VerityUtils.hasFsverity(filePath)) { try { VerityUtils.setUpFsverity(filePath, signaturePath); } catch (IOException | DigestException | NoSuchAlgorithmException | SecurityException e) { } catch (IOException e) { throw new PrepareFailure(PackageManager.INSTALL_FAILED_BAD_SIGNATURE, "Failed to enable fs-verity: " + e); } services/core/java/com/android/server/security/VerityUtils.java +26 −162 Original line number Diff line number Diff line Loading @@ -26,27 +26,19 @@ import android.util.Slog; import android.util.apk.ApkSignatureVerifier; import android.util.apk.ByteBufferFactory; import android.util.apk.SignatureNotFoundException; import android.util.apk.VerityBuilder; import libcore.util.HexEncoding; import java.io.File; import java.io.FileDescriptor; import java.io.IOException; import java.io.RandomAccessFile; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio.channels.FileChannel; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.security.DigestException; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.Arrays; import sun.security.pkcs.PKCS7; /** Provides fsverity related operations. */ abstract public class VerityUtils { private static final String TAG = "VerityUtils"; Loading @@ -60,8 +52,6 @@ abstract public class VerityUtils { /** The maximum size of signature file. This is just to avoid potential abuse. */ private static final int MAX_SIGNATURE_FILE_SIZE_BYTES = 8192; private static final int COMMON_LINUX_PAGE_SIZE_IN_BYTES = 4096; private static final boolean DEBUG = false; /** Returns true if the given file looks like containing an fs-verity signature. */ Loading @@ -74,42 +64,15 @@ abstract public class VerityUtils { return filePath + FSVERITY_SIGNATURE_FILE_EXTENSION; } /** Generates Merkle tree and fs-verity metadata then enables fs-verity. */ public static void setUpFsverity(@NonNull String filePath, String signaturePath) throws IOException, DigestException, NoSuchAlgorithmException { final PKCS7 pkcs7 = new PKCS7(Files.readAllBytes(Paths.get(signaturePath))); final byte[] expectedMeasurement = pkcs7.getContentInfo().getContentBytes(); if (DEBUG) { Slog.d(TAG, "Enabling fs-verity with signed fs-verity measurement " + bytesToString(expectedMeasurement)); Slog.d(TAG, "PKCS#7 info: " + pkcs7); } final TrackedBufferFactory bufferFactory = new TrackedBufferFactory(); final byte[] actualMeasurement = generateFsverityMetadata(filePath, signaturePath, bufferFactory); try (RandomAccessFile raf = new RandomAccessFile(filePath, "rw")) { FileChannel ch = raf.getChannel(); ch.position(roundUpToNextMultiple(ch.size(), COMMON_LINUX_PAGE_SIZE_IN_BYTES)); ByteBuffer buffer = bufferFactory.getBuffer(); long offset = buffer.position(); long size = buffer.limit(); while (offset < size) { long s = ch.write(buffer); offset += s; size -= s; } } if (!Arrays.equals(expectedMeasurement, actualMeasurement)) { throw new SecurityException("fs-verity measurement mismatch: " + bytesToString(actualMeasurement) + " != " + bytesToString(expectedMeasurement)); /** Enables fs-verity for the file with a PKCS#7 detached signature file. */ public static void setUpFsverity(@NonNull String filePath, @NonNull String signaturePath) throws IOException { if (Files.size(Paths.get(signaturePath)) > MAX_SIGNATURE_FILE_SIZE_BYTES) { throw new SecurityException("Signature file is unexpectedly large: " + signaturePath); } // This can fail if the public key is not already in .fs-verity kernel keyring. int errno = enableFsverityNative(filePath); byte[] pkcs7Signature = Files.readAllBytes(Paths.get(signaturePath)); // This will fail if the public key is not already in .fs-verity kernel keyring. int errno = enableFsverityNative(filePath, pkcs7Signature); if (errno != 0) { throw new IOException("Failed to enable fs-verity on " + filePath + ": " + Os.strerror(errno)); Loading @@ -131,12 +94,19 @@ abstract public class VerityUtils { return true; } private static native int enableFsverityNative(@NonNull String filePath, @NonNull byte[] pkcs7Signature); private static native int measureFsverityNative(@NonNull String filePath); /** * Generates legacy Merkle tree and fs-verity metadata with Signing Block skipped. * * @deprecated This is only used for previous fs-verity implementation, and should never be used * on new devices. * @return {@code SetupResult} that contains the result code, and when success, the * {@code FileDescriptor} to read all the data from. */ @Deprecated public static SetupResult generateApkVeritySetupData(@NonNull String apkPath) { if (DEBUG) { Slog.d(TAG, "Trying to install legacy apk verity to " + apkPath); Loading Loading @@ -173,7 +143,10 @@ abstract public class VerityUtils { /** * {@see ApkSignatureVerifier#generateApkVerityRootHash(String)}. * @deprecated This is only used for previous fs-verity implementation, and should never be used * on new devices. */ @Deprecated public static byte[] generateApkVerityRootHash(@NonNull String apkPath) throws NoSuchAlgorithmException, DigestException, IOException { return ApkSignatureVerifier.generateApkVerityRootHash(apkPath); Loading @@ -181,103 +154,15 @@ abstract public class VerityUtils { /** * {@see ApkSignatureVerifier#getVerityRootHash(String)}. * @deprecated This is only used for previous fs-verity implementation, and should never be used * on new devices. */ @Deprecated public static byte[] getVerityRootHash(@NonNull String apkPath) throws IOException, SignatureNotFoundException { return ApkSignatureVerifier.getVerityRootHash(apkPath); } /** * Generates fs-verity metadata for {@code filePath} in the buffer created by {@code * trackedBufferFactory}. The metadata contains the Merkle tree, fs-verity descriptor and * extensions, including a PKCS#7 signature provided in {@code signaturePath}. * * <p>It is worthy to note that {@code trackedBufferFactory} generates a "tracked" {@code * ByteBuffer}. The data will be used outside this method via the factory itself. * * @return fs-verity signed data (struct fsverity_digest_disk) of {@code filePath}, which * includes SHA-256 of fs-verity descriptor and authenticated extensions. */ private static byte[] generateFsverityMetadata(String filePath, String signaturePath, @NonNull ByteBufferFactory trackedBufferFactory) throws IOException, DigestException, NoSuchAlgorithmException { try (RandomAccessFile file = new RandomAccessFile(filePath, "r")) { VerityBuilder.VerityResult result = VerityBuilder.generateFsVerityTree( file, trackedBufferFactory); ByteBuffer buffer = result.verityData; buffer.position(result.merkleTreeSize); final byte[] measurement = generateFsverityDescriptorAndMeasurement(file, result.rootHash, signaturePath, buffer); buffer.flip(); return constructFsveritySignedDataNative(measurement); } } /** * Generates fs-verity descriptor including the extensions to the {@code output} and returns the * fs-verity measurement. * * @return fs-verity measurement, which is a SHA-256 of fs-verity descriptor and authenticated * extensions. */ private static byte[] generateFsverityDescriptorAndMeasurement( @NonNull RandomAccessFile file, @NonNull byte[] rootHash, @NonNull String pkcs7SignaturePath, @NonNull ByteBuffer output) throws IOException, NoSuchAlgorithmException, DigestException { final short kRootHashExtensionId = 1; final short kPkcs7SignatureExtensionId = 3; final int origPosition = output.position(); // For generating fs-verity file measurement, which consists of the descriptor and // authenticated extensions (but not unauthenticated extensions and the footer). MessageDigest md = MessageDigest.getInstance("SHA-256"); // 1. Generate fs-verity descriptor. final byte[] desc = constructFsverityDescriptorNative(file.length()); output.put(desc); md.update(desc); // 2. Generate authenticated extensions. final byte[] authExt = constructFsverityExtensionNative(kRootHashExtensionId, rootHash.length); output.put(authExt); output.put(rootHash); md.update(authExt); md.update(rootHash); // 3. Generate unauthenticated extensions. ByteBuffer header = ByteBuffer.allocate(8).order(ByteOrder.LITTLE_ENDIAN); output.putShort((short) 1); // number of unauthenticated extensions below output.position(output.position() + 6); // Generate PKCS#7 extension. NB: We do not verify agaist trusted certificate (should be // done by the caller if needed). Path path = Paths.get(pkcs7SignaturePath); if (Files.size(path) > MAX_SIGNATURE_FILE_SIZE_BYTES) { throw new IllegalArgumentException("Signature size is unexpectedly large: " + pkcs7SignaturePath); } final byte[] pkcs7Signature = Files.readAllBytes(path); output.put(constructFsverityExtensionNative(kPkcs7SignatureExtensionId, pkcs7Signature.length)); output.put(pkcs7Signature); // 4. Generate the footer. output.put(constructFsverityFooterNative(output.position() - origPosition)); return md.digest(); } private static native int enableFsverityNative(@NonNull String filePath); private static native int measureFsverityNative(@NonNull String filePath); private static native byte[] constructFsveritySignedDataNative(@NonNull byte[] measurement); private static native byte[] constructFsverityDescriptorNative(long fileSize); private static native byte[] constructFsverityExtensionNative(short extensionId, int extensionDataSize); private static native byte[] constructFsverityFooterNative(int offsetToDescriptorHead); /** * Returns a pair of {@code SharedMemory} and {@code Integer}. The {@code SharedMemory} contains * Merkle tree and fsverity headers for the given apk, in the form that can immediately be used Loading Loading @@ -313,6 +198,11 @@ abstract public class VerityUtils { return HexEncoding.encodeToString(bytes); } /** * @deprecated This is only used for previous fs-verity implementation, and should never be used * on new devices. */ @Deprecated public static class SetupResult { /** Result code if verity is set up correctly. */ private static final int RESULT_OK = 1; Loading Loading @@ -401,30 +291,4 @@ abstract public class VerityUtils { return mBuffer == null ? -1 : mBuffer.limit(); } } /** A {@code ByteBufferFactory} that tracks the {@code ByteBuffer} it creates. */ private static class TrackedBufferFactory implements ByteBufferFactory { private ByteBuffer mBuffer; @Override public ByteBuffer create(int capacity) { if (mBuffer != null) { throw new IllegalStateException("Multiple instantiation from this factory"); } mBuffer = ByteBuffer.allocate(capacity); return mBuffer; } public ByteBuffer getBuffer() { return mBuffer; } } /** Round up the number to the next multiple of the divisor. */ private static long roundUpToNextMultiple(long number, long divisor) { if (number > (Long.MAX_VALUE - divisor)) { throw new IllegalArgumentException("arithmetic overflow"); } return ((number + (divisor - 1)) / divisor) * divisor; } } services/core/jni/com_android_server_security_VerityUtils.cpp +30 −147 Original line number Diff line number Diff line Loading @@ -36,61 +36,33 @@ #include <linux/fsverity.h> #else // Before fs-verity is upstreamed, use the current snapshot for development. // https://git.kernel.org/pub/scm/linux/kernel/git/ebiggers/linux.git/tree/include/uapi/linux/fsverity.h?h=fsverity #include <linux/limits.h> #include <linux/ioctl.h> #include <linux/types.h> #define FS_VERITY_HASH_ALG_SHA256 1 struct fsverity_enable_arg { __u32 version; __u32 hash_algorithm; __u32 block_size; __u32 salt_size; __u64 salt_ptr; __u32 sig_size; __u32 __reserved1; __u64 sig_ptr; __u64 __reserved2[11]; }; struct fsverity_digest { __u16 digest_algorithm; __u16 digest_size; /* input/output */ __u8 digest[]; }; #define FS_IOC_ENABLE_VERITY _IO('f', 133) #define FS_IOC_ENABLE_VERITY _IOW('f', 133, struct fsverity_enable_arg) #define FS_IOC_MEASURE_VERITY _IOWR('f', 134, struct fsverity_digest) #define FS_VERITY_MAGIC "FSVerity" #define FS_VERITY_ALG_SHA256 1 struct fsverity_descriptor { __u8 magic[8]; /* must be FS_VERITY_MAGIC */ __u8 major_version; /* must be 1 */ __u8 minor_version; /* must be 0 */ __u8 log_data_blocksize;/* log2(data-bytes-per-hash), e.g. 12 for 4KB */ __u8 log_tree_blocksize;/* log2(tree-bytes-per-hash), e.g. 12 for 4KB */ __le16 data_algorithm; /* hash algorithm for data blocks */ __le16 tree_algorithm; /* hash algorithm for tree blocks */ __le32 flags; /* flags */ __le32 __reserved1; /* must be 0 */ __le64 orig_file_size; /* size of the original file data */ __le16 auth_ext_count; /* number of authenticated extensions */ __u8 __reserved2[30]; /* must be 0 */ }; #define FS_VERITY_EXT_ROOT_HASH 1 #define FS_VERITY_EXT_PKCS7_SIGNATURE 3 struct fsverity_extension { __le32 length; __le16 type; /* Type of this extension (see codes above) */ __le16 __reserved; /* Reserved, must be 0 */ }; struct fsverity_digest_disk { __le16 digest_algorithm; __le16 digest_size; __u8 digest[]; }; struct fsverity_footer { __le32 desc_reverse_offset; /* distance to fsverity_descriptor */ __u8 magic[8]; /* FS_VERITY_MAGIC */ } __packed; #endif const int kSha256Bytes = 32; Loading @@ -99,52 +71,24 @@ namespace android { namespace { class JavaByteArrayHolder { public: JavaByteArrayHolder(const JavaByteArrayHolder &other) = delete; JavaByteArrayHolder(JavaByteArrayHolder &&other) : mEnv(other.mEnv), mBytes(other.mBytes), mElements(other.mElements) { other.mElements = nullptr; } static JavaByteArrayHolder newArray(JNIEnv* env, jsize size) { return JavaByteArrayHolder(env, size); } jbyte* getRaw() { return mElements; } jbyteArray release() { mEnv->ReleaseByteArrayElements(mBytes, mElements, 0); mElements = nullptr; return mBytes; } ~JavaByteArrayHolder() { LOG_ALWAYS_FATAL_IF(mElements != nullptr, "Elements are not released"); } private: JavaByteArrayHolder(JNIEnv* env, jsize size) { mEnv = env; mBytes = mEnv->NewByteArray(size); mElements = mEnv->GetByteArrayElements(mBytes, nullptr); memset(mElements, 0, size); } JNIEnv* mEnv; jbyteArray mBytes; jbyte* mElements; }; int enableFsverity(JNIEnv* env, jobject /* clazz */, jstring filePath) { int enableFsverity(JNIEnv* env, jobject /* clazz */, jstring filePath, jbyteArray signature) { const char* path = env->GetStringUTFChars(filePath, nullptr); ::android::base::unique_fd rfd(open(path, O_RDONLY | O_CLOEXEC)); env->ReleaseStringUTFChars(filePath, path); if (rfd.get() < 0) { return errno; } if (ioctl(rfd.get(), FS_IOC_ENABLE_VERITY, nullptr) < 0) { fsverity_enable_arg arg = {}; arg.version = 1; arg.hash_algorithm = FS_VERITY_HASH_ALG_SHA256; arg.block_size = 4096; arg.salt_size = 0; arg.salt_ptr = reinterpret_cast<uintptr_t>(nullptr); arg.sig_size = env->GetArrayLength(signature); arg.sig_ptr = reinterpret_cast<uintptr_t>(signature); if (ioctl(rfd.get(), FS_IOC_ENABLE_VERITY, &arg) < 0) { return errno; } return 0; Loading @@ -159,6 +103,7 @@ int measureFsverity(JNIEnv* env, jobject /* clazz */, jstring filePath) { const char* path = env->GetStringUTFChars(filePath, nullptr); ::android::base::unique_fd rfd(open(path, O_RDONLY | O_CLOEXEC)); env->ReleaseStringUTFChars(filePath, path); if (rfd.get() < 0) { return errno; } Loading @@ -168,71 +113,9 @@ int measureFsverity(JNIEnv* env, jobject /* clazz */, jstring filePath) { return 0; } jbyteArray constructFsveritySignedData(JNIEnv* env, jobject /* clazz */, jbyteArray digest) { auto raii = JavaByteArrayHolder::newArray(env, sizeof(fsverity_digest_disk) + kSha256Bytes); fsverity_digest_disk* data = reinterpret_cast<fsverity_digest_disk*>(raii.getRaw()); data->digest_algorithm = FS_VERITY_ALG_SHA256; data->digest_size = kSha256Bytes; if (env->GetArrayLength(digest) != kSha256Bytes) { jniThrowExceptionFmt(env, "java/lang/IllegalArgumentException", "Invalid hash size of %d", env->GetArrayLength(digest)); return 0; } const jbyte* src = env->GetByteArrayElements(digest, nullptr); memcpy(data->digest, src, kSha256Bytes); return raii.release(); } jbyteArray constructFsverityDescriptor(JNIEnv* env, jobject /* clazz */, jlong fileSize) { auto raii = JavaByteArrayHolder::newArray(env, sizeof(fsverity_descriptor)); fsverity_descriptor* desc = reinterpret_cast<fsverity_descriptor*>(raii.getRaw()); memcpy(desc->magic, FS_VERITY_MAGIC, sizeof(desc->magic)); desc->major_version = 1; desc->minor_version = 0; desc->log_data_blocksize = 12; desc->log_tree_blocksize = 12; desc->data_algorithm = FS_VERITY_ALG_SHA256; desc->tree_algorithm = FS_VERITY_ALG_SHA256; desc->flags = 0; desc->orig_file_size = fileSize; desc->auth_ext_count = 1; return raii.release(); } jbyteArray constructFsverityExtension(JNIEnv* env, jobject /* clazz */, jshort extensionId, jint extensionDataSize) { auto raii = JavaByteArrayHolder::newArray(env, sizeof(fsverity_extension)); fsverity_extension* ext = reinterpret_cast<fsverity_extension*>(raii.getRaw()); ext->length = sizeof(fsverity_extension) + extensionDataSize; ext->type = extensionId; return raii.release(); } jbyteArray constructFsverityFooter(JNIEnv* env, jobject /* clazz */, jint offsetToDescriptorHead) { auto raii = JavaByteArrayHolder::newArray(env, sizeof(fsverity_footer)); fsverity_footer* footer = reinterpret_cast<fsverity_footer*>(raii.getRaw()); footer->desc_reverse_offset = offsetToDescriptorHead + sizeof(fsverity_footer); memcpy(footer->magic, FS_VERITY_MAGIC, sizeof(footer->magic)); return raii.release(); } const JNINativeMethod sMethods[] = { { "enableFsverityNative", "(Ljava/lang/String;)I", (void *)enableFsverity }, { "enableFsverityNative", "(Ljava/lang/String;[B)I", (void *)enableFsverity }, { "measureFsverityNative", "(Ljava/lang/String;)I", (void *)measureFsverity }, { "constructFsveritySignedDataNative", "([B)[B", (void *)constructFsveritySignedData }, { "constructFsverityDescriptorNative", "(J)[B", (void *)constructFsverityDescriptor }, { "constructFsverityExtensionNative", "(SI)[B", (void *)constructFsverityExtension }, { "constructFsverityFooterNative", "(I)[B", (void *)constructFsverityFooter }, }; } // namespace Loading Loading
services/core/java/com/android/server/pm/PackageManagerService.java +1 −2 Original line number Diff line number Diff line Loading @@ -17474,8 +17474,7 @@ public class PackageManagerService extends IPackageManager.Stub if (new File(signaturePath).exists() && !VerityUtils.hasFsverity(filePath)) { try { VerityUtils.setUpFsverity(filePath, signaturePath); } catch (IOException | DigestException | NoSuchAlgorithmException | SecurityException e) { } catch (IOException e) { throw new PrepareFailure(PackageManager.INSTALL_FAILED_BAD_SIGNATURE, "Failed to enable fs-verity: " + e); }
services/core/java/com/android/server/security/VerityUtils.java +26 −162 Original line number Diff line number Diff line Loading @@ -26,27 +26,19 @@ import android.util.Slog; import android.util.apk.ApkSignatureVerifier; import android.util.apk.ByteBufferFactory; import android.util.apk.SignatureNotFoundException; import android.util.apk.VerityBuilder; import libcore.util.HexEncoding; import java.io.File; import java.io.FileDescriptor; import java.io.IOException; import java.io.RandomAccessFile; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio.channels.FileChannel; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.security.DigestException; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.Arrays; import sun.security.pkcs.PKCS7; /** Provides fsverity related operations. */ abstract public class VerityUtils { private static final String TAG = "VerityUtils"; Loading @@ -60,8 +52,6 @@ abstract public class VerityUtils { /** The maximum size of signature file. This is just to avoid potential abuse. */ private static final int MAX_SIGNATURE_FILE_SIZE_BYTES = 8192; private static final int COMMON_LINUX_PAGE_SIZE_IN_BYTES = 4096; private static final boolean DEBUG = false; /** Returns true if the given file looks like containing an fs-verity signature. */ Loading @@ -74,42 +64,15 @@ abstract public class VerityUtils { return filePath + FSVERITY_SIGNATURE_FILE_EXTENSION; } /** Generates Merkle tree and fs-verity metadata then enables fs-verity. */ public static void setUpFsverity(@NonNull String filePath, String signaturePath) throws IOException, DigestException, NoSuchAlgorithmException { final PKCS7 pkcs7 = new PKCS7(Files.readAllBytes(Paths.get(signaturePath))); final byte[] expectedMeasurement = pkcs7.getContentInfo().getContentBytes(); if (DEBUG) { Slog.d(TAG, "Enabling fs-verity with signed fs-verity measurement " + bytesToString(expectedMeasurement)); Slog.d(TAG, "PKCS#7 info: " + pkcs7); } final TrackedBufferFactory bufferFactory = new TrackedBufferFactory(); final byte[] actualMeasurement = generateFsverityMetadata(filePath, signaturePath, bufferFactory); try (RandomAccessFile raf = new RandomAccessFile(filePath, "rw")) { FileChannel ch = raf.getChannel(); ch.position(roundUpToNextMultiple(ch.size(), COMMON_LINUX_PAGE_SIZE_IN_BYTES)); ByteBuffer buffer = bufferFactory.getBuffer(); long offset = buffer.position(); long size = buffer.limit(); while (offset < size) { long s = ch.write(buffer); offset += s; size -= s; } } if (!Arrays.equals(expectedMeasurement, actualMeasurement)) { throw new SecurityException("fs-verity measurement mismatch: " + bytesToString(actualMeasurement) + " != " + bytesToString(expectedMeasurement)); /** Enables fs-verity for the file with a PKCS#7 detached signature file. */ public static void setUpFsverity(@NonNull String filePath, @NonNull String signaturePath) throws IOException { if (Files.size(Paths.get(signaturePath)) > MAX_SIGNATURE_FILE_SIZE_BYTES) { throw new SecurityException("Signature file is unexpectedly large: " + signaturePath); } // This can fail if the public key is not already in .fs-verity kernel keyring. int errno = enableFsverityNative(filePath); byte[] pkcs7Signature = Files.readAllBytes(Paths.get(signaturePath)); // This will fail if the public key is not already in .fs-verity kernel keyring. int errno = enableFsverityNative(filePath, pkcs7Signature); if (errno != 0) { throw new IOException("Failed to enable fs-verity on " + filePath + ": " + Os.strerror(errno)); Loading @@ -131,12 +94,19 @@ abstract public class VerityUtils { return true; } private static native int enableFsverityNative(@NonNull String filePath, @NonNull byte[] pkcs7Signature); private static native int measureFsverityNative(@NonNull String filePath); /** * Generates legacy Merkle tree and fs-verity metadata with Signing Block skipped. * * @deprecated This is only used for previous fs-verity implementation, and should never be used * on new devices. * @return {@code SetupResult} that contains the result code, and when success, the * {@code FileDescriptor} to read all the data from. */ @Deprecated public static SetupResult generateApkVeritySetupData(@NonNull String apkPath) { if (DEBUG) { Slog.d(TAG, "Trying to install legacy apk verity to " + apkPath); Loading Loading @@ -173,7 +143,10 @@ abstract public class VerityUtils { /** * {@see ApkSignatureVerifier#generateApkVerityRootHash(String)}. * @deprecated This is only used for previous fs-verity implementation, and should never be used * on new devices. */ @Deprecated public static byte[] generateApkVerityRootHash(@NonNull String apkPath) throws NoSuchAlgorithmException, DigestException, IOException { return ApkSignatureVerifier.generateApkVerityRootHash(apkPath); Loading @@ -181,103 +154,15 @@ abstract public class VerityUtils { /** * {@see ApkSignatureVerifier#getVerityRootHash(String)}. * @deprecated This is only used for previous fs-verity implementation, and should never be used * on new devices. */ @Deprecated public static byte[] getVerityRootHash(@NonNull String apkPath) throws IOException, SignatureNotFoundException { return ApkSignatureVerifier.getVerityRootHash(apkPath); } /** * Generates fs-verity metadata for {@code filePath} in the buffer created by {@code * trackedBufferFactory}. The metadata contains the Merkle tree, fs-verity descriptor and * extensions, including a PKCS#7 signature provided in {@code signaturePath}. * * <p>It is worthy to note that {@code trackedBufferFactory} generates a "tracked" {@code * ByteBuffer}. The data will be used outside this method via the factory itself. * * @return fs-verity signed data (struct fsverity_digest_disk) of {@code filePath}, which * includes SHA-256 of fs-verity descriptor and authenticated extensions. */ private static byte[] generateFsverityMetadata(String filePath, String signaturePath, @NonNull ByteBufferFactory trackedBufferFactory) throws IOException, DigestException, NoSuchAlgorithmException { try (RandomAccessFile file = new RandomAccessFile(filePath, "r")) { VerityBuilder.VerityResult result = VerityBuilder.generateFsVerityTree( file, trackedBufferFactory); ByteBuffer buffer = result.verityData; buffer.position(result.merkleTreeSize); final byte[] measurement = generateFsverityDescriptorAndMeasurement(file, result.rootHash, signaturePath, buffer); buffer.flip(); return constructFsveritySignedDataNative(measurement); } } /** * Generates fs-verity descriptor including the extensions to the {@code output} and returns the * fs-verity measurement. * * @return fs-verity measurement, which is a SHA-256 of fs-verity descriptor and authenticated * extensions. */ private static byte[] generateFsverityDescriptorAndMeasurement( @NonNull RandomAccessFile file, @NonNull byte[] rootHash, @NonNull String pkcs7SignaturePath, @NonNull ByteBuffer output) throws IOException, NoSuchAlgorithmException, DigestException { final short kRootHashExtensionId = 1; final short kPkcs7SignatureExtensionId = 3; final int origPosition = output.position(); // For generating fs-verity file measurement, which consists of the descriptor and // authenticated extensions (but not unauthenticated extensions and the footer). MessageDigest md = MessageDigest.getInstance("SHA-256"); // 1. Generate fs-verity descriptor. final byte[] desc = constructFsverityDescriptorNative(file.length()); output.put(desc); md.update(desc); // 2. Generate authenticated extensions. final byte[] authExt = constructFsverityExtensionNative(kRootHashExtensionId, rootHash.length); output.put(authExt); output.put(rootHash); md.update(authExt); md.update(rootHash); // 3. Generate unauthenticated extensions. ByteBuffer header = ByteBuffer.allocate(8).order(ByteOrder.LITTLE_ENDIAN); output.putShort((short) 1); // number of unauthenticated extensions below output.position(output.position() + 6); // Generate PKCS#7 extension. NB: We do not verify agaist trusted certificate (should be // done by the caller if needed). Path path = Paths.get(pkcs7SignaturePath); if (Files.size(path) > MAX_SIGNATURE_FILE_SIZE_BYTES) { throw new IllegalArgumentException("Signature size is unexpectedly large: " + pkcs7SignaturePath); } final byte[] pkcs7Signature = Files.readAllBytes(path); output.put(constructFsverityExtensionNative(kPkcs7SignatureExtensionId, pkcs7Signature.length)); output.put(pkcs7Signature); // 4. Generate the footer. output.put(constructFsverityFooterNative(output.position() - origPosition)); return md.digest(); } private static native int enableFsverityNative(@NonNull String filePath); private static native int measureFsverityNative(@NonNull String filePath); private static native byte[] constructFsveritySignedDataNative(@NonNull byte[] measurement); private static native byte[] constructFsverityDescriptorNative(long fileSize); private static native byte[] constructFsverityExtensionNative(short extensionId, int extensionDataSize); private static native byte[] constructFsverityFooterNative(int offsetToDescriptorHead); /** * Returns a pair of {@code SharedMemory} and {@code Integer}. The {@code SharedMemory} contains * Merkle tree and fsverity headers for the given apk, in the form that can immediately be used Loading Loading @@ -313,6 +198,11 @@ abstract public class VerityUtils { return HexEncoding.encodeToString(bytes); } /** * @deprecated This is only used for previous fs-verity implementation, and should never be used * on new devices. */ @Deprecated public static class SetupResult { /** Result code if verity is set up correctly. */ private static final int RESULT_OK = 1; Loading Loading @@ -401,30 +291,4 @@ abstract public class VerityUtils { return mBuffer == null ? -1 : mBuffer.limit(); } } /** A {@code ByteBufferFactory} that tracks the {@code ByteBuffer} it creates. */ private static class TrackedBufferFactory implements ByteBufferFactory { private ByteBuffer mBuffer; @Override public ByteBuffer create(int capacity) { if (mBuffer != null) { throw new IllegalStateException("Multiple instantiation from this factory"); } mBuffer = ByteBuffer.allocate(capacity); return mBuffer; } public ByteBuffer getBuffer() { return mBuffer; } } /** Round up the number to the next multiple of the divisor. */ private static long roundUpToNextMultiple(long number, long divisor) { if (number > (Long.MAX_VALUE - divisor)) { throw new IllegalArgumentException("arithmetic overflow"); } return ((number + (divisor - 1)) / divisor) * divisor; } }
services/core/jni/com_android_server_security_VerityUtils.cpp +30 −147 Original line number Diff line number Diff line Loading @@ -36,61 +36,33 @@ #include <linux/fsverity.h> #else // Before fs-verity is upstreamed, use the current snapshot for development. // https://git.kernel.org/pub/scm/linux/kernel/git/ebiggers/linux.git/tree/include/uapi/linux/fsverity.h?h=fsverity #include <linux/limits.h> #include <linux/ioctl.h> #include <linux/types.h> #define FS_VERITY_HASH_ALG_SHA256 1 struct fsverity_enable_arg { __u32 version; __u32 hash_algorithm; __u32 block_size; __u32 salt_size; __u64 salt_ptr; __u32 sig_size; __u32 __reserved1; __u64 sig_ptr; __u64 __reserved2[11]; }; struct fsverity_digest { __u16 digest_algorithm; __u16 digest_size; /* input/output */ __u8 digest[]; }; #define FS_IOC_ENABLE_VERITY _IO('f', 133) #define FS_IOC_ENABLE_VERITY _IOW('f', 133, struct fsverity_enable_arg) #define FS_IOC_MEASURE_VERITY _IOWR('f', 134, struct fsverity_digest) #define FS_VERITY_MAGIC "FSVerity" #define FS_VERITY_ALG_SHA256 1 struct fsverity_descriptor { __u8 magic[8]; /* must be FS_VERITY_MAGIC */ __u8 major_version; /* must be 1 */ __u8 minor_version; /* must be 0 */ __u8 log_data_blocksize;/* log2(data-bytes-per-hash), e.g. 12 for 4KB */ __u8 log_tree_blocksize;/* log2(tree-bytes-per-hash), e.g. 12 for 4KB */ __le16 data_algorithm; /* hash algorithm for data blocks */ __le16 tree_algorithm; /* hash algorithm for tree blocks */ __le32 flags; /* flags */ __le32 __reserved1; /* must be 0 */ __le64 orig_file_size; /* size of the original file data */ __le16 auth_ext_count; /* number of authenticated extensions */ __u8 __reserved2[30]; /* must be 0 */ }; #define FS_VERITY_EXT_ROOT_HASH 1 #define FS_VERITY_EXT_PKCS7_SIGNATURE 3 struct fsverity_extension { __le32 length; __le16 type; /* Type of this extension (see codes above) */ __le16 __reserved; /* Reserved, must be 0 */ }; struct fsverity_digest_disk { __le16 digest_algorithm; __le16 digest_size; __u8 digest[]; }; struct fsverity_footer { __le32 desc_reverse_offset; /* distance to fsverity_descriptor */ __u8 magic[8]; /* FS_VERITY_MAGIC */ } __packed; #endif const int kSha256Bytes = 32; Loading @@ -99,52 +71,24 @@ namespace android { namespace { class JavaByteArrayHolder { public: JavaByteArrayHolder(const JavaByteArrayHolder &other) = delete; JavaByteArrayHolder(JavaByteArrayHolder &&other) : mEnv(other.mEnv), mBytes(other.mBytes), mElements(other.mElements) { other.mElements = nullptr; } static JavaByteArrayHolder newArray(JNIEnv* env, jsize size) { return JavaByteArrayHolder(env, size); } jbyte* getRaw() { return mElements; } jbyteArray release() { mEnv->ReleaseByteArrayElements(mBytes, mElements, 0); mElements = nullptr; return mBytes; } ~JavaByteArrayHolder() { LOG_ALWAYS_FATAL_IF(mElements != nullptr, "Elements are not released"); } private: JavaByteArrayHolder(JNIEnv* env, jsize size) { mEnv = env; mBytes = mEnv->NewByteArray(size); mElements = mEnv->GetByteArrayElements(mBytes, nullptr); memset(mElements, 0, size); } JNIEnv* mEnv; jbyteArray mBytes; jbyte* mElements; }; int enableFsverity(JNIEnv* env, jobject /* clazz */, jstring filePath) { int enableFsverity(JNIEnv* env, jobject /* clazz */, jstring filePath, jbyteArray signature) { const char* path = env->GetStringUTFChars(filePath, nullptr); ::android::base::unique_fd rfd(open(path, O_RDONLY | O_CLOEXEC)); env->ReleaseStringUTFChars(filePath, path); if (rfd.get() < 0) { return errno; } if (ioctl(rfd.get(), FS_IOC_ENABLE_VERITY, nullptr) < 0) { fsverity_enable_arg arg = {}; arg.version = 1; arg.hash_algorithm = FS_VERITY_HASH_ALG_SHA256; arg.block_size = 4096; arg.salt_size = 0; arg.salt_ptr = reinterpret_cast<uintptr_t>(nullptr); arg.sig_size = env->GetArrayLength(signature); arg.sig_ptr = reinterpret_cast<uintptr_t>(signature); if (ioctl(rfd.get(), FS_IOC_ENABLE_VERITY, &arg) < 0) { return errno; } return 0; Loading @@ -159,6 +103,7 @@ int measureFsverity(JNIEnv* env, jobject /* clazz */, jstring filePath) { const char* path = env->GetStringUTFChars(filePath, nullptr); ::android::base::unique_fd rfd(open(path, O_RDONLY | O_CLOEXEC)); env->ReleaseStringUTFChars(filePath, path); if (rfd.get() < 0) { return errno; } Loading @@ -168,71 +113,9 @@ int measureFsverity(JNIEnv* env, jobject /* clazz */, jstring filePath) { return 0; } jbyteArray constructFsveritySignedData(JNIEnv* env, jobject /* clazz */, jbyteArray digest) { auto raii = JavaByteArrayHolder::newArray(env, sizeof(fsverity_digest_disk) + kSha256Bytes); fsverity_digest_disk* data = reinterpret_cast<fsverity_digest_disk*>(raii.getRaw()); data->digest_algorithm = FS_VERITY_ALG_SHA256; data->digest_size = kSha256Bytes; if (env->GetArrayLength(digest) != kSha256Bytes) { jniThrowExceptionFmt(env, "java/lang/IllegalArgumentException", "Invalid hash size of %d", env->GetArrayLength(digest)); return 0; } const jbyte* src = env->GetByteArrayElements(digest, nullptr); memcpy(data->digest, src, kSha256Bytes); return raii.release(); } jbyteArray constructFsverityDescriptor(JNIEnv* env, jobject /* clazz */, jlong fileSize) { auto raii = JavaByteArrayHolder::newArray(env, sizeof(fsverity_descriptor)); fsverity_descriptor* desc = reinterpret_cast<fsverity_descriptor*>(raii.getRaw()); memcpy(desc->magic, FS_VERITY_MAGIC, sizeof(desc->magic)); desc->major_version = 1; desc->minor_version = 0; desc->log_data_blocksize = 12; desc->log_tree_blocksize = 12; desc->data_algorithm = FS_VERITY_ALG_SHA256; desc->tree_algorithm = FS_VERITY_ALG_SHA256; desc->flags = 0; desc->orig_file_size = fileSize; desc->auth_ext_count = 1; return raii.release(); } jbyteArray constructFsverityExtension(JNIEnv* env, jobject /* clazz */, jshort extensionId, jint extensionDataSize) { auto raii = JavaByteArrayHolder::newArray(env, sizeof(fsverity_extension)); fsverity_extension* ext = reinterpret_cast<fsverity_extension*>(raii.getRaw()); ext->length = sizeof(fsverity_extension) + extensionDataSize; ext->type = extensionId; return raii.release(); } jbyteArray constructFsverityFooter(JNIEnv* env, jobject /* clazz */, jint offsetToDescriptorHead) { auto raii = JavaByteArrayHolder::newArray(env, sizeof(fsverity_footer)); fsverity_footer* footer = reinterpret_cast<fsverity_footer*>(raii.getRaw()); footer->desc_reverse_offset = offsetToDescriptorHead + sizeof(fsverity_footer); memcpy(footer->magic, FS_VERITY_MAGIC, sizeof(footer->magic)); return raii.release(); } const JNINativeMethod sMethods[] = { { "enableFsverityNative", "(Ljava/lang/String;)I", (void *)enableFsverity }, { "enableFsverityNative", "(Ljava/lang/String;[B)I", (void *)enableFsverity }, { "measureFsverityNative", "(Ljava/lang/String;)I", (void *)measureFsverity }, { "constructFsveritySignedDataNative", "([B)[B", (void *)constructFsveritySignedData }, { "constructFsverityDescriptorNative", "(J)[B", (void *)constructFsverityDescriptor }, { "constructFsverityExtensionNative", "(SI)[B", (void *)constructFsverityExtension }, { "constructFsverityFooterNative", "(I)[B", (void *)constructFsverityFooter }, }; } // namespace Loading