From 9b3a65ef06ad9868b9dba667bef1878453ceca89 Mon Sep 17 00:00:00 2001 From: Jes Sorensen Date: Mon, 20 Apr 2020 11:11:25 -0400 Subject: [PATCH] plugins/fsverity: Install fsverity signatures This plugin installs fsverity signatures for regular files, when a signature is found in the RPM. It tries to enable them unconditionally, but fails gracefully if fsverity isn't supported or enabled. Signed-off-by: Jes Sorensen --- configure.ac | 5 ++ macros.in | 4 + plugins/Makefile.am | 6 ++ plugins/fsverity.c | 177 ++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 192 insertions(+) create mode 100644 plugins/fsverity.c diff --git a/configure.ac b/configure.ac index 6deff4d0e..38d3c286a 100644 --- a/configure.ac +++ b/configure.ac @@ -839,6 +839,11 @@ AS_IF([test "$enable_plugins" != no],[ ]) AM_CONDITIONAL(IMA, [test "x$ac_cv_func_lsetxattr" = xyes]) +AS_IF([test "$enable_plugins" != no],[ +AC_CHECK_HEADERS([linux/fsverity.h],[FSVERITY_IOCTL="yes"]) +]) +AM_CONDITIONAL(FSVERITY_IOCTL,[test "x$FSVERITY_IOCTL" = xyes]) + #================= # Check for audit library. AC_ARG_WITH(audit, diff --git a/macros.in b/macros.in index 565f6be6f..85b327f25 100644 --- a/macros.in +++ b/macros.in @@ -734,6 +734,9 @@ package or when debugging this package.\ # a wrong or missing signature. #%_ima_sign_config_files 0 +# Set to 1 to have fsverity signatures written for %config files. +#%_fsverity_sign_config_files 0 + # # Default output format string for rpm -qa # @@ -1173,6 +1176,7 @@ package or when debugging this package.\ %__transaction_selinux %{__plugindir}/selinux.so %__transaction_syslog %{__plugindir}/syslog.so %__transaction_ima %{__plugindir}/ima.so +%__transaction_fsverity %{__plugindir}/fsverity.so %__transaction_prioreset %{__plugindir}/prioreset.so %__transaction_audit %{__plugindir}/audit.so diff --git a/plugins/Makefile.am b/plugins/Makefile.am index d4ef039ed..53e4a5d26 100644 --- a/plugins/Makefile.am +++ b/plugins/Makefile.am @@ -43,6 +43,12 @@ ima_la_LIBADD = $(top_builddir)/lib/librpm.la $(top_builddir)/rpmio/librpmio.la plugins_LTLIBRARIES += ima.la endif +if FSVERITY_IOCTL +fsverity_la_sources = fsverity.c +fsverity_la_LIBADD = $(top_builddir)/lib/librpm.la $(top_builddir)/rpmio/librpmio.la +plugins_LTLIBRARIES += fsverity.la +endif + if AUDIT audit_la_sources = audit.c audit_la_LIBADD = $(top_builddir)/lib/librpm.la $(top_builddir)/rpmio/librpmio.la @WITH_AUDIT_LIB@ diff --git a/plugins/fsverity.c b/plugins/fsverity.c new file mode 100644 index 000000000..15ddcf33e --- /dev/null +++ b/plugins/fsverity.c @@ -0,0 +1,177 @@ +/** + * Copyright (C) 2020 Facebook + * + * Author: Jes Sorensen + */ + +#include "system.h" + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "lib/rpmfs.h" +#include "lib/rpmplugin.h" +#include "lib/rpmte_internal.h" + +#include "sign/rpmsignverity.h" + +static int sign_config_files = 0; + +/* + * This unconditionally tries to apply the fsverity signature to a file, + * but fails gracefully if the file system doesn't support it or the + * verity feature flag isn't enabled in the file system (ext4). + */ +static rpmRC fsverity_fsm_file_prepare(rpmPlugin plugin, rpmfi fi, + const char *path, const char *dest, + mode_t file_mode, rpmFsmOp op) +{ + struct fsverity_enable_arg arg; + const unsigned char * signature = NULL; + size_t len; + int rc = RPMRC_OK; + int fd; + rpmFileAction action = XFO_ACTION(op); + char *buffer; + + /* Ignore skipped files and unowned directories */ + if (XFA_SKIPPING(action) || (op & FAF_UNOWNED)) { + rpmlog(RPMLOG_DEBUG, "fsverity skipping early: path %s dest %s\n", + path, dest); + goto exit; + } + + /* + * Do not install signatures for config files unless the + * user explicitly asks for it. + */ + if (rpmfiFFlags(fi) & RPMFILE_CONFIG) { + if (!(rpmfiFMode(fi) & (S_IXUSR|S_IXGRP|S_IXOTH)) && + !sign_config_files) { + rpmlog(RPMLOG_DEBUG, "fsverity skipping: path %s dest %s\n", + path, dest); + + goto exit; + } + } + + /* + * Right now fsverity doesn't deal with symlinks or directories, so do + * not try to install signatures for non regular files. + */ + if (!S_ISREG(rpmfiFMode(fi))) { + rpmlog(RPMLOG_DEBUG, "fsverity skipping non regular: path %s dest %s\n", + path, dest); + goto exit; + } + + signature = rpmfiVSignature(fi, &len); + if (!signature || !len) { + rpmlog(RPMLOG_DEBUG, "fsverity no signature for: path %s dest %s\n", + path, dest); + goto exit; + } + + memset(&arg, 0, sizeof(arg)); + arg.version = 1; + arg.hash_algorithm = FS_VERITY_HASH_ALG_SHA256; + arg.block_size = RPM_FSVERITY_BLKSZ; + arg.sig_ptr = (uintptr_t)signature; + arg.sig_size = len; + + buffer = pgpHexStr(signature, arg.sig_size); + rpmlog(RPMLOG_DEBUG, "applying signature: %s\n", buffer); + free(buffer); + + fd = open(path, O_RDONLY); + if (fd < 0) { + rpmlog(RPMLOG_ERR, "failed to open path %s\n", path); + goto exit; + } + + /* + * Enable fsverity on the file. + * fsverity not supported by file system (ENOTTY) and fsverity not + * enabled on file system are expected and not considered + * errors. Every other non-zero error code will result in the + * installation failing. + */ + if (ioctl(fd, FS_IOC_ENABLE_VERITY, &arg) != 0) { + switch(errno) { + case EBADMSG: + rpmlog(RPMLOG_DEBUG, "invalid or malformed fsverity signature for %s\n", path); + rc = RPMRC_FAIL; + break; + case EEXIST: + rpmlog(RPMLOG_DEBUG, "fsverity signature already enabled %s\n", + path); + rc = RPMRC_FAIL; + break; + case EINVAL: + rpmlog(RPMLOG_DEBUG, "invalid arguments for ioctl %s\n", path); + rc = RPMRC_FAIL; + break; + case EKEYREJECTED: + rpmlog(RPMLOG_DEBUG, "signature doesn't match file %s\n", path); + rc = RPMRC_FAIL; + break; + case EMSGSIZE: + rpmlog(RPMLOG_DEBUG, "invalid signature size for %s\n", path); + rc = RPMRC_FAIL; + break; + case ENOPKG: + rpmlog(RPMLOG_DEBUG, "unsupported signature algoritm (%i) for %s\n", + arg.hash_algorithm, path); + rc = RPMRC_FAIL; + break; + case ETXTBSY: + rpmlog(RPMLOG_DEBUG, "file is open by other process %s\n", + path); + rc = RPMRC_FAIL; + break; + case ENOTTY: + rpmlog(RPMLOG_DEBUG, "fsverity not supported by file system for %s\n", + path); + break; + case EOPNOTSUPP: + rpmlog(RPMLOG_DEBUG, "fsverity not enabled on file system for %s\n", + path); + break; + default: + rpmlog(RPMLOG_DEBUG, "failed to enable verity (errno %i) for %s\n", + errno, path); + rc = RPMRC_FAIL; + break; + } + } + + rpmlog(RPMLOG_DEBUG, "fsverity enabled signature for: path %s dest %s\n", + path, dest); + close(fd); +exit: + return rc; +} + +static rpmRC fsverity_init(rpmPlugin plugin, rpmts ts) +{ + sign_config_files = rpmExpandNumeric("%{?_fsverity_sign_config_files}"); + + rpmlog(RPMLOG_DEBUG, "fsverity_init\n"); + + return RPMRC_OK; +} + +struct rpmPluginHooks_s fsverity_hooks = { + .init = fsverity_init, + .fsm_file_prepare = fsverity_fsm_file_prepare, +};