mirror of
https://github.com/ostreedev/ostree.git
synced 2025-01-10 05:18:30 +03:00
Merge pull request #2640 from alexlarsson/composefs
Add initial composefs integration
This commit is contained in:
commit
b6c054e1fa
4
.github/workflows/tests.yml
vendored
4
.github/workflows/tests.yml
vendored
@ -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
3
.gitmodules
vendored
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
1
composefs
Submodule
@ -0,0 +1 @@
|
||||
Subproject commit e5ab5e2dc6aa6bd2daab3052553b787efe16fc7d
|
32
configure.ac
32
configure.ac
@ -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"
|
||||
], [
|
||||
|
@ -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 (),
|
||||
|
631
src/libostree/ostree-repo-composefs.c
Normal file
631
src/libostree/ostree-repo-composefs.c
Normal 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;
|
||||
}
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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,
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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)
|
||||
|
@ -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);
|
||||
|
@ -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_ */
|
||||
|
@ -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);
|
||||
|
@ -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.
|
||||
|
@ -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);
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user