diff --git a/cmds/filesystem.c b/cmds/filesystem.c index 64846b699..b4179600c 100644 --- a/cmds/filesystem.c +++ b/cmds/filesystem.c @@ -1288,7 +1288,8 @@ static const char * const cmd_filesystem_resize_usage[] = { NULL }; -static int check_resize_args(const char *amount, const char *path, u64 *devid_ret) { +static int check_resize_args(const char *amount, const char *path, u64 *devid_ret) +{ struct btrfs_ioctl_fs_info_args fi_args; struct btrfs_ioctl_dev_info_args *di_args = NULL; int ret, i, dev_idx = -1; @@ -1421,15 +1422,16 @@ static int check_resize_args(const char *amount, const char *path, u64 *devid_re } new_size = round_down(new_size, fi_args.sectorsize); res_str = pretty_size_mode(new_size, UNITS_DEFAULT); + + if (new_size < 256 * SZ_1M) + warning("the new size %lld (%s) is < 256MiB, this may be rejected by kernel", + new_size, pretty_size_mode(new_size, UNITS_DEFAULT)); } pr_verbose(LOG_DEFAULT, "Resize device id %lld (%s) from %s to %s\n", devid, di_args[dev_idx].path, pretty_size_mode(di_args[dev_idx].total_bytes, UNITS_DEFAULT), res_str); - if (new_size < 256 * SZ_1M) - warning("the new size %lld (%s) is < 256MiB, this may be rejected by kernel", - new_size, pretty_size_mode(new_size, UNITS_DEFAULT)); out: free(di_args); diff --git a/mkfs/main.c b/mkfs/main.c index dc73de47a..beae94b85 100644 --- a/mkfs/main.c +++ b/mkfs/main.c @@ -442,6 +442,7 @@ static const char * const mkfs_usage[] = { OPTLINE("-b|--byte-count SIZE", "set size of each device to SIZE (filesystem size is sum of all device sizes)"), OPTLINE("-r|--rootdir DIR", "copy files from DIR to the image root directory, can be combined with --subvol"), OPTLINE("--compress ALGO[:LEVEL]", "compress files by algorithm and level, ALGO can be 'no' (default), zstd, lzo, zlib"), + OPTLINE("--fs-verity", "copy fs-verity metadata from files in the --rootdir"), OPTLINE("", "Built-in:"), #if COMPRESSION_ZSTD OPTLINE("", "- ZSTD: yes (levels 1..15)"), @@ -1206,6 +1207,7 @@ int BOX_MAIN(mkfs)(int argc, char **argv) bool has_default_subvol = false; enum btrfs_compression_type compression = BTRFS_COMPRESS_NONE; unsigned int compression_level = 0; + bool fsverity = false; LIST_HEAD(subvols); cpu_detect_flags(); @@ -1221,6 +1223,7 @@ int BOX_MAIN(mkfs)(int argc, char **argv) GETOPT_VAL_GLOBAL_ROOTS, GETOPT_VAL_DEVICE_UUID, GETOPT_VAL_COMPRESS, + GETOPT_VAL_FS_VERITY, }; static const struct option long_options[] = { { "byte-count", required_argument, NULL, 'b' }, @@ -1249,6 +1252,8 @@ int BOX_MAIN(mkfs)(int argc, char **argv) { "shrink", no_argument, NULL, GETOPT_VAL_SHRINK }, { "compress", required_argument, NULL, GETOPT_VAL_COMPRESS }, + { "fs-verity", no_argument, NULL, + GETOPT_VAL_FS_VERITY }, #if EXPERIMENTAL { "param", required_argument, NULL, GETOPT_VAL_PARAM }, { "num-global-roots", required_argument, NULL, GETOPT_VAL_GLOBAL_ROOTS }, @@ -1377,6 +1382,9 @@ int BOX_MAIN(mkfs)(int argc, char **argv) goto error; } break; + case GETOPT_VAL_FS_VERITY: + fsverity = true; + break; case GETOPT_VAL_DEVICE_UUID: strncpy_null(dev_uuid, optarg, BTRFS_UUID_UNPARSED_SIZE); break; @@ -1453,6 +1461,11 @@ int BOX_MAIN(mkfs)(int argc, char **argv) ret = 1; goto error; } + if (fsverity) { + error("--fs-verity must be used with --rootdir"); + ret = 1; + goto error; + } } list_for_each_entry(rds, &subvols, list) { @@ -2090,8 +2103,8 @@ int BOX_MAIN(mkfs)(int argc, char **argv) } ret = btrfs_mkfs_fill_dir(trans, source_dir, root, - &subvols, compression, - compression_level); + &subvols, fsverity, + compression, compression_level); if (ret) { errno = -ret; error("error while filling filesystem: %m"); diff --git a/mkfs/rootdir.c b/mkfs/rootdir.c index 5f4cfb93c..d7443b96a 100644 --- a/mkfs/rootdir.c +++ b/mkfs/rootdir.c @@ -17,6 +17,7 @@ */ #include "kerncompat.h" +#include #include #include #include @@ -25,6 +26,7 @@ #include #include #include +#include #include #include #if COMPRESSION_ZSTD @@ -35,6 +37,9 @@ #if COMPRESSION_LZO #include #endif +#ifdef HAVE_LINUX_FSVERITY_H +#include +#endif #include "kernel-lib/sizes.h" #include "kernel-shared/accessors.h" #include "kernel-shared/uapi/btrfs_tree.h" @@ -157,6 +162,7 @@ static u64 next_subvol_id = BTRFS_FIRST_FREE_OBJECTID; static u64 default_subvol_id; static enum btrfs_compression_type g_compression; static u64 g_compression_level; +static bool g_fsverity_enabled = true; static inline struct inode_entry *rootdir_path_last(struct rootdir_path *path) { @@ -1068,6 +1074,142 @@ static ssize_t zstd_compress_inline_extent(char *buf, u64 size, char **comp_buf) } #endif +static void hd(const char *id, int ofs, char *buf, int len) { + printf("HEX %s/%d (%d bytes)", id, ofs, len); + for (int i = 0; i < len; i++) + printf(" %02x", (unsigned char) buf[i]); + printf("\n"); +} + +#ifdef HAVE_LINUX_FSVERITY_H +/* Read as much data as possible into source->buf. Returns how many + * bytes were read. If the return value is less than MAX_EXTENT_SIZE + * then we've definitely hit the end, but if it's equal, then another + * call is required to check. + */ +static ssize_t read_fsverity_metadata(const struct source_descriptor *source, + uint32_t metadata_type, size_t start_offset) +{ + size_t n_bytes = 0; + + while (n_bytes < MAX_EXTENT_SIZE) { + struct fsverity_read_metadata_arg arg = { + .metadata_type = metadata_type, + .buf_ptr = (uint64_t) &source->buf[n_bytes], + .length = MAX_EXTENT_SIZE - n_bytes, + .offset = start_offset + n_bytes, + }; + ssize_t ret; + + ret = ioctl(source->fd, FS_IOC_READ_VERITY_METADATA, &arg); + if (ret < 0) { + if (errno == EINTR) + continue; + return -errno; + } else if (ret == 0) { + break; + } else { + hd("ioctl", arg.offset, (char*) arg.buf_ptr, ret); + n_bytes += ret; + } + } + + hd("RA", 0, source->buf, n_bytes); + return n_bytes; +} + +static int add_file_item_fsverity(struct btrfs_trans_handle *trans, + struct btrfs_root *root, + struct btrfs_inode_item *btrfs_inode, + u64 objectid, + const struct source_descriptor *source) +{ + int ret; + ssize_t s; + + /* First check for a descriptor */ + s = read_fsverity_metadata(source, FS_VERITY_METADATA_TYPE_DESCRIPTOR, 0); + if (s < 0) { + /* ENODATA means that the file doesn't have fs-verity + * enabled, so we don't need to do anything. */ + if (errno == ENODATA) + return 0; + error("cannot read fs-verity descriptor: %m"); + return s; + } else if (s == MAX_EXTENT_SIZE) { + error("fs-verity descriptor is implausibly large"); + return -E2BIG; + } + + /* Otherwise we have a descriptor. Write the header and the + * descriptor. */ + struct btrfs_key key = { + .objectid = objectid, + .type = BTRFS_VERITY_DESC_ITEM_KEY, + .offset = 0, + }; + struct btrfs_verity_descriptor_item descr_item = { }; + btrfs_set_stack_verity_descriptor_size(&descr_item, s); + ret = btrfs_insert_item(trans, root, &key, &descr_item, sizeof descr_item); + if (ret) + return ret; + + key.offset = 1; + ret = btrfs_insert_item(trans, root, &key, source->buf, s); + if (ret) + return ret; + + assert (fs_block_size == 4096); + + /* Now try to copy over the Merkle tree content. 'key.offset' + * must always be a multiple of the block size of the created + * filesystem. It is used both as an offset into the fs-verity + * data and also as the item index. + */ + key.type = BTRFS_VERITY_MERKLE_ITEM_KEY; + key.offset = 0; + do { + s = read_fsverity_metadata(source, + FS_VERITY_METADATA_TYPE_MERKLE_TREE, + key.offset); + if (s < 0) + return s; + if (s % fs_block_size != 0) { + error("read invalid partial fs-verity merkle tree block"); + return -EINVAL; + } + + /* We store a page at a time */ + for (int i = 0; i < s; i += fs_block_size) { + hd("insert", key.offset, source->buf + i, fs_block_size); + ret = btrfs_insert_item(trans, root, &key, + source->buf + i, fs_block_size); + if (ret < 0) + return ret; + key.offset += fs_block_size; + } + } while (s == MAX_EXTENT_SIZE); + + /* Set the correct flag on the inode. + * + * In btrfs_inode_item the flags and ro_flags fields from the + * inode are combined into a single 64bit 'flags' field with the + * ro_flags occupying the top 32 bits. Make sure we shift it. + */ + u64 flags = btrfs_stack_inode_flags(btrfs_inode); + flags |= ((uint64_t) BTRFS_INODE_RO_VERITY) << 32; + btrfs_set_stack_inode_flags(btrfs_inode, flags); + + /* Ensure the feature flag is set on the superblock as well... */ + u64 features = btrfs_super_compat_ro_flags(trans->fs_info->super_copy); + features |= BTRFS_FEATURE_COMPAT_RO_VERITY; + btrfs_set_super_compat_ro_flags(trans->fs_info->super_copy, features); + + /* And done! */ + return 0; +} +#endif + static int add_file_items(struct btrfs_trans_handle *trans, struct btrfs_root *root, struct btrfs_inode_item *btrfs_inode, u64 objectid, @@ -1082,7 +1224,7 @@ static int add_file_items(struct btrfs_trans_handle *trans, struct source_descriptor source; int fd; - if (st->st_size == 0) + if (st->st_size == 0) // TODO: may want to fs-verity the empty file anyway return 0; fd = open(path_name, O_RDONLY); @@ -1223,11 +1365,24 @@ static int add_file_items(struct btrfs_trans_handle *trans, source.comp_buf = comp_buf; source.wrkmem = wrkmem; + if (g_fsverity_enabled) { +#ifdef HAVE_LINUX_FSVERITY_H + ret = add_file_item_fsverity(trans, root, btrfs_inode, objectid, + &source); +#else + error("verity support not compiled in"); + ret = -EINVAL; +#endif + if (ret < 0) + goto end; + } + + while (file_pos < st->st_size) { ret = add_file_item_extent(trans, root, btrfs_inode, objectid, &source, file_pos); if (ret < 0) - break; + goto end; file_pos += ret; } @@ -1640,7 +1795,7 @@ static int set_default_subvolume(struct btrfs_trans_handle *trans) int btrfs_mkfs_fill_dir(struct btrfs_trans_handle *trans, const char *source_dir, struct btrfs_root *root, struct list_head *subvols, - enum btrfs_compression_type compression, + bool fsverity, enum btrfs_compression_type compression, unsigned int compression_level) { int ret; @@ -1688,6 +1843,7 @@ int btrfs_mkfs_fill_dir(struct btrfs_trans_handle *trans, const char *source_dir g_subvols = subvols; g_compression = compression; g_compression_level = compression_level; + g_fsverity_enabled = fsverity; INIT_LIST_HEAD(¤t_path.inode_list); ret = nftw(source_dir, ftw_add_inode, 32, FTW_PHYS); diff --git a/mkfs/rootdir.h b/mkfs/rootdir.h index b32fda5bf..b5026cd32 100644 --- a/mkfs/rootdir.h +++ b/mkfs/rootdir.h @@ -47,7 +47,7 @@ struct rootdir_subvol { int btrfs_mkfs_fill_dir(struct btrfs_trans_handle *trans, const char *source_dir, struct btrfs_root *root, struct list_head *subvols, - enum btrfs_compression_type compression, + bool fsverity, enum btrfs_compression_type compression, unsigned int compression_level); u64 btrfs_mkfs_size_dir(const char *dir_name, u32 sectorsize, u64 min_dev_size, u64 meta_profile, u64 data_profile);