From 339857c2f66bf0ae76cb96e273ff4f6760dd73f8 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Fri, 22 Sep 2023 11:55:20 +0200 Subject: [PATCH 1/4] Drop LCFS_MOUNT_FLAGS_DISABLE_VERITY During upstream overlayfs work, the default for verity xattr was changed to verity=off, so there is no need to specifically have a wey to disable it. All that results in is mount error on kernels that don't support verity=. So, we just support ENABLE_VERITY which implies verity=require, which is the thing we want anyway. Signed-off-by: Alexander Larsson --- libcomposefs/lcfs-mount.c | 10 +--------- libcomposefs/lcfs-mount.h | 1 - tools/mountcomposefs.c | 11 +---------- 3 files changed, 2 insertions(+), 20 deletions(-) diff --git a/libcomposefs/lcfs-mount.c b/libcomposefs/lcfs-mount.c index 0a4b08fd..cf7155e9 100644 --- a/libcomposefs/lcfs-mount.c +++ b/libcomposefs/lcfs-mount.c @@ -216,11 +216,6 @@ static int lcfs_validate_mount_options(struct lcfs_mount_state_s *state) return -EINVAL; } - if ((options->flags & LCFS_MOUNT_FLAGS_REQUIRE_VERITY) && - (options->flags & LCFS_MOUNT_FLAGS_DISABLE_VERITY)) { - return -EINVAL; /* Can't have both */ - } - if (options->n_objdirs == 0) return -EINVAL; @@ -435,14 +430,12 @@ static int lcfs_mount_erofs_ovl(struct lcfs_mount_state_s *state, const char *lowerdir_target = NULL; int loopfd; bool require_verity; - bool disable_verity; bool readonly; int mount_flags; image_flags = lcfs_u32_from_file(header->flags); require_verity = (options->flags & LCFS_MOUNT_FLAGS_REQUIRE_VERITY) != 0; - disable_verity = (options->flags & LCFS_MOUNT_FLAGS_DISABLE_VERITY) != 0; readonly = (options->flags & LCFS_MOUNT_FLAGS_READONLY) != 0; loopfd = setup_loopback(state->fd, state->image_path, loopname); @@ -509,8 +502,7 @@ static int lcfs_mount_erofs_ovl(struct lcfs_mount_state_s *state, lowerdir_target, upperdir ? ",upperdir=" : "", upperdir ? upperdir : "", workdir ? ",workdir=" : "", workdir ? workdir : "", - require_verity ? ",verity=require" : - (disable_verity ? ",verity=off" : "")); + require_verity ? ",verity=require" : ""); if (res < 0) { res = -ENOMEM; goto fail; diff --git a/libcomposefs/lcfs-mount.h b/libcomposefs/lcfs-mount.h index d8e837b1..ad430989 100644 --- a/libcomposefs/lcfs-mount.h +++ b/libcomposefs/lcfs-mount.h @@ -36,7 +36,6 @@ enum lcfs_mount_flags_t { LCFS_MOUNT_FLAGS_REQUIRE_VERITY = (1 << 0), LCFS_MOUNT_FLAGS_READONLY = (1 << 1), LCFS_MOUNT_FLAGS_IDMAP = (1 << 3), - LCFS_MOUNT_FLAGS_DISABLE_VERITY = (1 << 4), LCFS_MOUNT_FLAGS_MASK = (1 << 5) - 1, }; diff --git a/tools/mountcomposefs.c b/tools/mountcomposefs.c index 6f980052..784feed1 100644 --- a/tools/mountcomposefs.c +++ b/tools/mountcomposefs.c @@ -117,7 +117,6 @@ int main(int argc, char **argv) const char *opt_upperdir = NULL; const char *opt_workdir = NULL; bool opt_verity = false; - bool opt_noverity = false; bool opt_ro = false; int opt, fd, res, userns_fd; @@ -170,8 +169,6 @@ int main(int argc, char **argv) opt_digest = value; } else if (strcmp("verity", key) == 0) { opt_verity = true; - } else if (strcmp("noverity", key) == 0) { - opt_noverity = true; } else if (strcmp("upperdir", key) == 0) { if (value == NULL) printexit("No value specified for upperdir option\n"); @@ -229,14 +226,8 @@ int main(int argc, char **argv) options.expected_fsverity_digest = opt_digest; - if (opt_verity && opt_noverity) { - printexit("Incompatible options verity, noverity\n"); - } - - if (opt_verity) + if (opt_verity || opt_digest != NULL) options.flags |= LCFS_MOUNT_FLAGS_REQUIRE_VERITY; - if (opt_noverity) - options.flags |= LCFS_MOUNT_FLAGS_DISABLE_VERITY; if (opt_ro) options.flags |= LCFS_MOUNT_FLAGS_READONLY; From cf6368d1e1a9361e9629d5f22255540d71081cf6 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Fri, 22 Sep 2023 11:58:32 +0200 Subject: [PATCH 2/4] simplify mkcomposefs Simplifies the mkcomposefs arguments to always compute digest and by-digest payloads, dropping the --absolute option and related things, as we couldn't think of a usecase for it right now. If we have a more precise usecase later we can add it back in a way that makes sense for the usecase. Signed-off-by: Alexander Larsson --- tests/integration.sh | 4 +- tools/mkcomposefs.c | 173 +++++-------------------------------------- 2 files changed, 22 insertions(+), 155 deletions(-) diff --git a/tests/integration.sh b/tests/integration.sh index 5973d23a..52ec1f9f 100755 --- a/tests/integration.sh +++ b/tests/integration.sh @@ -16,7 +16,7 @@ run_test() { local dumpdir_args="$2" mkcomposefs --print-digest --digest-store=${cfsroot}/objects ${testsrc} ${cfsroot}/roots/test.cfs | tee digest.txt prev_digest=$(cat digest.txt) - new_digest=$(mkcomposefs --by-digest --print-digest-only ${testsrc}) + new_digest=$(mkcomposefs --print-digest-only ${testsrc}) test "$prev_digest" = "$new_digest" if which fsck.erofs &>/dev/null; then @@ -36,7 +36,7 @@ run_test() { exit 1 fi - new_digest=$(mkcomposefs --by-digest --print-digest-only mnt) + new_digest=$(mkcomposefs --print-digest-only mnt) test "$prev_digest" = "$new_digest" umount mnt diff --git a/tools/mkcomposefs.c b/tools/mkcomposefs.c index c6267814..ec3fdc95 100644 --- a/tools/mkcomposefs.c +++ b/tools/mkcomposefs.c @@ -49,21 +49,6 @@ static void digest_to_string(const uint8_t *csum, char *buf) buf[j] = '\0'; } -static void digest_to_path(const uint8_t *csum, char *buf) -{ - static const char hexchars[] = "0123456789abcdef"; - uint32_t i, j; - - for (i = 0, j = 0; i < LCFS_DIGEST_SIZE; i++, j += 2) { - uint8_t byte = csum[i]; - if (i == 1) - buf[j++] = '/'; - buf[j] = hexchars[byte >> 4]; - buf[j + 1] = hexchars[byte & 0xF]; - } - buf[j] = '\0'; -} - static int ensure_dir(const char *path, mode_t mode) { struct stat buf; @@ -284,67 +269,36 @@ static int copy_file_with_dirs_if_needed(const char *src, const char *dst_base, return 0; } -static int fill_payload(struct lcfs_node_s *node, const char *path, size_t len, - size_t path_start_offset, bool by_digest, - const char *digest_store_path) +static int fill_store(struct lcfs_node_s *node, const char *path, + const char *digest_store_path) { cleanup_free char *tmp_path = NULL; const char *fname; int ret; fname = lcfs_node_get_name(node); - if (fname) { ret = join_paths(&tmp_path, path, fname); if (ret < 0) return ret; - len += ret; path = tmp_path; } if (lcfs_node_dirp(node)) { - size_t i, n_children; - - n_children = lcfs_node_get_n_children(node); - for (i = 0; i < n_children; i++) { + size_t n_children = lcfs_node_get_n_children(node); + for (size_t i = 0; i < n_children; i++) { struct lcfs_node_s *child = lcfs_node_get_child(node, i); - ret = fill_payload(child, path, len, path_start_offset, - by_digest, digest_store_path); + ret = fill_store(child, path, digest_store_path); if (ret < 0) return ret; } - } else if ((lcfs_node_get_mode(node) & S_IFMT) == S_IFLNK) { - char target[PATH_MAX + 1]; - ssize_t s = readlink(path, target, sizeof(target)); - if (s < 0) - return s; - - target[s] = '\0'; - ret = lcfs_node_set_payload(node, target); - if (ret < 0) - return ret; } else if ((lcfs_node_get_mode(node) & S_IFMT) == S_IFREG && - lcfs_node_get_content(node) == NULL) { - const uint8_t *digest = NULL; - - if (by_digest) - digest = lcfs_node_get_fsverity_digest(node); - - if (digest) { /* Zero size files don't have a digest (since they are non-backed */ - char digest_path[LCFS_DIGEST_SIZE * 2 + 2]; - digest_to_path(digest, digest_path); + lcfs_node_get_content(node) == NULL && + lcfs_node_get_payload(node) != NULL) { + const char *payload = lcfs_node_get_payload(node); - if (digest_store_path) { - ret = copy_file_with_dirs_if_needed( - path, digest_store_path, digest_path, true); - if (ret < 0) - return ret; - } - - ret = lcfs_node_set_payload(node, digest_path); - } else { - ret = lcfs_node_set_payload(node, path + path_start_offset); - } + ret = copy_file_with_dirs_if_needed(path, digest_store_path, + payload, true); if (ret < 0) return ret; } @@ -359,15 +313,11 @@ static void usage(const char *argv0) argv0); } -#define OPT_ABSOLUTE 100 #define OPT_SKIP_XATTRS 102 #define OPT_USE_EPOCH 103 #define OPT_SKIP_DEVICES 104 -#define OPT_COMPUTE_DIGEST 106 -#define OPT_BY_DIGEST 107 #define OPT_DIGEST_STORE 108 #define OPT_PRINT_DIGEST 109 -#define OPT_FORMAT 110 #define OPT_PRINT_DIGEST_ONLY 111 #define OPT_USER_XATTRS 112 @@ -381,12 +331,6 @@ static ssize_t write_cb(void *_file, void *buf, size_t count) int main(int argc, char **argv) { const struct option longopts[] = { - { - name: "absolute", - has_arg: no_argument, - flag: NULL, - val: OPT_ABSOLUTE - }, { name: "skip-xattrs", has_arg: no_argument, @@ -411,18 +355,6 @@ int main(int argc, char **argv) flag: NULL, val: OPT_USE_EPOCH }, - { - name: "compute-digest", - has_arg: no_argument, - flag: NULL, - val: OPT_COMPUTE_DIGEST - }, - { - name: "by-digest", - has_arg: no_argument, - flag: NULL, - val: OPT_BY_DIGEST - }, { name: "digest-store", has_arg: required_argument, @@ -441,33 +373,26 @@ int main(int argc, char **argv) flag: NULL, val: OPT_PRINT_DIGEST_ONLY }, - { - name: "format", - has_arg: required_argument, - flag: NULL, - val: OPT_FORMAT - }, {}, }; struct lcfs_write_options_s options = { 0 }; const char *bin = argv[0]; int buildflags = 0; - bool absolute_path = false; - bool by_digest = false; bool print_digest = false; bool print_digest_only = false; - const char *format = "erofs"; struct lcfs_node_s *root; const char *out = NULL; const char *dir_path = NULL; const char *digest_store_path = NULL; - ssize_t path_start_offset; cleanup_free char *pathbuf = NULL; uint8_t digest[LCFS_DIGEST_SIZE]; int opt; FILE *out_file; char *failed_path; + /* We always compute the digest and reference by digest */ + buildflags |= LCFS_BUILD_COMPUTE_DIGEST | LCFS_BUILD_BY_DIGEST; + while ((opt = getopt_long(argc, argv, ":CR", longopts, NULL)) != -1) { switch (opt) { case OPT_USE_EPOCH: @@ -482,21 +407,9 @@ int main(int argc, char **argv) case OPT_SKIP_DEVICES: buildflags |= LCFS_BUILD_SKIP_DEVICES; break; - case OPT_COMPUTE_DIGEST: - buildflags |= LCFS_BUILD_COMPUTE_DIGEST; - break; - case OPT_ABSOLUTE: - absolute_path = true; - break; - case OPT_BY_DIGEST: - by_digest = true; - break; case OPT_DIGEST_STORE: digest_store_path = optarg; break; - case OPT_FORMAT: - format = optarg; - break; case OPT_PRINT_DIGEST: print_digest = true; break; @@ -522,6 +435,9 @@ int main(int argc, char **argv) } dir_path = argv[0]; + if (dir_path[0] == '\0') + error(EXIT_FAILURE, 0, "Empty source path specified"); + if (argc > 2) { fprintf(stderr, "Too many arguments\n"); usage(bin); @@ -545,16 +461,6 @@ int main(int argc, char **argv) assert(out || print_digest_only); - if (digest_store_path != NULL) - by_digest = true; /* implied */ - - if (by_digest) - buildflags |= LCFS_BUILD_COMPUTE_DIGEST; /* implied */ - - if (absolute_path && by_digest) - error(EXIT_FAILURE, 0, - "Can't specify both --absolute and --by-digest"); - if (print_digest_only) { out_file = NULL; } else if (strcmp(out, "-") == 0) { @@ -571,49 +477,8 @@ int main(int argc, char **argv) if (root == NULL) error(EXIT_FAILURE, errno, "error accessing %s", failed_path); - if (absolute_path) { - cleanup_free char *cwd_cleanup = NULL; - cleanup_free char *tmp_pathbuf = NULL; - cleanup_free char *absolute_prefix = NULL; - const char *cwd = ""; - int r; - - if (dir_path[0] != '/') { - cwd = cwd_cleanup = get_current_dir_name(); - if (cwd == NULL) - error(EXIT_FAILURE, errno, - "retrieve current working directory"); - } - (void)cwd_cleanup; // This is just used for cleanup - r = join_paths(&tmp_pathbuf, cwd, dir_path); - if (r < 0) - error(EXIT_FAILURE, errno, "compute directory path"); - - absolute_prefix = canonicalize_file_name(tmp_pathbuf); - if (absolute_prefix == NULL) - error(EXIT_FAILURE, errno, "failed to canonicalize %s", - tmp_pathbuf); - - r = join_paths(&pathbuf, absolute_prefix, ""); - if (r < 0) - error(EXIT_FAILURE, errno, "compute directory path"); - path_start_offset = 0; - } else { - if (dir_path[0] == '\0') - error(EXIT_FAILURE, 0, "invalid directory path"); - path_start_offset = join_paths(&pathbuf, dir_path, ""); - if (path_start_offset < 0) - error(EXIT_FAILURE, errno, "compute directory path"); - } - if (fill_payload(root, pathbuf, strlen(pathbuf), path_start_offset, - by_digest, digest_store_path) < 0) - error(EXIT_FAILURE, errno, "cannot fill payload"); - - if (strcmp(format, "erofs") == 0) { - options.format = LCFS_FORMAT_EROFS; - } else { - error(EXIT_FAILURE, errno, "Unknown format %s", format); - } + if (digest_store_path && fill_store(root, dir_path, digest_store_path) < 0) + error(EXIT_FAILURE, errno, "cannot fill store"); if (out_file) { options.file = out_file; @@ -622,6 +487,8 @@ int main(int argc, char **argv) if (print_digest) options.digest_out = digest; + options.format = LCFS_FORMAT_EROFS; + if (lcfs_write_to(root, &options) < 0) error(EXIT_FAILURE, errno, "cannot write file"); From 3b7f450dced1c5f09844feb50acd953bcbd5f11b Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Fri, 22 Sep 2023 12:00:08 +0200 Subject: [PATCH 3/4] Update usage and manpage for mkcomposefs Signed-off-by: Alexander Larsson --- man/mkcomposefs.md | 37 +++++++++++++++++++++++++++++++------ tools/mkcomposefs.c | 12 ++++++++++-- 2 files changed, 41 insertions(+), 8 deletions(-) diff --git a/man/mkcomposefs.md b/man/mkcomposefs.md index aa22228c..51fd6c7a 100644 --- a/man/mkcomposefs.md +++ b/man/mkcomposefs.md @@ -9,11 +9,13 @@ mkcomposefs - create a composefs filesystem image # DESCRIPTION -The composefs project uses EROFS to store metadata, and a distinct -underlying backing store for regular files. +The composefs project uses EROFS image file to store metadata, and one +or more separate directories containing content-addressed backing data +for regular files. **mkcomposefs** constructs the mountable "composefs image" using the -source directory as input. +source directory as input. It can also create the backing store +directory. # OPTIONS @@ -23,14 +25,37 @@ will be a mountable composefs image. **mkcomposefs** accepts the following options: + **\-\-digest-store**=*PATH* -: This path will become a composefs "object store". Non-empty regular files - in the *SOURCEDIR* will be copied (reflinked if possible) into this target - directory, named after their fsverity digest. +: This path will become a composefs "object store". Non-empty + regular files in the *SOURCEDIR* will be copied (reflinked if + possible) into this target directory, named after their fsverity + digest. If possible, the added files will have fs-verity enabled. + + This directory should be passed to the basedir option when you + mount the image. **\-\-print-digest** : Print the fsverity digest of the composefs metadata file. +**\-\-print-digest-only** +: Print the fsverity digest of the composefs metadata file, but + don't write the image. If this is passed, the *IMAGE* argument should + be left out. + +**\-\-use-epoch** +: Use a zero time (unix epoch) as the modification time for all files. + +**\-\-skip-devices** +: Don't add device nodes to the image. + +**\-\-skip-xattrs** +: Don't add xattrs to files in the image. + +**\-\-user-xattrs** +: Only add xattrs with the "user." prefix to files in the image. + + # SEE ALSO - [composefs upstream](https://github.com/containers/composefs) diff --git a/tools/mkcomposefs.c b/tools/mkcomposefs.c index ec3fdc95..d39f9fd5 100644 --- a/tools/mkcomposefs.c +++ b/tools/mkcomposefs.c @@ -308,9 +308,17 @@ static int fill_store(struct lcfs_node_s *node, const char *path, static void usage(const char *argv0) { + const char *bin = basename(argv0); fprintf(stderr, - "usage: %s [--use-epoch] [--skip-xattrs] [--absolute] [--by-digest] [--digest-store=path] [--print-digest] [--print-digest-only] [--skip-devices] [--compute-digest] SOURCEDIR IMAGE\n", - argv0); + "Usage: %s [OPTIONS] SOURCEDIR IMAGE\n" + "Options:\n" + " --digest-store=PATH Store content files in this directory\n" + " --use-epoch Make all mtimes zero\n" + " --skip-xattrs Don't store file xattrs\n" + " --user-xattrs Only store user.* xattrs\n" + " --print-digest Print the digest of the image\n" + " --print-digest-only Print the digest of the image, don't write image\n", + bin); } #define OPT_SKIP_XATTRS 102 From ce58c0d17642f1211fd2e65e9a5698daccc6e199 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Fri, 22 Sep 2023 12:00:25 +0200 Subject: [PATCH 4/4] Update manpage and usage for mount.composefs Signed-off-by: Alexander Larsson --- man/mount.composefs.md | 50 +++++++++++++++++++++++++++++++++++------- tools/mountcomposefs.c | 16 ++++++++++++-- 2 files changed, 56 insertions(+), 10 deletions(-) diff --git a/man/mount.composefs.md b/man/mount.composefs.md index 24c0df74..c5c83ca7 100644 --- a/man/mount.composefs.md +++ b/man/mount.composefs.md @@ -5,27 +5,61 @@ mount.composefs - mount a composefs filesystem image # SYNOPSIS -**mount.composefs** *IMAGE* *TARGETDIR* +**mount.composefs** [-o OPTIONS] *IMAGE* *TARGETDIR* # DESCRIPTION -The composefs project uses EROFS to store metadata, and a distinct -underlying backing store for regular files. At runtime, composefs -uses `overlayfs` on top of a loopback mount. +The composefs project uses EROFS image file to store metadata, and one +or more separate directories containing content-addressed backing data +for regular files. -**mount.composefs** +**mount.composefs** mounts such an EROFS file in combination with a given +set of basedir at the specified location. It can be called directly, or +as a mount helper by running `mount -t composefst ...`. # OPTIONS The provided *IMAGE* argument must be a valid composefs (EROFS) metadata image. The *TARGETDIR* will be used as a mount target. -**mount.composefs** accepts the following options: +**mount.composefs** accepts the following colon-separated mount +options when passed via the `-o OPTIONS` argument. -**\-\-basedir**=*PATH* +**basedir**=*PATH* : This path will be used to resolve non-empty file references stored in the composefs metadata image. A primary use case is to have - this be the same path provided to `mkcomposefs --digest-store`. + this be the same path provided to `mkcomposefs --digest-store=PATH`. + + Multiple paths can be specified, separated by `:`. + +**digest**=*DIGEST* +: The image file is validated to have the specified fs-verity digest + before being used. This allows a chain of trust the ensures only + the expected data is ever visible in the mount. + + This option also implies **verity**. + +**verity** +: If this is specified, all files in the *IMAGE* must specify an fs-verity + digest, and all the files in the base dirs must have a matching fs-verity + digest. + + Note: This needs support for the overlayfs "verity" option in the + kernel, which was added in 6.6rc1. + +**ro** +: Mounts the filesystem read-only. This is mainly useful when using + **upperdir** as unlayered composefs images are naturally readonly. + +**rw** +: Overrides a previous **ro** option + +**upperdir** +: Specify an upper dir in the overlayfs mount that composefs uses. This allows + a writable layer on top of the composefs image. See overlayfs docs for details. + +**workdir** +: Specifies an overlayfs workdir to go with **upperdir**. # SEE ALSO diff --git a/tools/mountcomposefs.c b/tools/mountcomposefs.c index 784feed1..931fbc71 100644 --- a/tools/mountcomposefs.c +++ b/tools/mountcomposefs.c @@ -51,11 +51,23 @@ static void printexit(const char *format, ...) static void usage(const char *argv0) { + const char *bin = basename(argv0); fprintf(stderr, "usage: %s [-t type] [-o opt[,opts..]] IMAGE MOUNTPOINT\n" "Example:\n" - " %s -o basedir=/composefs/objects exampleimage.cfs /mnt/exampleimage\n", - argv0, argv0); + " %s -o basedir=/composefs/objects exampleimage.cfs /mnt/exampleimage\n" + "or, as a mount helper:\n" + " mount -t composefs -o basedir=/composefs/objects exampleimage.cfs /mnt/exampleimage\n" + "\n" + "Supported options:\n" + " basedir=PATH[:PATH] Specify location of basedir(s)\n" + " digest=DIGEST Specify required image digest\n" + " verity Require all files to have specified and valid fs-verity digests\n" + " ro Read only\n" + " rw Read/write\n" + " upperdir Overlayfs upperdir\n" + " workdir Overlayfs workdir\n", + bin, bin); } static void unescape_option(char *s)