Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 6beab1fe authored by Victor Hsieh's avatar Victor Hsieh Committed by Christopher Ferris
Browse files

Change fs-verity setup to the new format

This is due to upstream kernel API change.  With simpler API, many setup
code can now be removed.

Test: atest com.android.cts.apkverity.ApkVerityTest
Bug: 112037636
Change-Id: Ie88d55630b5d20ddbcbea4be7af9cf1fee47e44a
Merged-In: Ie88d55630b5d20ddbcbea4be7af9cf1fee47e44a
(cherry picked from commit 40ed1b4a)
parent 560f980b
Loading
Loading
Loading
Loading
+1 −2
Original line number Diff line number Diff line
@@ -17471,8 +17471,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);
                    }
+26 −162
Original line number Diff line number Diff line
@@ -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";
@@ -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. */
@@ -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));
@@ -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);
@@ -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);
@@ -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
@@ -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;
@@ -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;
    }
}
+30 −147
Original line number Diff line number Diff line
@@ -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;
@@ -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;
@@ -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;
    }
@@ -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