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

Initial fs-verity support #1959

Merged
merged 1 commit into from
Jan 28, 2020
Merged
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
5 changes: 5 additions & 0 deletions configure.ac
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,10 @@ 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 @@ -618,6 +622,7 @@ echo "
HTTP backend: $fetcher_backend
\"ostree trivial-httpd\": $enable_trivial_httpd_cmdline
SELinux: $with_selinux
fs-verity: $ac_cv_header_linux_fsverity_h
cryptographic checksums: $with_crypto
systemd: $with_libsystemd
libmount: $with_libmount
Expand Down
114 changes: 114 additions & 0 deletions src/libostree/ostree-repo-commit.c
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,10 @@
#include <glib/gprintf.h>
#include <sys/ioctl.h>
#include <linux/fs.h>
#include <ext2fs/ext2_fs.h>
#ifdef HAVE_LINUX_FSVERITY_H
#include <linux/fsverity.h>
#endif

#include "otutil.h"
#include "ostree.h"
Expand Down Expand Up @@ -168,6 +172,113 @@ ot_security_smack_reset_fd (int fd)
#endif
}

/* 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)
{
/* Set this by default to simplify the code below */
if (supported)
*supported = FALSE;

if (fsverity_requested == _OSTREE_FEATURE_NO)
return TRUE;

#ifdef HAVE_LINUX_FSVERITY_H
GLNX_AUTO_PREFIX_ERROR ("fsverity", error);

/* fs-verity requires a read-only file descriptor */
if (!glnx_tmpfile_reopen_rdonly (tmpf, error))
return FALSE;

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;

if (ioctl (tmpf->fd, FS_IOC_ENABLE_VERITY, &arg) < 0)
{
switch (errno)
{
case ENOTTY:
case EOPNOTSUPP:
return TRUE;
default:
return glnx_throw_errno_prefix (error, "ioctl(FS_IOC_ENABLE_VERITY)");
}
}

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

/* Enable verity on a file, respecting the "wanted" and "supported" states.
* The main idea here is to optimize out pointlessly calling the ioctl()
* over and over in cases where it's not supported for the repo's filesystem,
* as well as to support "opportunistic" use (requested and if filesystem supports).
* */
gboolean
_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);

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;
}

gboolean supported = FALSE;
if (!_ostree_tmpf_fsverity_core (tmpf, fsverity_wanted, &supported, 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
return TRUE;
}

/* Given an O_TMPFILE regular file, link it into place. */
gboolean
_ostree_repo_commit_tmpf_final (OstreeRepo *self,
Expand All @@ -185,6 +296,9 @@ _ostree_repo_commit_tmpf_final (OstreeRepo *self,
cancellable, error))
return FALSE;

if (!_ostree_tmpf_fsverity (self, tmpf, error))
return FALSE;

if (!glnx_link_tmpfile_at (tmpf, GLNX_LINK_TMPFILE_NOREPLACE_IGNORE_EXIST,
dest_dfd, tmpbuf, error))
return FALSE;
Expand Down
20 changes: 20 additions & 0 deletions src/libostree/ostree-repo-private.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
#pragma once

#include <sys/statvfs.h>
#include "config.h"
#include "otutil.h"
#include "ostree-ref.h"
#include "ostree-repo.h"
Expand Down Expand Up @@ -97,6 +98,12 @@ typedef struct {
fsblkcnt_t max_blocks;
} OstreeRepoTxn;

typedef enum {
_OSTREE_FEATURE_NO,
_OSTREE_FEATURE_MAYBE,
_OSTREE_FEATURE_YES,
} _OstreeFeatureSupport;

/**
* OstreeRepo:
*
Expand Down Expand Up @@ -127,6 +134,8 @@ struct OstreeRepo {
GMutex txn_lock;
OstreeRepoTxn txn;
gboolean txn_locked;
_OstreeFeatureSupport fs_verity_wanted;
_OstreeFeatureSupport fs_verity_supported;

GMutex cache_lock;
guint dirmeta_cache_refcount;
Expand Down Expand Up @@ -471,4 +480,15 @@ 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_tmpf_fsverity_core (GLnxTmpfile *tmpf,
_OstreeFeatureSupport fsverity_requested,
gboolean *supported,
GError **error);

gboolean
_ostree_tmpf_fsverity (OstreeRepo *self,
GLnxTmpfile *tmpf,
GError **error);

G_END_DECLS
30 changes: 30 additions & 0 deletions src/libostree/ostree-repo.c
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
#include "libglnx.h"
#include "otutil.h"
#include <glnx-console.h>
#include <linux/magic.h>

#include "ostree-core-private.h"
#include "ostree-sysroot-private.h"
Expand All @@ -47,6 +48,7 @@
#include <glib/gstdio.h>
#include <sys/file.h>
#include <sys/statvfs.h>
#include <sys/statfs.h>

#define REPO_LOCK_DISABLED (-2)
#define REPO_LOCK_BLOCKING (-1)
Expand Down Expand Up @@ -3033,6 +3035,34 @@ reload_core_config (OstreeRepo *self,
}
}

/* 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;
}

{
g_clear_pointer (&self->collection_id, g_free);
if (!ot_keyfile_get_value_with_default (self->config, "core", "collection-id",
Expand Down
99 changes: 68 additions & 31 deletions src/libostree/ostree-sysroot-deploy.c
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@

#include "otutil.h"
#include "ostree.h"
#include "ostree-repo-private.h"
#include "ostree-sysroot-private.h"
#include "ostree-sepolicy-private.h"
#include "ostree-bootloader-zipl.h"
Expand Down Expand Up @@ -104,7 +105,8 @@ sysroot_flags_to_copy_flags (GLnxFileCopyFlags defaults,
* hardlink if we're on the same partition.
*/
static gboolean
install_into_boot (OstreeSePolicy *sepolicy,
install_into_boot (OstreeRepo *repo,
OstreeSePolicy *sepolicy,
int src_dfd,
const char *src_subpath,
int dest_dfd,
Expand All @@ -113,32 +115,67 @@ install_into_boot (OstreeSePolicy *sepolicy,
GCancellable *cancellable,
GError **error)
{
if (linkat (src_dfd, src_subpath, dest_dfd, dest_subpath, 0) != 0)
{
if (G_IN_SET (errno, EMLINK, EXDEV))
{
/* Be sure we relabel when copying the kernel, as in current
* e.g. Fedora it might be labeled module_object_t or usr_t,
* but policy may not allow other processes to read from that
* like kdump.
* See also https://github.com/fedora-selinux/selinux-policy/commit/747f4e6775d773ab74efae5aa37f3e5e7f0d4aca
* This means we also drop xattrs but...I doubt anyone uses
* non-SELinux xattrs for the kernel anyways aside from perhaps
* IMA but that's its own story.
*/
g_auto(OstreeSepolicyFsCreatecon) fscreatecon = { 0, };
const char *boot_path = glnx_strjoina ("/boot/", glnx_basename (dest_subpath));
if (!_ostree_sepolicy_preparefscreatecon (&fscreatecon, sepolicy,
boot_path, S_IFREG | 0644,
error))
return FALSE;
return glnx_file_copy_at (src_dfd, src_subpath, NULL, dest_dfd, dest_subpath,
cgwalters marked this conversation as resolved.
Show resolved Hide resolved
GLNX_FILE_COPY_NOXATTRS | GLNX_FILE_COPY_DATASYNC,
cancellable, error);
}
else
return glnx_throw_errno_prefix (error, "linkat(%s)", dest_subpath);
}
if (linkat (src_dfd, src_subpath, dest_dfd, dest_subpath, 0) == 0)
return TRUE; /* Note early return */
if (!G_IN_SET (errno, EMLINK, EXDEV))
return glnx_throw_errno_prefix (error, "linkat(%s)", dest_subpath);

/* Otherwise, copy */
struct stat src_stbuf;
if (!glnx_fstatat (src_dfd, src_subpath, &src_stbuf, AT_SYMLINK_NOFOLLOW, error))
return FALSE;

glnx_autofd int src_fd = -1;
if (!glnx_openat_rdonly (src_dfd, src_subpath, FALSE, &src_fd, error))
return FALSE;

/* Be sure we relabel when copying the kernel, as in current
* e.g. Fedora it might be labeled module_object_t or usr_t,
* but policy may not allow other processes to read from that
* like kdump.
* See also https://github.com/fedora-selinux/selinux-policy/commit/747f4e6775d773ab74efae5aa37f3e5e7f0d4aca
* This means we also drop xattrs but...I doubt anyone uses
* non-SELinux xattrs for the kernel anyways aside from perhaps
* IMA but that's its own story.
*/
g_auto(OstreeSepolicyFsCreatecon) fscreatecon = { 0, };
const char *boot_path = glnx_strjoina ("/boot/", glnx_basename (dest_subpath));
if (!_ostree_sepolicy_preparefscreatecon (&fscreatecon, sepolicy,
boot_path, S_IFREG | 0644,
error))
return FALSE;

g_auto(GLnxTmpfile) tmp_dest = { 0, };
if (!glnx_open_tmpfile_linkable_at (dest_dfd, ".", O_WRONLY | O_CLOEXEC,
&tmp_dest, error))
return FALSE;

if (glnx_regfile_copy_bytes (src_fd, tmp_dest.fd, (off_t) -1) < 0)
return glnx_throw_errno_prefix (error, "regfile copy");

/* Kernel data should always be root-owned */
if (fchown (tmp_dest.fd, src_stbuf.st_uid, src_stbuf.st_gid) != 0)
return glnx_throw_errno_prefix (error, "fchown");

if (fchmod (tmp_dest.fd, src_stbuf.st_mode & 07777) != 0)
return glnx_throw_errno_prefix (error, "fchmod");

if (fdatasync (tmp_dest.fd) < 0)
return glnx_throw_errno_prefix (error, "fdatasync");

/* Today we don't have a config flag to *require* verity on /boot,
* and at least for Fedora CoreOS we're not likely to do fsverity on
* /boot soon due to wanting to support mounting it from old Linux
* kernels. So change "required" to "maybe".
*/
_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))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Won't this cause one ioctl each time even if it's not supported? Hmm, how about we add an optional fsverity_wanted parameter to _ostree_tmpf_fsverity that overrides the one from OstreeRepo? Then we could use it here too and get the automatic no-op.

Ohh, but right the kernel might support it, but not the filesystem on /boot. Hmm, so probably instead we should have a boot_fsverity in the OstreeSysroot object? But ehhh, this is good as is too. The number of files we install in /boot pales in comparison to / so it's a small optimization we could do later.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, an extra syscall or two for this doesn't matter much, but I try to keep the total small since I sometimes just resort to strace -f - we have a lot more objects than kernels.

return FALSE;

if (!glnx_link_tmpfile_at (&tmp_dest, GLNX_LINK_TMPFILE_NOREPLACE, dest_dfd, dest_subpath, error))
return FALSE;

return TRUE;
}
Expand Down Expand Up @@ -1666,7 +1703,7 @@ install_deployment_kernel (OstreeSysroot *sysroot,
return FALSE;
if (errno == ENOENT)
{
if (!install_into_boot (sepolicy, kernel_layout->boot_dfd, kernel_layout->kernel_srcpath,
if (!install_into_boot (repo, sepolicy, kernel_layout->boot_dfd, kernel_layout->kernel_srcpath,
bootcsum_dfd, kernel_layout->kernel_namever,
sysroot->debug_flags,
cancellable, error))
Expand All @@ -1683,7 +1720,7 @@ install_deployment_kernel (OstreeSysroot *sysroot,
return FALSE;
if (errno == ENOENT)
{
if (!install_into_boot (sepolicy, kernel_layout->boot_dfd, kernel_layout->initramfs_srcpath,
if (!install_into_boot (repo, sepolicy, kernel_layout->boot_dfd, kernel_layout->initramfs_srcpath,
bootcsum_dfd, kernel_layout->initramfs_namever,
sysroot->debug_flags,
cancellable, error))
Expand All @@ -1698,7 +1735,7 @@ install_deployment_kernel (OstreeSysroot *sysroot,
return FALSE;
if (errno == ENOENT)
{
if (!install_into_boot (sepolicy, kernel_layout->boot_dfd, kernel_layout->devicetree_srcpath,
if (!install_into_boot (repo, sepolicy, kernel_layout->boot_dfd, kernel_layout->devicetree_srcpath,
bootcsum_dfd, kernel_layout->devicetree_namever,
sysroot->debug_flags,
cancellable, error))
Expand All @@ -1712,7 +1749,7 @@ install_deployment_kernel (OstreeSysroot *sysroot,
return FALSE;
if (errno == ENOENT)
{
if (!install_into_boot (sepolicy, kernel_layout->boot_dfd, kernel_layout->kernel_hmac_srcpath,
if (!install_into_boot (repo, sepolicy, kernel_layout->boot_dfd, kernel_layout->kernel_hmac_srcpath,
bootcsum_dfd, kernel_layout->kernel_hmac_namever,
sysroot->debug_flags,
cancellable, error))
Expand Down