diff --git a/configure.ac b/configure.ac index 98725fc4fb..a28172a10d 100644 --- a/configure.ac +++ b/configure.ac @@ -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]) @@ -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 diff --git a/src/libostree/ostree-repo-commit.c b/src/libostree/ostree-repo-commit.c index 8c5d94110d..38fc6ef16f 100644 --- a/src/libostree/ostree-repo-commit.c +++ b/src/libostree/ostree-repo-commit.c @@ -32,6 +32,10 @@ #include #include #include +#include +#ifdef HAVE_LINUX_FSVERITY_H +#include +#endif #include "otutil.h" #include "ostree.h" @@ -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, @@ -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; diff --git a/src/libostree/ostree-repo-private.h b/src/libostree/ostree-repo-private.h index b57ad799f7..3b6299130f 100644 --- a/src/libostree/ostree-repo-private.h +++ b/src/libostree/ostree-repo-private.h @@ -22,6 +22,7 @@ #pragma once #include +#include "config.h" #include "otutil.h" #include "ostree-ref.h" #include "ostree-repo.h" @@ -97,6 +98,12 @@ typedef struct { fsblkcnt_t max_blocks; } OstreeRepoTxn; +typedef enum { + _OSTREE_FEATURE_NO, + _OSTREE_FEATURE_MAYBE, + _OSTREE_FEATURE_YES, +} _OstreeFeatureSupport; + /** * OstreeRepo: * @@ -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; @@ -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 diff --git a/src/libostree/ostree-repo.c b/src/libostree/ostree-repo.c index cff70d474e..b6a5db6981 100644 --- a/src/libostree/ostree-repo.c +++ b/src/libostree/ostree-repo.c @@ -31,6 +31,7 @@ #include "libglnx.h" #include "otutil.h" #include +#include #include "ostree-core-private.h" #include "ostree-sysroot-private.h" @@ -47,6 +48,7 @@ #include #include #include +#include #define REPO_LOCK_DISABLED (-2) #define REPO_LOCK_BLOCKING (-1) @@ -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", diff --git a/src/libostree/ostree-sysroot-deploy.c b/src/libostree/ostree-sysroot-deploy.c index 0dcda7328f..ee00c02c5e 100644 --- a/src/libostree/ostree-sysroot-deploy.c +++ b/src/libostree/ostree-sysroot-deploy.c @@ -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" @@ -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, @@ -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, - 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)) + return FALSE; + + if (!glnx_link_tmpfile_at (&tmp_dest, GLNX_LINK_TMPFILE_NOREPLACE, dest_dfd, dest_subpath, error)) + return FALSE; return TRUE; } @@ -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)) @@ -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)) @@ -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)) @@ -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))