From e2956e2c08fb3e11e249b540db3a5b760dd04fac Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Thu, 9 Jun 2022 17:15:29 +0200 Subject: [PATCH] lib: Add (private) API for checking out commits into a composefs image This supports checking out a commit into a tree which is then converted into a composefs image containing fs-verity digests for all the regular files, and payloads that are relative to a the `repo/objects` directory of a bare ostree repo. Some specal files are always created in the image. This ensures that various directories (usr, etc, boot, var, sysroot) exists in the created image, even if they were not in the source commit. These are needed (as bindmount targets) if you want to boot from the image. In the non-composefs case these are just created as needed in the checked out deploydir, but we can't do that here. This is all controlled by the new ex-integrity config section, which has the following layout: ``` [ex-integrity] fsverity=yes/no/maybe composefs=yes/no/maybe composefs-apply-sig=yes/no composefs-add-metadata=yes/no composefs-keyfiile=/a/path composefs-certfile=/a/path ``` The `fsverity` key overrides the old `ex-fsverity` section if specified. The default for all these is for the new behaviour to be disabled. Additionally, enabling composefs implies fsverity defaults to `maybe`, to avoid having to set both. --- Makefile-libostree.am | 6 + configure.ac | 29 +- src/libostree/ostree-repo-composefs.c | 560 ++++++++++++++++++++++++++ src/libostree/ostree-repo-private.h | 21 + src/libostree/ostree-repo-verity.c | 54 ++- src/libostree/ostree-repo.c | 3 + 6 files changed, 658 insertions(+), 15 deletions(-) create mode 100644 src/libostree/ostree-repo-composefs.c diff --git a/Makefile-libostree.am b/Makefile-libostree.am index d18714ae..0e984c7d 100644 --- a/Makefile-libostree.am +++ b/Makefile-libostree.am @@ -87,6 +87,7 @@ libostree_1_la_SOURCES = \ src/libostree/ostree-repo.c \ src/libostree/ostree-repo-checkout.c \ src/libostree/ostree-repo-commit.c \ + src/libostree/ostree-repo-composefs.c \ src/libostree/ostree-repo-pull.c \ src/libostree/ostree-repo-pull-private.h \ src/libostree/ostree-repo-pull-verify.c \ @@ -266,6 +267,11 @@ libostree_1_la_CFLAGS += $(OT_DEP_LIBSODIUM_CFLAGS) libostree_1_la_LIBADD += $(OT_DEP_LIBSODIUM_LIBS) endif # USE_LIBSODIUM +if USE_COMPOSEFS +libostree_1_la_CFLAGS += $(OT_DEP_COMPOSEFS_CFLAGS) +libostree_1_la_LIBADD += $(OT_DEP_COMPOSEFS_LIBS) +endif # USE_COMPOSEFS + # XXX: work around clang being passed -fstack-clash-protection which it doesn't understand # See: https://bugzilla.redhat.com/show_bug.cgi?id=1672012 INTROSPECTION_SCANNER_ENV = CC=gcc diff --git a/configure.ac b/configure.ac index ce449cb0..95f0047d 100644 --- a/configure.ac +++ b/configure.ac @@ -280,6 +280,32 @@ AS_IF([test x$have_gpgme = xyes], ) AM_CONDITIONAL(USE_GPGME, test "x$have_gpgme" = xyes) +COMPOSEFS_DEPENDENCY="composefs >= 0.1.2" +AC_ARG_WITH(composefs, + AS_HELP_STRING([--without-composefs], [Do not use composefs]), + :, with_composefs=maybe) + +AS_IF([ test x$with_composefs != xno ], [ + AC_MSG_CHECKING([for $COMPOSEFS_DEPENDENCY]) + PKG_CHECK_EXISTS($COMPOSEFS_DEPENDENCY, have_composefs=yes, have_composefs=no) + AC_MSG_RESULT([$have_composefs]) + AS_IF([ test x$have_composefs = xno && test x$with_composefs != xmaybe ], [ + AC_MSG_ERROR([composefs is enabled but could not be found]) + ]) + AS_IF([ test x$have_composefs = xyes], [ + AC_DEFINE([HAVE_COMPOSEFS], 1, [Define if we have composefs.pc]) + PKG_CHECK_MODULES(OT_DEP_COMPOSEFS, $COMPOSEFS_DEPENDENCY) + save_LIBS=$LIBS + LIBS=$OT_DEP_COMPOSEFS_LIBS + AC_CHECK_FUNCS(lcfs_node_new) + LIBS=$save_LIBS + with_composefs=yes + ], [ + with_composefs=no + ]) +], [ with_composefs=no ]) +if test x$with_composefs != xno; then OSTREE_FEATURES="$OSTREE_FEATURES composefs"; fi +AM_CONDITIONAL(USE_COMPOSEFS, test $with_composefs != no) LIBSODIUM_DEPENDENCY="1.0.14" AC_ARG_WITH(ed25519_libsodium, @@ -675,7 +701,8 @@ echo " gjs-based tests: $have_gjs dracut: $with_dracut mkinitcpio: $with_mkinitcpio - Static compiler for ostree-prepare-root: $with_static_compiler" + Static compiler for ostree-prepare-root: $with_static_compiler + Composefs: $with_composefs" AS_IF([test x$with_builtin_grub2_mkconfig = xyes], [ echo " builtin grub2-mkconfig (instead of system): $with_builtin_grub2_mkconfig" ], [ diff --git a/src/libostree/ostree-repo-composefs.c b/src/libostree/ostree-repo-composefs.c new file mode 100644 index 00000000..3df1cf98 --- /dev/null +++ b/src/libostree/ostree-repo-composefs.c @@ -0,0 +1,560 @@ +/* + * Copyright (C) Red Hat, Inc. + * + * SPDX-License-Identifier: LGPL-2.0+ + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + */ + +#include "config.h" + +#include +#include +#include + +#include "ostree-core-private.h" +#include "ostree-repo-file.h" +#include "ostree-repo-private.h" + +#ifdef HAVE_COMPOSEFS +#include +#endif + +#ifdef HAVE_LINUX_FSVERITY_H +#include +#endif + +gboolean +_ostree_repo_parse_composefs_config (OstreeRepo *self, GError **error) +{ + /* Currently experimental */ + OtTristate use_composefs; + + if (!ot_keyfile_get_tristate_with_default (self->config, _OSTREE_INTEGRITY_SECTION, "composefs", + OT_TRISTATE_NO, &use_composefs, error)) + return FALSE; + + self->composefs_wanted = use_composefs; +#ifdef HAVE_COMPOSEFS + self->composefs_supported = TRUE; +#else + self->composefs_supported = FALSE; +#endif + + if (use_composefs == OT_TRISTATE_YES && !self->composefs_supported) + return glnx_throw (error, "composefs required, but libostree compiled without support"); + + return TRUE; +} + +struct OstreeComposefsTarget +{ +#ifdef HAVE_COMPOSEFS + struct lcfs_node_s *dest; +#endif + int ref_count; +}; + +/** + * ostree_composefs_target_new: + * + * Creates a #OstreeComposefsTarget which can be used with + * ostree_repo_checkout_composefs() to create a composefs image based + * on a set of checkouts. + * + * Returns: (transfer full): a new of #OstreeComposefsTarget + */ +OstreeComposefsTarget * +ostree_composefs_target_new (void) +{ + OstreeComposefsTarget *target; + + target = g_slice_new0 (OstreeComposefsTarget); + +#ifdef HAVE_COMPOSEFS + target->dest = lcfs_node_new (); + lcfs_node_set_mode (target->dest, 0755 | S_IFDIR); +#endif + + target->ref_count = 1; + + return target; +} + +/** + * ostree_composefs_target_ref: + * @target: an #OstreeComposefsTarget + * + * Increase the reference count on the given @target. + * + * Returns: (transfer full): a copy of @target, for convenience + */ +OstreeComposefsTarget * +ostree_composefs_target_ref (OstreeComposefsTarget *target) +{ + gint refcount; + g_return_val_if_fail (target != NULL, NULL); + refcount = g_atomic_int_add (&target->ref_count, 1); + g_assert (refcount > 0); + return target; +} + +/** + * ostree_composefs_target_unref: + * @target: (transfer full): an #OstreeComposefsTarget + * + * Decrease the reference count on the given @target and free it if the + * reference count reaches 0. + */ +void +ostree_composefs_target_unref (OstreeComposefsTarget *target) +{ + g_return_if_fail (target != NULL); + g_return_if_fail (target->ref_count > 0); + + if (g_atomic_int_dec_and_test (&target->ref_count)) + { +#ifdef HAVE_COMPOSEFS + g_clear_pointer (&target->dest, lcfs_node_unref); +#endif + g_slice_free (OstreeComposefsTarget, target); + } +} + +G_DEFINE_BOXED_TYPE (OstreeComposefsTarget, ostree_composefs_target, ostree_composefs_target_ref, + ostree_composefs_target_unref); + +#ifdef HAVE_COMPOSEFS + +static ssize_t +_composefs_read_cb (void *_file, void *buf, size_t count) +{ + GInputStream *in = _file; + gsize bytes_read; + + if (!g_input_stream_read_all (in, buf, count, &bytes_read, NULL, NULL)) + { + errno = EIO; + return -1; + } + + return bytes_read; +} + +static ssize_t +_composefs_write_cb (void *file, void *buf, size_t len) +{ + int fd = GPOINTER_TO_INT (file); + const char *content = buf; + ssize_t res = 0; + + while (len > 0) + { + res = write (fd, content, len); + if (res < 0 && errno == EINTR) + continue; + + if (res <= 0) + { + if (res == 0) /* Unexpected short write, should not happen when writing to a file */ + errno = ENOSPC; + return -1; + } + + break; + } + + return res; +} + +#endif + +/** + * ostree_composefs_target_write: + * @target: an #OstreeComposefsTarget + * @fd: Write image here (or -1 to not write) + * @out_fsverity_digest: (out) (array fixed-size=32) (nullable): Return location for the fsverity + * binary digest, or %NULL to not compute it + * @cancellable: Cancellable + * @error: Error + * + * Writes a composefs image file to the filesystem at the + * path specified by @destination_dfd and destination_path (if not %NULL) + * and (optionally) computes the fsverity digest of the image. + * + * Returns: %TRUE on success, %FALSE on failure + */ +gboolean +ostree_composefs_target_write (OstreeComposefsTarget *target, int fd, guchar **out_fsverity_digest, + GCancellable *cancellable, GError **error) +{ +#ifdef HAVE_COMPOSEFS + g_autoptr (GOutputStream) tmp_out = NULL; + g_autoptr (GOutputStream) out = NULL; + struct lcfs_node_s *root; + g_autofree guchar *fsverity_digest = NULL; + struct lcfs_write_options_s options = { + LCFS_FORMAT_EROFS, + }; + + root = lcfs_node_lookup_child (target->dest, "root"); + if (root == NULL) + root = target->dest; /* Nothing was checked out, use an empty dir */ + + if (out_fsverity_digest) + { + fsverity_digest = g_malloc (OSTREE_SHA256_DIGEST_LEN); + options.digest_out = fsverity_digest; + } + + if (fd != -1) + { + options.file = GINT_TO_POINTER (fd); + options.file_write_cb = _composefs_write_cb; + } + + if (lcfs_write_to (root, &options) != 0) + return glnx_throw_errno (error); + + if (out_fsverity_digest) + *out_fsverity_digest = g_steal_pointer (&fsverity_digest); + + return TRUE; +#else + g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, + "Composeefs is not supported in this ostree build"); + return FALSE; +#endif +} + +#ifdef HAVE_COMPOSEFS +static gboolean +_ostree_composefs_set_xattrs (struct lcfs_node_s *node, GVariant *xattrs, GCancellable *cancellable, + GError **error) +{ + const guint n = g_variant_n_children (xattrs); + for (guint i = 0; i < n; i++) + { + const guint8 *name; + g_autoptr (GVariant) value = NULL; + g_variant_get_child (xattrs, i, "(^&ay@ay)", &name, &value); + + gsize value_len; + const guint8 *value_data = g_variant_get_fixed_array (value, &value_len, 1); + + if (lcfs_node_set_xattr (node, (char *)name, (char *)value_data, value_len) != 0) + return glnx_throw_errno_prefix (error, "Setting composefs xattrs for %s", name); + } + + return TRUE; +} + +static gboolean +checkout_one_composefs_file_at (OstreeRepo *repo, const char *checksum, struct lcfs_node_s *parent, + const char *destination_name, GCancellable *cancellable, + GError **error) +{ + g_autoptr (GInputStream) input = NULL; + g_autoptr (GVariant) xattrs = NULL; + struct lcfs_node_s *existing; + + /* Validate this up front to prevent path traversal attacks */ + if (!ot_util_filename_validate (destination_name, error)) + return FALSE; + + existing = lcfs_node_lookup_child (parent, destination_name); + if (existing != NULL) + return glnx_throw (error, "Target checkout file already exist"); + + g_autoptr (GFileInfo) source_info = NULL; + if (!ostree_repo_load_file (repo, checksum, &input, &source_info, &xattrs, cancellable, error)) + return FALSE; + + const guint32 source_mode = g_file_info_get_attribute_uint32 (source_info, "unix::mode"); + const guint32 source_uid = g_file_info_get_attribute_uint32 (source_info, "unix::uid"); + const guint32 source_gid = g_file_info_get_attribute_uint32 (source_info, "unix::gid"); + const guint64 source_size = g_file_info_get_size (source_info); + const char *source_symlink_target = g_file_info_get_symlink_target (source_info); + const gboolean is_symlink + = (g_file_info_get_file_type (source_info) == G_FILE_TYPE_SYMBOLIC_LINK); + + struct lcfs_node_s *node = lcfs_node_new (); + if (node == NULL) + return glnx_throw (error, "Out of memory"); + + /* Takes ownership on success */ + if (lcfs_node_add_child (parent, node, destination_name) != 0) + { + lcfs_node_unref (node); + return glnx_throw_errno (error); + } + + lcfs_node_set_mode (node, source_mode); + lcfs_node_set_uid (node, source_uid); + lcfs_node_set_gid (node, source_gid); + lcfs_node_set_size (node, source_size); + if (is_symlink) + { + if (lcfs_node_set_payload (node, source_symlink_target) != 0) + return glnx_throw_errno (error); + } + else if (source_size != 0) + { + char loose_path_buf[_OSTREE_LOOSE_PATH_MAX]; + _ostree_loose_path (loose_path_buf, checksum, OSTREE_OBJECT_TYPE_FILE, OSTREE_REPO_MODE_BARE); + if (lcfs_node_set_payload (node, loose_path_buf) != 0) + return glnx_throw_errno (error); + + guchar *known_digest = NULL; + +#ifdef HAVE_LINUX_FSVERITY_H + /* First try to get the digest directly from the bare repo file. + * This is the typical case when we're pulled into the target + * system repo with verity on and are recreating the composefs + * image during deploy. */ + char buf[sizeof (struct fsverity_digest) + OSTREE_SHA256_DIGEST_LEN]; + + if (G_IS_UNIX_INPUT_STREAM (input)) + { + int content_fd = g_unix_input_stream_get_fd (G_UNIX_INPUT_STREAM (input)); + struct fsverity_digest *d = (struct fsverity_digest *)&buf; + d->digest_size = OSTREE_SHA256_DIGEST_LEN; + + if (ioctl (content_fd, FS_IOC_MEASURE_VERITY, d) == 0 + && d->digest_size == OSTREE_SHA256_DIGEST_LEN + && d->digest_algorithm == FS_VERITY_HASH_ALG_SHA256) + known_digest = d->digest; + } +#endif + + if (known_digest) + lcfs_node_set_fsverity_digest (node, known_digest); + else if (lcfs_node_set_fsverity_from_content (node, input, _composefs_read_cb) != 0) + return glnx_throw_errno (error); + } + + if (xattrs) + { + if (!_ostree_composefs_set_xattrs (node, xattrs, cancellable, error)) + return FALSE; + } + + g_clear_object (&input); + + return TRUE; +} + +static gboolean +checkout_composefs_recurse (OstreeRepo *self, const char *dirtree_checksum, + const char *dirmeta_checksum, struct lcfs_node_s *parent, + const char *name, GCancellable *cancellable, GError **error) +{ + g_autoptr (GVariant) dirtree = NULL; + g_autoptr (GVariant) dirmeta = NULL; + g_autoptr (GVariant) xattrs = NULL; + struct lcfs_node_s *directory; + + if (!ostree_repo_load_variant (self, OSTREE_OBJECT_TYPE_DIR_TREE, dirtree_checksum, &dirtree, + error)) + return FALSE; + if (!ostree_repo_load_variant (self, OSTREE_OBJECT_TYPE_DIR_META, dirmeta_checksum, &dirmeta, + error)) + return FALSE; + + /* Parse OSTREE_OBJECT_TYPE_DIR_META */ + guint32 uid, gid, mode; + g_variant_get (dirmeta, "(uuu@a(ayay))", &uid, &gid, &mode, &xattrs); + uid = GUINT32_FROM_BE (uid); + gid = GUINT32_FROM_BE (gid); + mode = GUINT32_FROM_BE (mode); + + directory = lcfs_node_lookup_child (parent, name); + if (directory != NULL && lcfs_node_get_mode (directory) != 0) + { + return glnx_throw (error, "Target checkout directory already exist"); + } + else + { + directory = lcfs_node_new (); + if (directory == NULL) + return glnx_throw (error, "Out of memory"); + + /* Takes ownership on success */ + if (lcfs_node_add_child (parent, directory, name) != 0) + { + lcfs_node_unref (directory); + return glnx_throw_errno (error); + } + } + + lcfs_node_set_mode (directory, mode); + lcfs_node_set_uid (directory, uid); + lcfs_node_set_gid (directory, gid); + + /* Set the xattrs if we created the dir */ + if (xattrs && !_ostree_composefs_set_xattrs (directory, xattrs, cancellable, error)) + return FALSE; + + /* Process files in this subdir */ + { + g_autoptr (GVariant) dir_file_contents = g_variant_get_child_value (dirtree, 0); + GVariantIter viter; + g_variant_iter_init (&viter, dir_file_contents); + const char *fname; + g_autoptr (GVariant) contents_csum_v = NULL; + while (g_variant_iter_loop (&viter, "(&s@ay)", &fname, &contents_csum_v)) + { + char tmp_checksum[OSTREE_SHA256_STRING_LEN + 1]; + _ostree_checksum_inplace_from_bytes_v (contents_csum_v, tmp_checksum); + + if (!checkout_one_composefs_file_at (self, tmp_checksum, directory, fname, cancellable, + error)) + return FALSE; + } + contents_csum_v = NULL; /* iter_loop freed it */ + } + + /* Process subdirectories */ + { + g_autoptr (GVariant) dir_subdirs = g_variant_get_child_value (dirtree, 1); + const char *dname; + g_autoptr (GVariant) subdirtree_csum_v = NULL; + g_autoptr (GVariant) subdirmeta_csum_v = NULL; + GVariantIter viter; + g_variant_iter_init (&viter, dir_subdirs); + while ( + g_variant_iter_loop (&viter, "(&s@ay@ay)", &dname, &subdirtree_csum_v, &subdirmeta_csum_v)) + { + /* Validate this up front to prevent path traversal attacks. Note that + * we don't validate at the top of this function like we do for + * checkout_one_file_at() becuase I believe in some cases this function + * can be called *initially* with user-specified paths for the root + * directory. + */ + if (!ot_util_filename_validate (dname, error)) + return FALSE; + + char subdirtree_checksum[OSTREE_SHA256_STRING_LEN + 1]; + _ostree_checksum_inplace_from_bytes_v (subdirtree_csum_v, subdirtree_checksum); + char subdirmeta_checksum[OSTREE_SHA256_STRING_LEN + 1]; + _ostree_checksum_inplace_from_bytes_v (subdirmeta_csum_v, subdirmeta_checksum); + if (!checkout_composefs_recurse (self, subdirtree_checksum, subdirmeta_checksum, directory, + dname, cancellable, error)) + return FALSE; + } + } + + return TRUE; +} + +/* Begin a checkout process */ +static gboolean +checkout_composefs_tree (OstreeRepo *self, OstreeComposefsTarget *target, OstreeRepoFile *source, + GFileInfo *source_info, GCancellable *cancellable, GError **error) +{ + if (g_file_info_get_file_type (source_info) != G_FILE_TYPE_DIRECTORY) + return glnx_throw (error, "Root checkout of composefs must be directory"); + + /* Cache any directory metadata we read during this operation; + * see commit b7afe91e21143d7abb0adde440683a52712aa246 + */ + g_auto (OstreeRepoMemoryCacheRef) memcache_ref; + _ostree_repo_memory_cache_ref_init (&memcache_ref, self); + + g_assert_cmpint (g_file_info_get_file_type (source_info), ==, G_FILE_TYPE_DIRECTORY); + + const char *dirtree_checksum = ostree_repo_file_tree_get_contents_checksum (source); + const char *dirmeta_checksum = ostree_repo_file_tree_get_metadata_checksum (source); + return checkout_composefs_recurse (self, dirtree_checksum, dirmeta_checksum, target->dest, "root", + cancellable, error); +} + +static struct lcfs_node_s * +ensure_lcfs_dir (struct lcfs_node_s *parent, const char *name, GError **error) +{ + struct lcfs_node_s *node; + + node = lcfs_node_lookup_child (parent, name); + if (node != NULL) + return node; + + node = lcfs_node_new (); + lcfs_node_set_mode (node, 0755 | S_IFDIR); + if (lcfs_node_add_child (parent, node, name) != 0) + { + lcfs_node_unref (node); + glnx_throw_errno (error); + return NULL; + } + + return node; +} +#endif + +/** + * ostree_repo_checkout_composefs: + * @self: Repo + * @target: A target for the checkout + * @source: Source tree + * @cancellable: Cancellable + * @error: Error + * + * Check out @source into @target, which is an in-memory + * representation of a composefs image. The @target can be reused + * multiple times to layer multiple checkouts before writing out the + * image to disk using ostree_composefs_target_write(). + * + * There are various options specified by @options that affect + * how the image is created. + * + * Returns: %TRUE on success, %FALSE on failure + */ +gboolean +ostree_repo_checkout_composefs (OstreeRepo *self, OstreeComposefsTarget *target, + OstreeRepoFile *source, GCancellable *cancellable, GError **error) +{ +#ifdef HAVE_COMPOSEFS + char *root_dirs[] = { "usr", "etc", "boot", "var", "sysroot" }; + int i; + struct lcfs_node_s *root, *dir; + + g_autoptr (GFileInfo) target_info + = g_file_query_info (G_FILE (source), OSTREE_GIO_FAST_QUERYINFO, + G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, cancellable, error); + if (!target_info) + return FALSE; + + if (!checkout_composefs_tree (self, target, source, target_info, cancellable, error)) + return FALSE; + + /* We need a root dir */ + root = ensure_lcfs_dir (target->dest, "root", error); + if (root == NULL) + return FALSE; + + /* To work as a rootfs we need some root directories to use as bind-mounts */ + for (i = 0; i < G_N_ELEMENTS (root_dirs); i++) + { + dir = ensure_lcfs_dir (root, root_dirs[i], error); + if (dir == NULL) + return FALSE; + } + + return TRUE; +#else + g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, + "Composeefs is not supported in this ostree build"); + return FALSE; +#endif +} diff --git a/src/libostree/ostree-repo-private.h b/src/libostree/ostree-repo-private.h index 98719f5a..15121372 100644 --- a/src/libostree/ostree-repo-private.h +++ b/src/libostree/ostree-repo-private.h @@ -65,6 +65,8 @@ G_BEGIN_DECLS #define OSTREE_COMMIT_TIMESTAMP "ostree.commit.timestamp" #define OSTREE_COMMIT_VERSION "ostree.commit.version" +#define _OSTREE_INTEGRITY_SECTION "ex-integrity" + typedef enum { OSTREE_REPO_TEST_ERROR_PRE_COMMIT = (1 << 0), @@ -176,6 +178,8 @@ struct OstreeRepo gboolean txn_locked; _OstreeFeatureSupport fs_verity_wanted; _OstreeFeatureSupport fs_verity_supported; + OtTristate composefs_wanted; + gboolean composefs_supported; GMutex cache_lock; guint dirmeta_cache_refcount; @@ -388,6 +392,7 @@ gboolean _ostree_repo_maybe_regenerate_summary (OstreeRepo *self, GCancellable * GError **error); gboolean _ostree_repo_parse_fsverity_config (OstreeRepo *self, GError **error); +gboolean _ostree_repo_parse_composefs_config (OstreeRepo *self, GError **error); gboolean _ostree_tmpf_fsverity_core (GLnxTmpfile *tmpf, _OstreeFeatureSupport fsverity_requested, GBytes *signature, gboolean *supported, GError **error); @@ -446,4 +451,20 @@ G_DEFINE_AUTOPTR_CLEANUP_FUNC (OstreeRepoAutoTransaction, _ostree_repo_auto_tran * should not be made into public API, even if the rest is */ OstreeRepoAutoTransaction *_ostree_repo_auto_transaction_new (OstreeRepo *repo); +typedef struct OstreeComposefsTarget OstreeComposefsTarget; + +GType ostree_composefs_target_get_type (void) G_GNUC_CONST; +OstreeComposefsTarget *ostree_composefs_target_new (void); +OstreeComposefsTarget *ostree_composefs_target_ref (OstreeComposefsTarget *target); +void ostree_composefs_target_unref (OstreeComposefsTarget *target); +gboolean ostree_composefs_target_write (OstreeComposefsTarget *target, int fd, + guchar **out_fsverity_digest, GCancellable *cancellable, + GError **error); + +gboolean ostree_repo_checkout_composefs (OstreeRepo *self, OstreeComposefsTarget *target, + OstreeRepoFile *source, GCancellable *cancellable, + GError **error); + +G_DEFINE_AUTOPTR_CLEANUP_FUNC (OstreeComposefsTarget, ostree_composefs_target_unref) + G_END_DECLS diff --git a/src/libostree/ostree-repo-verity.c b/src/libostree/ostree-repo-verity.c index 5b2a621a..512386a9 100644 --- a/src/libostree/ostree-repo-verity.c +++ b/src/libostree/ostree-repo-verity.c @@ -46,33 +46,59 @@ gboolean _ostree_repo_parse_fsverity_config (OstreeRepo *self, GError **error) { /* Currently experimental */ - static const char fsverity_key[] = "ex-fsverity"; - self->fs_verity_wanted = _OSTREE_FEATURE_NO; + OtTristate use_composefs; + OtTristate use_fsverity; + #ifdef HAVE_LINUX_FSVERITY_H self->fs_verity_supported = _OSTREE_FEATURE_MAYBE; #else self->fs_verity_supported = _OSTREE_FEATURE_NO; #endif - gboolean fsverity_required = FALSE; - if (!ot_keyfile_get_boolean_with_default (self->config, fsverity_key, "required", FALSE, - &fsverity_required, error)) + + /* Composefs use implies fsverity default of maybe */ + if (!ot_keyfile_get_tristate_with_default (self->config, _OSTREE_INTEGRITY_SECTION, "composefs", + OT_TRISTATE_NO, &use_composefs, error)) + return FALSE; + + if (!ot_keyfile_get_tristate_with_default (self->config, _OSTREE_INTEGRITY_SECTION, "fsverity", + (use_composefs != OT_TRISTATE_NO) ? OT_TRISTATE_MAYBE + : OT_TRISTATE_NO, + &use_fsverity, error)) return FALSE; - if (fsverity_required) + + if (use_fsverity != OT_TRISTATE_NO) { - self->fs_verity_wanted = _OSTREE_FEATURE_YES; - if (self->fs_verity_supported == _OSTREE_FEATURE_NO) - return glnx_throw (error, "fsverity required, but libostree compiled without support"); + self->fs_verity_wanted = (_OstreeFeatureSupport)use_fsverity; } else { - gboolean fsverity_opportunistic = FALSE; - if (!ot_keyfile_get_boolean_with_default (self->config, fsverity_key, "opportunistic", FALSE, - &fsverity_opportunistic, error)) + /* Fall back to old configuration key */ + static const char fsverity_section[] = "ex-fsverity"; + + self->fs_verity_wanted = _OSTREE_FEATURE_NO; + gboolean fsverity_required = FALSE; + if (!ot_keyfile_get_boolean_with_default (self->config, fsverity_section, "required", FALSE, + &fsverity_required, error)) return FALSE; - if (fsverity_opportunistic) - self->fs_verity_wanted = _OSTREE_FEATURE_MAYBE; + if (fsverity_required) + { + self->fs_verity_wanted = _OSTREE_FEATURE_YES; + } + else + { + gboolean fsverity_opportunistic = FALSE; + if (!ot_keyfile_get_boolean_with_default (self->config, fsverity_section, "opportunistic", + FALSE, &fsverity_opportunistic, error)) + return FALSE; + if (fsverity_opportunistic) + self->fs_verity_wanted = _OSTREE_FEATURE_MAYBE; + } } + if (self->fs_verity_wanted == _OSTREE_FEATURE_YES + && self->fs_verity_supported == _OSTREE_FEATURE_NO) + return glnx_throw (error, "fsverity required, but libostree compiled without support"); + return TRUE; } diff --git a/src/libostree/ostree-repo.c b/src/libostree/ostree-repo.c index 6e58253d..8633701f 100644 --- a/src/libostree/ostree-repo.c +++ b/src/libostree/ostree-repo.c @@ -3154,6 +3154,9 @@ reload_core_config (OstreeRepo *self, GCancellable *cancellable, GError **error) if (!_ostree_repo_parse_fsverity_config (self, error)) return FALSE; + if (!_ostree_repo_parse_composefs_config (self, error)) + return FALSE; + { g_clear_pointer (&self->collection_id, g_free); if (!ot_keyfile_get_value_with_default (self->config, "core", "collection-id", NULL, -- 2.30.2