Skip to content

Commit

Permalink
WIP: Use fsverity library, require keys and add signatures too
Browse files Browse the repository at this point in the history
At the time we added fsverity code to ostree, fsverity was
just a CLI tool; since then it has
gained a C shared library which wraps all the signature
bits and the enablement `ioctl()` conveniently.

This makes it much easier for us to support signatures, so
do so.  Note that at this time, because ostree doesn't define
a mechanism to transport fsverity signatures across repositories,
this is mostly only useful for locally-generated signatures.

The idea however is this is a starting point from which we can
build more support, including signature transport, remote keys,
etc.

In order to simplify things, drop support for "opportunistic"
use of fsverity.  In practice we expect people using this
to set it up fully, or not at all.  The "read only files"
aspect *is* useful, but complicated the code too much relative
to its benefit.

Also drop support for using fsverity for `/boot` for now; in
practice most things there are read by the bootloader,
which doesn't know about fsverity.  Instead those should
be covered by e.g. Secure Boot.  This ensures that
we only have one high level API `_ostree_tmpf_fsverity()`
that is invoked from the core commit path.

xref https://lwn.net/Articles/842002
xref ostreedev#1959
xref coreos/rpm-ostree#1883
  • Loading branch information
cgwalters committed Jan 17, 2021
1 parent c216a43 commit 829f82c
Show file tree
Hide file tree
Showing 10 changed files with 260 additions and 143 deletions.
6 changes: 4 additions & 2 deletions Makefile-libostree.am
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,6 @@ libostree_1_la_SOURCES = \
src/libostree/ostree-repo-libarchive.c \
src/libostree/ostree-repo-prune.c \
src/libostree/ostree-repo-refs.c \
src/libostree/ostree-repo-verity.c \
src/libostree/ostree-repo-traverse.c \
src/libostree/ostree-repo-private.h \
src/libostree/ostree-repo-file.c \
Expand Down Expand Up @@ -163,6 +162,9 @@ else # if ENABLE_EXPERIMENTAL_API
libostree_1_la_SOURCES += \
$(NULL)
endif
if BUILDOPT_FSVERITY
libostree_1_la_SOURCES += src/libostree/ostree-repo-verity.c
endif

if USE_AVAHI
libostree_1_la_SOURCES += \
Expand Down Expand Up @@ -203,7 +205,7 @@ libostree_1_la_CFLAGS = $(AM_CFLAGS) -I$(srcdir)/bsdiff -I$(srcdir)/libglnx -I$(
-fvisibility=hidden '-D_OSTREE_PUBLIC=__attribute__((visibility("default"))) extern'
libostree_1_la_LDFLAGS = -version-number 1:0:0 -Bsymbolic-functions $(addprefix $(wl_versionscript_arg),$(symbol_files))
libostree_1_la_LIBADD = libotutil.la libglnx.la libbsdiff.la $(OT_INTERNAL_GIO_UNIX_LIBS) $(OT_INTERNAL_GPGME_LIBS) \
$(OT_DEP_LZMA_LIBS) $(OT_DEP_ZLIB_LIBS) $(OT_DEP_CRYPTO_LIBS)
$(OT_DEP_LZMA_LIBS) $(OT_DEP_ZLIB_LIBS) $(OT_DEP_CRYPTO_LIBS) $(FSVERITY_LIBS)
# Some change between rust-1.21.0-1.fc27 and rust-1.22.1-1.fc27.x86_64
if ENABLE_RUST
libostree_1_la_LIBADD += -ldl
Expand Down
27 changes: 22 additions & 5 deletions configure.ac
Original file line number Diff line number Diff line change
Expand Up @@ -252,14 +252,31 @@ AS_IF([test x$with_ed25519_libsodium != xno], [
], with_ed25519_libsodium=no )
AM_CONDITIONAL(USE_LIBSODIUM, test "x$have_libsodium" = xyes)

AC_ARG_WITH(fsverity,
AS_HELP_STRING([--with-fsverity], [Support fsverity @<:@default=maybe@:>@]),
[], [with_fsverity=maybe])
AS_IF([test x$with_fsverity != xno], [
AC_CHECK_HEADERS([libfsverity.h])
AM_CONDITIONAL(BUILDOPT_FSVERITY, [test x$ac_cv_header_libfsverity_h = xyes])
AM_COND_IF([BUILDOPT_FSVERITY],
[
AC_DEFINE([BUILDOPT_FSVERITY], 1, [Define if using fsverity])
OSTREE_FEATURES="$OSTREE_FEATURES ex-fsverity"
buildopt_fsverity=yes
FSVERITY_LIBS="-lfsverity"
AC_SUBST(FSVERITY_LIBS)
],
[
AS_IF([test x$with_fsverity != xyes], [AC_MSG_ERROR([libfsverity requested but not found])])
buildopt_fsverity=no
]
)
])

LIBARCHIVE_DEPENDENCY="libarchive >= 2.8.0"
# What's in RHEL7.2.
FUSE_DEPENDENCY="fuse >= 2.9.2"

AC_CHECK_HEADERS([linux/fsverity.h])
AS_IF([test x$ac_cv_header_linux_fsverity_h = xyes ],
[OSTREE_FEATURES="$OSTREE_FEATURES ex-fsverity"])

# check for gtk-doc
m4_ifdef([GTK_DOC_CHECK], [
GTK_DOC_CHECK([1.15], [--flavour no-tmpl])
Expand Down Expand Up @@ -632,7 +649,7 @@ echo "
HTTP backend: $fetcher_backend
\"ostree trivial-httpd\": $enable_trivial_httpd_cmdline
SELinux: $with_selinux
fs-verity: $ac_cv_header_linux_fsverity_h
fs-verity: $buildopt_fsverity
cryptographic checksums: $with_crypto
systemd: $with_libsystemd
libmount: $with_libmount
Expand Down
26 changes: 16 additions & 10 deletions src/libostree/ostree-repo-private.h
Original file line number Diff line number Diff line change
Expand Up @@ -162,8 +162,11 @@ struct OstreeRepo {
GMutex txn_lock;
OstreeRepoTxn txn;
gboolean txn_locked;
_OstreeFeatureSupport fs_verity_wanted;
_OstreeFeatureSupport fs_verity_supported;
gboolean fs_verity_wanted;
#ifdef BUILDOPT_FSVERITY
char *fsverity_cert;
char *fsverity_key;
#endif

GMutex cache_lock;
guint dirmeta_cache_refcount;
Expand Down Expand Up @@ -521,18 +524,21 @@ OstreeRepoAutoLock * _ostree_repo_auto_lock_push (OstreeRepo *self,
void _ostree_repo_auto_lock_cleanup (OstreeRepoAutoLock *lock);
G_DEFINE_AUTOPTR_CLEANUP_FUNC (OstreeRepoAutoLock, _ostree_repo_auto_lock_cleanup)

gboolean _ostree_repo_parse_fsverity_config (OstreeRepo *self, GError **error);

gboolean
_ostree_tmpf_fsverity_core (GLnxTmpfile *tmpf,
_OstreeFeatureSupport fsverity_requested,
gboolean *supported,
GError **error);

#define _OSTREE_FSVERITY_CONFIG_KEY "ex-fsverity"
#ifdef BUILDOPT_FSVERITY
gboolean
_ostree_repo_parse_fsverity_config (OstreeRepo *self, GError **error);
gboolean
_ostree_tmpf_fsverity (OstreeRepo *self,
GLnxTmpfile *tmpf,
GError **error);
#else
static inline gboolean
_ostree_repo_parse_fsverity_config (OstreeRepo *self, GError **error) { return TRUE;}
static inline gboolean
_ostree_tmpf_fsverity (OstreeRepo *self, GLnxTmpfile *tmpf, GError **error) { return TRUE; }
#endif


gboolean
_ostree_repo_verify_bindings (const char *collection_id,
Expand Down
224 changes: 111 additions & 113 deletions src/libostree/ostree-repo-verity.c
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
* 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
Expand All @@ -27,94 +27,98 @@
#include "ostree-repo-private.h"
#include "otutil.h"
#include "ot-fs-utils.h"
#ifdef HAVE_LINUX_FSVERITY_H
#include <linux/fsverity.h>
#endif
#include <libfsverity.h>

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;
#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))
return FALSE;
if (fsverity_required)
{
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");
}
else
{
gboolean fsverity_opportunistic = FALSE;
if (!ot_keyfile_get_boolean_with_default (self->config, fsverity_key, "opportunistic",
FALSE, &fsverity_opportunistic, error))
return FALSE;
if (fsverity_opportunistic)
self->fs_verity_wanted = _OSTREE_FEATURE_MAYBE;
}
/* libfsverity has a thread-unsafe process global error callback */
G_LOCK_DEFINE_STATIC(ot_fsverity_err_lock);
static char *ot_last_fsverity_error;

return TRUE;
static void
ot_on_libfsverity_error(const char *msg)
{
G_LOCK (ot_fsverity_err_lock);
g_free (ot_last_fsverity_error);
ot_last_fsverity_error = g_strdup (msg);
G_UNLOCK (ot_fsverity_err_lock);
}

static gboolean
throw_fsverity_error (GError **error, const char *prefix)
{
g_autofree char *msg = NULL;
G_LOCK (ot_fsverity_err_lock);
msg = g_steal_pointer (&ot_last_fsverity_error);
G_UNLOCK (ot_fsverity_err_lock);
return glnx_throw (error, "%s: %s", prefix, msg);
}

/* Wrapper around the fsverity ioctl, compressing the result to
* "success, unsupported or error". This is used for /boot where
* we enable verity if supported.
* */
gboolean
_ostree_tmpf_fsverity_core (GLnxTmpfile *tmpf,
_OstreeFeatureSupport fsverity_requested,
gboolean *supported,
GError **error)
static char *
maybe_make_repo_relative (OstreeRepo *repo, const char *path)
{
/* Set this by default to simplify the code below */
if (supported)
*supported = FALSE;
if (path[0] == '/')
return g_strdup (path);
return g_strdup_printf ("/proc/self/fd/%d/%s", repo->repo_dir_fd, glnx_basename (path));
}

if (fsverity_requested == _OSTREE_FEATURE_NO)
gboolean
_ostree_repo_parse_fsverity_config (OstreeRepo *self, GError **error)
{
struct stat stbuf;
g_autofree char *keypath = NULL;
if (!ot_keyfile_get_value_with_default (self->config, _OSTREE_FSVERITY_CONFIG_KEY, "key",
NULL, &keypath, error))
return FALSE;
/* If no key is set, we're done */
if (keypath == NULL)
return TRUE;

#ifdef HAVE_LINUX_FSVERITY_H
GLNX_AUTO_PREFIX_ERROR ("fsverity", error);
self->fsverity_key = maybe_make_repo_relative (self, keypath);
if (!glnx_fstatat (self->repo_dir_fd, self->fsverity_key, &stbuf, 0, error))
return glnx_prefix_error (error, "Couldn't access fsverity key");
/* Enforce not-world-readable for the same reason as ssh */
if (stbuf.st_mode & S_IROTH)
return glnx_throw (error, "fsverity key must not be world-readable");

/* fs-verity requires a read-only file descriptor */
if (!glnx_tmpfile_reopen_rdonly (tmpf, error))
g_autofree char *certpath = NULL;
if (!ot_keyfile_get_value_with_default (self->config, _OSTREE_FSVERITY_CONFIG_KEY, "cert", NULL, &certpath, error))
return FALSE;
if (!certpath)
return glnx_throw (error, "fsverity key specified, but no certificate");
self->fsverity_cert = maybe_make_repo_relative (self, certpath);
if (!glnx_fstatat (self->repo_dir_fd, self->fsverity_cert, &stbuf, 0, error))
return glnx_prefix_error (error, "Couldn't access fsverity certificate");

/* Process global state is bad. We want to support multiple ostree repos per process.
* At some point we should try patching
* libfsverity to have something GError like that gives us a string too.
*/
static gsize initialized = 0;
if (g_once_init_enter (&initialized))
{
libfsverity_set_error_callback (ot_on_libfsverity_error);
g_once_init_leave (&initialized, 1);
}

struct fsverity_enable_arg arg = { 0, };
arg.version = 1;
arg.hash_algorithm = FS_VERITY_HASH_ALG_SHA256; /* TODO configurable? */
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;
return TRUE;
}

if (ioctl (tmpf->fd, FS_IOC_ENABLE_VERITY, &arg) < 0)
static int
ot_fsverity_read_callback (void *file, void *bufp, size_t count)
{
errno = 0;
int fd = GPOINTER_TO_INT (file);
guint8* buf = bufp;
while (count > 0)
{
switch (errno)
{
case ENOTTY:
case EOPNOTSUPP:
return TRUE;
default:
return glnx_throw_errno_prefix (error, "ioctl(FS_IOC_ENABLE_VERITY)");
}
ssize_t n = read (fd, buf, MIN (count, INT_MAX));
if (n < 0)
return -errno;
if (n == 0)
return -EIO;
buf += n;
count -= n;
}

if (supported)
*supported = TRUE;
#endif
return TRUE;
return 0;
}

/* Enable verity on a file, respecting the "wanted" and "supported" states.
Expand All @@ -127,48 +131,42 @@ _ostree_tmpf_fsverity (OstreeRepo *self,
GLnxTmpfile *tmpf,
GError **error)
{
#ifdef HAVE_LINUX_FSVERITY_H
g_mutex_lock (&self->txn_lock);
_OstreeFeatureSupport fsverity_wanted = self->fs_verity_wanted;
_OstreeFeatureSupport fsverity_supported = self->fs_verity_supported;
g_mutex_unlock (&self->txn_lock);
GLNX_AUTO_PREFIX_ERROR ("ostree/fsverity", error);

switch (fsverity_wanted)
{
case _OSTREE_FEATURE_YES:
{
if (fsverity_supported == _OSTREE_FEATURE_NO)
return glnx_throw (error, "fsverity required but filesystem does not support it");
}
break;
case _OSTREE_FEATURE_MAYBE:
break;
case _OSTREE_FEATURE_NO:
return TRUE;
}
if (!self->fs_verity_wanted)
return TRUE;

struct libfsverity_signature_params sig_params = {
.keyfile = self->fsverity_key,
.certfile = self->fsverity_cert,
};

gboolean supported = FALSE;
if (!_ostree_tmpf_fsverity_core (tmpf, fsverity_wanted, &supported, error))
/* fs-verity requires a read-only file descriptor */
if (!glnx_tmpfile_reopen_rdonly (tmpf, error))
return FALSE;

if (!supported)
{
if (G_UNLIKELY (fsverity_wanted == _OSTREE_FEATURE_YES))
return glnx_throw (error, "fsverity required but filesystem does not support it");

/* If we got here, we must be trying "opportunistic" use of fs-verity */
g_assert_cmpint (fsverity_wanted, ==, _OSTREE_FEATURE_MAYBE);
g_mutex_lock (&self->txn_lock);
self->fs_verity_supported = _OSTREE_FEATURE_NO;
g_mutex_unlock (&self->txn_lock);
return TRUE;
}

g_mutex_lock (&self->txn_lock);
self->fs_verity_supported = _OSTREE_FEATURE_YES;
g_mutex_unlock (&self->txn_lock);
#else
g_assert_cmpint (self->fs_verity_wanted, !=, _OSTREE_FEATURE_YES);
#endif
struct stat stbuf;
if (!glnx_fstat (tmpf->fd, &stbuf, error))
return FALSE;

struct libfsverity_merkle_tree_params tree_params = {
.version = 1,
.hash_algorithm = FS_VERITY_HASH_ALG_SHA256,
.file_size = stbuf.st_size,
.block_size = 4096,
/* TODO: salt? */
};
g_autofree struct libfsverity_digest *digest = NULL;
if (libfsverity_compute_digest (GINT_TO_POINTER (tmpf->fd), ot_fsverity_read_callback, &tree_params, &digest) < 0)
return throw_fsverity_error (error, "failed to compute digest");

guint8 *sig = NULL;
size_t sig_size;
if (libfsverity_sign_digest (digest, &sig_params, &sig, &sig_size) < 0)
return throw_fsverity_error (error, "failed to generate signature");

if (libfsverity_enable_with_sig (tmpf->fd, &tree_params, sig, sig_size) < 0)
return throw_fsverity_error (error, "failed to enable fsverity for file");

return TRUE;
}
Loading

0 comments on commit 829f82c

Please sign in to comment.