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

Commit aa6cb130 authored by Victor Hsieh's avatar Victor Hsieh
Browse files

Set up fs-verity in system_server process

fs-verity setup no longer needs CAP_SYS_ADMIN, so move the setup into
system_server to avoid extra hop to installd.  The old path still needs
to be kept for legacy mode unfortunately.

Merkel tree is now also built in private memory instead of shared
memory.

Test: adb install foo.apk
Test: adb install-multiple foo.apk foo.apk.apk_sig
Bug: 112037636
Change-Id: I5c7ac50052fdb242aa60c8eadf97c24a1655b006
parent 8c30fbae
Loading
Loading
Loading
Loading
+18 −8
Original line number Diff line number Diff line
@@ -16911,19 +16911,29 @@ public class PackageManagerService extends IPackageManager.Stub
            final String filePath = entry.getKey();
            final String signaturePath = entry.getValue();
            final VerityUtils.SetupResult result = VerityUtils.generateApkVeritySetupData(
                    filePath, signaturePath, legacyMode);
            if (!legacyMode) {
                // fs-verity is optional for now.  Only set up if signature is provided.
                if (new File(signaturePath).exists()) {
                    try {
                        VerityUtils.setUpFsverity(filePath, signaturePath);
                    } catch (IOException | DigestException | NoSuchAlgorithmException
                            | SecurityException e) {
                        throw new PrepareFailure(PackageManager.INSTALL_FAILED_BAD_SIGNATURE,
                                "Failed to enable fs-verity: " + e);
                    }
                }
                continue;
            }
            // In legacy mode, fs-verity can only be enabled by process with CAP_SYS_ADMIN.
            final VerityUtils.SetupResult result = VerityUtils.generateApkVeritySetupData(filePath);
            if (result.isOk()) {
                if (Build.IS_DEBUGGABLE) Slog.i(TAG, "Enabling verity to " + filePath);
                final FileDescriptor fd = result.getUnownedFileDescriptor();
                try {
                    mInstaller.installApkVerity(filePath, fd, result.getContentSize());
                    // In legacy mode, fs-verity can only be enabled by process with CAP_SYS_ADMIN.
                    if (legacyMode) {
                    final byte[] rootHash = VerityUtils.generateApkVerityRootHash(filePath);
                    mInstaller.assertFsverityRootHashMatches(filePath, rootHash);
                    }
                } finally {
                    IoUtils.closeQuietly(fd);
                }
+88 −39
Original line number Diff line number Diff line
@@ -36,6 +36,7 @@ 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;
@@ -59,6 +60,8 @@ 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. */
@@ -71,6 +74,48 @@ 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));
        }

        // This can fail if the public key is not already in .fs-verity kernel keyring.
        int errno = enableFsverityNative(filePath);
        if (errno != 0) {
            throw new IOException("Failed to enable fs-verity on " + filePath + ": "
                    + Os.strerror(errno));
        }
    }

    /** Returns whether the file has fs-verity enabled. */
    public static boolean hasFsverity(@NonNull String filePath) {
        // NB: only measure but not check the actual measurement here. As long as this succeeds,
@@ -87,36 +132,18 @@ abstract public class VerityUtils {
    }

    /**
     * Generates Merkle tree and fs-verity metadata.
     * Generates legacy Merkle tree and fs-verity metadata with Signing Block skipped.
     *
     * @return {@code SetupResult} that contains the result code, and when success, the
     *         {@code FileDescriptor} to read all the data from.
     */
    public static SetupResult generateApkVeritySetupData(@NonNull String apkPath,
            String signaturePath, boolean skipSigningBlock) {
    public static SetupResult generateApkVeritySetupData(@NonNull String apkPath) {
        if (DEBUG) {
            Slog.d(TAG, "Trying to install apk verity to " + apkPath + " with signature file "
                    + signaturePath);
            Slog.d(TAG, "Trying to install legacy apk verity to " + apkPath);
        }
        SharedMemory shm = null;
        try {
            byte[] signedVerityHash;
            if (skipSigningBlock) {
                signedVerityHash = ApkSignatureVerifier.getVerityRootHash(apkPath);
            } else {
                Path path = Paths.get(signaturePath);
                if (Files.exists(path)) {
                    // TODO(112037636): fail early if the signing key is not in .fs-verity keyring.
                    PKCS7 pkcs7 = new PKCS7(Files.readAllBytes(path));
                    signedVerityHash = pkcs7.getContentInfo().getContentBytes();
                    if (DEBUG) {
                        Slog.d(TAG, "fs-verity measurement = " + bytesToString(signedVerityHash));
                    }
                } else {
                    signedVerityHash = null;
                }
            }

            final byte[] signedVerityHash = ApkSignatureVerifier.getVerityRootHash(apkPath);
            if (signedVerityHash == null) {
                if (DEBUG) {
                    Slog.d(TAG, "Skip verity tree generation since there is no signed root hash");
@@ -124,8 +151,8 @@ abstract public class VerityUtils {
                return SetupResult.skipped();
            }

            Pair<SharedMemory, Integer> result = generateFsVerityIntoSharedMemory(apkPath,
                    signaturePath, signedVerityHash, skipSigningBlock);
            Pair<SharedMemory, Integer> result =
                    generateFsVerityIntoSharedMemory(apkPath, signedVerityHash);
            shm = result.first;
            int contentSize = result.second;
            FileDescriptor rfd = shm.getFileDescriptor();
@@ -156,7 +183,7 @@ abstract public class VerityUtils {
     * {@see ApkSignatureVerifier#getVerityRootHash(String)}.
     */
    public static byte[] getVerityRootHash(@NonNull String apkPath)
            throws IOException, SignatureNotFoundException, SecurityException {
            throws IOException, SignatureNotFoundException {
        return ApkSignatureVerifier.getVerityRootHash(apkPath);
    }

@@ -172,9 +199,8 @@ abstract public class VerityUtils {
     *         includes SHA-256 of fs-verity descriptor and authenticated extensions.
     */
    private static byte[] generateFsverityMetadata(String filePath, String signaturePath,
            @NonNull TrackedShmBufferFactory trackedBufferFactory)
            throws IOException, SignatureNotFoundException, SecurityException, DigestException,
                   NoSuchAlgorithmException {
            @NonNull ByteBufferFactory trackedBufferFactory)
            throws IOException, DigestException, NoSuchAlgorithmException {
        try (RandomAccessFile file = new RandomAccessFile(filePath, "r")) {
            VerityBuilder.VerityResult result = VerityBuilder.generateFsVerityTree(
                    file, trackedBufferFactory);
@@ -184,6 +210,7 @@ abstract public class VerityUtils {

            final byte[] measurement = generateFsverityDescriptorAndMeasurement(file,
                    result.rootHash, signaturePath, buffer);
            buffer.flip();
            return constructFsveritySignedDataNative(measurement);
        }
    }
@@ -243,6 +270,7 @@ abstract public class VerityUtils {
        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);
@@ -256,18 +284,13 @@ abstract public class VerityUtils {
     * for fsverity setup. The data is aligned to the beginning of {@code SharedMemory}, and has
     * length equals to the returned {@code Integer}.
     */
    private static Pair<SharedMemory, Integer> generateFsVerityIntoSharedMemory(
            String apkPath, String signaturePath, @NonNull byte[] expectedRootHash,
            boolean skipSigningBlock)
            throws IOException, SecurityException, DigestException, NoSuchAlgorithmException,
    private static Pair<SharedMemory, Integer> generateFsVerityIntoSharedMemory(String apkPath,
            @NonNull byte[] expectedRootHash)
            throws IOException, DigestException, NoSuchAlgorithmException,
                   SignatureNotFoundException {
        TrackedShmBufferFactory shmBufferFactory = new TrackedShmBufferFactory();
        byte[] generatedRootHash;
        if (skipSigningBlock) {
            generatedRootHash = ApkSignatureVerifier.generateApkVerity(apkPath, shmBufferFactory);
        } else {
            generatedRootHash = generateFsverityMetadata(apkPath, signaturePath, shmBufferFactory);
        }
        byte[] generatedRootHash =
                ApkSignatureVerifier.generateApkVerity(apkPath, shmBufferFactory);
        // We only generate Merkle tree once here, so it's important to make sure the root hash
        // matches the signed one in the apk.
        if (!Arrays.equals(expectedRootHash, generatedRootHash)) {
@@ -345,7 +368,7 @@ abstract public class VerityUtils {
        private ByteBuffer mBuffer;

        @Override
        public ByteBuffer create(int capacity) throws SecurityException {
        public ByteBuffer create(int capacity) {
            try {
                if (DEBUG) Slog.d(TAG, "Creating shared memory for apk verity");
                // NB: This method is supposed to be called once according to the contract with
@@ -378,4 +401,30 @@ 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;
    }
}
+23 −2
Original line number Diff line number Diff line
@@ -75,6 +75,23 @@ class JavaByteArrayHolder {
    jbyte* mElements;
};

int enableFsverity(JNIEnv* env, jobject /* clazz */, jstring filePath) {
#if HAS_FSVERITY
    const char* path = env->GetStringUTFChars(filePath, nullptr);
    ::android::base::unique_fd rfd(open(path, O_RDONLY | O_CLOEXEC));
    if (rfd.get() < 0) {
      return errno;
    }
    if (ioctl(rfd.get(), FS_IOC_ENABLE_VERITY, nullptr) < 0) {
      return errno;
    }
    return 0;
#else
    LOG_ALWAYS_FATAL("fs-verity is used while not enabled");
    return ENOSYS;
#endif  // HAS_FSVERITY
}

int measureFsverity(JNIEnv* env, jobject /* clazz */, jstring filePath) {
#if HAS_FSVERITY
    auto raii = JavaByteArrayHolder::newArray(env, sizeof(fsverity_digest) + kSha256Bytes);
@@ -82,14 +99,17 @@ int measureFsverity(JNIEnv* env, jobject /* clazz */, jstring filePath) {
    data->digest_size = kSha256Bytes;  // the only input/output parameter

    const char* path = env->GetStringUTFChars(filePath, nullptr);
    ::android::base::unique_fd rfd(open(path, O_RDONLY));
    ::android::base::unique_fd rfd(open(path, O_RDONLY | O_CLOEXEC));
    if (rfd.get() < 0) {
      return errno;
    }
    if (ioctl(rfd.get(), FS_IOC_MEASURE_VERITY, data) < 0) {
      return errno;
    }
    return 0;
#else
    LOG_ALWAYS_FATAL("fs-verity is used while not enabled");
    return -1;
    return ENOSYS;
#endif  // HAS_FSVERITY
}

@@ -172,6 +192,7 @@ jbyteArray constructFsverityFooter(JNIEnv* env, jobject /* clazz */,
}

const JNINativeMethod sMethods[] = {
    { "enableFsverityNative", "(Ljava/lang/String;)I", (void *)enableFsverity },
    { "measureFsverityNative", "(Ljava/lang/String;)I", (void *)measureFsverity },
    { "constructFsveritySignedDataNative", "([B)[B", (void *)constructFsveritySignedData },
    { "constructFsverityDescriptorNative", "(J)[B", (void *)constructFsverityDescriptor },