Added fapolicyd rpm plugin

Fapolicyd (File Access Policy Daemon) implements application whitelisting
to decide file access rights. Applications that are known via a reputation
source are allowed access while unknown applications are not.

The rpm plugin allows us to use rpm database as a source of trust.
We used dnf plugin since the beggining but it only provides notification
when transaction ends. With "integrity checking" requirement we need
a continual addition of files which are installed during the system
update. With fapolicyd rpm plugin we can allow using of recently
added/updated files in scriptlets during rpm transaction.

The fapolicyd plugin gathers metadata of currently installed files.
It sends the information about files and about ongoing rpm transaction
to the fapolicyd daemon. The information is written to Linux pipe which
is placed in /var/run/fapolicyd/fapolicyd.fifo.

The data format is "%s %lu %64s\n". [path, size, sha256]

The fapolicyd rpm plugin can be enabled with "--with-fapolicyd"
configure option.

Related PRs:
https://github.com/linux-application-whitelisting/fapolicyd/pull/105
https://github.com/linux-application-whitelisting/fapolicyd/pull/106

Signed-off-by: Radovan Sroka <rsroka@redhat.com>
This commit is contained in:
Radovan Sroka 2020-10-27 16:18:04 +01:00 committed by Panu Matilainen
parent 986be669fb
commit 39595ccee3
7 changed files with 231 additions and 0 deletions

View File

@ -14,6 +14,7 @@ DISTCHECK_CONFIGURE_FLAGS = \
--with-audit \
--with-selinux \
--with-imaevm \
--with-fapolicyd \
--with-fsverity \
--disable-dependency-tracking

View File

@ -845,6 +845,14 @@ AC_CHECK_HEADERS([linux/fsverity.h],[FSVERITY_IOCTL="yes"])
])
AM_CONDITIONAL(FSVERITY_IOCTL,[test "x$FSVERITY_IOCTL" = xyes])
#=================
# Check for fapolicyd support
AC_ARG_WITH(fapolicyd,
AS_HELP_STRING([--with-fapolicyd],[build with File Access Policy Daemon support]),
with_fapolicyd=$withval,
with_fapolicyd=auto)
AM_CONDITIONAL(FAPOLICYD,[test "$with_fapolicyd" = yes])
#=================
# Check for audit library.
AC_ARG_WITH(audit,

View File

@ -25,6 +25,9 @@ endif
if IMA
man_man8_DATA += rpm-plugin-ima.8
endif
if FAPOLICYD
man_man8_DATA += rpm-plugin-fapolicyd.8
endif
if SELINUX
man_man8_DATA += rpm-plugin-selinux.8
endif
@ -37,6 +40,8 @@ endif
EXTRA_DIST += rpm-plugins.8 rpm-plugin-prioreset.8 rpm-plugin-syslog.8
EXTRA_DIST += rpm-plugin-audit.8 rpm-plugin-systemd-inhibit.8
EXTRA_DIST += rpm-plugin-ima.8 rpm-plugin-selinux.8 rpm2archive.8
EXTRA_DIST += rpm-plugin-fapolicyd.8
man_fr_man8dir = $(mandir)/fr/man8
man_fr_man8_DATA = fr/rpm.8

View File

@ -0,0 +1,21 @@
'\" t
.TH "RPM-FAPOLICYD" "8" "28 Jan 2021" "Red Hat, Inc."
.SH NAME
rpm-plugin-fapolicyd \- Fapolicyd plugin for the RPM Package Manager
.SH Description
The plugin gathers metadata of currently installed files. It sends the
information about files and about ongoing rpm transaction to the fapolicyd daemon.
The information is written to Linux pipe which is placed in
/var/run/fapolicyd/fapolicyd.fifo.
.SH Configuration
There are currently no options for this plugin in particular. See
.BR rpm-plugins (8)
on how to control plugins in general.
.SH SEE ALSO
.IR fapolicyd (8)
.IR rpm-plugins (8)

View File

@ -1179,6 +1179,7 @@ package or when debugging this package.\
%__transaction_selinux %{__plugindir}/selinux.so
%__transaction_syslog %{__plugindir}/syslog.so
%__transaction_ima %{__plugindir}/ima.so
%__transaction_fapolicyd %{__plugindir}/fapolicyd.so
%__transaction_fsverity %{__plugindir}/fsverity.so
%__transaction_prioreset %{__plugindir}/prioreset.so
%__transaction_audit %{__plugindir}/audit.so

View File

@ -43,6 +43,12 @@ ima_la_LIBADD = $(top_builddir)/lib/librpm.la $(top_builddir)/rpmio/librpmio.la
plugins_LTLIBRARIES += ima.la
endif
if FAPOLICYD
fapolicyd_la_sources = fapolicyd.c
fapolicyd_la_LIBADD = $(top_builddir)/lib/librpm.la $(top_builddir)/rpmio/librpmio.la
plugins_LTLIBRARIES += fapolicyd.la
endif
if FSVERITY_IOCTL
fsverity_la_sources = fsverity.c
fsverity_la_LIBADD = $(top_builddir)/lib/librpm.la $(top_builddir)/rpmio/librpmio.la

189
plugins/fapolicyd.c Normal file
View File

@ -0,0 +1,189 @@
#include "system.h"
#include <rpm/rpmts.h>
#include <rpm/rpmlog.h>
#include "lib/rpmplugin.h"
#include <fcntl.h>
#include <errno.h>
#include <unistd.h>
#include <sys/stat.h>
struct fapolicyd_data {
int fd;
long changed_files;
const char * fifo_path;
};
static struct fapolicyd_data fapolicyd_state = {
.fd = -1,
.changed_files = 0,
.fifo_path = "/run/fapolicyd/fapolicyd.fifo",
};
static rpmRC open_fifo(struct fapolicyd_data* state)
{
int fd = -1;
struct stat s;
fd = open(state->fifo_path, O_RDWR);
if (fd == -1) {
rpmlog(RPMLOG_DEBUG, "Open: %s -> %s\n", state->fifo_path, strerror(errno));
goto bad;
}
if (stat(state->fifo_path, &s) == -1) {
rpmlog(RPMLOG_DEBUG, "Stat: %s -> %s\n", state->fifo_path, strerror(errno));
goto bad;
}
if (!S_ISFIFO(s.st_mode)) {
rpmlog(RPMLOG_DEBUG, "File: %s exists but it is not a pipe!\n", state->fifo_path);
goto bad;
}
/* keep only file's permition bits */
mode_t mode = s.st_mode & ~S_IFMT;
/* we require pipe to have 0660 permission */
if (mode != 0660) {
rpmlog(RPMLOG_ERR, "File: %s has %o instead of 0660 \n",
state->fifo_path,
mode );
goto bad;
}
state->fd = fd;
/* considering success */
return RPMRC_OK;
bad:
if (fd > 0)
close(fd);
return RPMRC_FAIL;
}
static rpmRC write_fifo(struct fapolicyd_data* state, const char * str)
{
ssize_t len = strlen(str);
ssize_t written = 0;
ssize_t n = 0;
while (written < len) {
if ((n = write(state->fd, str + written, len - written)) < 0) {
if (errno == EINTR || errno == EAGAIN)
continue;
rpmlog(RPMLOG_DEBUG, "Write: %s -> %s\n", state->fifo_path, strerror(errno));
goto bad;
}
written += n;
}
return RPMRC_OK;
bad:
return RPMRC_FAIL;
}
static rpmRC fapolicyd_init(rpmPlugin plugin, rpmts ts)
{
if (rpmtsFlags(ts) & (RPMTRANS_FLAG_TEST|RPMTRANS_FLAG_BUILD_PROBS))
goto end;
if (!rstreq(rpmtsRootDir(ts), "/"))
goto end;
(void) open_fifo(&fapolicyd_state);
end:
return RPMRC_OK;
}
static void fapolicyd_cleanup(rpmPlugin plugin)
{
if (fapolicyd_state.fd > 0)
(void) close(fapolicyd_state.fd);
fapolicyd_state.fd = -1;
}
static rpmRC fapolicyd_tsm_post(rpmPlugin plugin, rpmts ts, int res)
{
if (rpmtsFlags(ts) & (RPMTRANS_FLAG_TEST|RPMTRANS_FLAG_BUILD_PROBS))
goto end;
/* we are ready */
if (fapolicyd_state.fd > 0) {
/* send a signal that transaction is over */
(void) write_fifo(&fapolicyd_state, "1\n");
/* flush cache */
(void) write_fifo(&fapolicyd_state, "2\n");
}
end:
return RPMRC_OK;
}
static rpmRC fapolicyd_scriptlet_pre(rpmPlugin plugin, const char *s_name,
int type)
{
if (fapolicyd_state.fd == -1)
goto end;
if (fapolicyd_state.changed_files > 0) {
/* send signal to flush cache */
(void) write_fifo(&fapolicyd_state, "2\n");
/* optimize flushing */
/* flush only when there was an actual change */
fapolicyd_state.changed_files = 0;
}
end:
return RPMRC_OK;
}
static rpmRC fapolicyd_fsm_file_prepare(rpmPlugin plugin, rpmfi fi,
const char *path, const char *dest,
mode_t file_mode, rpmFsmOp op)
{
/* not ready */
if (fapolicyd_state.fd == -1)
goto end;
rpmFileAction action = XFO_ACTION(op);
/* Ignore skipped files and unowned directories */
if (XFA_SKIPPING(action) || (op & FAF_UNOWNED)) {
rpmlog(RPMLOG_DEBUG, "fapolicyd skipping early: path %s dest %s\n",
path, dest);
goto end;
}
if (!S_ISREG(rpmfiFMode(fi))) {
rpmlog(RPMLOG_DEBUG, "fapolicyd skipping non regular: path %s dest %s\n",
path, dest);
goto end;
}
fapolicyd_state.changed_files++;
char buffer[4096];
rpm_loff_t size = rpmfiFSize(fi);
char * sha = rpmfiFDigestHex(fi, NULL);
snprintf(buffer, 4096, "%s %lu %64s\n", dest, size, sha);
(void) write_fifo(&fapolicyd_state, buffer);
end:
return RPMRC_OK;
}
struct rpmPluginHooks_s fapolicyd_hooks = {
.init = fapolicyd_init,
.cleanup = fapolicyd_cleanup,
.scriptlet_pre = fapolicyd_scriptlet_pre,
.tsm_post = fapolicyd_tsm_post,
.fsm_file_prepare = fapolicyd_fsm_file_prepare,
};