From b0079f3377a09a7c62b4675767a323b7932dac4b Mon Sep 17 00:00:00 2001 From: CentOS Sources Date: Mar 28 2023 10:02:17 +0000 Subject: import fapolicyd-1.1.3-104.el9 --- diff --git a/SOURCES/fapolicyd-already-started.patch b/SOURCES/fapolicyd-already-started.patch new file mode 100644 index 0000000..6a68b62 --- /dev/null +++ b/SOURCES/fapolicyd-already-started.patch @@ -0,0 +1,110 @@ +diff -up ./src/daemon/fapolicyd.c.already-started ./src/daemon/fapolicyd.c +--- ./src/daemon/fapolicyd.c.already-started 2023-01-12 17:40:45.366909652 +0100 ++++ ./src/daemon/fapolicyd.c 2023-01-12 17:46:22.458139519 +0100 +@@ -378,6 +378,58 @@ static void usage(void) + } + + ++int already_running(void) ++{ ++ int pidfd = open(pidfile, O_RDONLY); ++ if (pidfd >= 0) { ++ char pid_buf[16]; ++ ++ if (fd_fgets(pid_buf, sizeof(pid_buf), pidfd)) { ++ int pid; ++ char exe_buf[80], my_path[80]; ++ ++ // Get our path ++ if (get_program_from_pid(getpid(), ++ sizeof(exe_buf), my_path) == NULL) ++ goto err_out; // shouldn't happen, but be safe ++ ++ // convert pidfile to integer ++ errno = 0; ++ pid = strtoul(pid_buf, NULL, 10); ++ if (errno) ++ goto err_out; // shouldn't happen, but be safe ++ ++ // verify it really is fapolicyd ++ if (get_program_from_pid(pid, ++ sizeof(exe_buf), exe_buf) == NULL) ++ goto good; //if pid doesn't exist, we're OK ++ ++ // If the path doesn't have fapolicyd in it, we're OK ++ if (strstr(exe_buf, "fapolicyd") == NULL) ++ goto good; ++ ++ if (strcmp(exe_buf, my_path) == 0) ++ goto err_out; // if the same, we need to exit ++ ++ // one last sanity check in case path is unexpected ++ // for example: /sbin/fapolicyd & /home/test/fapolicyd ++ if (pid != getpid()) ++ goto err_out; ++good: ++ close(pidfd); ++ unlink(pidfile); ++ return 0; ++ } else ++ msg(LOG_ERR, "fapolicyd pid file found but unreadable"); ++err_out: // At this point, we have a pid file, let's just assume it's alive ++ // because if 2 are running, it deadlocks the machine ++ close(pidfd); ++ return 1; ++ } ++ return 0; // pid file doesn't exist, we're good to go ++} ++ ++ + int main(int argc, const char *argv[]) + { + struct pollfd pfd[2]; +@@ -428,6 +480,11 @@ int main(int argc, const char *argv[]) + } + } + ++ if (already_running()) { ++ msg(LOG_ERR, "fapolicyd is already running"); ++ exit(1); ++ } ++ + // Set a couple signal handlers + sa.sa_flags = 0; + sigemptyset(&sa.sa_mask); +@@ -446,9 +503,6 @@ int main(int argc, const char *argv[]) + setrlimit(RLIMIT_FSIZE, &limit); + setrlimit(RLIMIT_NOFILE, &limit); + +- // Set strict umask +- (void) umask( 0117 ); +- + // get more time slices because everything is waiting on us + rc = nice(-config.nice_val); + if (rc == -1) +@@ -473,17 +527,20 @@ int main(int argc, const char *argv[]) + exit(1); + } + +- if (preconstruct_fifo(&config)) { +- msg(LOG_ERR, "Cannot contruct a pipe"); +- exit(1); +- } +- + // Setup filesystem to watch list + init_fs_list(config.watch_fs); + + // Write the pid file for the init system + write_pid_file(); + ++ // Set strict umask ++ (void) umask( 0117 ); ++ ++ if (preconstruct_fifo(&config)) { ++ msg(LOG_ERR, "Cannot contruct a pipe"); ++ exit(1); ++ } ++ + // If we are not going to be root, then setup necessary capabilities + if (config.uid != 0) { + capng_clear(CAPNG_SELECT_BOTH); diff --git a/SOURCES/fapolicyd-exclude-list.patch b/SOURCES/fapolicyd-exclude-list.patch new file mode 100644 index 0000000..87d00e8 --- /dev/null +++ b/SOURCES/fapolicyd-exclude-list.patch @@ -0,0 +1,1088 @@ +From 4066d92395c18ad435ee6ff8e1da2745a68bacc1 Mon Sep 17 00:00:00 2001 +From: Radovan Sroka +Date: Fri, 24 Jun 2022 15:03:28 +0200 +Subject: [PATCH] Introduce filtering of rpmdb + +- this feature introduces very flexible filter syntax +- original filter was compiled in so this is very useful +- filter needs to keep a minimal set of files that will be excuted + on the system eventually +- all the configuration can be done in /etc/fapolicyd/rpm-filter.conf + +Signed-off-by: Radovan Sroka +--- + doc/Makefile.am | 3 +- + doc/rpm-filter.conf.5 | 63 +++++ + fapolicyd.spec | 1 + + init/Makefile.am | 2 + + init/rpm-filter.conf | 42 ++++ + src/Makefile.am | 8 +- + src/library/llist.c | 23 +- + src/library/llist.h | 1 + + src/library/rpm-backend.c | 79 ++----- + src/library/rpm-filter.c | 487 ++++++++++++++++++++++++++++++++++++++ + src/library/rpm-filter.h | 67 ++++++ + src/library/stack.c | 89 +++++++ + src/library/stack.h | 41 ++++ + 13 files changed, 837 insertions(+), 69 deletions(-) + create mode 100644 doc/rpm-filter.conf.5 + create mode 100644 init/rpm-filter.conf + create mode 100644 src/library/rpm-filter.c + create mode 100644 src/library/rpm-filter.h + create mode 100644 src/library/stack.c + create mode 100644 src/library/stack.h + +diff --git a/doc/Makefile.am b/doc/Makefile.am +index f0b79080..726218ed 100644 +--- a/doc/Makefile.am ++++ b/doc/Makefile.am +@@ -28,4 +28,5 @@ man_MANS = \ + fapolicyd-cli.1 \ + fapolicyd.rules.5 \ + fapolicyd.trust.5 \ +- fapolicyd.conf.5 ++ fapolicyd.conf.5 \ ++ rpm-filter.conf.5 +diff --git a/doc/rpm-filter.conf.5 b/doc/rpm-filter.conf.5 +new file mode 100644 +index 00000000..d415bd80 +--- /dev/null ++++ b/doc/rpm-filter.conf.5 +@@ -0,0 +1,63 @@ ++.TH RPM_FILTER.CONF: "5" "January 2023" "Red Hat" "System Administration Utilities" ++.SH NAME ++rpm-filter.conf \- fapolicyd filter configuration file ++.SH DESCRIPTION ++The file ++.I /etc/fapolicyd/rpm-filter.conf ++contains configuration of the filter for the application allowlisting daemon. This filter specifies an allow or exclude list of files from rpm. Valid line starts with character '+', '-' or '#' for comments. The rest of the line contains a path specification. Space can be used as indentation to add more specific filters to the previous one. Note, that only one space is required for one level of an indent. If there are multiple specifications on the same indentation level they extend the previous line with lower indentation, usually a directory. The path may be specified using the glob pattern. A directory specification has to end with a slash ‘/’. ++ ++The filters are processed as follows: Starting from the up the to bottom while in case of a match the result (+/-) is set unless there is an indented block which describes more detailed specification of the parent level match. The same processing logic is applied to the inner filters definitions. If there is no match, the parent’s result is set. If there is no match at all, the default result is minus (-). ++ ++If the result was a plus (+), the respective file from the rpmdb is imported to the TrustDB. Vice versa, if the result was a minus (-), the respective file is not imported. ++ ++From a performance point of view it is better to design an indented filter because in the ideal situation each component of the path is compared only once. In contrast to it, a filter without any indentation has to contain a full path which makes the pattern more complicated and thus slower to process. The motivation behind this is to have a flexible configuration and keep the TrustDB as small as possible to make the look-ups faster. ++ ++ ++ ++.nf ++.B # this is simple allow list ++.B - /usr/bin/some_binary1 ++.B - /usr/bin/some_binary2 ++.B + / ++.fi ++ ++.nf ++.B # this is the same ++.B + / ++.B \ + usr/bin/ ++.B \ \ - some_binary1 ++.B \ \ - some_binary2 ++.fi ++ ++.nf ++.B # this is similar allow list with a wildcard ++.B - /usr/bin/some_binary? ++.B + / ++.fi ++ ++.nf ++.B # this is similar with another wildcard ++.B + / ++.B \ - usr/bin/some_binary* ++.fi ++ ++.nf ++.B # keeps everything except usr/share except python and perl files ++.B # /usr/bin/ls - result is '+' ++.B # /usr/share/something - result is '-' ++.B # /usr/share/abcd.py - result is '+' ++.B + / ++.B \ - usr/share/ ++.B \ \ + *.py ++.B \ \ + *.pl ++.fi ++ ++.SH "SEE ALSO" ++.BR fapolicyd (8), ++.BR fapolicyd-cli (1) ++.BR fapolicy.rules (5) ++and ++.BR glob (7) ++ ++.SH AUTHOR ++Radovan Sroka +diff --git a/init/Makefile.am b/init/Makefile.am +index da948e4e..1f23dffe 100644 +--- a/init/Makefile.am ++++ b/init/Makefile.am +@@ -1,6 +1,7 @@ + EXTRA_DIST = \ + fapolicyd.service \ + fapolicyd.conf \ ++ rpm-filter.conf \ + fapolicyd.trust \ + fapolicyd-tmpfiles.conf \ + fapolicyd-magic \ +@@ -11,6 +12,7 @@ fapolicyddir = $(sysconfdir)/fapolicyd + + dist_fapolicyd_DATA = \ + fapolicyd.conf \ ++ rpm-filter.conf \ + fapolicyd.trust + + systemdservicedir = $(systemdsystemunitdir) +diff --git a/init/rpm-filter.conf b/init/rpm-filter.conf +new file mode 100644 +index 00000000..0c8fca40 +--- /dev/null ++++ b/init/rpm-filter.conf +@@ -0,0 +1,42 @@ ++# default filter file for fedora ++ +++ / ++ - usr/include/ ++ - usr/share/ ++ # Python byte code ++ + *.py? ++ # Python text files ++ + *.py ++ # Some apps have a private libexec ++ + */libexec/* ++ # Ruby ++ + *.rb ++ # Perl ++ + *.pl ++ # System tap ++ + *.stp ++ # Javascript ++ + *.js ++ # Java archive ++ + *.jar ++ # M4 ++ + *.m4 ++ # PHP ++ + *.php ++ # Perl Modules ++ + *.pm ++ # Lua ++ + *.lua ++ # Java ++ + *.class ++ # Typescript ++ + *.ts ++ # Typescript JSX ++ + *.tsx ++ # Lisp ++ + *.el ++ # Compiled Lisp ++ + *.elc ++ - usr/src/kernel*/ ++ + */scripts/* ++ + */tools/objtool/* +diff --git a/src/Makefile.am b/src/Makefile.am +index 547ea486..fd08eb06 100644 +--- a/src/Makefile.am ++++ b/src/Makefile.am +@@ -62,13 +62,19 @@ libfapolicyd_la_SOURCES = \ + library/subject-attr.h \ + library/subject.c \ + library/subject.h \ ++ library/stack.c \ ++ library/stack.h \ + library/string-util.c \ + library/string-util.h \ + library/trust-file.c \ + library/trust-file.h + + if WITH_RPM +-libfapolicyd_la_SOURCES += library/rpm-backend.c ++libfapolicyd_la_SOURCES += \ ++ library/rpm-backend.c \ ++ library/rpm-filter.c \ ++ library/rpm-filter.h ++ + endif + + libfapolicyd_la_CFLAGS = $(fapolicyd_CFLAGS) +diff --git a/src/library/llist.c b/src/library/llist.c +index 6132805a..44cfb4a3 100644 +--- a/src/library/llist.c ++++ b/src/library/llist.c +@@ -45,19 +45,36 @@ list_item_t *list_get_first(const list_t *list) + return list->first; + } + +- +-int list_append(list_t *list, const char *index, const char *data) ++static list_item_t * create_item(const char *index, const char *data) + { + list_item_t *item = malloc(sizeof(list_item_t)); + if (!item) { + msg(LOG_ERR, "Malloc failed"); +- return 1; ++ return item; + } + + item->index = index; + item->data = data; + item->next = NULL; + ++ return item; ++} ++ ++int list_prepend(list_t *list, const char *index, const char *data) ++{ ++ list_item_t *item = create_item(index, data); ++ ++ item->next = list->first; ++ list->first = item; ++ ++ ++list->count; ++ return 0; ++} ++ ++int list_append(list_t *list, const char *index, const char *data) ++{ ++ list_item_t *item = create_item(index, data); ++ + if (list->first) { + list->last->next = item; + list->last = item; +diff --git a/src/library/llist.h b/src/library/llist.h +index 0c1d85a7..59eccf17 100644 +--- a/src/library/llist.h ++++ b/src/library/llist.h +@@ -40,6 +40,7 @@ typedef struct list_header { + + void list_init(list_t *list); + list_item_t *list_get_first(const list_t *list); ++int list_prepend(list_t *list, const char *index, const char *data); + int list_append(list_t *list, const char *index, const char *data); + void list_destroy_item(list_item_t **item); + void list_empty(list_t *list); +diff --git a/src/library/rpm-backend.c b/src/library/rpm-backend.c +index 7f1af438..0887d36a 100644 +--- a/src/library/rpm-backend.c ++++ b/src/library/rpm-backend.c +@@ -40,6 +40,8 @@ + #include "fapolicyd-backend.h" + #include "llist.h" + ++#include "rpm-filter.h" ++ + static int rpm_init_backend(void); + static int rpm_load_list(const conf_t *); + static int rpm_destroy_backend(void); +@@ -176,69 +178,6 @@ static void close_rpm(void) + rpmlogClose(); + } + +-// This function will check a passed file name to see if the path should +-// be kept or dropped. 1 means discard it, and 0 means keep it. +-static int drop_path(const char *file_name) +-{ +- const char *p = file_name; +- if (!strncmp(p, "/usr", 4)) { +- p += 4; +- +- // Drop anything in /usr/include +- if (!strncmp(p, "/include", 8)) +- return 1; +- +- // Only keep languages from /usr/share +- if (!strncmp(p, "/share", 6)) { +- p += 6; +- +- // These are roughly ordered by quantity +- static const char *arr_share[] = { +- "*.py?", // Python byte code +- "*.py", // Python text files +- "*/libexec/*", // Some apps have a private libexec +- "*.rb", // Ruby +- "*.pl", // Perl +- "*.stp", // System tap +- "*.js", // Javascript +- "*.jar", // Java archive +- "*.m4", // M4 +- "*.php", // PHP +- "*.pm", // Perl Modules +- "*.lua", // Lua +- "*.class", // Java +- "*.ts", // Typescript +- "*.tsx", // Typescript JSX +- "*.el", // Lisp +- "*.elc", // Compiled Lisp +- NULL +- }; +- +- for (int i = 0; arr_share[i]; ++i) +- if (!fnmatch(arr_share[i], p, 0)) +- return 0; +- return 1; +- } +- +- // Akmod needs scripts in /usr/src/kernel +- if (!strncmp(p, "/src/kernel", 11)) { +- p += 11; +- +- static const char *arr_src_kernel[] = { +- "*/scripts/*", +- "*/tools/objtool/*", +- NULL +- }; +- +- for (int i = 0; arr_src_kernel[i]; ++i) +- if (!fnmatch(arr_src_kernel[i], p, 0)) +- return 0; +- return 1; +- } +- } +- return 0; +-} +- + struct _hash_record { + const char * key; + UT_hash_handle hh; +@@ -290,7 +229,8 @@ static int rpm_load_list(const conf_t *conf) + if (file_name == NULL) + continue; + +- if (drop_path(file_name)) { ++ // should we drop a path? ++ if (!filter_check(file_name)) { + free((void *)file_name); + free((void *)sha); + continue; +@@ -358,12 +298,23 @@ static int rpm_load_list(const conf_t *conf) + + static int rpm_init_backend(void) + { ++ if (filter_init()) ++ return 1; ++ ++ if (filter_load_file()) { ++ filter_destroy(); ++ return 1; ++ } ++ ++ + list_init(&rpm_backend.list); ++ + return 0; + } + + static int rpm_destroy_backend(void) + { ++ filter_destroy(); + list_empty(&rpm_backend.list); + return 0; + } +diff --git a/src/library/rpm-filter.c b/src/library/rpm-filter.c +new file mode 100644 +index 00000000..e3e3eb38 +--- /dev/null ++++ b/src/library/rpm-filter.c +@@ -0,0 +1,487 @@ ++/* ++* rpm-filter.c - filter for rpm trust source ++* Copyright (c) 2023 Red Hat Inc., Durham, North Carolina. ++* All Rights Reserved. ++* ++* This software may be freely redistributed and/or modified under the ++* terms of the GNU General Public License as published by the Free ++* Software Foundation; either version 2, or (at your option) any ++* later version. ++* ++* This program is distributed in the hope that it will be useful, ++* but WITHOUT ANY WARRANTY; without even the implied warranty of ++* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++* GNU General Public License for more details. ++* ++* You should have received a copy of the GNU General Public License ++* along with this program; see the file COPYING. If not, write to the ++* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor ++* Boston, MA 02110-1335, USA. ++* ++* Authors: ++* Radovan Sroka ++*/ ++ ++#include "rpm-filter.h" ++ ++#include ++#include ++#include ++#include ++ ++#include "llist.h" ++#include "stack.h" ++#include "message.h" ++#include "string-util.h" ++ ++ ++#define RPM_FILTER_FILE "/etc/fapolicyd/rpm-filter.conf" ++ ++rpm_filter_t *global_filter = NULL; ++ ++static rpm_filter_t *filter_create_obj(void); ++static void filter_destroy_obj(rpm_filter_t *_filter); ++ ++// init fuction of this module ++int filter_init(void) ++{ ++ global_filter = filter_create_obj(); ++ if (global_filter == NULL) ++ return 1; ++ ++ return 0; ++} ++ ++// destroy funtion of this module ++void filter_destroy(void) ++{ ++ filter_destroy_obj(global_filter); ++ global_filter = NULL; ++} ++ ++// alocate new filter object and fill with the defaults ++static rpm_filter_t *filter_create_obj(void) ++{ ++ rpm_filter_t *filter = malloc(sizeof(rpm_filter_t)); ++ if (filter) { ++ filter->type = NONE; ++ filter->path = NULL; ++ filter->len = 0; ++ filter->matched = 0; ++ filter->processed = 0; ++ list_init(&filter->list); ++ } ++ return filter; ++} ++ ++// free all nested filters ++static void filter_destroy_obj(rpm_filter_t *_filter) ++{ ++ if (_filter == NULL) ++ return; ++ ++ rpm_filter_t *filter = _filter; ++ stack_t stack; ++ stack_init(&stack); ++ ++ stack_push(&stack, filter); ++ ++ while (!stack_is_empty(&stack)) { ++ filter = (rpm_filter_t*)stack_top(&stack); ++ if (filter->processed) { ++ (void)free(filter->path); ++ // asume that item->data is NULL ++ list_empty(&filter->list); ++ (void)free(filter); ++ stack_pop(&stack); ++ continue; ++ } ++ ++ list_item_t *item = list_get_first(&filter->list); ++ for (; item != NULL ; item = item->next) { ++ rpm_filter_t *next_filter = (rpm_filter_t*)item->data; ++ // we can use list_empty() later ++ // we dont want to free filter right now ++ // it will freed after popping ++ item->data = NULL; ++ stack_push(&stack, next_filter); ++ } ++ filter->processed = 1; ++ } ++ stack_destroy(&stack); ++} ++ ++// create struct and push it to the top of stack ++static void stack_push_vars(stack_t *_stack, int _level, int _offset, rpm_filter_t *_filter) ++{ ++ if (_stack == NULL) ++ return; ++ ++ stack_item_t *item = malloc(sizeof(stack_item_t)); ++ if (item == NULL) ++ return; ++ ++ item->level = _level; ++ item->offset = _offset; ++ item->filter = _filter; ++ ++ stack_push(_stack, item); ++} ++ ++// pop stack_item_t and free it ++static void stack_pop_vars(stack_t *_stack) ++{ ++ if (_stack == NULL) ++ return; ++ ++ stack_item_t * item = (stack_item_t*)stack_top(_stack); ++ free(item); ++ stack_pop(_stack); ++} ++ ++// pop all the stack_item_t and free them ++static void stack_pop_all_vars(stack_t *_stack) ++{ ++ if (_stack == NULL) ++ return; ++ ++ while (!stack_is_empty(_stack)) ++ stack_pop_vars(_stack); ++} ++ ++// reset filter to default, pop top and free ++static void stack_pop_reset(stack_t *_stack) ++{ ++ if (_stack == NULL) ++ return; ++ ++ stack_item_t *stack_item = (stack_item_t*)stack_top(_stack); ++ if (stack_item) { ++ stack_item->filter->matched = 0; ++ stack_item->filter->processed = 0; ++ } ++ free(stack_item); ++ stack_pop(_stack); ++} ++ ++// reset and pop all the stack_item_t ++static void stack_pop_all_reset(stack_t *_stack) ++{ ++ if (_stack == NULL) ++ return; ++ ++ while (!stack_is_empty(_stack)) ++ stack_pop_reset(_stack); ++} ++ ++// this funtion gets full path and checks it against filter ++// returns 1 for keeping the file and 0 for dropping it ++int filter_check(const char *_path) ++{ ++ if (_path == NULL) { ++ msg(LOG_ERR, "filter_check: path is NULL, something is wrong!"); ++ return 0; ++ } ++ ++ rpm_filter_t *filter = global_filter; ++ char *path = strdup(_path); ++ size_t path_len = strlen(_path); ++ size_t offset = 0; ++ // Create a stack to store the filters that need to be checked ++ stack_t stack; ++ stack_init(&stack); ++ ++ int res = 0; ++ int level = 0; ++ ++ stack_push_vars(&stack, level, offset, filter); ++ ++ while(!stack_is_empty(&stack)) { ++ int matched = 0; ++ filter->processed = 1; ++ ++ // this is starting branch of the algo ++ // assuming that in root filter filter->path is NULL ++ if (filter->path == NULL) { ++ list_item_t *item = list_get_first(&filter->list); ++ // push all the descendants to the stack ++ for (; item != NULL ; item = item->next) { ++ rpm_filter_t *next_filter = (rpm_filter_t*)item->data; ++ stack_push_vars(&stack, level+1, offset, next_filter); ++ } ++ ++ // usual branch, start with processing ++ } else { ++ // wildcard contition ++ char *is_wildcard = strpbrk(filter->path, "?*["); ++ if (is_wildcard) { ++ int count = 0; ++ char *filter_lim, *filter_old_lim; ++ filter_lim = filter_old_lim = filter->path; ++ ++ char *path_lim, *path_old_lim; ++ path_lim = path_old_lim = path+offset; ++ ++ // there can be wildcard in the dir name as well ++ // we need to count how many chars can be eaten by wildcard ++ while(1) { ++ filter_lim = strchr(filter_lim, '/'); ++ path_lim = strchr(path_lim, '/'); ++ ++ if (filter_lim) { ++ count++; ++ filter_old_lim = filter_lim; ++ filter_lim++; ++ } else ++ break; ++ ++ if (path_lim) { ++ path_old_lim = path_lim; ++ path_lim++; ++ } else ++ break; ++ ++ } ++ // put 0 after the last / ++ char tmp = '\0'; ++ if (count && *(filter_old_lim+1) == '\0') { ++ tmp = *(path_old_lim+1); ++ *(path_old_lim+1) = '\0'; ++ } ++ ++ // check fnmatch ++ matched = !fnmatch(filter->path, path+offset, 0); ++ ++ // and set back ++ if (count && *(filter_old_lim+1) == '\0') ++ *(path_old_lim+1) = tmp; ++ ++ if (matched) { ++ offset = path_old_lim - path+offset; ++ } ++ } else { ++ // match normal path or just specific part of it ++ matched = !strncmp(path+offset, filter->path, filter->len); ++ if (matched) ++ offset += filter->len; ++ } ++ ++ if (matched) { ++ level++; ++ filter->matched = 1; ++ ++ // if matched we need ot push descendants to the stack ++ list_item_t *item = list_get_first(&filter->list); ++ ++ // if there are no descendants and it is a wildcard then it's a match ++ if (item == NULL && is_wildcard) { ++ // if '+' ret 1 and if '-' ret 0 ++ res = filter->type == ADD ? 1 : 0; ++ goto end; ++ } ++ ++ // no descendants, and already compared whole path string so its a match ++ if (item == NULL && path_len == offset) { ++ // if '+' ret 1 and if '-' ret 0 ++ res = filter->type == ADD ? 1 : 0; ++ goto end; ++ } ++ ++ // push descendants to the stack ++ for (; item != NULL ; item = item->next) { ++ rpm_filter_t *next_filter = (rpm_filter_t*)item->data; ++ stack_push_vars(&stack, level, offset, next_filter); ++ } ++ ++ } ++ ++ } ++ ++ stack_item_t * stack_item = NULL; ++ // popping processed filters from the top of the stack ++ do { ++ if (stack_item) { ++ filter = stack_item->filter; ++ offset = stack_item->offset; ++ level = stack_item->level; ++ ++ // assuimg that nothing has matched on the upper level so it's a directory match ++ if (filter->matched && filter->path[filter->len-1] == '/') { ++ res = filter->type == ADD ? 1 : 0; ++ goto end; ++ } ++ ++ // reset processed flag ++ stack_pop_reset(&stack); ++ } ++ ++ stack_item = (stack_item_t*)stack_top(&stack); ++ } while(stack_item && stack_item->filter->processed); ++ ++ if (!stack_item) ++ break; ++ ++ filter = stack_item->filter; ++ offset = stack_item->offset; ++ level = stack_item->level; ++ } ++ ++end: ++ // Clean up the stack ++ stack_pop_all_reset(&stack); ++ stack_destroy(&stack); ++ free(path); ++ return res; ++} ++ ++// load rpm filter configuration file and fill the filter structure ++int filter_load_file(void) ++{ ++ int res = 0; ++ FILE *stream = fopen(RPM_FILTER_FILE, "r"); ++ ++ if (stream == NULL) { ++ msg(LOG_ERR, "Cannot open filter file %s", RPM_FILTER_FILE); ++ return 1; ++ } ++ ++ ssize_t nread; ++ size_t len = 0; ++ char * line = NULL; ++ long line_number = 0; ++ int last_level = 0; ++ ++ stack_t stack; ++ stack_init(&stack); ++ stack_push_vars(&stack, last_level, 0, global_filter); ++ ++ while ((nread = getline(&line, &len, stream)) != -1) { ++ line_number++; ++ ++ if (line[0] == '\0' || line[0] == '\n') { ++ free(line); ++ line = NULL; ++ continue; ++ } ++ ++ // get rid of the new line char ++ char * new_line = strchr(line, '\n'); ++ if (new_line) { ++ *new_line = '\0'; ++ len--; ++ } ++ ++ int level = 1; ++ char * rest = line; ++ rpm_filter_type_t type = NONE; ++ ++ for (size_t i = 0 ; i < len ; i++) { ++ switch (line[i]) { ++ case ' ': ++ level++; ++ continue; ++ case '+': ++ type = ADD; ++ break; ++ case '-': ++ type = SUB; ++ break; ++ case '#': ++ type = COMMENT; ++ break; ++ default: ++ type = BAD; ++ break; ++ } ++ ++ // continue with next char ++ // skip + and space ++ rest = fapolicyd_strtrim(&(line[i+2])); ++ break; ++ } ++ ++ // ignore comment ++ if (type == COMMENT) { ++ free(line); ++ line = NULL; ++ continue; ++ } ++ ++ // if something bad return error ++ if (type == BAD) { ++ msg(LOG_ERR, "filter_load_file: cannot parse line number %ld, \"%s\"", line_number, line); ++ free(line); ++ line = NULL; ++ goto bad; ++ } ++ ++ rpm_filter_t * filter = filter_create_obj(); ++ ++ if (filter) { ++ filter->path = strdup(rest); ++ filter->len = strlen(filter->path); ++ filter->type = type; ++ } ++ ++ // comparing level of indetantion between the last line and the current one ++ last_level = ((stack_item_t*)stack_top(&stack))->level; ++ if (level == last_level) { ++ ++ // since we are at the same level as filter before ++ // we need to pop the previous filter from the top ++ stack_pop_vars(&stack); ++ ++ // pushing filter to the list of top's children list ++ list_prepend(&((stack_item_t*)stack_top(&stack))->filter->list, NULL, (void*)filter); ++ ++ // pushing filter to the top of the stack ++ stack_push_vars(&stack, level, 0, filter); ++ ++ } else if (level == last_level + 1) { ++ // this filter has higher level tha privious one ++ // we wont do pop just push ++ ++ // pushing filter to the list of top's children list ++ list_prepend(&((stack_item_t*)stack_top(&stack))->filter->list, NULL, (void*)filter); ++ ++ // pushing filter to the top of the stack ++ stack_push_vars(&stack, level, 0, filter); ++ ++ } else if (level < last_level){ ++ // level of indentation dropped ++ // we need to pop ++ // +1 is meant for getting rid of the current level so we can again push ++ for (int i = 0 ; i < last_level - level + 1; i++) { ++ stack_pop_vars(&stack); ++ } ++ ++ // pushing filter to the list of top's children list ++ list_prepend(&((stack_item_t*)stack_top(&stack))->filter->list, NULL, (void*)filter); ++ ++ // pushing filter to the top of the stack ++ stack_push_vars(&stack, level, 0, filter); ++ ++ } else { ++ msg(LOG_ERR, "filter_load_file: paring error line: %ld, \"%s\"", line_number, line); ++ filter_destroy_obj(filter); ++ free(line); ++ goto bad; ++ } ++ ++ free(line); ++ line = NULL; ++ } ++ ++ goto good; ++bad: ++ res = 1; ++ ++good: ++ fclose(stream); ++ stack_pop_all_vars(&stack); ++ stack_destroy(&stack); ++ if (global_filter->list.count == 0) { ++ msg(LOG_ERR, "filter_load_file: no valid filter provided in %s", RPM_FILTER_FILE); ++ } ++ return res; ++} +diff --git a/src/library/rpm-filter.h b/src/library/rpm-filter.h +new file mode 100644 +index 00000000..2c49d338 +--- /dev/null ++++ b/src/library/rpm-filter.h +@@ -0,0 +1,67 @@ ++/* ++* rpm-filter.h - Header for rpm filter implementation ++* Copyright (c) 2023 Red Hat Inc., Durham, North Carolina. ++* All Rights Reserved. ++* ++* This software may be freely redistributed and/or modified under the ++* terms of the GNU General Public License as published by the Free ++* Software Foundation; either version 2, or (at your option) any ++* later version. ++* ++* This program is distributed in the hope that it will be useful, ++* but WITHOUT ANY WARRANTY; without even the implied warranty of ++* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++* GNU General Public License for more details. ++* ++* You should have received a copy of the GNU General Public License ++* along with this program; see the file COPYING. If not, write to the ++* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor ++* Boston, MA 02110-1335, USA. ++* ++* Authors: ++* Radovan Sroka ++*/ ++ ++#ifndef FILTER_H_ ++#define FILTER_H_ ++ ++#include ++#include ++ ++#include "llist.h" ++ ++typedef enum rpm_filter_type ++{ ++ NONE, ++ ADD, ++ SUB, ++ COMMENT, ++ BAD, ++} rpm_filter_type_t; ++ ++typedef struct _rpm_filter ++{ ++ rpm_filter_type_t type; ++ char * path; ++ size_t len; ++ int processed; ++ int matched; ++ list_t list; ++} rpm_filter_t; ++ ++ ++typedef struct _stack_item ++{ ++ int level; ++ int offset; ++ rpm_filter_t *filter; ++} stack_item_t; ++ ++ ++int filter_init(void); ++void filter_destroy(void); ++int filter_check(const char *_path); ++int filter_load_file(void); ++ ++ ++#endif // FILTER_H_ +diff --git a/src/library/stack.c b/src/library/stack.c +new file mode 100644 +index 00000000..93141b2c +--- /dev/null ++++ b/src/library/stack.c +@@ -0,0 +1,89 @@ ++/* ++* stack.c - generic stack impementation ++* Copyright (c) 2023 Red Hat Inc., Durham, North Carolina. ++* All Rights Reserved. ++* ++* This software may be freely redistributed and/or modified under the ++* terms of the GNU General Public License as published by the Free ++* Software Foundation; either version 2, or (at your option) any ++* later version. ++* ++* This program is distributed in the hope that it will be useful, ++* but WITHOUT ANY WARRANTY; without even the implied warranty of ++* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++* GNU General Public License for more details. ++* ++* You should have received a copy of the GNU General Public License ++* along with this program; see the file COPYING. If not, write to the ++* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor ++* Boston, MA 02110-1335, USA. ++* ++* Authors: ++* Radovan Sroka ++*/ ++ ++#include "stack.h" ++#include ++ ++// init of the stack struct ++void stack_init(stack_t *_stack) ++{ ++ if (_stack == NULL) ++ return; ++ ++ list_init(_stack); ++} ++ ++// free all the resources from the stack ++void stack_destroy(stack_t *_stack) ++{ ++ if (_stack == NULL) ++ return; ++ ++ list_empty(_stack); ++} ++ ++// push to the top of the stack ++void stack_push(stack_t *_stack, void *_data) ++{ ++ if (_stack == NULL) ++ return; ++ ++ list_prepend(_stack, NULL, (void *)_data); ++} ++ ++// pop the the top without returning what was on the top ++void stack_pop(stack_t *_stack) ++{ ++ if (_stack == NULL) ++ return; ++ ++ list_item_t *first = _stack->first; ++ _stack->first = first->next; ++ first->data = NULL; ++ list_destroy_item(&first); ++ _stack->count--; ++ ++ return; ++} ++ ++// function returns 1 if stack is emtpy 0 if it's not ++int stack_is_empty(stack_t *_stack) ++{ ++ if (_stack == NULL) ++ return -1; ++ ++ if (_stack->count == 0) ++ return 1; ++ ++ return 0; ++} ++ ++// return top of the stack without popping ++const void *stack_top(stack_t *_stack) ++{ ++ if (_stack == NULL) ++ return NULL; ++ ++ return _stack->first ? _stack->first->data : NULL; ++} +diff --git a/src/library/stack.h b/src/library/stack.h +new file mode 100644 +index 00000000..042476e3 +--- /dev/null ++++ b/src/library/stack.h +@@ -0,0 +1,41 @@ ++/* ++* stack.h - header for generic stack implementation ++* Copyright (c) 2023 Red Hat Inc., Durham, North Carolina. ++* All Rights Reserved. ++* ++* This software may be freely redistributed and/or modified under the ++* terms of the GNU General Public License as published by the Free ++* Software Foundation; either version 2, or (at your option) any ++* later version. ++* ++* This program is distributed in the hope that it will be useful, ++* but WITHOUT ANY WARRANTY; without even the implied warranty of ++* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++* GNU General Public License for more details. ++* ++* You should have received a copy of the GNU General Public License ++* along with this program; see the file COPYING. If not, write to the ++* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor ++* Boston, MA 02110-1335, USA. ++* ++* Authors: ++* Radovan Sroka ++*/ ++ ++ ++#ifndef STACK_H_ ++#define STACK_H_ ++ ++#include "llist.h" ++ ++typedef list_t stack_t; ++ ++void stack_init(stack_t *_stack); ++void stack_destroy(stack_t *_stack); ++void stack_push(stack_t *_stack, void *_data); ++void stack_pop(stack_t *_stack); ++int stack_is_empty(stack_t *_stack); ++const void *stack_top(stack_t *_stack); ++ ++ ++#endif // STACK_H_ diff --git a/SOURCES/fapolicyd-falcon-sensor.patch b/SOURCES/fapolicyd-falcon-sensor.patch new file mode 100644 index 0000000..751d9fa --- /dev/null +++ b/SOURCES/fapolicyd-falcon-sensor.patch @@ -0,0 +1,79 @@ +From 2b13715219bbb6a84a73e007cea84f0d5d1d39ab Mon Sep 17 00:00:00 2001 +From: Radovan Sroka +Date: Tue, 6 Dec 2022 15:09:44 +0100 +Subject: [PATCH] Extend new_event state machine + +- allow other opens before dynamic linker execution +- split original STATE_REOPEN to the new STATE_REOPEN and STATE_DEFAULT_REOPEN + +- STATE_REOPEN now behaves as loop state for new opens (from the same subject), + uses skip_path +- STATE_DEFAULT_REOPEN is needed when dynamic linker is directly executed + in such scenario we need to be sure that non of the following opens will + skip the path + +Signed-off-by: Radovan Sroka +--- + src/library/event.c | 16 ++++++++++++++++ + src/library/process.h | 3 ++- + 2 files changed, 18 insertions(+), 1 deletion(-) + +diff --git a/src/library/event.c b/src/library/event.c +index 4d79eb98..649cb9d6 100644 +--- a/src/library/event.c ++++ b/src/library/event.c +@@ -133,6 +133,12 @@ int new_event(const struct fanotify_event_metadata *m, event_t *e) + (e->type & FAN_OPEN_PERM) && !rc) { + skip_path = 1; + s->info->state = STATE_REOPEN; ++ ++ // special branch after ld_so exec ++ // next opens will go fall trough ++ if (s->info->path1 && ++ (strcmp(s->info->path1, SYSTEM_LD_SO) == 0)) ++ s->info->state = STATE_DEFAULT_REOPEN; + } + + // If not same proc or we detect execution, evict +@@ -164,6 +170,7 @@ int new_event(const struct fanotify_event_metadata *m, event_t *e) + skip_path = 1; + } + ++ + // If we've seen the reopen and its an execute and process + // has an interpreter and we're the same process, don't evict + // and don't collect the path since reopen interp will. The +@@ -172,10 +179,19 @@ int new_event(const struct fanotify_event_metadata *m, event_t *e) + if ((s->info->state == STATE_REOPEN) && !skip_path && + (e->type & FAN_OPEN_EXEC_PERM) && + (s->info->elf_info & HAS_INTERP) && !rc) { ++ s->info->state = STATE_DEFAULT_REOPEN; + evict = 0; + skip_path = 1; + } + ++ // this is how STATE_REOPEN and ++ // STATE_DEFAULT_REOPEN differs ++ // in STATE_REOPEN path is always skipped ++ if ((s->info->state == STATE_REOPEN) && !skip_path && ++ (e->type & FAN_OPEN_PERM) && !rc) { ++ skip_path = 1; ++ } ++ + if (evict) { + lru_evict(subj_cache, key); + q_node = check_lru_cache(subj_cache, key); +diff --git a/src/library/process.h b/src/library/process.h +index daa9d0d0..a741d1ac 100644 +--- a/src/library/process.h ++++ b/src/library/process.h +@@ -31,7 +31,8 @@ + #include "gcc-attributes.h" + + typedef enum { STATE_COLLECTING=0, // initial state - execute +- STATE_REOPEN, // anticipating open perm next ++ STATE_REOPEN, // anticipating open perm next, always skips the path ++ STATE_DEFAULT_REOPEN, // reopen after dyn. linker exec, never skips the path + STATE_STATIC_REOPEN, // static app aniticipating + STATE_PARTIAL, // second path collected + STATE_STATIC_PARTIAL, // second path collected diff --git a/SOURCES/fapolicyd-markfs-1.patch b/SOURCES/fapolicyd-markfs-1.patch new file mode 100644 index 0000000..041c878 --- /dev/null +++ b/SOURCES/fapolicyd-markfs-1.patch @@ -0,0 +1,170 @@ +From 2d15ea13e2a3dca1bb159f2cf031ca437c0b9aa1 Mon Sep 17 00:00:00 2001 +From: Steve Grubb +Date: Tue, 27 Sep 2022 10:33:44 -0400 +Subject: [PATCH] Add support for using FAN_MARK_FILESYSTEM to see bind mounted + accesses + +--- + ChangeLog | 1 + + configure.ac | 1 + + doc/fapolicyd.conf.5 | 5 ++++- + init/fapolicyd.conf | 1 + + src/daemon/notify.c | 12 ++++++++++-- + src/library/conf.h | 3 ++- + src/library/daemon-config.c | 28 +++++++++++++++++++++++++++- + 7 files changed, 46 insertions(+), 5 deletions(-) + +diff --git a/configure.ac b/configure.ac +index 4437685..a67c46b 100644 +--- a/configure.ac ++++ b/configure.ac +@@ -56,6 +56,7 @@ AC_CHECK_DECLS([FAN_OPEN_EXEC_PERM], [perm=yes], [perm=no], [[#include ]]) + + withval="" + AC_ARG_WITH(rpm, +diff --git a/doc/fapolicyd.conf.5 b/doc/fapolicyd.conf.5 +index 812cfa4..d8cb296 100644 +--- a/doc/fapolicyd.conf.5 ++++ b/doc/fapolicyd.conf.5 +@@ -1,4 +1,4 @@ +-.TH FAPOLICYD.CONF: "6" "October 2021" "Red Hat" "System Administration Utilities" ++.TH FAPOLICYD.CONF: "6" "September 2022" "Red Hat" "System Administration Utilities" + .SH NAME + fapolicyd.conf \- fapolicyd configuration file + .SH DESCRIPTION +@@ -87,6 +87,9 @@ Example: + .B rpm_sha256_only + The option set to 1 forces the daemon to work only with SHA256 hashes. This is useful on the systems where the integrity is set to SHA256 or IMA and some rpms were originally built with e.g. SHA1. The daemon will ingore these SHA1 entries therefore they can be added manually via CLI with correct SHA256 to a trust file later. If set to 0 the daemon stores SHA1 in trustdb as well. This is compatible with older behavior which works with the integrity set to NONE and SIZE. The NONE or SIZE integrity setting considers the files installed via rpm as trusted and it does not care about their hashes at all. On the other hand the integrity set to SHA256 or IMA will never consider a file with SHA1 in trustdb as trusted. The default value is 0. + ++.TP ++.B allow_filesystem_mark ++When this option is set to 1, it allows fapolicyd to monitor file access events on the underlying file system when they are bind mounted or are overlayed (e.g. the overlayfs). Normally they block fapolicyd from seeing events on the underlying file systems. This may or may not be desirable. For example, you might start seeing containers accessing things outside of the container but there is no source of trust for the container. In that case you probably do not want to see access from the container. Or maybe you do not use containers but want to control anything run by systemd-run when dynamic users are allowed. In that case you probably want to turn it on. Not all kernel's supoport this option. Therefore the default value is 0. + + .SH "SEE ALSO" + .BR fapolicyd (8), +diff --git a/init/fapolicyd.conf b/init/fapolicyd.conf +index 42e8798..8363b89 100644 +--- a/init/fapolicyd.conf ++++ b/init/fapolicyd.conf +@@ -18,3 +18,4 @@ trust = rpmdb,file + integrity = none + syslog_format = rule,dec,perm,auid,pid,exe,:,path,ftype,trust + rpm_sha256_only = 0 ++allow_filesystem_mark = 0 +diff --git a/src/daemon/notify.c b/src/daemon/notify.c +index f550e99..c91abc4 100644 +--- a/src/daemon/notify.c ++++ b/src/daemon/notify.c +@@ -123,8 +123,16 @@ int init_fanotify(const conf_t *conf, mlist *m) + path = mlist_first(m); + while (path) { + retry_mark: +- if (fanotify_mark(fd, FAN_MARK_ADD | FAN_MARK_MOUNT, +- mask, -1, path) == -1) { ++ unsigned int flags = FAN_MARK_ADD; ++#ifdef HAVE_DECL_FAN_MARK_FILESYSTEM ++ if (conf->allow_filesystem_mark) ++ flags |= FAN_MARK_FILESYSTEM; ++#else ++ if (conf->allow_filesystem_mark) ++ msg(LOG_ERR, ++ "allow_filesystem_mark is unsupported for this kernel - ignoring"); ++#endif ++ if (fanotify_mark(fd, flags, mask, -1, path) == -1) { + /* + * The FAN_OPEN_EXEC_PERM mask is not supported by + * all kernel releases prior to 5.0. Retry setting +diff --git a/src/library/conf.h b/src/library/conf.h +index e774ff6..57c19a2 100644 +--- a/src/library/conf.h ++++ b/src/library/conf.h +@@ -1,5 +1,5 @@ + /* conf.h configuration structure +- * Copyright 2018-20 Red Hat Inc. ++ * Copyright 2018-20,22 Red Hat Inc. + * All Rights Reserved. + * + * This program is free software; you can redistribute it and/or modify +@@ -45,6 +45,7 @@ typedef struct conf + integrity_t integrity; + const char *syslog_format; + unsigned int rpm_sha256_only; ++ unsigned int allow_filesystem_mark; + } conf_t; + + #endif +diff --git a/src/library/daemon-config.c b/src/library/daemon-config.c +index e803e0b..89b7f68 100644 +--- a/src/library/daemon-config.c ++++ b/src/library/daemon-config.c +@@ -1,7 +1,7 @@ + /* + * daemon-config.c - This is a config file parser + * +- * Copyright 2018-21 Red Hat Inc. ++ * Copyright 2018-22 Red Hat Inc. + * All Rights Reserved. + * + * This library is free software; you can redistribute it and/or +@@ -92,6 +92,8 @@ static int syslog_format_parser(const struct nv_pair *nv, int line, + conf_t *config); + static int rpm_sha256_only_parser(const struct nv_pair *nv, int line, + conf_t *config); ++static int fs_mark_parser(const struct nv_pair *nv, int line, ++ conf_t *config); + + static const struct kw_pair keywords[] = + { +@@ -110,6 +112,7 @@ static const struct kw_pair keywords[] = + {"integrity", integrity_parser }, + {"syslog_format", syslog_format_parser }, + {"rpm_sha256_only", rpm_sha256_only_parser}, ++ {"allow_filesystem_mark", fs_mark_parser }, + { NULL, NULL } + }; + +@@ -138,6 +141,7 @@ static void clear_daemon_config(conf_t *config) + config->syslog_format = + strdup("rule,dec,perm,auid,pid,exe,:,path,ftype"); + config->rpm_sha256_only = 0; ++ config->allow_filesystem_mark = 0; + } + + int load_daemon_config(conf_t *config) +@@ -590,6 +594,7 @@ static int syslog_format_parser(const struct nv_pair *nv, int line, + return 1; + } + ++ + static int rpm_sha256_only_parser(const struct nv_pair *nv, int line, + conf_t *config) + { +@@ -607,3 +612,24 @@ static int rpm_sha256_only_parser(const struct nv_pair *nv, int line, + + return rc; + } ++ ++ ++static int fs_mark_parser(const struct nv_pair *nv, int line, ++ conf_t *config) ++{ ++ int rc = 0; ++#ifndef HAVE_DECL_FAN_MARK_FILESYSTEM ++ msg(LOG_WARNING, ++ "allow_filesystem_mark is unsupported on this kernel - ignoring"); ++#else ++ rc = unsigned_int_parser(&(config->allow_filesystem_mark), nv->value, line); ++ ++ if (rc == 0 && config->allow_filesystem_mark > 1) { ++ msg(LOG_WARNING, ++ "allow_filesystem_mark value reset to 0 - line %d", line); ++ config->allow_filesystem_mark = 0; ++ } ++#endif ++ ++ return rc; ++} diff --git a/SOURCES/fapolicyd-markfs-2.patch b/SOURCES/fapolicyd-markfs-2.patch new file mode 100644 index 0000000..3b66146 --- /dev/null +++ b/SOURCES/fapolicyd-markfs-2.patch @@ -0,0 +1,38 @@ +From ca225c8e83b37e5f29703d7352af0b937b2e933c Mon Sep 17 00:00:00 2001 +From: Steve Grubb +Date: Tue, 27 Sep 2022 19:41:24 -0400 +Subject: [PATCH] Correct the optional inclusion of code based on + HAVE_DECL_FAN_MARK_FILESYSTEM + +--- + ChangeLog | 1 + + src/daemon/notify.c | 2 +- + src/library/daemon-config.c | 2 +- + 3 files changed, 3 insertions(+), 2 deletions(-) + +diff --git a/src/daemon/notify.c b/src/daemon/notify.c +index c91abc4..f36b644 100644 +--- a/src/daemon/notify.c ++++ b/src/daemon/notify.c +@@ -124,7 +124,7 @@ int init_fanotify(const conf_t *conf, mlist *m) + while (path) { + retry_mark: + unsigned int flags = FAN_MARK_ADD; +-#ifdef HAVE_DECL_FAN_MARK_FILESYSTEM ++#if HAVE_DECL_FAN_MARK_FILESYSTEM != 0 + if (conf->allow_filesystem_mark) + flags |= FAN_MARK_FILESYSTEM; + #else +diff --git a/src/library/daemon-config.c b/src/library/daemon-config.c +index 89b7f68..778b89a 100644 +--- a/src/library/daemon-config.c ++++ b/src/library/daemon-config.c +@@ -618,7 +618,7 @@ static int fs_mark_parser(const struct nv_pair *nv, int line, + conf_t *config) + { + int rc = 0; +-#ifndef HAVE_DECL_FAN_MARK_FILESYSTEM ++#if HAVE_DECL_FAN_MARK_FILESYSTEM == 0 + msg(LOG_WARNING, + "allow_filesystem_mark is unsupported on this kernel - ignoring"); + #else diff --git a/SOURCES/fapolicyd-markfs-3.patch b/SOURCES/fapolicyd-markfs-3.patch new file mode 100644 index 0000000..e2af31d --- /dev/null +++ b/SOURCES/fapolicyd-markfs-3.patch @@ -0,0 +1,53 @@ +From cd315ebb45e3a095f612ec0e03f606a5383c39ba Mon Sep 17 00:00:00 2001 +From: Steve Grubb +Date: Wed, 28 Sep 2022 16:36:28 -0400 +Subject: [PATCH] Add a check to see if they are defined before using them + +--- + src/daemon/notify.c | 2 +- + src/library/daemon-config.c | 14 ++++++++------ + 2 files changed, 9 insertions(+), 7 deletions(-) + +diff --git a/src/daemon/notify.c b/src/daemon/notify.c +index f36b644..3986390 100644 +--- a/src/daemon/notify.c ++++ b/src/daemon/notify.c +@@ -124,7 +124,7 @@ int init_fanotify(const conf_t *conf, mlist *m) + while (path) { + retry_mark: + unsigned int flags = FAN_MARK_ADD; +-#if HAVE_DECL_FAN_MARK_FILESYSTEM != 0 ++#if defined HAVE_DECL_FAN_MARK_FILESYSTEM && HAVE_DECL_FAN_MARK_FILESYSTEM != 0 + if (conf->allow_filesystem_mark) + flags |= FAN_MARK_FILESYSTEM; + #else +diff --git a/src/library/daemon-config.c b/src/library/daemon-config.c +index 778b89a..ba8ade0 100644 +--- a/src/library/daemon-config.c ++++ b/src/library/daemon-config.c +@@ -618,17 +618,19 @@ static int fs_mark_parser(const struct nv_pair *nv, int line, + conf_t *config) + { + int rc = 0; +-#if HAVE_DECL_FAN_MARK_FILESYSTEM == 0 +- msg(LOG_WARNING, +- "allow_filesystem_mark is unsupported on this kernel - ignoring"); +-#else +- rc = unsigned_int_parser(&(config->allow_filesystem_mark), nv->value, line); ++#if defined HAVE_DECL_FAN_MARK_FILESYSTEM && HAVE_DECL_FAN_MARK_FILESYSTEM != 0 ++ rc = unsigned_int_parser(&(config->allow_filesystem_mark), ++ nv->value, line); + + if (rc == 0 && config->allow_filesystem_mark > 1) { + msg(LOG_WARNING, +- "allow_filesystem_mark value reset to 0 - line %d", line); ++ "allow_filesystem_mark value reset to 0 - line %d", ++ line); + config->allow_filesystem_mark = 0; + } ++#else ++ msg(LOG_WARNING, ++ "allow_filesystem_mark is unsupported on this kernel - ignoring"); + #endif + + return rc; diff --git a/SOURCES/fapolicyd-markfs-4.patch b/SOURCES/fapolicyd-markfs-4.patch new file mode 100644 index 0000000..b9f4429 --- /dev/null +++ b/SOURCES/fapolicyd-markfs-4.patch @@ -0,0 +1,29 @@ +From 194ac1b87ba46ea9e26a865e8432e228cf8fefef Mon Sep 17 00:00:00 2001 +From: Steven Brzozowski +Date: Thu, 20 Oct 2022 17:55:30 -0400 +Subject: [PATCH] Add `FAN_MARK_MOUNT` when opting out of `FAN_MARK_FILESYSTEM` + (#210) + +Without `FAN_MARK_MOUNT`, fapolicyd will not receive events for any subdirectories specified by the path parameter. +--- + src/daemon/notify.c | 3 +++ + 1 file changed, 3 insertions(+) + +diff --git a/src/daemon/notify.c b/src/daemon/notify.c +index 586b6df..5e4f160 100644 +--- a/src/daemon/notify.c ++++ b/src/daemon/notify.c +@@ -128,10 +128,13 @@ int init_fanotify(const conf_t *conf, mlist *m) + #if defined HAVE_DECL_FAN_MARK_FILESYSTEM && HAVE_DECL_FAN_MARK_FILESYSTEM != 0 + if (conf->allow_filesystem_mark) + flags |= FAN_MARK_FILESYSTEM; ++ else ++ flags |= FAN_MARK_MOUNT; + #else + if (conf->allow_filesystem_mark) + msg(LOG_ERR, + "allow_filesystem_mark is unsupported for this kernel - ignoring"); ++ flags |= FAN_MARK_MOUNT; + #endif + if (fanotify_mark(fd, flags, mask, -1, path) == -1) { + /* diff --git a/SOURCES/fapolicyd-selinux-1.patch b/SOURCES/fapolicyd-selinux-1.patch new file mode 100644 index 0000000..5f5fea5 --- /dev/null +++ b/SOURCES/fapolicyd-selinux-1.patch @@ -0,0 +1,173 @@ +diff -up ./fapolicyd-selinux-0.4/fapolicyd.if.selinux ./fapolicyd-selinux-0.4/fapolicyd.if +--- ./fapolicyd-selinux-0.4/fapolicyd.if.selinux 2021-03-23 10:21:31.000000000 +0100 ++++ ./fapolicyd-selinux-0.4/fapolicyd.if 2022-06-30 10:52:05.112355159 +0200 +@@ -2,6 +2,122 @@ + + ######################################## + ## ++## Watch_mount directories in /boot. ++## ++## ++## ++## Domain allowed access. ++## ++## ++# ++ ++ifndef(`files_watch_mount_boot_dirs',` ++ interface(`files_watch_mount_boot_dirs',` ++ gen_require(` ++ type boot_t; ++ ') ++ ++ allow $1 boot_t:dir watch_mount_dir_perms; ++ ') ++') ++ ++ ++######################################## ++## ++## Watch_mount home directories. ++## ++## ++## ++## Domain allowed access. ++## ++## ++# ++ ++ifndef(`files_watch_mount_home',` ++ interface(`files_watch_mount_home',` ++ gen_require(` ++ type home_root_t; ++ ') ++ ++ allow $1 home_root_t:dir watch_mount_dir_perms; ++ ') ++') ++ ++ ++######################################## ++## ++## Watch_with_perm home directories. ++## ++## ++## ++## Domain allowed access. ++## ++## ++# ++ ++ifndef(`files_watch_with_perm_home',` ++interface(`files_watch_with_perm_home',` ++ gen_require(` ++ type home_root_t; ++ ') ++ ++ allow $1 home_root_t:dir watch_with_perm_dir_perms; ++') ++') ++ ++ ++######################################## ++## ++## Watch_mount dirs on a DOS filesystem. ++## ++## ++## ++## Domain allowed access. ++## ++## ++# ++ ++ifndef(`fs_watch_mount_dos_dirs',` ++interface(`fs_watch_mount_dos_dirs',` ++ gen_require(` ++ type dosfs_t; ++ ') ++ ++ watch_mount_dirs_pattern($1, dosfs_t, dosfs_t) ++') ++') ++ ++ ++ ++######################################## ++## ++## Watch_with_perm dirs on a DOS filesystem. ++## ++## ++## ++## Domain allowed access. ++## ++## ++# ++ ++ifndef(`fs_watch_with_perm_dos_dirs',` ++interface(`fs_watch_with_perm_dos_dirs',` ++ gen_require(` ++ type dosfs_t; ++ ') ++ ++ watch_with_perm_dirs_pattern($1, dosfs_t, dosfs_t) ++') ++') ++ ++ ++################################################################################################### ++ ++ ++ ++ ++######################################## ++## + ## Execute fapolicyd_exec_t in the fapolicyd domain. + ## + ## +diff -up ./fapolicyd-selinux-0.4/fapolicyd.te.selinux ./fapolicyd-selinux-0.4/fapolicyd.te +--- ./fapolicyd-selinux-0.4/fapolicyd.te.selinux 2021-03-23 10:21:31.000000000 +0100 ++++ ./fapolicyd-selinux-0.4/fapolicyd.te 2022-06-30 10:53:01.693055971 +0200 +@@ -1,5 +1,6 @@ + policy_module(fapolicyd, 1.0.0) + ++ + ######################################## + # + # Declarations +@@ -36,6 +37,12 @@ allow fapolicyd_t self:process { setcap + allow fapolicyd_t self:unix_stream_socket create_stream_socket_perms; + allow fapolicyd_t self:unix_dgram_socket create_socket_perms; + ++gen_require(` ++ attribute file_type; ++') ++allow fapolicyd_t file_type:dir { watch_mount watch_with_perm }; ++allow fapolicyd_t file_type:file { watch_mount watch_with_perm }; ++ + manage_files_pattern(fapolicyd_t, fapolicyd_log_t, fapolicyd_log_t) + logging_log_filetrans(fapolicyd_t, fapolicyd_log_t, file) + +@@ -61,16 +68,22 @@ corecmd_exec_bin(fapolicyd_t) + + domain_read_all_domains_state(fapolicyd_t) + +-files_mmap_usr_files(fapolicyd_t) ++files_mmap_all_files(fapolicyd_t) + files_read_all_files(fapolicyd_t) ++files_watch_mount_boot_dirs(fapolicyd_t) ++files_watch_with_perm_boot_dirs(fapolicyd_t) + files_watch_mount_generic_tmp_dirs(fapolicyd_t) + files_watch_with_perm_generic_tmp_dirs(fapolicyd_t) ++files_watch_mount_home(fapolicyd_t) ++files_watch_with_perm_home(fapolicyd_t) + files_watch_mount_root_dirs(fapolicyd_t) + files_watch_with_perm_root_dirs(fapolicyd_t) + + fs_getattr_xattr_fs(fapolicyd_t) + fs_watch_mount_tmpfs_dirs(fapolicyd_t) + fs_watch_with_perm_tmpfs_dirs(fapolicyd_t) ++fs_watch_mount_dos_dirs(fapolicyd_t) ++fs_watch_with_perm_dos_dirs(fapolicyd_t) + + logging_send_syslog_msg(fapolicyd_t) + dbus_system_bus_client(fapolicyd_t) diff --git a/SOURCES/fapolicyd-selinux-2.patch b/SOURCES/fapolicyd-selinux-2.patch new file mode 100644 index 0000000..80ffe37 --- /dev/null +++ b/SOURCES/fapolicyd-selinux-2.patch @@ -0,0 +1,19 @@ +diff -up ./fapolicyd-selinux-0.4/fapolicyd.te.selinux2 ./fapolicyd-selinux-0.4/fapolicyd.te +--- ./fapolicyd-selinux-0.4/fapolicyd.te.selinux2 2022-11-11 10:46:51.016420807 +0100 ++++ ./fapolicyd-selinux-0.4/fapolicyd.te 2022-11-11 10:47:25.161793205 +0100 +@@ -39,10 +39,15 @@ allow fapolicyd_t self:unix_dgram_socket + + gen_require(` + attribute file_type; ++ attribute filesystem_type; ++ attribute mountpoint; + ') + allow fapolicyd_t file_type:dir { watch_mount watch_with_perm }; + allow fapolicyd_t file_type:file { watch_mount watch_with_perm }; + ++allow fapolicyd_t filesystem_type : filesystem { watch }; ++allow fapolicyd_t mountpoint : dir { watch_sb }; ++ + manage_files_pattern(fapolicyd_t, fapolicyd_log_t, fapolicyd_log_t) + logging_log_filetrans(fapolicyd_t, fapolicyd_log_t, file) + diff --git a/SOURCES/fapolicyd-selinux.patch b/SOURCES/fapolicyd-selinux.patch deleted file mode 100644 index 5f5fea5..0000000 --- a/SOURCES/fapolicyd-selinux.patch +++ /dev/null @@ -1,173 +0,0 @@ -diff -up ./fapolicyd-selinux-0.4/fapolicyd.if.selinux ./fapolicyd-selinux-0.4/fapolicyd.if ---- ./fapolicyd-selinux-0.4/fapolicyd.if.selinux 2021-03-23 10:21:31.000000000 +0100 -+++ ./fapolicyd-selinux-0.4/fapolicyd.if 2022-06-30 10:52:05.112355159 +0200 -@@ -2,6 +2,122 @@ - - ######################################## - ## -+## Watch_mount directories in /boot. -+## -+## -+## -+## Domain allowed access. -+## -+## -+# -+ -+ifndef(`files_watch_mount_boot_dirs',` -+ interface(`files_watch_mount_boot_dirs',` -+ gen_require(` -+ type boot_t; -+ ') -+ -+ allow $1 boot_t:dir watch_mount_dir_perms; -+ ') -+') -+ -+ -+######################################## -+## -+## Watch_mount home directories. -+## -+## -+## -+## Domain allowed access. -+## -+## -+# -+ -+ifndef(`files_watch_mount_home',` -+ interface(`files_watch_mount_home',` -+ gen_require(` -+ type home_root_t; -+ ') -+ -+ allow $1 home_root_t:dir watch_mount_dir_perms; -+ ') -+') -+ -+ -+######################################## -+## -+## Watch_with_perm home directories. -+## -+## -+## -+## Domain allowed access. -+## -+## -+# -+ -+ifndef(`files_watch_with_perm_home',` -+interface(`files_watch_with_perm_home',` -+ gen_require(` -+ type home_root_t; -+ ') -+ -+ allow $1 home_root_t:dir watch_with_perm_dir_perms; -+') -+') -+ -+ -+######################################## -+## -+## Watch_mount dirs on a DOS filesystem. -+## -+## -+## -+## Domain allowed access. -+## -+## -+# -+ -+ifndef(`fs_watch_mount_dos_dirs',` -+interface(`fs_watch_mount_dos_dirs',` -+ gen_require(` -+ type dosfs_t; -+ ') -+ -+ watch_mount_dirs_pattern($1, dosfs_t, dosfs_t) -+') -+') -+ -+ -+ -+######################################## -+## -+## Watch_with_perm dirs on a DOS filesystem. -+## -+## -+## -+## Domain allowed access. -+## -+## -+# -+ -+ifndef(`fs_watch_with_perm_dos_dirs',` -+interface(`fs_watch_with_perm_dos_dirs',` -+ gen_require(` -+ type dosfs_t; -+ ') -+ -+ watch_with_perm_dirs_pattern($1, dosfs_t, dosfs_t) -+') -+') -+ -+ -+################################################################################################### -+ -+ -+ -+ -+######################################## -+## - ## Execute fapolicyd_exec_t in the fapolicyd domain. - ## - ## -diff -up ./fapolicyd-selinux-0.4/fapolicyd.te.selinux ./fapolicyd-selinux-0.4/fapolicyd.te ---- ./fapolicyd-selinux-0.4/fapolicyd.te.selinux 2021-03-23 10:21:31.000000000 +0100 -+++ ./fapolicyd-selinux-0.4/fapolicyd.te 2022-06-30 10:53:01.693055971 +0200 -@@ -1,5 +1,6 @@ - policy_module(fapolicyd, 1.0.0) - -+ - ######################################## - # - # Declarations -@@ -36,6 +37,12 @@ allow fapolicyd_t self:process { setcap - allow fapolicyd_t self:unix_stream_socket create_stream_socket_perms; - allow fapolicyd_t self:unix_dgram_socket create_socket_perms; - -+gen_require(` -+ attribute file_type; -+') -+allow fapolicyd_t file_type:dir { watch_mount watch_with_perm }; -+allow fapolicyd_t file_type:file { watch_mount watch_with_perm }; -+ - manage_files_pattern(fapolicyd_t, fapolicyd_log_t, fapolicyd_log_t) - logging_log_filetrans(fapolicyd_t, fapolicyd_log_t, file) - -@@ -61,16 +68,22 @@ corecmd_exec_bin(fapolicyd_t) - - domain_read_all_domains_state(fapolicyd_t) - --files_mmap_usr_files(fapolicyd_t) -+files_mmap_all_files(fapolicyd_t) - files_read_all_files(fapolicyd_t) -+files_watch_mount_boot_dirs(fapolicyd_t) -+files_watch_with_perm_boot_dirs(fapolicyd_t) - files_watch_mount_generic_tmp_dirs(fapolicyd_t) - files_watch_with_perm_generic_tmp_dirs(fapolicyd_t) -+files_watch_mount_home(fapolicyd_t) -+files_watch_with_perm_home(fapolicyd_t) - files_watch_mount_root_dirs(fapolicyd_t) - files_watch_with_perm_root_dirs(fapolicyd_t) - - fs_getattr_xattr_fs(fapolicyd_t) - fs_watch_mount_tmpfs_dirs(fapolicyd_t) - fs_watch_with_perm_tmpfs_dirs(fapolicyd_t) -+fs_watch_mount_dos_dirs(fapolicyd_t) -+fs_watch_with_perm_dos_dirs(fapolicyd_t) - - logging_send_syslog_msg(fapolicyd_t) - dbus_system_bus_client(fapolicyd_t) diff --git a/SOURCES/fapolicyd-static-app.patch b/SOURCES/fapolicyd-static-app.patch new file mode 100644 index 0000000..34f4510 --- /dev/null +++ b/SOURCES/fapolicyd-static-app.patch @@ -0,0 +1,22 @@ +From 67c116d07ed4e73127392a2100a042882488585a Mon Sep 17 00:00:00 2001 +From: Steve Grubb +Date: Tue, 27 Sep 2022 10:32:28 -0400 +Subject: [PATCH] Detect trusted static apps running programs by ld.so + +--- + ChangeLog | 1 + + src/library/event.c | 1 - + 2 files changed, 1 insertion(+), 1 deletion(-) + +diff --git a/src/library/event.c b/src/library/event.c +index cbb4292..4d79eb9 100644 +--- a/src/library/event.c ++++ b/src/library/event.c +@@ -149,7 +149,6 @@ int new_event(const struct fanotify_event_metadata *m, event_t *e) + skip_path = 1; + } + evict = 0; +- skip_path = 1; + subject_reset(s, EXE); + subject_reset(s, COMM); + subject_reset(s, EXE_TYPE); diff --git a/SPECS/fapolicyd.spec b/SPECS/fapolicyd.spec index 229a5dd..dae0084 100644 --- a/SPECS/fapolicyd.spec +++ b/SPECS/fapolicyd.spec @@ -5,7 +5,7 @@ Summary: Application Whitelisting Daemon Name: fapolicyd Version: 1.1.3 -Release: 102%{?dist} +Release: 104%{?dist} License: GPLv3+ URL: http://people.redhat.com/sgrubb/fapolicyd Source0: https://people.redhat.com/sgrubb/fapolicyd/%{name}-%{version}.tar.gz @@ -31,7 +31,7 @@ Requires(preun): systemd-units Requires(postun): systemd-units Patch1: fapolicyd-uthash-bundle.patch -Patch2: fapolicyd-selinux.patch +Patch2: fapolicyd-selinux-1.patch Patch3: fagenrules-group.patch Patch4: fapolicyd-fgets-update-thread.patch Patch5: fapolicyd-openssl.patch @@ -40,6 +40,18 @@ Patch7: fapolicyd-cli-segfault.patch Patch8: fapolicyd-sighup.patch Patch9: fapolicyd-readme.patch +Patch10: fapolicyd-static-app.patch +Patch11: fapolicyd-markfs-1.patch +Patch12: fapolicyd-markfs-2.patch +Patch13: fapolicyd-markfs-3.patch +Patch14: fapolicyd-markfs-4.patch + +Patch15: fapolicyd-selinux-2.patch + +Patch16: fapolicyd-falcon-sensor.patch +Patch17: fapolicyd-exclude-list.patch +Patch18: fapolicyd-already-started.patch + %description Fapolicyd (File Access Policy Daemon) implements application whitelisting to decide file access rights. Applications that are known via a reputation @@ -71,7 +83,7 @@ The %{name}-selinux package contains selinux policy for the %{name} daemon. %patch1 -p1 -b .uthash %endif -%patch2 -p1 -b .selinux +%patch2 -p1 -b .selinux1 %patch3 -p1 -b .group %patch4 -p1 -b .update-thread %patch5 -p1 -b .openssl @@ -80,6 +92,18 @@ The %{name}-selinux package contains selinux policy for the %{name} daemon. %patch8 -p1 -b .sighup %patch9 -p1 -b .readme +%patch10 -p1 -b .static +%patch11 -p1 -b .markfs1 +%patch12 -p1 -b .markfs2 +%patch13 -p1 -b .markfs3 +%patch14 -p1 -b .markfs4 + +%patch15 -p1 -b .selinux2 + +%patch16 -p1 -b .event +%patch17 -p1 -b .exclude +%patch18 -p1 -b .already-started + # generate rules for python sed -i "s|%python2_path%|`readlink -f %{__python2}`|g" rules.d/*.rules sed -i "s|%python3_path%|`readlink -f %{__python3}`|g" rules.d/*.rules @@ -227,6 +251,7 @@ fi %ghost %verify(not md5 size mtime) %attr(644,root,%{name}) %{_sysconfdir}/%{name}/rules.d/* %ghost %verify(not md5 size mtime) %attr(644,root,%{name}) %{_sysconfdir}/%{name}/%{name}.rules %config(noreplace) %attr(644,root,%{name}) %{_sysconfdir}/%{name}/%{name}.conf +%config(noreplace) %attr(644,root,%{name}) %{_sysconfdir}/%{name}/rpm-filter.conf %config(noreplace) %attr(644,root,%{name}) %{_sysconfdir}/%{name}/%{name}.trust %ghost %attr(644,root,%{name}) %{_sysconfdir}/%{name}/compiled.rules %attr(644,root,root) %{_unitdir}/%{name}.service @@ -263,6 +288,19 @@ fi %selinux_relabel_post -s %{selinuxtype} %changelog +* Mon Jan 30 2023 Radovan Sroka - 1.1.3-104 +RHEL 9.2.0 ERRATUM +- statically linked app can execute untrusted app +Resolves: rhbz#2097077 +- fapolicyd ineffective with systemd DynamicUser=yes +Resolves: rhbz#2136802 +- Starting manually fapolicyd while the service is already running breaks the system +Resolves: rhbz#2160517 +- Cannot execute /usr/libexec/grepconf.sh when falcon-sensor is enabled +Resolves: rhbz#2160518 +- fapolicyd: Introduce filtering of rpmdb +Resolves: RHEL-192 + * Fri Aug 05 2022 Radovan Sroka - 1.1.3-102 RHEL 9.1.0 ERRATUM - rebase fapolicyd to the latest stable vesion