1e84b1
From c33faabc2d09b9ad8c80b941b6114c1e4c2be80f Mon Sep 17 00:00:00 2001
1e84b1
Message-Id: <c33faabc2d09b9ad8c80b941b6114c1e4c2be80f.1612252390.git.pmatilai@redhat.com>
1e84b1
From: Radovan Sroka <rsroka@redhat.com>
1e84b1
Date: Tue, 27 Oct 2020 16:18:04 +0100
1e84b1
Subject: [PATCH] Added fapolicyd rpm plugin
1e84b1
1e84b1
Fapolicyd (File Access Policy Daemon) implements application whitelisting
1e84b1
to decide file access rights. Applications that are known via a reputation
1e84b1
source are allowed access while unknown applications are not.
1e84b1
1e84b1
The rpm plugin allows us to use rpm database as a source of trust.
1e84b1
We used dnf plugin since the beggining but it only provides notification
1e84b1
when transaction ends. With "integrity checking" requirement we need
1e84b1
a continual addition of files which are installed during the system
1e84b1
update. With fapolicyd rpm plugin we can allow using of recently
1e84b1
added/updated files in scriptlets during rpm transaction.
1e84b1
1e84b1
The fapolicyd plugin gathers metadata of currently installed files.
1e84b1
It sends the information about files and about ongoing rpm transaction
1e84b1
to the fapolicyd daemon. The information is written to Linux pipe which
1e84b1
is placed in /var/run/fapolicyd/fapolicyd.fifo.
1e84b1
1e84b1
The data format is "%s %lu %64s\n". [path, size, sha256]
1e84b1
1e84b1
The fapolicyd rpm plugin can be enabled with "--with-fapolicyd"
1e84b1
configure option.
1e84b1
1e84b1
Related PRs:
1e84b1
https://github.com/linux-application-whitelisting/fapolicyd/pull/105
1e84b1
https://github.com/linux-application-whitelisting/fapolicyd/pull/106
1e84b1
1e84b1
Signed-off-by: Radovan Sroka <rsroka@redhat.com>
1e84b1
(cherry picked from commit 39595ccee321497dc3b08c7cab8a10304345429c)
1e84b1
1e84b1
Backported from commit 39595ccee321497dc3b08c7cab8a10304345429c
1e84b1
---
1e84b1
 Makefile.am                |   1 +
1e84b1
 configure.ac               |   8 ++
1e84b1
 doc/Makefile.am            |   2 +-
1e84b1
 doc/rpm-plugin-fapolicyd.8 |  21 +++++
1e84b1
 macros.in                  |   1 +
1e84b1
 plugins/Makefile.am        |   6 ++
1e84b1
 plugins/fapolicyd.c        | 189 +++++++++++++++++++++++++++++++++++++
1e84b1
 7 files changed, 227 insertions(+), 1 deletion(-)
1e84b1
 create mode 100644 doc/rpm-plugin-fapolicyd.8
1e84b1
 create mode 100644 plugins/fapolicyd.c
1e84b1
1e84b1
diff --git a/Makefile.am b/Makefile.am
1e84b1
index 1f20f05b7..8e92f0cde 100644
1e84b1
--- a/Makefile.am
1e84b1
+++ b/Makefile.am
1e84b1
@@ -16,6 +16,7 @@ DISTCHECK_CONFIGURE_FLAGS = \
1e84b1
 	--with-selinux \
1e84b1
 	--with-imaevm \
1e84b1
 	--with-crypto=openssl \
1e84b1
+	--with-fapolicyd \
1e84b1
 	--disable-dependency-tracking
1e84b1
 
1e84b1
 include $(top_srcdir)/rpm.am
1e84b1
diff --git a/configure.ac b/configure.ac
1e84b1
index 3fcb3ff20..3d0e9ef91 100644
1e84b1
--- a/configure.ac
1e84b1
+++ b/configure.ac
1e84b1
@@ -983,6 +983,14 @@ AS_IF([test "$enable_inhibit_plugin" = yes],[
1e84b1
 ])
1e84b1
 AM_CONDITIONAL(ENABLE_INHIBIT_PLUGIN,[test "$enable_inhibit_plugin" = yes])
1e84b1
 
1e84b1
+#=================
1e84b1
+# Check for fapolicyd support
1e84b1
+AC_ARG_WITH(fapolicyd,
1e84b1
+AS_HELP_STRING([--with-fapolicyd],[build with File Access Policy Daemon support]),
1e84b1
+with_fapolicyd=$withval,
1e84b1
+with_fapolicyd=auto)
1e84b1
+AM_CONDITIONAL(FAPOLICYD,[test "$with_fapolicyd" = yes])
1e84b1
+
1e84b1
 with_dbus=no
1e84b1
 AS_IF([test "$enable_plugins" != no],[
1e84b1
  AS_IF([test "$enable_inhibit_plugin" != no],[
1e84b1
diff --git a/doc/Makefile.am b/doc/Makefile.am
1e84b1
index d2f520d64..535ad3ec3 100644
1e84b1
--- a/doc/Makefile.am
1e84b1
+++ b/doc/Makefile.am
1e84b1
@@ -9,7 +9,7 @@ EXTRA_DIST += $(man_man1_DATA)
1e84b1
 man_man8dir = $(mandir)/man8
1e84b1
 man_man8_DATA = rpm.8 rpm-misc.8 rpmbuild.8 rpmdeps.8 rpmgraph.8 rpm2cpio.8
1e84b1
 man_man8_DATA += rpmdb.8 rpmkeys.8 rpmsign.8 rpmspec.8
1e84b1
-man_man8_DATA += rpm-plugin-systemd-inhibit.8
1e84b1
+man_man8_DATA += rpm-plugin-systemd-inhibit.8 rpm-plugin-fapolicyd.8
1e84b1
 EXTRA_DIST += $(man_man8_DATA)
1e84b1
 
1e84b1
 man_fr_man8dir = $(mandir)/fr/man8
1e84b1
diff --git a/doc/rpm-plugin-fapolicyd.8 b/doc/rpm-plugin-fapolicyd.8
1e84b1
new file mode 100644
1e84b1
index 000000000..fe7a8c78e
1e84b1
--- /dev/null
1e84b1
+++ b/doc/rpm-plugin-fapolicyd.8
1e84b1
@@ -0,0 +1,21 @@
1e84b1
+'\" t
1e84b1
+.TH "RPM-FAPOLICYD" "8" "28 Jan 2021" "Red Hat, Inc."
1e84b1
+.SH NAME
1e84b1
+rpm-plugin-fapolicyd \- Fapolicyd plugin for the RPM Package Manager
1e84b1
+
1e84b1
+.SH Description
1e84b1
+
1e84b1
+The plugin gathers metadata of currently installed files. It sends the
1e84b1
+information about files and about ongoing rpm transaction to the fapolicyd daemon.
1e84b1
+The information is written to Linux pipe which is placed in
1e84b1
+/var/run/fapolicyd/fapolicyd.fifo.
1e84b1
+
1e84b1
+.SH Configuration
1e84b1
+
1e84b1
+There are currently no options for this plugin in particular. See
1e84b1
+.BR rpm-plugins (8)
1e84b1
+on how to control plugins in general.
1e84b1
+
1e84b1
+.SH SEE ALSO
1e84b1
+.IR fapolicyd (8)
1e84b1
+.IR rpm-plugins (8)
1e84b1
diff --git a/macros.in b/macros.in
1e84b1
index a6069ee4d..2fbda64cc 100644
1e84b1
--- a/macros.in
1e84b1
+++ b/macros.in
1e84b1
@@ -1173,6 +1173,7 @@ package or when debugging this package.\
1e84b1
 %__transaction_selinux		%{__plugindir}/selinux.so
1e84b1
 %__transaction_syslog		%{__plugindir}/syslog.so
1e84b1
 %__transaction_ima		%{__plugindir}/ima.so
1e84b1
+%__transaction_fapolicyd	%{__plugindir}/fapolicyd.so
1e84b1
 %__transaction_prioreset	%{__plugindir}/prioreset.so
1e84b1
 
1e84b1
 #------------------------------------------------------------------------------
1e84b1
diff --git a/plugins/Makefile.am b/plugins/Makefile.am
1e84b1
index ab4eee34f..cbfb81e19 100644
1e84b1
--- a/plugins/Makefile.am
1e84b1
+++ b/plugins/Makefile.am
1e84b1
@@ -42,3 +42,9 @@ ima_la_sources = ima.c
1e84b1
 ima_la_LIBADD = $(top_builddir)/lib/librpm.la $(top_builddir)/rpmio/librpmio.la
1e84b1
 plugins_LTLIBRARIES += ima.la
1e84b1
 endif
1e84b1
+
1e84b1
+if FAPOLICYD
1e84b1
+fapolicyd_la_sources = fapolicyd.c
1e84b1
+fapolicyd_la_LIBADD = $(top_builddir)/lib/librpm.la $(top_builddir)/rpmio/librpmio.la
1e84b1
+plugins_LTLIBRARIES += fapolicyd.la
1e84b1
+endif
1e84b1
diff --git a/plugins/fapolicyd.c b/plugins/fapolicyd.c
1e84b1
new file mode 100644
1e84b1
index 000000000..50f50155c
1e84b1
--- /dev/null
1e84b1
+++ b/plugins/fapolicyd.c
1e84b1
@@ -0,0 +1,189 @@
1e84b1
+#include "system.h"
1e84b1
+
1e84b1
+#include <rpm/rpmts.h>
1e84b1
+#include <rpm/rpmlog.h>
1e84b1
+#include "lib/rpmplugin.h"
1e84b1
+
1e84b1
+#include <fcntl.h>
1e84b1
+#include <errno.h>
1e84b1
+#include <unistd.h>
1e84b1
+#include <sys/stat.h>
1e84b1
+
1e84b1
+struct fapolicyd_data {
1e84b1
+    int fd;
1e84b1
+    long changed_files;
1e84b1
+    const char * fifo_path;
1e84b1
+};
1e84b1
+
1e84b1
+static struct fapolicyd_data fapolicyd_state = {
1e84b1
+    .fd = -1,
1e84b1
+    .changed_files = 0,
1e84b1
+    .fifo_path = "/run/fapolicyd/fapolicyd.fifo",
1e84b1
+};
1e84b1
+
1e84b1
+static rpmRC open_fifo(struct fapolicyd_data* state)
1e84b1
+{
1e84b1
+    int fd = -1;
1e84b1
+    struct stat s;
1e84b1
+
1e84b1
+    fd = open(state->fifo_path, O_RDWR);
1e84b1
+    if (fd == -1) {
1e84b1
+        rpmlog(RPMLOG_DEBUG, "Open: %s -> %s\n", state->fifo_path, strerror(errno));
1e84b1
+        goto bad;
1e84b1
+    }
1e84b1
+
1e84b1
+    if (stat(state->fifo_path, &s) == -1) {
1e84b1
+        rpmlog(RPMLOG_DEBUG, "Stat: %s -> %s\n", state->fifo_path, strerror(errno));
1e84b1
+        goto bad;
1e84b1
+    }
1e84b1
+
1e84b1
+    if (!S_ISFIFO(s.st_mode)) {
1e84b1
+        rpmlog(RPMLOG_DEBUG, "File: %s exists but it is not a pipe!\n", state->fifo_path);
1e84b1
+        goto bad;
1e84b1
+    }
1e84b1
+
1e84b1
+    /* keep only file's permition bits */
1e84b1
+    mode_t mode = s.st_mode & ~S_IFMT;
1e84b1
+
1e84b1
+    /* we require pipe to have 0660 permission */
1e84b1
+    if (mode != 0660) {
1e84b1
+        rpmlog(RPMLOG_ERR, "File: %s has %o instead of 0660 \n",
1e84b1
+               state->fifo_path,
1e84b1
+               mode );
1e84b1
+        goto bad;
1e84b1
+    }
1e84b1
+
1e84b1
+    state->fd = fd;
1e84b1
+    /* considering success */
1e84b1
+    return RPMRC_OK;
1e84b1
+
1e84b1
+ bad:
1e84b1
+    if (fd > 0)
1e84b1
+        close(fd);
1e84b1
+    return RPMRC_FAIL;
1e84b1
+}
1e84b1
+
1e84b1
+static rpmRC write_fifo(struct fapolicyd_data* state, const char * str)
1e84b1
+{
1e84b1
+    ssize_t len = strlen(str);
1e84b1
+    ssize_t written = 0;
1e84b1
+    ssize_t n = 0;
1e84b1
+
1e84b1
+    while (written < len) {
1e84b1
+        if ((n = write(state->fd, str + written, len - written)) < 0) {
1e84b1
+            if (errno == EINTR || errno == EAGAIN)
1e84b1
+                continue;
1e84b1
+            rpmlog(RPMLOG_DEBUG, "Write: %s -> %s\n", state->fifo_path, strerror(errno));
1e84b1
+            goto bad;
1e84b1
+        }
1e84b1
+        written += n;
1e84b1
+    }
1e84b1
+
1e84b1
+    return RPMRC_OK;
1e84b1
+
1e84b1
+ bad:
1e84b1
+    return RPMRC_FAIL;
1e84b1
+}
1e84b1
+
1e84b1
+static rpmRC fapolicyd_init(rpmPlugin plugin, rpmts ts)
1e84b1
+{
1e84b1
+    if (rpmtsFlags(ts) & (RPMTRANS_FLAG_TEST|RPMTRANS_FLAG_BUILD_PROBS))
1e84b1
+        goto end;
1e84b1
+
1e84b1
+    if (!rstreq(rpmtsRootDir(ts), "/"))
1e84b1
+        goto end;
1e84b1
+
1e84b1
+    (void) open_fifo(&fapolicyd_state);
1e84b1
+
1e84b1
+ end:
1e84b1
+    return RPMRC_OK;
1e84b1
+}
1e84b1
+
1e84b1
+static void fapolicyd_cleanup(rpmPlugin plugin)
1e84b1
+{
1e84b1
+    if (fapolicyd_state.fd > 0)
1e84b1
+        (void) close(fapolicyd_state.fd);
1e84b1
+
1e84b1
+    fapolicyd_state.fd = -1;
1e84b1
+}
1e84b1
+
1e84b1
+static rpmRC fapolicyd_tsm_post(rpmPlugin plugin, rpmts ts, int res)
1e84b1
+{
1e84b1
+    if (rpmtsFlags(ts) & (RPMTRANS_FLAG_TEST|RPMTRANS_FLAG_BUILD_PROBS))
1e84b1
+        goto end;
1e84b1
+
1e84b1
+    /* we are ready */
1e84b1
+    if (fapolicyd_state.fd > 0) {
1e84b1
+        /* send a signal that transaction is over */
1e84b1
+        (void) write_fifo(&fapolicyd_state, "1\n");
1e84b1
+        /* flush cache */
1e84b1
+        (void) write_fifo(&fapolicyd_state, "2\n");
1e84b1
+    }
1e84b1
+
1e84b1
+ end:
1e84b1
+    return RPMRC_OK;
1e84b1
+}
1e84b1
+
1e84b1
+static rpmRC fapolicyd_scriptlet_pre(rpmPlugin plugin, const char *s_name,
1e84b1
+                                     int type)
1e84b1
+{
1e84b1
+    if (fapolicyd_state.fd == -1)
1e84b1
+        goto end;
1e84b1
+
1e84b1
+    if (fapolicyd_state.changed_files > 0) {
1e84b1
+        /* send signal to flush cache */
1e84b1
+        (void) write_fifo(&fapolicyd_state, "2\n");
1e84b1
+
1e84b1
+        /* optimize flushing */
1e84b1
+        /* flush only when there was an actual change */
1e84b1
+        fapolicyd_state.changed_files = 0;
1e84b1
+    }
1e84b1
+
1e84b1
+ end:
1e84b1
+    return RPMRC_OK;
1e84b1
+}
1e84b1
+
1e84b1
+static rpmRC fapolicyd_fsm_file_prepare(rpmPlugin plugin, rpmfi fi,
1e84b1
+                                        const char *path, const char *dest,
1e84b1
+                                        mode_t file_mode, rpmFsmOp op)
1e84b1
+{
1e84b1
+    /* not ready  */
1e84b1
+    if (fapolicyd_state.fd == -1)
1e84b1
+        goto end;
1e84b1
+
1e84b1
+    rpmFileAction action = XFO_ACTION(op);
1e84b1
+
1e84b1
+    /* Ignore skipped files and unowned directories */
1e84b1
+    if (XFA_SKIPPING(action) || (op & FAF_UNOWNED)) {
1e84b1
+        rpmlog(RPMLOG_DEBUG, "fapolicyd skipping early: path %s dest %s\n",
1e84b1
+               path, dest);
1e84b1
+        goto end;
1e84b1
+    }
1e84b1
+
1e84b1
+    if (!S_ISREG(rpmfiFMode(fi))) {
1e84b1
+        rpmlog(RPMLOG_DEBUG, "fapolicyd skipping non regular: path %s dest %s\n",
1e84b1
+               path, dest);
1e84b1
+        goto end;
1e84b1
+    }
1e84b1
+
1e84b1
+    fapolicyd_state.changed_files++;
1e84b1
+
1e84b1
+    char buffer[4096];
1e84b1
+
1e84b1
+    rpm_loff_t size = rpmfiFSize(fi);
1e84b1
+    char * sha = rpmfiFDigestHex(fi, NULL);
1e84b1
+
1e84b1
+    snprintf(buffer, 4096, "%s %lu %64s\n", dest, size, sha);
1e84b1
+    (void) write_fifo(&fapolicyd_state, buffer);
1e84b1
+
1e84b1
+ end:
1e84b1
+    return RPMRC_OK;
1e84b1
+}
1e84b1
+
1e84b1
+struct rpmPluginHooks_s fapolicyd_hooks = {
1e84b1
+    .init = fapolicyd_init,
1e84b1
+    .cleanup = fapolicyd_cleanup,
1e84b1
+    .scriptlet_pre = fapolicyd_scriptlet_pre,
1e84b1
+    .tsm_post = fapolicyd_tsm_post,
1e84b1
+    .fsm_file_prepare = fapolicyd_fsm_file_prepare,
1e84b1
+};
1e84b1
-- 
1e84b1
2.29.2
1e84b1
0c0d5f
commit c66cee32e74ce1e507c031605e3d7b2c1391a52c
0c0d5f
Author: Radovan Sroka <rsroka@redhat.com>
0c0d5f
Date:   Wed Feb 10 17:04:55 2021 +0100
0c0d5f
0c0d5f
    Fixed issues find by coverity
0c0d5f
    
0c0d5f
    - enhance the check for the file descriptor fd because 0 is also a valid
0c0d5f
    descriptor
0c0d5f
    
0c0d5f
    - added free() for sha so it doesn't leak memory for every file that is
0c0d5f
    processed
0c0d5f
    
0c0d5f
    Signed-off-by: Radovan Sroka <rsroka@redhat.com>
0c0d5f
0c0d5f
diff --git a/plugins/fapolicyd.c b/plugins/fapolicyd.c
0c0d5f
index 50f50155c..48f65ae11 100644
0c0d5f
--- a/plugins/fapolicyd.c
0c0d5f
+++ b/plugins/fapolicyd.c
0c0d5f
@@ -58,7 +58,7 @@ static rpmRC open_fifo(struct fapolicyd_data* state)
0c0d5f
     return RPMRC_OK;
0c0d5f
 
0c0d5f
  bad:
0c0d5f
-    if (fd > 0)
0c0d5f
+    if (fd >= 0)
0c0d5f
         close(fd);
0c0d5f
     return RPMRC_FAIL;
0c0d5f
 }
0c0d5f
@@ -176,6 +176,8 @@ static rpmRC fapolicyd_fsm_file_prepare(rpmPlugin plugin, rpmfi fi,
0c0d5f
     snprintf(buffer, 4096, "%s %lu %64s\n", dest, size, sha);
0c0d5f
     (void) write_fifo(&fapolicyd_state, buffer);
0c0d5f
 
0c0d5f
+    free(sha);
0c0d5f
+
0c0d5f
  end:
0c0d5f
     return RPMRC_OK;
0c0d5f
 }