Merge pull request #2640 from alexlarsson/composefs

Add initial composefs integration
This commit is contained in:
Colin Walters 2023-06-02 09:26:04 -04:00 committed by GitHub
commit b6c054e1fa
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 1356 additions and 49 deletions

View File

@ -73,7 +73,7 @@ jobs:
- name: Build
run: |
env NOCONFIGURE=1 ./autogen.sh &&
./configure --with-curl --with-selinux --with-dracut=yesbutnoconf &&
./configure --with-curl --with-selinux --with-dracut=yesbutnoconf --with-composefs &&
make -j 4 && make install DESTDIR=$(pwd)/install && tar -c -C install --zstd -f inst.tar.zst .
- name: Upload binary
uses: actions/upload-artifact@v2
@ -193,6 +193,8 @@ jobs:
pre-checkout-setup: |
apt-get update
apt-get install -y git
configure-options: >-
--with-composefs
# A build using libsoup3. After bookworm is released, this can
# be switched to Debian Stable.

3
.gitmodules vendored
View File

@ -4,3 +4,6 @@
[submodule "bsdiff"]
path = bsdiff
url = https://github.com/mendsley/bsdiff
[submodule "composefs"]
path = composefs
url = https://github.com/containers/composefs.git

View File

@ -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 \
@ -184,7 +185,7 @@ EXTRA_DIST += \
$(top_srcdir)/src/libostree/libostree-released.sym \
$(NULL)
libostree_1_la_CFLAGS = $(AM_CFLAGS) -I$(srcdir)/bsdiff -I$(srcdir)/libglnx -I$(srcdir)/src/libotutil -I$(srcdir)/src/libostree -I$(builddir)/src/libostree \
libostree_1_la_CFLAGS = $(AM_CFLAGS) -I$(srcdir)/bsdiff -I$(srcdir)/libglnx -I$(srcdir)/composefs -I$(srcdir)/src/libotutil -I$(srcdir)/src/libostree -I$(builddir)/src/libostree \
$(OT_INTERNAL_GIO_UNIX_CFLAGS) $(OT_INTERNAL_GPGME_CFLAGS) $(OT_DEP_LZMA_CFLAGS) $(OT_DEP_ZLIB_CFLAGS) $(OT_DEP_CRYPTO_CFLAGS) \
-fvisibility=hidden '-D_OSTREE_PUBLIC=__attribute__((visibility("default"))) extern' \
-DPKGLIBEXECDIR=\"$(pkglibexecdir)\"
@ -266,6 +267,10 @@ 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_LIBADD += libcomposefs.la
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

View File

@ -27,7 +27,9 @@ ostree_prepare_root_SOURCES = \
src/switchroot/ostree-mount-util.h \
src/switchroot/ostree-prepare-root.c \
$(NULL)
ostree_prepare_root_CFLAGS =
ostree_prepare_root_CPPFLAGS = $(AM_CPPFLAGS)
ostree_prepare_root_LDADD =
if BUILDOPT_USE_STATIC_COMPILER
# ostree-prepare-root can be used as init in a system without a populated /lib.
@ -46,7 +48,7 @@ ostree-prepare-root : $(ostree_prepare_root_SOURCES)
$(STATIC_COMPILER) -o $@ -static $(top_srcdir)/src/switchroot/ostree-prepare-root.c $(ostree_prepare_root_CPPFLAGS) $(AM_CFLAGS) $(DEFAULT_INCLUDES) -DOSTREE_PREPARE_ROOT_STATIC=1
else
ostree_boot_PROGRAMS += ostree-prepare-root
ostree_prepare_root_CFLAGS = $(AM_CFLAGS) -Isrc/switchroot
ostree_prepare_root_CFLAGS += $(AM_CFLAGS) -Isrc/switchroot -I$(srcdir)/composefs
endif
ostree_remount_SOURCES = \
@ -56,9 +58,13 @@ ostree_remount_SOURCES = \
ostree_remount_CPPFLAGS = $(AM_CPPFLAGS) $(OT_INTERNAL_GIO_UNIX_CFLAGS) -Isrc/switchroot -I$(srcdir)/libglnx
ostree_remount_LDADD = $(AM_LDFLAGS) $(OT_INTERNAL_GIO_UNIX_LIBS) libglnx.la
if USE_COMPOSEFS
ostree_prepare_root_LDADD += libcomposefs.la
endif
if BUILDOPT_SYSTEMD
ostree_prepare_root_CPPFLAGS += -DHAVE_SYSTEMD=1
ostree_prepare_root_LDADD = $(AM_LDFLAGS) $(LIBSYSTEMD_LIBS)
ostree_prepare_root_LDADD += $(AM_LDFLAGS) $(LIBSYSTEMD_LIBS)
endif
# This is the "new mode" of using a generator for /var; see

View File

@ -117,6 +117,15 @@ include bsdiff/Makefile-bsdiff.am.inc
EXTRA_DIST += bsdiff/Makefile-bsdiff.am
noinst_LTLIBRARIES += libbsdiff.la
COMPOSEFSDIR=$(srcdir)/composefs/libcomposefs
LCFS_DEP_CRYPTO_CFLAGS=$(OT_DEP_CRYPTO_CFLAGS)
LCFS_DEP_CRYPTO_LIBS=$(OT_DEP_CRYPTO_LIBS)
include composefs/libcomposefs/Makefile-lib.am.inc
EXTRA_DIST += composefs/libcomposefs/Makefile-lib.am
if USE_COMPOSEFS
noinst_LTLIBRARIES += libcomposefs.la
endif
include Makefile-otutil.am
include Makefile-libostree.am
include Makefile-ostree.am

View File

@ -35,6 +35,7 @@ fi
# changing this, please also change Makefile.am.
sed -e 's,$(libglnx_srcpath),libglnx,g' < libglnx/Makefile-libglnx.am >libglnx/Makefile-libglnx.am.inc
sed -e 's,$(libbsdiff_srcpath),bsdiff,g' < bsdiff/Makefile-bsdiff.am >bsdiff/Makefile-bsdiff.am.inc
sed -e 's,$(COMPOSEFSDIR),composefs/libcomposefs,g' < composefs/libcomposefs/Makefile-lib.am >composefs/libcomposefs/Makefile-lib.am.inc
# FIXME - figure out how to get aclocal to find this by default
ln -sf ../libglnx/libglnx.m4 buildutil/libglnx.m4

1
composefs Submodule

@ -0,0 +1 @@
Subproject commit e5ab5e2dc6aa6bd2daab3052553b787efe16fc7d

View File

@ -280,6 +280,35 @@ AS_IF([test x$have_gpgme = xyes],
)
AM_CONDITIONAL(USE_GPGME, test "x$have_gpgme" = xyes)
# These are needed by libcomposefs
AC_MSG_CHECKING([for new mount API (fsconfig)])
AC_COMPILE_IFELSE(
[AC_LANG_SOURCE([[
#include <sys/mount.h>
int cmd = FSCONFIG_CMD_CREATE;
]])],
[AC_MSG_RESULT(yes)
AC_DEFINE([HAVE_FSCONFIG_CMD_CREATE_SYS_MOUNT_H], 1, [Define if FSCONFIG_CMD_CREATE is available in sys/mount.h])],
[AC_MSG_RESULT(no)])
AC_COMPILE_IFELSE(
[AC_LANG_SOURCE([[
/* also make sure it doesn't conflict with <sys/mount.h> since it is always used. */
#include <sys/mount.h>
#include <linux/mount.h>
int cmd = FSCONFIG_CMD_CREATE;
]])],
[AC_MSG_RESULT(yes)
AC_DEFINE([HAVE_FSCONFIG_CMD_CREATE_LINUX_MOUNT_H], 1, [Define if FSCONFIG_CMD_CREATE is available in linux/mount.h])],
[AC_MSG_RESULT(no)])
AC_ARG_WITH(composefs,
AS_HELP_STRING([--with-composefs], [Support composefs]),
:, with_composefs=no)
if test x$with_composefs != xno; then OSTREE_FEATURES="$OSTREE_FEATURES composefs";
AC_DEFINE([HAVE_COMPOSEFS], 1, [Define if we have libcomposefs])
fi
AM_CONDITIONAL(USE_COMPOSEFS, test $with_composefs != no)
LIBSODIUM_DEPENDENCY="1.0.14"
AC_ARG_WITH(ed25519_libsodium,
@ -675,7 +704,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"
], [

View File

@ -184,7 +184,7 @@ _ostree_repo_commit_tmpf_final (OstreeRepo *self, const char *checksum, OstreeOb
if (!_ostree_repo_ensure_loose_objdir_at (dest_dfd, tmpbuf, cancellable, error))
return FALSE;
if (!_ostree_tmpf_fsverity (self, tmpf, error))
if (!_ostree_tmpf_fsverity (self, tmpf, NULL, error))
return FALSE;
if (!glnx_link_tmpfile_at (tmpf, GLNX_LINK_TMPFILE_NOREPLACE_IGNORE_EXIST, dest_dfd, tmpbuf,
@ -398,14 +398,9 @@ compare_ascii_checksums_for_sorting (gconstpointer a_pp, gconstpointer b_pp)
/*
* Create sizes metadata GVariant and add it to the metadata variant given.
*/
static GVariant *
add_size_index_to_metadata (OstreeRepo *self, GVariant *original_metadata)
static void
add_size_index_to_metadata (OstreeRepo *self, GVariantBuilder *builder)
{
g_autoptr (GVariantBuilder) builder = NULL;
/* original_metadata may be NULL */
builder = ot_util_variant_builder_from_variant (original_metadata, G_VARIANT_TYPE ("a{sv}"));
if (self->object_sizes && g_hash_table_size (self->object_sizes) > 0)
{
GVariantBuilder index_builder;
@ -443,8 +438,6 @@ add_size_index_to_metadata (OstreeRepo *self, GVariant *original_metadata)
/* Clear the object sizes hash table for a subsequent commit. */
g_hash_table_remove_all (self->object_sizes);
}
return g_variant_ref_sink (g_variant_builder_end (builder));
}
static gboolean
@ -2912,6 +2905,23 @@ ostree_repo_write_commit (OstreeRepo *self, const char *parent, const char *subj
out_commit, cancellable, error);
}
static GVariant *
add_auto_metadata (OstreeRepo *self, GVariant *original_metadata, OstreeRepoFile *repo_root,
GCancellable *cancellable, GError **error)
{
g_autoptr (GVariantBuilder) builder = NULL;
/* original_metadata may be NULL */
builder = ot_util_variant_builder_from_variant (original_metadata, G_VARIANT_TYPE ("a{sv}"));
add_size_index_to_metadata (self, builder);
if (!ostree_repo_commit_add_composefs_metadata (self, builder, repo_root, cancellable, error))
return NULL;
return g_variant_ref_sink (g_variant_builder_end (builder));
}
/**
* ostree_repo_write_commit_with_time:
* @self: Repo
@ -2938,7 +2948,10 @@ ostree_repo_write_commit_with_time (OstreeRepo *self, const char *parent, const
OstreeRepoFile *repo_root = OSTREE_REPO_FILE (root);
/* Add sizes information to our metadata object */
g_autoptr (GVariant) new_metadata = add_size_index_to_metadata (self, metadata);
g_autoptr (GVariant) new_metadata
= add_auto_metadata (self, metadata, repo_root, cancellable, error);
if (new_metadata == NULL)
return FALSE;
g_autoptr (GVariant) commit = g_variant_new (
"(@a{sv}@ay@a(say)sst@ay@ay)", new_metadata ? new_metadata : create_empty_gvariant_dict (),

View File

@ -0,0 +1,631 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/
#include "config.h"
#include <gio/gunixinputstream.h>
#include <gio/gunixoutputstream.h>
#include <sys/ioctl.h>
#include "ostree-core-private.h"
#include "ostree-repo-file.h"
#include "ostree-repo-private.h"
#ifdef HAVE_COMPOSEFS
#include <libcomposefs/lcfs-writer.h>
#endif
#ifdef HAVE_LINUX_FSVERITY_H
#include <linux/fsverity.h>
#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
}
#ifdef HAVE_COMPOSEFS
static gboolean
ostree_repo_commit_add_composefs_sig (OstreeRepo *self, GVariantBuilder *builder,
guchar *fsverity_digest, GCancellable *cancellable,
GError **error)
{
g_autofree char *certfile = NULL;
g_autofree char *keyfile = NULL;
g_autoptr (GBytes) sig = NULL;
certfile
= g_key_file_get_string (self->config, _OSTREE_INTEGRITY_SECTION, "composefs-certfile", NULL);
keyfile
= g_key_file_get_string (self->config, _OSTREE_INTEGRITY_SECTION, "composefs-keyfile", NULL);
if (certfile == NULL && keyfile == NULL)
return TRUE;
if (certfile == NULL)
return glnx_throw (error, "Error signing compoosefs: keyfile specified but certfile is not");
if (keyfile == NULL)
return glnx_throw (error, "Error signing compoosefs: certfile specified but keyfile is not");
if (!_ostree_fsverity_sign (certfile, keyfile, fsverity_digest, &sig, cancellable, error))
return FALSE;
g_variant_builder_add (builder, "{sv}", "ostree.composefs-sig", ot_gvariant_new_ay_bytes (sig));
return TRUE;
}
#endif
gboolean
ostree_repo_commit_add_composefs_metadata (OstreeRepo *self, GVariantBuilder *builder,
OstreeRepoFile *repo_root, GCancellable *cancellable,
GError **error)
{
gboolean add_metadata;
if (!ot_keyfile_get_boolean_with_default (self->config, _OSTREE_INTEGRITY_SECTION,
"composefs-add-metadata", FALSE, &add_metadata, error))
return FALSE;
if (add_metadata)
{
#ifdef HAVE_COMPOSEFS
/* Create a composefs image and put in deploy dir as .ostree.cfs */
g_autoptr (OstreeComposefsTarget) target = ostree_composefs_target_new ();
if (!ostree_repo_checkout_composefs (self, target, repo_root, cancellable, error))
return FALSE;
g_autofree guchar *fsverity_digest = NULL;
if (!ostree_composefs_target_write (target, -1, &fsverity_digest, cancellable, error))
return FALSE;
g_variant_builder_add (builder, "{sv}", "ostree.composefs",
ot_gvariant_new_bytearray (fsverity_digest, OSTREE_SHA256_DIGEST_LEN));
if (!ostree_repo_commit_add_composefs_sig (self, builder, fsverity_digest, cancellable,
error))
return FALSE;
#else
return glnx_throw (error, "composefs required, but libostree compiled without support");
#endif
}
return TRUE;
}

View File

@ -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,11 +392,16 @@ 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,
gboolean *supported, GError **error);
GBytes *signature, gboolean *supported, GError **error);
gboolean _ostree_tmpf_fsverity (OstreeRepo *self, GLnxTmpfile *tmpf, GError **error);
gboolean _ostree_tmpf_fsverity (OstreeRepo *self, GLnxTmpfile *tmpf, GBytes *signature,
GError **error);
gboolean _ostree_fsverity_sign (const char *certfile, const char *keyfile,
const guchar *fsverity_digest, GBytes **data_out,
GCancellable *cancellable, GError **error);
gboolean _ostree_repo_verify_bindings (const char *collection_id, const char *ref_name,
GVariant *commit, GError **error);
@ -442,4 +451,24 @@ 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);
gboolean ostree_repo_commit_add_composefs_metadata (OstreeRepo *self, GVariantBuilder *builder,
OstreeRepoFile *repo_root,
GCancellable *cancellable, GError **error);
G_DEFINE_AUTOPTR_CLEANUP_FUNC (OstreeComposefsTarget, ostree_composefs_target_unref)
G_END_DECLS

View File

@ -29,37 +29,76 @@
#include <linux/fsverity.h>
#endif
#if defined(HAVE_OPENSSL)
#include <openssl/bio.h>
#include <openssl/engine.h>
#include <openssl/err.h>
#include <openssl/pem.h>
#include <openssl/pkcs7.h>
G_DEFINE_AUTOPTR_CLEANUP_FUNC (X509, X509_free);
G_DEFINE_AUTOPTR_CLEANUP_FUNC (EVP_PKEY, EVP_PKEY_free);
G_DEFINE_AUTOPTR_CLEANUP_FUNC (BIO, BIO_free);
G_DEFINE_AUTOPTR_CLEANUP_FUNC (PKCS7, PKCS7_free);
#endif
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 (fsverity_required)
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 (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;
}
@ -69,7 +108,7 @@ _ostree_repo_parse_fsverity_config (OstreeRepo *self, GError **error)
* */
gboolean
_ostree_tmpf_fsverity_core (GLnxTmpfile *tmpf, _OstreeFeatureSupport fsverity_requested,
gboolean *supported, GError **error)
GBytes *signature, gboolean *supported, GError **error)
{
/* Set this by default to simplify the code below */
if (supported)
@ -93,8 +132,8 @@ _ostree_tmpf_fsverity_core (GLnxTmpfile *tmpf, _OstreeFeatureSupport fsverity_re
arg.block_size = 4096; /* FIXME query */
arg.salt_size = 0; /* TODO store salt in ostree repo config */
arg.salt_ptr = 0;
arg.sig_size = 0; /* We don't currently expect use of in-kernel signature verification */
arg.sig_ptr = 0;
arg.sig_size = signature ? g_bytes_get_size (signature) : 0;
arg.sig_ptr = signature ? (guint64)g_bytes_get_data (signature, NULL) : 0;
if (ioctl (tmpf->fd, FS_IOC_ENABLE_VERITY, &arg) < 0)
{
@ -120,7 +159,7 @@ _ostree_tmpf_fsverity_core (GLnxTmpfile *tmpf, _OstreeFeatureSupport fsverity_re
* as well as to support "opportunistic" use (requested and if filesystem supports).
* */
gboolean
_ostree_tmpf_fsverity (OstreeRepo *self, GLnxTmpfile *tmpf, GError **error)
_ostree_tmpf_fsverity (OstreeRepo *self, GLnxTmpfile *tmpf, GBytes *signature, GError **error)
{
#ifdef HAVE_LINUX_FSVERITY_H
g_mutex_lock (&self->txn_lock);
@ -143,7 +182,7 @@ _ostree_tmpf_fsverity (OstreeRepo *self, GLnxTmpfile *tmpf, GError **error)
}
gboolean supported = FALSE;
if (!_ostree_tmpf_fsverity_core (tmpf, fsverity_wanted, &supported, error))
if (!_ostree_tmpf_fsverity_core (tmpf, fsverity_wanted, signature, &supported, error))
return FALSE;
if (!supported)
@ -167,3 +206,131 @@ _ostree_tmpf_fsverity (OstreeRepo *self, GLnxTmpfile *tmpf, GError **error)
#endif
return TRUE;
}
#if defined(HAVE_OPENSSL)
static gboolean
read_pem_x509_certificate (const char *certfile, X509 **cert_ret, GError **error)
{
g_autoptr (BIO) bio = NULL;
X509 *cert;
errno = 0;
bio = BIO_new_file (certfile, "r");
if (!bio)
return glnx_throw_errno_prefix (error, "Error loading composefs certfile '%s'", certfile);
cert = PEM_read_bio_X509 (bio, NULL, NULL, NULL);
if (!cert)
return glnx_throw (error, "Error parsing composefs certfile '%s'", certfile);
*cert_ret = cert;
return TRUE;
}
static gboolean
read_pem_pkcs8_private_key (const char *keyfile, EVP_PKEY **pkey_ret, GError **error)
{
g_autoptr (BIO) bio;
EVP_PKEY *pkey;
errno = 0;
bio = BIO_new_file (keyfile, "r");
if (!bio)
return glnx_throw_errno_prefix (error, "Error loading composefs keyfile '%s'", keyfile);
pkey = PEM_read_bio_PrivateKey (bio, NULL, NULL, NULL);
if (!pkey)
return glnx_throw (error, "Error parsing composefs keyfile '%s'", keyfile);
*pkey_ret = pkey;
return TRUE;
}
static gboolean
sign_pkcs7 (const void *data_to_sign, size_t data_size, EVP_PKEY *pkey, X509 *cert,
const EVP_MD *md, BIO **res, GError **error)
{
int pkcs7_flags = PKCS7_BINARY | PKCS7_DETACHED | PKCS7_NOATTR | PKCS7_NOCERTS | PKCS7_PARTIAL;
g_autoptr (BIO) bio = NULL;
g_autoptr (BIO) bio_res = NULL;
g_autoptr (PKCS7) p7 = NULL;
bio = BIO_new_mem_buf ((void *)data_to_sign, data_size);
if (!bio)
return glnx_throw (error, "Can't allocate buffer");
p7 = PKCS7_sign (NULL, NULL, NULL, bio, pkcs7_flags);
if (!p7)
return glnx_throw (error, "Can't initialize PKCS#7");
if (!PKCS7_sign_add_signer (p7, cert, pkey, md, pkcs7_flags))
return glnx_throw (error, "Can't add signer to PKCS#7");
if (PKCS7_final (p7, bio, pkcs7_flags) != 1)
return glnx_throw (error, "Can't finalize PKCS#7");
bio_res = BIO_new (BIO_s_mem ());
if (!bio_res)
return glnx_throw (error, "Can't allocate buffer");
if (i2d_PKCS7_bio (bio_res, p7) != 1)
return glnx_throw (error, "Can't DER-encode PKCS#7 signature object");
*res = g_steal_pointer (&bio_res);
return TRUE;
}
gboolean
_ostree_fsverity_sign (const char *certfile, const char *keyfile, const guchar *fsverity_digest,
GBytes **data_out, GCancellable *cancellable, GError **error)
{
g_autofree struct fsverity_formatted_digest *d = NULL;
gsize d_size;
g_autoptr (X509) cert = NULL;
g_autoptr (EVP_PKEY) pkey = NULL;
g_autoptr (BIO) bio_sig = NULL;
const EVP_MD *md;
guchar *sig;
long sig_size;
if (certfile == NULL)
return glnx_throw (error, "certfile not specified");
if (keyfile == NULL)
return glnx_throw (error, "keyfile not specified");
if (!read_pem_x509_certificate (certfile, &cert, error))
return FALSE;
if (!read_pem_pkcs8_private_key (keyfile, &pkey, error))
return FALSE;
md = EVP_sha256 ();
if (md == NULL)
return glnx_throw (error, "No sha256 support in openssl");
d_size = sizeof (struct fsverity_formatted_digest) + OSTREE_SHA256_DIGEST_LEN;
d = g_malloc0 (d_size);
memcpy (d->magic, "FSVerity", 8);
d->digest_algorithm = GUINT16_TO_LE (FS_VERITY_HASH_ALG_SHA256);
d->digest_size = GUINT16_TO_LE (OSTREE_SHA256_DIGEST_LEN);
memcpy (d->digest, fsverity_digest, OSTREE_SHA256_DIGEST_LEN);
if (!sign_pkcs7 (d, d_size, pkey, cert, md, &bio_sig, error))
return FALSE;
sig_size = BIO_get_mem_data (bio_sig, &sig);
*data_out = g_bytes_new (sig, sig_size);
return TRUE;
}
#else
gboolean
_ostree_fsverity_sign (const char *certfile, const char *keyfile, const guchar *fsverity_digest,
GBytes **data_out, GCancellable *cancellable, GError **error)
{
return glnx_throw (error, "fsverity signature support not built");
}
#endif

View File

@ -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,

View File

@ -163,7 +163,7 @@ install_into_boot (OstreeRepo *repo, OstreeSePolicy *sepolicy, int src_dfd, cons
_OstreeFeatureSupport boot_verity = _OSTREE_FEATURE_NO;
if (repo->fs_verity_wanted != _OSTREE_FEATURE_NO)
boot_verity = _OSTREE_FEATURE_MAYBE;
if (!_ostree_tmpf_fsverity_core (&tmp_dest, boot_verity, NULL, error))
if (!_ostree_tmpf_fsverity_core (&tmp_dest, boot_verity, NULL, NULL, error))
return FALSE;
if (!glnx_link_tmpfile_at (&tmp_dest, GLNX_LINK_TMPFILE_NOREPLACE, dest_dfd, dest_subpath, error))
@ -582,13 +582,45 @@ merge_configuration_from (OstreeSysroot *sysroot, OstreeDeployment *merge_deploy
return TRUE;
}
#ifdef HAVE_COMPOSEFS
static gboolean
compare_verity_digests (GVariant *metadata_composefs, const guchar *fsverity_digest, GError **error)
{
const guchar *expected_digest;
if (metadata_composefs == NULL)
return TRUE;
if (g_variant_n_children (metadata_composefs) != OSTREE_SHA256_DIGEST_LEN)
return glnx_throw (error, "Expected composefs fs-verity in metadata has the wrong size");
expected_digest = g_variant_get_data (metadata_composefs);
if (memcmp (fsverity_digest, expected_digest, OSTREE_SHA256_DIGEST_LEN) != 0)
{
char actual_checksum[OSTREE_SHA256_STRING_LEN + 1];
char expected_checksum[OSTREE_SHA256_STRING_LEN + 1];
ostree_checksum_inplace_from_bytes (fsverity_digest, actual_checksum);
ostree_checksum_inplace_from_bytes (expected_digest, expected_checksum);
return glnx_throw (error,
"Generated composefs image digest (%s) doesn't match expected digest (%s)",
actual_checksum, expected_checksum);
}
return TRUE;
}
#endif
/* Look up @revision in the repository, and check it out in
* /ostree/deploy/OS/deploy/${treecsum}.${deployserial}.
* A dfd for the result is returned in @out_deployment_dfd.
*/
static gboolean
checkout_deployment_tree (OstreeSysroot *sysroot, OstreeRepo *repo, OstreeDeployment *deployment,
int *out_deployment_dfd, GCancellable *cancellable, GError **error)
const char *revision, int *out_deployment_dfd, GCancellable *cancellable,
GError **error)
{
GLNX_AUTO_PREFIX_ERROR ("Checking out deployment tree", error);
/* Find the directory with deployments for this stateroot */
@ -614,6 +646,92 @@ checkout_deployment_tree (OstreeSysroot *sysroot, OstreeRepo *repo, OstreeDeploy
cancellable, error))
return FALSE;
#ifdef HAVE_COMPOSEFS
if (repo->composefs_wanted != OT_TRISTATE_NO)
{
gboolean apply_composefs_signature;
g_autofree guchar *fsverity_digest = NULL;
g_auto (GLnxTmpfile) tmpf = {
0,
};
g_autoptr (GVariant) commit_variant = NULL;
if (!ostree_repo_load_commit (repo, revision, &commit_variant, NULL, error))
return FALSE;
if (!ot_keyfile_get_boolean_with_default (repo->config, _OSTREE_INTEGRITY_SECTION,
"composefs-apply-sig", TRUE,
&apply_composefs_signature, error))
return FALSE;
g_autoptr (GVariant) metadata = g_variant_get_child_value (commit_variant, 0);
g_autoptr (GVariant) metadata_composefs
= g_variant_lookup_value (metadata, "ostree.composefs", G_VARIANT_TYPE_BYTESTRING);
g_autoptr (GVariant) metadata_composefs_sig
= g_variant_lookup_value (metadata, "ostree.composefs-sig", G_VARIANT_TYPE_BYTESTRING);
/* Create a composefs image and put in deploy dir as .ostree.cfs */
g_autoptr (OstreeComposefsTarget) target = ostree_composefs_target_new ();
g_autoptr (GFile) commit_root = NULL;
if (!ostree_repo_read_commit (repo, csum, &commit_root, NULL, cancellable, error))
return FALSE;
if (!ostree_repo_checkout_composefs (repo, target, (OstreeRepoFile *)commit_root, cancellable,
error))
return FALSE;
g_autofree char *composefs_cfs_path
= g_strdup_printf ("%s/.ostree.cfs", checkout_target_name);
if (!glnx_open_tmpfile_linkable_at (osdeploy_dfd, checkout_target_name, O_WRONLY | O_CLOEXEC,
&tmpf, error))
return FALSE;
if (!ostree_composefs_target_write (target, tmpf.fd, &fsverity_digest, cancellable, error))
return FALSE;
/* If the commit specified a composefs digest, verify it */
if (!compare_verity_digests (metadata_composefs, fsverity_digest, error))
return FALSE;
if (!glnx_fchmod (tmpf.fd, 0644, error))
return FALSE;
if (metadata_composefs_sig && apply_composefs_signature)
{
/* We can't apply the signature during deploy, because the corresponding public key for
this commit is not loaded into the keyring. So, we delay fs-verity application to the
first boot. */
g_autofree char *composefs_sig_path
= g_strdup_printf ("%s/.ostree.cfs.sig", checkout_target_name);
g_autoptr (GBytes) sig = g_variant_get_data_as_bytes (metadata_composefs_sig);
if (!glnx_file_replace_contents_at (osdeploy_dfd, composefs_sig_path,
g_bytes_get_data (sig, NULL), g_bytes_get_size (sig),
0, cancellable, error))
return FALSE;
}
else
{
if (!_ostree_tmpf_fsverity (repo, &tmpf, NULL, error))
return FALSE;
}
if (!glnx_link_tmpfile_at (&tmpf, GLNX_LINK_TMPFILE_REPLACE, osdeploy_dfd, composefs_cfs_path,
error))
return FALSE;
/* This is where the erofs image will be temporarily mounted */
g_autofree char *composefs_mnt_path
= g_strdup_printf ("%s/.ostree.mnt", checkout_target_name);
if (!glnx_shutil_mkdir_p_at (osdeploy_dfd, composefs_mnt_path, 0775, cancellable, error))
return FALSE;
}
#endif
return glnx_opendirat (osdeploy_dfd, checkout_target_name, TRUE, out_deployment_dfd, error);
}
@ -3003,7 +3121,8 @@ sysroot_initialize_deployment (OstreeSysroot *self, const char *osname, const ch
/* Check out the userspace tree onto the filesystem */
glnx_autofd int deployment_dfd = -1;
if (!checkout_deployment_tree (self, repo, new_deployment, &deployment_dfd, cancellable, error))
if (!checkout_deployment_tree (self, repo, new_deployment, revision, &deployment_dfd, cancellable,
error))
return FALSE;
g_autoptr (OstreeKernelLayout) kernel_layout = NULL;

View File

@ -70,9 +70,11 @@ struct OstreeSysroot
OstreeSysrootLoadState loadstate;
gboolean mount_namespace_in_use; /* TRUE if caller has told us they used CLONE_NEWNS */
gboolean root_is_ostree_booted; /* TRUE if sysroot is / and we are booted via ostree */
/* The device/inode for /, used to detect booted deployment */
/* The device/inode for / and /etc, used to detect booted deployment */
dev_t root_device;
ino_t root_inode;
dev_t etc_device;
ino_t etc_inode;
gboolean is_physical; /* TRUE if we're pointed at physical storage root and not a deployment */
GPtrArray *deployments;

View File

@ -799,14 +799,26 @@ parse_deployment (OstreeSysroot *self, const char *boot_link, OstreeDeployment *
if (looking_for_booted_deployment)
{
struct stat stbuf;
struct stat etc_stbuf = {};
if (!glnx_fstat (deployment_dfd, &stbuf, error))
return FALSE;
/* We look for either the root or the etc subdir of the
* deployment. We need to do this, because when using composefs,
* the root is not a bind mount of the deploy dir, but the etc
* dir is.
*/
if (!glnx_fstatat_allow_noent (deployment_dfd, "etc", &etc_stbuf, 0, error))
return FALSE;
/* A bit ugly, we're assigning to a sysroot-owned variable from deep in
* this parsing code. But eh, if something fails the sysroot state can't
* be relied on anyways.
*/
is_booted_deployment
= (stbuf.st_dev == self->root_device && stbuf.st_ino == self->root_inode);
= (stbuf.st_dev == self->root_device && stbuf.st_ino == self->root_inode)
|| (etc_stbuf.st_dev == self->etc_device && etc_stbuf.st_ino == self->etc_inode);
}
g_autoptr (OstreeDeployment) ret_deployment
@ -1003,6 +1015,17 @@ ostree_sysroot_initialize (OstreeSysroot *self, GError **error)
self->root_inode = root_stbuf.st_ino;
}
{
struct stat etc_stbuf;
if (!glnx_fstatat_allow_noent (AT_FDCWD, "/etc", &etc_stbuf, 0, error))
return FALSE;
if (errno != ENOENT)
{
self->etc_device = etc_stbuf.st_dev;
self->etc_inode = etc_stbuf.st_ino;
}
}
struct stat self_stbuf;
if (!glnx_fstatat (AT_FDCWD, gs_file_get_path_cached (self->path), &self_stbuf, 0, error))
return FALSE;

View File

@ -60,6 +60,50 @@ ot_keyfile_get_boolean_with_default (GKeyFile *keyfile, const char *section, con
return TRUE;
}
gboolean
ot_keyfile_get_tristate_with_default (GKeyFile *keyfile, const char *section, const char *value,
OtTristate default_value, OtTristate *out_tri, GError **error)
{
g_return_val_if_fail (keyfile != NULL, FALSE);
g_return_val_if_fail (section != NULL, FALSE);
g_return_val_if_fail (value != NULL, FALSE);
GError *temp_error = NULL;
g_autofree char *ret_value = g_key_file_get_value (keyfile, section, value, &temp_error);
if (temp_error)
{
if (is_notfound (temp_error))
{
g_clear_error (&temp_error);
g_assert (ret_value == NULL);
*out_tri = default_value;
return TRUE;
}
g_propagate_error (error, temp_error);
return FALSE;
}
ret_value = g_strstrip (ret_value);
if (strcmp (ret_value, "yes") == 0 || strcmp (ret_value, "true") == 0
|| strcmp (ret_value, "1") == 0)
*out_tri = OT_TRISTATE_YES;
else if (strcmp (ret_value, "no") == 0 || strcmp (ret_value, "false") == 0
|| strcmp (ret_value, "0") == 0)
*out_tri = OT_TRISTATE_NO;
else if (strcmp (ret_value, "maybe") == 0)
*out_tri = OT_TRISTATE_MAYBE;
else
{
g_set_error (error, G_KEY_FILE_ERROR, G_KEY_FILE_ERROR_INVALID_VALUE,
"Invalid tri-state value: %s", ret_value);
return FALSE;
}
return TRUE;
}
gboolean
ot_keyfile_get_value_with_default (GKeyFile *keyfile, const char *section, const char *value,
const char *default_value, char **out_value, GError **error)

View File

@ -23,12 +23,23 @@
#include <gio/gio.h>
typedef enum
{
OT_TRISTATE_NO,
OT_TRISTATE_MAYBE,
OT_TRISTATE_YES,
} OtTristate;
G_BEGIN_DECLS
gboolean ot_keyfile_get_boolean_with_default (GKeyFile *keyfile, const char *section,
const char *value, gboolean default_value,
gboolean *out_bool, GError **error);
gboolean ot_keyfile_get_tristate_with_default (GKeyFile *keyfile, const char *section,
const char *value, OtTristate default_value,
OtTristate *out_tri, GError **error);
gboolean ot_keyfile_get_value_with_default (GKeyFile *keyfile, const char *section,
const char *value, const char *default_value,
char **out_value, GError **error);

View File

@ -24,14 +24,21 @@
#include <err.h>
#include <fcntl.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/statvfs.h>
#include <unistd.h>
#ifdef HAVE_LINUX_FSVERITY_H
#include <linux/fsverity.h>
#endif
#define INITRAMFS_MOUNT_VAR "/run/ostree/initramfs-mount-var"
#define _OSTREE_SYSROOT_READONLY_STAMP "/run/ostree-sysroot-ro.stamp"
#define _OSTREE_COMPOSEFS_ROOT_STAMP "/run/ostree-composefs-root.stamp"
static inline int
path_is_on_readonly_fs (const char *path)
@ -73,11 +80,12 @@ out:
}
static inline char *
read_proc_cmdline_ostree (void)
read_proc_cmdline_key (const char *key)
{
char *cmdline = NULL;
const char *iter;
char *ret = NULL;
size_t key_len = strlen (key);
cmdline = read_proc_cmdline ();
if (!cmdline)
@ -90,9 +98,9 @@ read_proc_cmdline_ostree (void)
const char *next_nonspc = next;
while (next_nonspc && *next_nonspc == ' ')
next_nonspc += 1;
if (strncmp (iter, "ostree=", strlen ("ostree=")) == 0)
if (strncmp (iter, key, key_len) == 0 && iter[key_len] == '=')
{
const char *start = iter + strlen ("ostree=");
const char *start = iter + key_len + 1;
if (next)
ret = strndup (start, next - start);
else
@ -121,4 +129,65 @@ touch_run_ostree (void)
(void)close (fd);
}
static inline unsigned char *
read_file (const char *path, size_t *out_len)
{
int fd;
fd = open (path, O_RDONLY);
if (fd < 0)
{
if (errno == ENOENT)
return NULL;
err (EXIT_FAILURE, "failed to open %s", path);
}
struct stat stbuf;
if (fstat (fd, &stbuf))
err (EXIT_FAILURE, "fstat(%s) failed", path);
size_t file_size = stbuf.st_size;
unsigned char *buf = malloc (file_size);
if (buf == NULL)
err (EXIT_FAILURE, "Out of memory");
size_t file_read = 0;
while (file_read < file_size)
{
ssize_t bytes_read;
do
bytes_read = read (fd, buf + file_read, file_size - file_read);
while (bytes_read == -1 && errno == EINTR);
if (bytes_read == -1)
err (EXIT_FAILURE, "read_file(%s) failed", path);
if (bytes_read == 0)
break;
file_read += bytes_read;
}
close (fd);
*out_len = file_read;
return buf;
}
static inline void
fsverity_sign (int fd, unsigned char *signature, size_t signature_len)
{
#ifdef HAVE_LINUX_FSVERITY_H
struct fsverity_enable_arg arg = {
0,
};
arg.version = 1;
arg.hash_algorithm = FS_VERITY_HASH_ALG_SHA256;
arg.block_size = 4096;
arg.sig_size = signature_len;
arg.sig_ptr = (uint64_t)signature;
if (ioctl (fd, FS_IOC_ENABLE_VERITY, &arg) < 0)
err (EXIT_FAILURE, "failed to fs-verity sign file");
#endif
}
#endif /* __OSTREE_MOUNT_UTIL_H_ */

View File

@ -66,6 +66,7 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/mount.h>
#include <sys/param.h>
#include <sys/stat.h>
@ -73,6 +74,10 @@
#include <sys/types.h>
#include <unistd.h>
/* We can't include both linux/fs.h and sys/mount.h, so define these directly */
#define FS_VERITY_FL 0x00100000 /* Verity protected inode */
#define FS_IOC_GETFLAGS _IOR ('f', 1, long)
#if defined(HAVE_LIBSYSTEMD) && !defined(OSTREE_PREPARE_ROOT_STATIC)
#define USE_LIBSYSTEMD
#endif
@ -86,8 +91,21 @@
// A temporary mount point
#define TMP_SYSROOT "/sysroot.tmp"
#ifdef HAVE_COMPOSEFS
#include <libcomposefs/lcfs-mount.h>
#endif
#include "ostree-mount-util.h"
typedef enum
{
OSTREE_COMPOSEFS_MODE_OFF, /* Never use composefs */
OSTREE_COMPOSEFS_MODE_MAYBE, /* Use if supported and image exists in deploy */
OSTREE_COMPOSEFS_MODE_ON, /* Always use (and fail if not working) */
OSTREE_COMPOSEFS_MODE_SIGNED, /* Always use and require it to be signed */
OSTREE_COMPOSEFS_MODE_DIGEST, /* Always use and require specific digest */
} OstreeComposefsMode;
static inline bool
sysroot_is_configured_ro (const char *sysroot)
{
@ -135,7 +153,7 @@ resolve_deploy_path (const char *root_mountpoint)
struct stat stbuf;
char *ostree_target, *deploy_path;
ostree_target = read_proc_cmdline_ostree ();
ostree_target = read_proc_cmdline_key ("ostree");
if (!ostree_target)
errx (EXIT_FAILURE, "No OSTree target; expected ostree=/ostree/boot.N/...");
@ -227,6 +245,34 @@ main (int argc, char *argv[])
err (EXIT_FAILURE, "failed to umount proc from /proc");
}
OstreeComposefsMode composefs_mode = OSTREE_COMPOSEFS_MODE_MAYBE;
char *ot_composefs = read_proc_cmdline_key ("ot-composefs");
char *composefs_digest = NULL;
if (ot_composefs)
{
if (strcmp (ot_composefs, "off") == 0)
composefs_mode = OSTREE_COMPOSEFS_MODE_OFF;
else if (strcmp (ot_composefs, "maybe") == 0)
composefs_mode = OSTREE_COMPOSEFS_MODE_MAYBE;
else if (strcmp (ot_composefs, "on") == 0)
composefs_mode = OSTREE_COMPOSEFS_MODE_ON;
else if (strcmp (ot_composefs, "signed") == 0)
composefs_mode = OSTREE_COMPOSEFS_MODE_SIGNED;
else if (strncmp (ot_composefs, "digest=", strlen ("digest=")) == 0)
{
composefs_mode = OSTREE_COMPOSEFS_MODE_DIGEST;
composefs_digest = ot_composefs + strlen ("digest=");
}
else
err (EXIT_FAILURE, "Unsupported ot-composefs option: '%s'", ot_composefs);
}
#ifndef HAVE_COMPOSEFS
if (composefs_mode == OSTREE_COMPOSEFS_MODE_MAYBE)
composefs_mode = OSTREE_COMPOSEFS_MODE_OFF;
(void)composefs_digest;
#endif
/* Query the repository configuration - this is an operating system builder
* choice. More info: https://github.com/ostreedev/ostree/pull/1767
*/
@ -254,11 +300,99 @@ main (int argc, char *argv[])
if (chdir (deploy_path) < 0)
err (EXIT_FAILURE, "failed to chdir to deploy_path");
/* Currently always false */
bool using_composefs = false;
/* We construct the new sysroot in /sysroot.tmp, which is either the composfs
mount or a bind mount of the deploy-dir */
if (composefs_mode != OSTREE_COMPOSEFS_MODE_OFF)
{
#ifdef HAVE_COMPOSEFS
const char *objdirs[] = { "/sysroot/ostree/repo/objects" };
struct lcfs_mount_options_s cfs_options = {
objdirs,
1,
};
int cfs_fd;
unsigned cfs_flags;
cfs_fd = open (".ostree.cfs", O_RDONLY);
if (cfs_fd < 0)
{
if (errno == ENOENT)
goto nocfs;
err (EXIT_FAILURE, "failed to open .ostree.cfs");
}
/* Check if file is already fsverity */
if (ioctl (cfs_fd, FS_IOC_GETFLAGS, &cfs_flags) < 0)
err (EXIT_FAILURE, "failed to get .ostree.cfs flags");
/* It is not, apply signature (if it exists) */
if ((cfs_flags & FS_VERITY_FL) == 0)
{
unsigned char *signature;
size_t signature_len;
signature = read_file (".ostree.cfs.sig", &signature_len);
if (signature != NULL)
{
/* If we're read-only we temporarily make it read-write to sign the image */
if (!sysroot_currently_writable
&& mount (root_mountpoint, root_mountpoint, NULL, MS_REMOUNT | MS_SILENT, NULL)
< 0)
err (EXIT_FAILURE, "failed to remount rootfs writable (for signing)");
fsverity_sign (cfs_fd, signature, signature_len);
free (signature);
if (!sysroot_currently_writable
&& mount (root_mountpoint, root_mountpoint, NULL,
MS_REMOUNT | MS_RDONLY | MS_SILENT, NULL)
< 0)
err (EXIT_FAILURE, "failed to remount rootfs back read-only (after signing)");
}
}
cfs_options.flags = LCFS_MOUNT_FLAGS_READONLY;
if (snprintf (srcpath, sizeof (srcpath), "%s/.ostree.mnt", deploy_path) < 0)
err (EXIT_FAILURE, "failed to assemble /boot/loader path");
cfs_options.image_mountdir = srcpath;
if (composefs_mode == OSTREE_COMPOSEFS_MODE_SIGNED)
{
cfs_options.flags |= LCFS_MOUNT_FLAGS_REQUIRE_SIGNATURE | LCFS_MOUNT_FLAGS_REQUIRE_VERITY;
}
else if (composefs_mode == OSTREE_COMPOSEFS_MODE_DIGEST)
{
cfs_options.flags |= LCFS_MOUNT_FLAGS_REQUIRE_VERITY;
cfs_options.expected_digest = composefs_digest;
}
if (lcfs_mount_fd (cfs_fd, TMP_SYSROOT, &cfs_options) == 0)
{
int fd = open (_OSTREE_COMPOSEFS_ROOT_STAMP, O_WRONLY | O_CREAT | O_CLOEXEC, 0644);
if (fd < 0)
err (EXIT_FAILURE, "failed to create %s", _OSTREE_COMPOSEFS_ROOT_STAMP);
(void)close (fd);
using_composefs = 1;
}
close (cfs_fd);
nocfs:
#else
err (EXIT_FAILURE, "Composefs not supported");
#endif
}
if (!using_composefs)
{
if (composefs_mode > OSTREE_COMPOSEFS_MODE_MAYBE)
err (EXIT_FAILURE, "Failed to mount composefs");
/* The deploy root starts out bind mounted to sysroot.tmp */
if (mount (deploy_path, TMP_SYSROOT, NULL, MS_BIND | MS_SILENT, NULL) < 0)
err (EXIT_FAILURE, "failed to make initial bind mount %s", deploy_path);

View File

@ -95,7 +95,12 @@ main (int argc, char *argv[])
if (mount ("none", "/sysroot", NULL, MS_REC | MS_PRIVATE, NULL) < 0)
perror ("warning: While remounting /sysroot MS_PRIVATE");
if (path_is_on_readonly_fs ("/"))
bool root_is_composefs = false;
struct stat stbuf;
if (fstatat (AT_FDCWD, _OSTREE_COMPOSEFS_ROOT_STAMP, &stbuf, 0) == 0)
root_is_composefs = true;
if (path_is_on_readonly_fs ("/") && !root_is_composefs)
{
/* If / isn't writable, don't do any remounts; we don't want
* to clear the readonly flag in that case.

View File

@ -63,7 +63,7 @@ main (int argc, char *argv[])
* exit so that we don't error, but at the same time work where switchroot
* is PID 1 (and so hasn't created /run/ostree-booted).
*/
char *ostree_cmdline = read_proc_cmdline_ostree ();
char *ostree_cmdline = read_proc_cmdline_key ("ostree");
if (!ostree_cmdline)
exit (EXIT_SUCCESS);