Skip to content

!WIP! mkfs: copy verity metadata from the --rootdir #971

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 2 commits into
base: devel
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 6 additions & 4 deletions cmds/filesystem.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
Expand Down
17 changes: 15 additions & 2 deletions mkfs/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -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)"),
Expand Down Expand Up @@ -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();
Expand All @@ -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' },
Expand Down Expand Up @@ -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 },
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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");
Expand Down
162 changes: 159 additions & 3 deletions mkfs/rootdir.c
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
*/

#include "kerncompat.h"
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <sys/xattr.h>
#include <dirent.h>
Expand All @@ -25,6 +26,7 @@
#include <ftw.h>
#include <errno.h>
#include <limits.h>
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#if COMPRESSION_ZSTD
Expand All @@ -35,6 +37,9 @@
#if COMPRESSION_LZO
#include <lzo/lzo1x.h>
#endif
#ifdef HAVE_LINUX_FSVERITY_H
#include <linux/fsverity.h>
#endif
#include "kernel-lib/sizes.h"
#include "kernel-shared/accessors.h"
#include "kernel-shared/uapi/btrfs_tree.h"
Expand Down Expand Up @@ -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)
{
Expand Down Expand Up @@ -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,
Expand All @@ -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);
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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(&current_path.inode_list);

ret = nftw(source_dir, ftw_add_inode, 32, FTW_PHYS);
Expand Down
2 changes: 1 addition & 1 deletion mkfs/rootdir.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down