diff --git a/src/basic/include/linux/fsverity.h b/src/basic/include/linux/fsverity.h new file mode 100644 index 0000000000..bdc2ca6639 --- /dev/null +++ b/src/basic/include/linux/fsverity.h @@ -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 +#include + +#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 */ diff --git a/src/shared/copy.c b/src/shared/copy.c index ca2325459d..0c79121b68 100644 --- a/src/shared/copy.c +++ b/src/shared/copy.c @@ -2,6 +2,8 @@ #include #include +#include +#include #include #include #include @@ -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; diff --git a/src/shared/copy.h b/src/shared/copy.h index 3efe877c4b..1d17f9b01f 100644 --- a/src/shared/copy.h +++ b/src/shared/copy.h @@ -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 {