Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP: Use fsverity library, require keys and add signatures too #2269

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 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 @@ -200,10 +202,11 @@ EXTRA_DIST += \

libostree_1_la_CFLAGS = $(AM_CFLAGS) -I$(srcdir)/bsdiff -I$(srcdir)/libglnx -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) \
$(OT_DEP_FSVERITY_CFLAGS) \
-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) $(OT_DEP_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
19 changes: 14 additions & 5 deletions configure.ac
Original file line number Diff line number Diff line change
Expand Up @@ -252,14 +252,23 @@ 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], [
PKG_CHECK_MODULES(OT_DEP_FSVERITY, libfsverity, have_fsverity=yes, have_fsverity=no)
AC_DEFINE([HAVE_FSVERITY], 1, [Define if using libfsverity])
AM_CONDITIONAL(BUILDOPT_FSVERITY, [test x$ac_cv_header_libfsverity_h = xyes])
AS_IF([ test x$have_fsverity = xno && test x$with_fsverity = xyes ], [
AC_MSG_ERROR([Missing fsverity])
])
OSTREE_FEATURES="$OSTREE_FEATURES ex-fsverity"
])

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 +641,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