From 721df6f60d67718c036f8e82d0d5c6d79026ce29 Mon Sep 17 00:00:00 2001 From: Anand Jain Date: Sat, 11 Jan 2025 00:11:35 +0800 Subject: [PATCH 1/2] btrfs-progs: resize: remove the misleading warning for the 'max' option The disk max size cannot be 256MiB because Btrfs does not allow creating a filesystem on disks smaller than 256MiB. Remove the incorrect warning for the 'max' option.` $ btrfs fi resize max /btrfs Resize device id 1 (/dev/sda) from 3.00GiB to max WARNING: the new size 0 (0.00B) is < 256MiB, this may be rejected by kernel Fixes: 158a25af0d61 ("btrfs-progs: fi resize: warn if new size is < 256M") Signed-off-by: Anand Jain Signed-off-by: David Sterba --- cmds/filesystem.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) 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); From 72c3ab803678c6628cd15aa6f348a6d3642ddc28 Mon Sep 17 00:00:00 2001 From: Allison Karlitskaya Date: Mon, 17 Mar 2025 09:05:31 +0100 Subject: [PATCH 2/2] !WIP! mkfs: copy verity metadata from the --rootdir Add a new --fs-verity option. If it's specified, then check if the regular files in the --rootdir have fs-verity enabled, and if they do, copy the metadata into the created filesystem. We could have done this implicitly on creation of all filesystems, but I believe the new --fs-verity option is justified (over simply always checking) for a number of reasons: - (most importantly) if querying the fs-verity metadata on a file fails, we want to hear about it, loudly. Querying fs-verity metadata on btrfs itself is only supported on kernel 6.14 and later, and on ext4 fs-verity isn't enabled by default. We want to catch those cases and treat them as errors instead of silently proceeding. If we were to always query fs-verity metadata then we'd have to ignore the unsupported cases. - tools and scripts invoking mkfs.btrfs and expecting the created filesystem to actually have fs-verity enabled on the relevant files need to be sure of this. We want to fail if the new --fs-verity option isn't present. - most people are probably not interested in fs-verity data, and querying every single file (when no file has fs-verity enabled) would slow down filesystem creation - finally, doing this without an option would be a change in existing behaviour Issue: #929 Signed-off-by: Allison Karlitskaya --- mkfs/main.c | 17 +++++- mkfs/rootdir.c | 162 ++++++++++++++++++++++++++++++++++++++++++++++++- mkfs/rootdir.h | 2 +- 3 files changed, 175 insertions(+), 6 deletions(-) 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);