]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
copy: add support for copying fs-verity status
authorAllison Karlitskaya <allison.karlitskaya@redhat.com>
Thu, 28 Nov 2024 13:06:34 +0000 (14:06 +0100)
committerAllison Karlitskaya <allison.karlitskaya@redhat.com>
Tue, 27 May 2025 06:08:34 +0000 (08:08 +0200)
Add a new member to CopyFlags to request copying of fs-verity status.

If copying of fs-verity is requested then we query the descriptor from
each regular file.  If it has one, we use it to setup fs-verity on the
destination, using the same parameters.

Signatures don't seem to be a particularly well-loved (or used) feature
of fs-verity and we don't bother to query them here.  Support for that
could be added later, if desired.

This change means that, with the correct combination of flags, we might
end up calling `fsync()` on a read-only file descriptor.  This is
permitted by POSIX and supported on Linux.

Nothing uses this yet.

Signed-off-by: Allison Karlitskaya <allison.karlitskaya@redhat.com>
src/basic/include/linux/fsverity.h [new file with mode: 0644]
src/shared/copy.c
src/shared/copy.h

diff --git a/src/basic/include/linux/fsverity.h b/src/basic/include/linux/fsverity.h
new file mode 100644 (file)
index 0000000..bdc2ca6
--- /dev/null
@@ -0,0 +1,96 @@
+/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
+/*
+ * fs-verity user API
+ *
+ * These ioctls can be used on filesystems that support fs-verity.  See the
+ * "User API" section of Documentation/filesystems/fsverity.rst.
+ *
+ * Copyright 2019 Google LLC
+ */
+#ifndef _LINUX_FSVERITY_H
+#define _LINUX_FSVERITY_H
+
+#include <linux/ioctl.h>
+#include <linux/types.h>
+
+#define FS_VERITY_HASH_ALG_SHA256      1
+#define FS_VERITY_HASH_ALG_SHA512      2
+
+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[];
+};
+
+/*
+ * Struct containing a file's Merkle tree properties.  The fs-verity file digest
+ * is the hash of this struct.  A userspace program needs this struct only if it
+ * needs to compute fs-verity file digests itself, e.g. in order to sign files.
+ * It isn't needed just to enable fs-verity on a file.
+ *
+ * Note: when computing the file digest, 'sig_size' and 'signature' must be left
+ * zero and empty, respectively.  These fields are present only because some
+ * filesystems reuse this struct as part of their on-disk format.
+ */
+struct fsverity_descriptor {
+       __u8 version;           /* must be 1 */
+       __u8 hash_algorithm;    /* Merkle tree hash algorithm */
+       __u8 log_blocksize;     /* log2 of size of data and tree blocks */
+       __u8 salt_size;         /* size of salt in bytes; 0 if none */
+       __le32 __reserved_0x04; /* must be 0 */
+       __le64 data_size;       /* size of file the Merkle tree is built over */
+       __u8 root_hash[64];     /* Merkle tree root hash */
+       __u8 salt[32];          /* salt prepended to each hashed block */
+       __u8 __reserved[144];   /* must be 0's */
+};
+
+/*
+ * Format in which fs-verity file digests are signed in built-in signatures.
+ * This is the same as 'struct fsverity_digest', except here some magic bytes
+ * are prepended to provide some context about what is being signed in case the
+ * same key is used for non-fsverity purposes, and here the fields have fixed
+ * endianness.
+ *
+ * This struct is specific to the built-in signature verification support, which
+ * is optional.  fs-verity users may also verify signatures in userspace, in
+ * which case userspace is responsible for deciding on what bytes are signed.
+ * This struct may still be used, but it doesn't have to be.  For example,
+ * userspace could instead use a string like "sha256:$digest_as_hex_string".
+ */
+struct fsverity_formatted_digest {
+       char magic[8];                  /* must be "FSVerity" */
+       __le16 digest_algorithm;
+       __le16 digest_size;
+       __u8 digest[];
+};
+
+#define FS_VERITY_METADATA_TYPE_MERKLE_TREE    1
+#define FS_VERITY_METADATA_TYPE_DESCRIPTOR     2
+#define FS_VERITY_METADATA_TYPE_SIGNATURE      3
+
+struct fsverity_read_metadata_arg {
+       __u64 metadata_type;
+       __u64 offset;
+       __u64 length;
+       __u64 buf_ptr;
+       __u64 __reserved;
+};
+
+#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_IOC_READ_VERITY_METADATA \
+       _IOWR('f', 135, struct fsverity_read_metadata_arg)
+
+#endif /* _LINUX_FSVERITY_H */
index ca2325459d9d1863de4cf31822985a104f784f1a..0c79121b68eb26482d0f4d071c9b00c3a571b655 100644 (file)
@@ -2,6 +2,8 @@
 
 #include <fcntl.h>
 #include <linux/btrfs.h>
+#include <linux/fsverity.h>
+#include <stddef.h>
 #include <stdio.h>
 #include <sys/file.h>
 #include <sys/ioctl.h>
@@ -790,6 +792,64 @@ static int prepare_nocow(int fdf, const char *from, int fdt, unsigned *chattr_ma
         return 0;
 }
 
+/* Copies fs-verity status.  May re-open fdt to do its job. */
+static int copy_fs_verity(int fdf, int *fdt) {
+        int r;
+
+        assert(fdf >= 0);
+        assert(fdt);
+        assert(*fdt >= 0);
+
+        r = fd_verify_regular(fdf);
+        if (r < 0)
+                return r;
+
+        struct fsverity_descriptor desc = {};
+        struct fsverity_read_metadata_arg read_arg = {
+                .metadata_type = FS_VERITY_METADATA_TYPE_DESCRIPTOR,
+                .buf_ptr = (uintptr_t) &desc,
+                .length = sizeof(desc),
+        };
+
+        r = ioctl(fdf, FS_IOC_READ_VERITY_METADATA, &read_arg);
+        if (r < 0) {
+                /* ENODATA means that the file doesn't have fs-verity,
+                 * so the correct thing to do is to do nothing at all. */
+                if (errno == ENODATA)
+                        return 0;
+                return log_error_errno(errno, "Failed to read fs-verity metadata from source file: %m");
+        }
+
+        /* Make sure that the descriptor is completely initialized */
+        assert(r == (int) sizeof desc);
+
+        r = fd_verify_regular(*fdt);
+        if (r < 0)
+                return r;
+
+        /* Okay. We're doing this now. We need to re-open fdt as read-only because
+         * we can't enable fs-verity while writable file descriptors are outstanding. */
+        _cleanup_close_ int reopened_fd = -EBADF;
+        r = fd_reopen_condition(*fdt, O_RDONLY|O_CLOEXEC|O_NOCTTY, O_ACCMODE_STRICT|O_PATH, &reopened_fd);
+        if (r < 0)
+                return r;
+        if (reopened_fd >= 0)
+                close_and_replace(*fdt, reopened_fd);
+
+        struct fsverity_enable_arg enable_arg = {
+                .version = desc.version,
+                .hash_algorithm = desc.hash_algorithm,
+                .block_size = UINT32_C(1) << desc.log_blocksize,
+                .salt_size = desc.salt_size,
+                .salt_ptr = (uintptr_t) &desc.salt,
+        };
+
+        if (ioctl(*fdt, FS_IOC_ENABLE_VERITY, &enable_arg) < 0)
+                return log_error_errno(errno, "Failed to set fs-verity metadata: %m");
+
+        return 0;
+}
+
 static int fd_copy_tree_generic(
                 int df,
                 const char *from,
@@ -874,6 +934,15 @@ static int fd_copy_regular(
                         return r;
         }
 
+        /* NB: fs-verity cannot be enabled when a writable file descriptor is outstanding.
+         * copy_fs_verity() may well re-open 'fdt' as O_RDONLY. All code below this point
+         * needs to be able to work with a read-only file descriptor. */
+        if (FLAGS_SET(copy_flags, COPY_PRESERVE_FS_VERITY)) {
+                r = copy_fs_verity(fdf, &fdt);
+                if (r < 0)
+                        goto fail;
+        }
+
         if (copy_flags & COPY_FSYNC) {
                 if (fsync(fdt) < 0) {
                         r = -errno;
index 3efe877c4b94de5711d21b4aa92cef5a46544ffc..1d17f9b01fa2cf437afe0e20a8eeaa9a6c19d6ff 100644 (file)
@@ -32,6 +32,7 @@ typedef enum CopyFlags {
          * copy because reflinking from COW to NOCOW files is not supported.
          */
         COPY_NOCOW_AFTER                  = 1 << 20,
+        COPY_PRESERVE_FS_VERITY           = 1 << 21, /* Preserve fs-verity when copying. */
 } CopyFlags;
 
 typedef enum DenyType {