]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
journal: filter log based on LogFilterPatterns
authorQuentin Deslandes <qde@naccy.de>
Tue, 13 Sep 2022 15:15:13 +0000 (16:15 +0100)
committerQuentin Deslandes <qde@naccy.de>
Thu, 15 Dec 2022 09:57:39 +0000 (09:57 +0000)
Use LogFilterPatterns from the unit's cgroup xattr in order to keep or
discard log messages before writing them to the journal.
When a log message is discarded, it won't be written to syslog, console...
either.

When a native, syslog, or standard output log message is received,
systemd-journald will process it if it matches against at least one
allowed pattern (if any) and none of the denied patterns (if any).

src/journal/journald-client.c [new file with mode: 0644]
src/journal/journald-client.h [new file with mode: 0644]
src/journal/journald-context.c
src/journal/journald-context.h
src/journal/journald-native.c
src/journal/journald-stream.c
src/journal/journald-syslog.c
src/journal/meson.build

diff --git a/src/journal/journald-client.c b/src/journal/journald-client.c
new file mode 100644 (file)
index 0000000..22090aa
--- /dev/null
@@ -0,0 +1,110 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include "cgroup-util.h"
+#include "errno-util.h"
+#include "journald-client.h"
+#include "nulstr-util.h"
+#include "pcre2-util.h"
+
+/* This consumes both `allow_list` and `deny_list` arguments. Hence, those arguments are not owned by the
+ * caller anymore and should not be freed. */
+static void client_set_filtering_patterns(ClientContext *c, Set *allow_list, Set *deny_list) {
+        assert(c);
+
+        set_free_and_replace(c->log_filter_allowed_patterns, allow_list);
+        set_free_and_replace(c->log_filter_denied_patterns, deny_list);
+}
+
+static int client_parse_log_filter_nulstr(const char *nulstr, size_t len, Set **ret) {
+        _cleanup_set_free_ Set *s = NULL;
+        _cleanup_strv_free_ char **patterns_strv = NULL;
+        int r;
+
+        assert(nulstr);
+        assert(ret);
+
+        patterns_strv = strv_parse_nulstr(nulstr, len);
+        if (!patterns_strv)
+                return log_oom_debug();
+
+        STRV_FOREACH(pattern, patterns_strv) {
+                _cleanup_(pattern_freep) pcre2_code *compiled_pattern = NULL;
+
+                r = pattern_compile_and_log(*pattern, 0, &compiled_pattern);
+                if (r < 0)
+                        return r;
+
+                r = set_ensure_consume(&s, &pcre2_code_hash_ops_free, TAKE_PTR(compiled_pattern));
+                if (r < 0)
+                        return log_debug_errno(r, "Failed to insert regex into set: %m");
+        }
+
+        *ret = TAKE_PTR(s);
+
+        return 0;
+}
+
+int client_context_read_log_filter_patterns(ClientContext *c, const char *cgroup) {
+        char *deny_list_xattr, *xattr_end;
+        _cleanup_free_ char *xattr = NULL;
+        _cleanup_set_free_ Set *allow_list = NULL, *deny_list = NULL;
+        int r;
+
+        assert(c);
+
+        r = cg_get_xattr_malloc(SYSTEMD_CGROUP_CONTROLLER, cgroup, "user.journald_log_filter_patterns", &xattr);
+        if (r < 0) {
+                if (!ERRNO_IS_XATTR_ABSENT(r))
+                        return log_debug_errno(r, "Failed to get user.journald_log_filter_patterns xattr for %s: %m", cgroup);
+
+                client_set_filtering_patterns(c, NULL, NULL);
+                return 0;
+        }
+
+        xattr_end = xattr + r;
+
+        /* We expect '0xff' to be present in the attribute, even if the lists are empty. We expect the
+         * following:
+         * - Allow list, but no deny list: 0xXX, ...., 0xff
+         * - No allow list, but deny list: 0xff, 0xXX, ....
+         * - Allow list, and deny list:    0xXX, ...., 0xff, 0xXX, ....
+         * This is due to the fact allowed and denied patterns list are two nulstr joined together with '0xff'.
+         * None of the allowed or denied nulstr have a nul-termination character.
+         *
+         * We do not expect both the allow list and deny list to be empty, as this condition is tested
+         * before writing to xattr. */
+        deny_list_xattr = memchr(xattr, (char)0xff, r);
+        if (!deny_list_xattr)
+                return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "Missing delimiter in cgroup user.journald_log_filter_patterns attribute: %m");
+
+        r = client_parse_log_filter_nulstr(xattr, deny_list_xattr - xattr, &allow_list);
+        if (r < 0)
+                return r;
+
+        /* Use 'deny_list_xattr + 1' to skip '0xff'. */
+        ++deny_list_xattr;
+        r = client_parse_log_filter_nulstr(deny_list_xattr, xattr_end - deny_list_xattr, &deny_list);
+        if (r < 0)
+                return r;
+
+        client_set_filtering_patterns(c, TAKE_PTR(allow_list), TAKE_PTR(deny_list));
+
+        return 0;
+}
+
+int client_context_check_keep_log(ClientContext *c, const char *message, size_t len) {
+        pcre2_code *regex;
+
+        if (!c || !message)
+                return true;
+
+        SET_FOREACH(regex, c->log_filter_denied_patterns)
+                if (pattern_matches_and_log(regex, message, len, NULL) > 0)
+                        return false;
+
+        SET_FOREACH(regex, c->log_filter_allowed_patterns)
+                if (pattern_matches_and_log(regex, message, len, NULL) > 0)
+                        return true;
+
+        return set_isempty(c->log_filter_allowed_patterns);
+}
diff --git a/src/journal/journald-client.h b/src/journal/journald-client.h
new file mode 100644 (file)
index 0000000..629cd41
--- /dev/null
@@ -0,0 +1,7 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include "journald-context.h"
+
+int client_context_read_log_filter_patterns(ClientContext *c, const char *cgroup);
+int client_context_check_keep_log(ClientContext *c, const char *message, size_t len);
index 222855ae60f3c58eebe03f675cf7c50dd0e94cc2..55e657c7e1fae501663e4c9abd7206126cb590d3 100644 (file)
@@ -14,6 +14,7 @@
 #include "io-util.h"
 #include "journal-internal.h"
 #include "journal-util.h"
+#include "journald-client.h"
 #include "journald-context.h"
 #include "parse-util.h"
 #include "path-util.h"
@@ -180,6 +181,9 @@ static void client_context_reset(Server *s, ClientContext *c) {
 
         c->log_ratelimit_interval = s->ratelimit_interval;
         c->log_ratelimit_burst = s->ratelimit_burst;
+
+        c->log_filter_allowed_patterns = set_free(c->log_filter_allowed_patterns);
+        c->log_filter_denied_patterns = set_free(c->log_filter_denied_patterns);
 }
 
 static ClientContext* client_context_free(Server *s, ClientContext *c) {
@@ -290,6 +294,8 @@ static int client_context_read_cgroup(Server *s, ClientContext *c, const char *u
                 return r;
         }
 
+        (void) client_context_read_log_filter_patterns(c, t);
+
         /* Let's shortcut this if the cgroup path didn't change */
         if (streq_ptr(c->cgroup, t))
                 return 0;
index 9bf74b23471b9d83fa21da7d761659105d8e8afd..4a998ba42e3f5de7f4907f9932b9e746b31ba673 100644 (file)
@@ -7,6 +7,7 @@
 
 #include "sd-id128.h"
 
+#include "set.h"
 #include "time-util.h"
 
 typedef struct ClientContext ClientContext;
@@ -55,6 +56,9 @@ struct ClientContext {
 
         usec_t log_ratelimit_interval;
         unsigned log_ratelimit_burst;
+
+        Set *log_filter_allowed_patterns;
+        Set *log_filter_denied_patterns;
 };
 
 int client_context_get(
index 847f69c1ffebe545b37ac864e9c7958dd53d0cd8..ca23508454fd1f9ea86dee9680c6644e943756b1 100644 (file)
@@ -13,6 +13,7 @@
 #include "journal-importer.h"
 #include "journal-internal.h"
 #include "journal-util.h"
+#include "journald-client.h"
 #include "journald-console.h"
 #include "journald-kmsg.h"
 #include "journald-native.h"
@@ -261,6 +262,13 @@ static int server_process_entry(
                 goto finish;
 
         if (message) {
+                /* Ensure message is not NULL, otherwise strlen(message) would crash. This check needs to
+                 * be here until server_process_entry() is able to process messages containing \0 characters,
+                 * as we would have access to the actual size of message. */
+                r = client_context_check_keep_log(context, message, strlen(message));
+                if (r <= 0)
+                        goto finish;
+
                 if (s->forward_to_syslog)
                         server_forward_syslog(s, syslog_fixup_facility(priority), identifier, message, ucred, tv);
 
index 49f28972ea205edf140b4fde8f7da0744871ea1c..4446b2daec7ce63e44a37066cc56c004bed2e414 100644 (file)
@@ -20,6 +20,7 @@
 #include "fs-util.h"
 #include "io-util.h"
 #include "journal-internal.h"
+#include "journald-client.h"
 #include "journald-console.h"
 #include "journald-context.h"
 #include "journald-kmsg.h"
@@ -284,6 +285,10 @@ static int stdout_stream_log(
         if (isempty(p))
                 return 0;
 
+        r = client_context_check_keep_log(s->context, p, strlen(p));
+        if (r <= 0)
+                return r;
+
         if (s->forward_to_syslog || s->server->forward_to_syslog)
                 server_forward_syslog(s->server, syslog_fixup_facility(priority), s->identifier, p, &s->ucred, NULL);
 
index d8708b07755ce98676ecbbfc79d349b11c43ea79..1ecbd226da923d0bfa7fbf34125a944e25025489 100644 (file)
@@ -11,6 +11,7 @@
 #include "format-util.h"
 #include "io-util.h"
 #include "journal-internal.h"
+#include "journald-client.h"
 #include "journald-console.h"
 #include "journald-kmsg.h"
 #include "journald-server.h"
@@ -374,6 +375,9 @@ void server_process_syslog_message(
         if (!client_context_test_priority(context, priority))
                 return;
 
+        if (client_context_check_keep_log(context, msg, strlen(msg)) <= 0)
+                return;
+
         syslog_ts = msg;
         syslog_ts_len = syslog_skip_timestamp(&msg);
         if (syslog_ts_len == 0)
index 1e41ea149b02762e6b3b8a12e2b7452327a1d150..9abab298ccf17070ef7a3c362b575a0708468b3e 100644 (file)
@@ -3,6 +3,8 @@
 sources = files(
         'journald-audit.c',
         'journald-audit.h',
+        'journald-client.c',
+        'journald-client.h',
         'journald-console.c',
         'journald-console.h',
         'journald-context.c',