From 523ea1237a3db96c98eae02d87ec189816437c4c Mon Sep 17 00:00:00 2001 From: Quentin Deslandes Date: Mon, 7 Nov 2022 20:30:01 +0100 Subject: journal: log filtering options support in PID1 Define new unit parameter (LogFilterPatterns) to filter logs processed by journald. This option is used to store a regular expression which is carried from PID1 to systemd-journald through a cgroup xattrs: `user.journald_log_filter_patterns`. --- docs/TRANSIENT-SETTINGS.md | 1 + man/org.freedesktop.systemd1.xml | 24 ++++++ man/systemd.exec.xml | 28 +++++++ src/core/cgroup.c | 46 +++++++++++ src/core/cgroup.h | 1 + src/core/dbus-execute.c | 104 ++++++++++++++++++++++++ src/core/execute.c | 16 +++- src/core/execute.h | 3 + src/core/load-fragment-gperf.gperf.in | 1 + src/core/load-fragment.c | 53 ++++++++++++ src/core/load-fragment.h | 1 + src/shared/bus-unit-util.c | 10 +++ src/test/test-load-fragment.c | 51 ++++++++++++ test/fuzz/fuzz-unit-file/directives-all.service | 1 + 14 files changed, 339 insertions(+), 1 deletion(-) diff --git a/docs/TRANSIENT-SETTINGS.md b/docs/TRANSIENT-SETTINGS.md index 2c893cad6e..07e248f8d5 100644 --- a/docs/TRANSIENT-SETTINGS.md +++ b/docs/TRANSIENT-SETTINGS.md @@ -148,6 +148,7 @@ All execution-related settings are available for transient units. ✓ SyslogLevelPrefix= ✓ LogLevelMax= ✓ LogExtraFields= +✓ LogFilterPatterns= ✓ LogRateLimitIntervalSec= ✓ LogRateLimitBurst= ✓ SecureBits= diff --git a/man/org.freedesktop.systemd1.xml b/man/org.freedesktop.systemd1.xml index 4f8936e866..32ead7f272 100644 --- a/man/org.freedesktop.systemd1.xml +++ b/man/org.freedesktop.systemd1.xml @@ -2928,6 +2928,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice { @org.freedesktop.DBus.Property.EmitsChangedSignal("const") readonly aay LogExtraFields = [[...], ...]; @org.freedesktop.DBus.Property.EmitsChangedSignal("const") + readonly a(bs) LogFilterPatterns = [...]; + @org.freedesktop.DBus.Property.EmitsChangedSignal("const") readonly s LogNamespace = '...'; @org.freedesktop.DBus.Property.EmitsChangedSignal("const") readonly i SecureBits = ...; @@ -3479,6 +3481,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice { + + @@ -4083,6 +4087,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice { + + @@ -4822,6 +4828,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2esocket { @org.freedesktop.DBus.Property.EmitsChangedSignal("const") readonly aay LogExtraFields = [[...], ...]; @org.freedesktop.DBus.Property.EmitsChangedSignal("const") + readonly a(bs) LogFilterPatterns = [...]; + @org.freedesktop.DBus.Property.EmitsChangedSignal("const") readonly s LogNamespace = '...'; @org.freedesktop.DBus.Property.EmitsChangedSignal("const") readonly i SecureBits = ...; @@ -5397,6 +5405,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2esocket { + + @@ -5995,6 +6005,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2esocket { + + @@ -6623,6 +6635,8 @@ node /org/freedesktop/systemd1/unit/home_2emount { @org.freedesktop.DBus.Property.EmitsChangedSignal("const") readonly aay LogExtraFields = [[...], ...]; @org.freedesktop.DBus.Property.EmitsChangedSignal("const") + readonly a(bs) LogFilterPatterns = [...]; + @org.freedesktop.DBus.Property.EmitsChangedSignal("const") readonly s LogNamespace = '...'; @org.freedesktop.DBus.Property.EmitsChangedSignal("const") readonly i SecureBits = ...; @@ -7126,6 +7140,8 @@ node /org/freedesktop/systemd1/unit/home_2emount { + + @@ -7642,6 +7658,8 @@ node /org/freedesktop/systemd1/unit/home_2emount { + + @@ -8397,6 +8415,8 @@ node /org/freedesktop/systemd1/unit/dev_2dsda3_2eswap { @org.freedesktop.DBus.Property.EmitsChangedSignal("const") readonly aay LogExtraFields = [[...], ...]; @org.freedesktop.DBus.Property.EmitsChangedSignal("const") + readonly a(bs) LogFilterPatterns = [...]; + @org.freedesktop.DBus.Property.EmitsChangedSignal("const") readonly s LogNamespace = '...'; @org.freedesktop.DBus.Property.EmitsChangedSignal("const") readonly i SecureBits = ...; @@ -8886,6 +8906,8 @@ node /org/freedesktop/systemd1/unit/dev_2dsda3_2eswap { + + @@ -9388,6 +9410,8 @@ node /org/freedesktop/systemd1/unit/dev_2dsda3_2eswap { + + diff --git a/man/systemd.exec.xml b/man/systemd.exec.xml index d003ab1838..5e6658ff06 100644 --- a/man/systemd.exec.xml +++ b/man/systemd.exec.xml @@ -2919,6 +2919,34 @@ StandardInputData=V2XigLJyZSBubyBzdHJhbmdlcnMgdG8gbG92ZQpZb3Uga25vdyB0aGUgcnVsZX + + LogFilterPatterns= + + Define an extended regular expression to filter log messages based on the + MESSAGE= field of the structured message. If the first character of the pattern is + ~, log entries matching the pattern should be discarded. This option takes a single + pattern as an argument but can be used multiple times to create a list of allowed and denied patterns. + If the empty string is assigned, the filter is reset, and all prior assignments will have no effect. + + Because the ~ character is used to define denied patterns, it must be replaced + with \x7e to allow a message starting with ~. For example, + ~foobar would add a pattern matching foobar to the deny list, while + \x7efoobar would add a pattern matching ~foobar to the allow list. + + Log messages are tested against denied patterns (if any), then against allowed patterns + (if any). If a log message matches any of the denied patterns, it will be discarded, whatever the + allowed patterns. Then, remaining log messages are tested against allowed patterns. Messages matching + against none of the allowed pattern are discarded. If no allowed patterns are defined, then all + messages are processed directly after going through denied filters. + + Filtering is based on the unit for which LogFilterPatterns= is defined, meaning log + messages coming from + systemd1 about the + unit are not taken into account. Filtered log messages won't be forwarded to traditional syslog daemons, + the kernel log buffer (kmsg), the systemd console, or sent as wall messages to all logged-in + users. + + LogNamespace= diff --git a/src/core/cgroup.c b/src/core/cgroup.c index ecc3cb32ef..2d671566ac 100644 --- a/src/core/cgroup.c +++ b/src/core/cgroup.c @@ -781,6 +781,51 @@ void cgroup_oomd_xattr_apply(Unit *u, const char *cgroup_path) { unit_remove_xattr_graceful(u, cgroup_path, "user.oomd_omit"); } +int cgroup_log_xattr_apply(Unit *u, const char *cgroup_path) { + ExecContext *c; + size_t len, allowed_patterns_len, denied_patterns_len; + _cleanup_free_ char *patterns = NULL, *allowed_patterns = NULL, *denied_patterns = NULL; + int r; + + assert(u); + + c = unit_get_exec_context(u); + if (!c) + /* Some unit types have a cgroup context but no exec context, so we do not log + * any error here to avoid confusion. */ + return 0; + + if (set_isempty(c->log_filter_allowed_patterns) && set_isempty(c->log_filter_denied_patterns)) { + unit_remove_xattr_graceful(u, cgroup_path, "user.journald_log_filter_patterns"); + return 0; + } + + r = set_make_nulstr(c->log_filter_allowed_patterns, &allowed_patterns, &allowed_patterns_len); + if (r < 0) + return log_debug_errno(r, "Failed to make nulstr from set: %m"); + + r = set_make_nulstr(c->log_filter_denied_patterns, &denied_patterns, &denied_patterns_len); + if (r < 0) + return log_debug_errno(r, "Failed to make nulstr from set: %m"); + + /* Use nul character separated strings without trailing nul */ + allowed_patterns_len = LESS_BY(allowed_patterns_len, 1u); + denied_patterns_len = LESS_BY(denied_patterns_len, 1u); + + len = allowed_patterns_len + 1 + denied_patterns_len; + patterns = new(char, len); + if (!patterns) + return log_oom_debug(); + + memcpy_safe(patterns, allowed_patterns, allowed_patterns_len); + patterns[allowed_patterns_len] = '\xff'; + memcpy_safe(&patterns[allowed_patterns_len + 1], denied_patterns, denied_patterns_len); + + unit_set_xattr_graceful(u, cgroup_path, "user.journald_log_filter_patterns", patterns, len); + + return 0; +} + static void cgroup_xattr_apply(Unit *u) { bool b; @@ -788,6 +833,7 @@ static void cgroup_xattr_apply(Unit *u) { /* The 'user.*' xattrs can be set from a user manager. */ cgroup_oomd_xattr_apply(u, u->cgroup_path); + cgroup_log_xattr_apply(u, u->cgroup_path); if (!MANAGER_IS_SYSTEM(u->manager)) return; diff --git a/src/core/cgroup.h b/src/core/cgroup.h index 09352bafc6..a6a3d186ac 100644 --- a/src/core/cgroup.h +++ b/src/core/cgroup.h @@ -240,6 +240,7 @@ int cgroup_add_device_allow(CGroupContext *c, const char *dev, const char *mode) int cgroup_add_bpf_foreign_program(CGroupContext *c, uint32_t attach_type, const char *path); void cgroup_oomd_xattr_apply(Unit *u, const char *cgroup_path); +int cgroup_log_xattr_apply(Unit *u, const char *cgroup_path); CGroupMask unit_get_own_mask(Unit *u); CGroupMask unit_get_delegate_mask(Unit *u); diff --git a/src/core/dbus-execute.c b/src/core/dbus-execute.c index c5a51bf5cd..86f88cb1c0 100644 --- a/src/core/dbus-execute.c +++ b/src/core/dbus-execute.c @@ -31,6 +31,7 @@ #include "namespace.h" #include "parse-util.h" #include "path-util.h" +#include "pcre2-util.h" #include "process-util.h" #include "rlimit-util.h" #if HAVE_SECCOMP @@ -799,6 +800,53 @@ static int property_get_log_extra_fields( return sd_bus_message_close_container(reply); } +static int sd_bus_message_append_log_filter_patterns(sd_bus_message *reply, Set *patterns, bool is_allowlist) { + const char *pattern; + int r; + + assert(reply); + + SET_FOREACH(pattern, patterns) { + r = sd_bus_message_append(reply, "(bs)", is_allowlist, pattern); + if (r < 0) + return r; + } + + return 0; +} + +static int property_get_log_filter_patterns( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + ExecContext *c = userdata; + int r; + + assert(c); + assert(reply); + + r = sd_bus_message_open_container(reply, 'a', "(bs)"); + if (r < 0) + return r; + + r = sd_bus_message_append_log_filter_patterns(reply, c->log_filter_allowed_patterns, + /* is_allowlist = */ true); + if (r < 0) + return r; + + r = sd_bus_message_append_log_filter_patterns(reply, c->log_filter_denied_patterns, + /* is_allowlist = */ false); + if (r < 0) + return r; + + return sd_bus_message_close_container(reply); +} + static int property_get_set_credential( sd_bus *bus, const char *path, @@ -1195,6 +1243,7 @@ const sd_bus_vtable bus_exec_vtable[] = { SD_BUS_PROPERTY("LogRateLimitIntervalUSec", "t", bus_property_get_usec, offsetof(ExecContext, log_ratelimit_interval_usec), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("LogRateLimitBurst", "u", bus_property_get_unsigned, offsetof(ExecContext, log_ratelimit_burst), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("LogExtraFields", "aay", property_get_log_extra_fields, 0, SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("LogFilterPatterns", "a(bs)", property_get_log_filter_patterns, 0, SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("LogNamespace", "s", NULL, offsetof(ExecContext, log_namespace), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("SecureBits", "i", bus_property_get_int, offsetof(ExecContext, secure_bits), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("CapabilityBoundingSet", "t", NULL, offsetof(ExecContext, capability_bounding_set), SD_BUS_VTABLE_PROPERTY_CONST), @@ -1792,6 +1841,61 @@ int bus_exec_context_set_transient_property( if (streq(name, "LogRateLimitBurst")) return bus_set_transient_unsigned(u, name, &c->log_ratelimit_burst, message, flags, error); + if (streq(name, "LogFilterPatterns")) { + /* Use _cleanup_free_, not _cleanup_strv_free_, as we don't want the content of the strv + * to be freed. */ + _cleanup_free_ char **allow_list = NULL, **deny_list = NULL; + const char *pattern; + int is_allowlist; + + r = sd_bus_message_enter_container(message, 'a', "(bs)"); + if (r < 0) + return r; + + while ((r = sd_bus_message_read(message, "(bs)", &is_allowlist, &pattern)) > 0) { + _cleanup_(pattern_freep) pcre2_code *compiled_pattern = NULL; + + if (isempty(pattern)) + continue; + + r = pattern_compile_and_log(pattern, 0, &compiled_pattern); + if (r < 0) + return r; + + r = strv_push(is_allowlist ? &allow_list : &deny_list, (char *)pattern); + if (r < 0) + return r; + } + if (r < 0) + return r; + + r = sd_bus_message_exit_container(message); + if (r < 0) + return r; + + if (!UNIT_WRITE_FLAGS_NOOP(flags)) { + if (strv_isempty(allow_list) && strv_isempty(deny_list)) { + c->log_filter_allowed_patterns = set_free(c->log_filter_allowed_patterns); + c->log_filter_denied_patterns = set_free(c->log_filter_denied_patterns); + unit_write_settingf(u, flags, name, "%s=", name); + } else { + r = set_put_strdupv(&c->log_filter_allowed_patterns, allow_list); + if (r < 0) + return r; + r = set_put_strdupv(&c->log_filter_denied_patterns, deny_list); + if (r < 0) + return r; + + STRV_FOREACH(unit_pattern, allow_list) + unit_write_settingf(u, flags, name, "%s=%s", name, *unit_pattern); + STRV_FOREACH(unit_pattern, deny_list) + unit_write_settingf(u, flags, name, "%s=~%s", name, *unit_pattern); + } + } + + return 1; + } + if (streq(name, "Personality")) return bus_set_transient_personality(u, name, &c->personality, message, flags, error); diff --git a/src/core/execute.c b/src/core/execute.c index 7963582ea2..42c95556ac 100644 --- a/src/core/execute.c +++ b/src/core/execute.c @@ -5250,9 +5250,10 @@ int exec_spawn(Unit *unit, if (r < 0) return log_unit_error_errno(unit, r, "Failed to create control group '%s': %m", subcgroup_path); - /* Normally we would not propagate the oomd xattrs to children but since we created this + /* Normally we would not propagate the xattrs to children but since we created this * sub-cgroup internally we should do it. */ cgroup_oomd_xattr_apply(unit, subcgroup_path); + cgroup_log_xattr_apply(unit, subcgroup_path); } } @@ -5406,6 +5407,8 @@ void exec_context_done(ExecContext *c) { c->log_level_max = -1; exec_context_free_log_extra_fields(c); + c->log_filter_allowed_patterns = set_free(c->log_filter_allowed_patterns); + c->log_filter_denied_patterns = set_free(c->log_filter_denied_patterns); c->log_ratelimit_interval_usec = 0; c->log_ratelimit_burst = 0; @@ -6000,6 +6003,17 @@ void exec_context_dump(const ExecContext *c, FILE* f, const char *prefix) { if (c->log_ratelimit_burst > 0) fprintf(f, "%sLogRateLimitBurst: %u\n", prefix, c->log_ratelimit_burst); + if (!set_isempty(c->log_filter_allowed_patterns) || !set_isempty(c->log_filter_denied_patterns)) { + fprintf(f, "%sLogFilterPatterns:", prefix); + + char *pattern; + SET_FOREACH(pattern, c->log_filter_allowed_patterns) + fprintf(f, " %s", pattern); + SET_FOREACH(pattern, c->log_filter_denied_patterns) + fprintf(f, " ~%s", pattern); + fputc('\n', f); + } + for (size_t j = 0; j < c->n_log_extra_fields; j++) { fprintf(f, "%sLogExtraFields: ", prefix); fwrite(c->log_extra_fields[j].iov_base, diff --git a/src/core/execute.h b/src/core/execute.h index a2cf22806b..24cd4640d7 100644 --- a/src/core/execute.h +++ b/src/core/execute.h @@ -24,6 +24,7 @@ typedef struct Manager Manager; #include "nsflags.h" #include "numa-util.h" #include "path-util.h" +#include "set.h" #include "time-util.h" #define EXEC_STDIN_DATA_MAX (64U*1024U*1024U) @@ -286,6 +287,8 @@ struct ExecContext { struct iovec* log_extra_fields; size_t n_log_extra_fields; + Set *log_filter_allowed_patterns; + Set *log_filter_denied_patterns; usec_t log_ratelimit_interval_usec; unsigned log_ratelimit_burst; diff --git a/src/core/load-fragment-gperf.gperf.in b/src/core/load-fragment-gperf.gperf.in index b315dd0afa..2850da5cc1 100644 --- a/src/core/load-fragment-gperf.gperf.in +++ b/src/core/load-fragment-gperf.gperf.in @@ -52,6 +52,7 @@ {{type}}.LogRateLimitIntervalSec, config_parse_sec, 0, offsetof({{type}}, exec_context.log_ratelimit_interval_usec) {{type}}.LogRateLimitBurst, config_parse_unsigned, 0, offsetof({{type}}, exec_context.log_ratelimit_burst) {{type}}.LogExtraFields, config_parse_log_extra_fields, 0, offsetof({{type}}, exec_context) +{{type}}.LogFilterPatterns, config_parse_log_filter_patterns, 0, offsetof({{type}}, exec_context) {{type}}.Capabilities, config_parse_warn_compat, DISABLED_LEGACY, offsetof({{type}}, exec_context) {{type}}.SecureBits, config_parse_exec_secure_bits, 0, offsetof({{type}}, exec_context.secure_bits) {{type}}.CapabilityBoundingSet, config_parse_capability_set, 0, offsetof({{type}}, exec_context.capability_bounding_set) diff --git a/src/core/load-fragment.c b/src/core/load-fragment.c index 734a5941cc..83e88971be 100644 --- a/src/core/load-fragment.c +++ b/src/core/load-fragment.c @@ -52,6 +52,7 @@ #include "parse-helpers.h" #include "parse-util.h" #include "path-util.h" +#include "pcre2-util.h" #include "percent-util.h" #include "process-util.h" #if HAVE_SECCOMP @@ -6225,6 +6226,7 @@ void unit_dump_config_items(FILE *f) { { config_parse_job_mode, "MODE" }, { config_parse_job_mode_isolate, "BOOLEAN" }, { config_parse_personality, "PERSONALITY" }, + { config_parse_log_filter_patterns, "REGEX" }, }; const char *prev = NULL; @@ -6489,3 +6491,54 @@ int config_parse_tty_size( return config_parse_unsigned(unit, filename, line, section, section_line, lvalue, ltype, rvalue, data, userdata); } + +int config_parse_log_filter_patterns( + const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + ExecContext *c = ASSERT_PTR(data); + _cleanup_(pattern_freep) pcre2_code *compiled_pattern = NULL; + const char *pattern = ASSERT_PTR(rvalue); + bool is_allowlist = true; + int r; + + assert(filename); + assert(lvalue); + + if (isempty(pattern)) { + /* Empty assignment resets the lists. */ + c->log_filter_allowed_patterns = set_free(c->log_filter_allowed_patterns); + c->log_filter_denied_patterns = set_free(c->log_filter_denied_patterns); + return 0; + } + + if (pattern[0] == '~') { + is_allowlist = false; + pattern++; + if (isempty(pattern)) + /* LogFilterPatterns=~ is not considered a valid pattern. */ + return log_syntax(unit, LOG_WARNING, filename, line, 0, + "Regex pattern invalid, ignoring: %s=%s", lvalue, rvalue); + } + + if (pattern_compile_and_log(pattern, 0, &compiled_pattern) < 0) + return 0; + + r = set_put_strdup(is_allowlist ? &c->log_filter_allowed_patterns : &c->log_filter_denied_patterns, + pattern); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to store log filtering pattern, ignoring: %s=%s", lvalue, rvalue); + return 0; + } + + return 0; +} diff --git a/src/core/load-fragment.h b/src/core/load-fragment.h index c57a6b2277..74b3633695 100644 --- a/src/core/load-fragment.h +++ b/src/core/load-fragment.h @@ -150,6 +150,7 @@ CONFIG_PARSER_PROTOTYPE(config_parse_cgroup_socket_bind); CONFIG_PARSER_PROTOTYPE(config_parse_restrict_network_interfaces); CONFIG_PARSER_PROTOTYPE(config_parse_watchdog_sec); CONFIG_PARSER_PROTOTYPE(config_parse_tty_size); +CONFIG_PARSER_PROTOTYPE(config_parse_log_filter_patterns); /* gperf prototypes */ const struct ConfigPerfItem* load_fragment_gperf_lookup(const char *key, GPERF_LEN_TYPE length); diff --git a/src/shared/bus-unit-util.c b/src/shared/bus-unit-util.c index 6b6383b60b..12b14ae584 100644 --- a/src/shared/bus-unit-util.c +++ b/src/shared/bus-unit-util.c @@ -1218,6 +1218,16 @@ static int bus_append_execute_property(sd_bus_message *m, const char *field, con return 1; } + if (streq(field, "LogFilterPatterns")) { + r = sd_bus_message_append(m, "(sv)", "LogFilterPatterns", "a(bs)", 1, + eq[0] != '~', + eq[0] != '~' ? eq : eq + 1); + if (r < 0) + return bus_log_create_error(r); + + return 1; + } + if (STR_IN_SET(field, "StandardInput", "StandardOutput", "StandardError")) { diff --git a/src/test/test-load-fragment.c b/src/test/test-load-fragment.c index 997c2b2524..8d3c58a800 100644 --- a/src/test/test-load-fragment.c +++ b/src/test/test-load-fragment.c @@ -22,6 +22,7 @@ #include "load-fragment.h" #include "macro.h" #include "memory-util.h" +#include "pcre2-util.h" #include "rm-rf.h" #include "specifier.h" #include "string-util.h" @@ -997,6 +998,56 @@ TEST(unit_is_recursive_template_dependency) { assert_se(unit_is_likely_recursive_template_dependency(u, "foobar@foobar@123.mount", "foobar@%n.mount") == 0); } +#define TEST_PATTERN(_regex, _allowed_patterns_count, _denied_patterns_count) \ + { \ + .regex = _regex, \ + .allowed_patterns_count = _allowed_patterns_count, \ + .denied_patterns_count = _denied_patterns_count \ + } + +TEST(config_parse_log_filter_patterns) { + ExecContext c = {}; + int r; + + static const struct { + const char *regex; + size_t allowed_patterns_count; + size_t denied_patterns_count; + } regex_tests[] = { + TEST_PATTERN("", 0, 0), + TEST_PATTERN(".*", 1, 0), + TEST_PATTERN("~.*", 1, 1), + TEST_PATTERN("", 0, 0), + TEST_PATTERN("~.*", 0, 1), + TEST_PATTERN("[.*", 0, 1), /* Invalid pattern. */ + TEST_PATTERN(".*gg.*", 1, 1), + TEST_PATTERN("~.*", 1, 1), /* Already in the patterns list. */ + TEST_PATTERN("[.*", 1, 1), /* Invalid pattern. */ + TEST_PATTERN("\\x7ehello", 2, 1), + TEST_PATTERN("", 0, 0), + TEST_PATTERN("~foobar", 0, 1), + }; + + if (ERRNO_IS_NOT_SUPPORTED(dlopen_pcre2())) + return (void) log_tests_skipped("PCRE2 support is not available"); + + for (size_t i = 0; i < ELEMENTSOF(regex_tests); i++) { + r = config_parse_log_filter_patterns(NULL, "fake", 1, "section", 1, "LogFilterPatterns", 1, + regex_tests[i].regex, &c, NULL); + assert_se(r >= 0); + + assert_se(set_size(c.log_filter_allowed_patterns) == regex_tests[i].allowed_patterns_count); + assert_se(set_size(c.log_filter_denied_patterns) == regex_tests[i].denied_patterns_count); + + /* Ensure `~` is properly removed */ + const char *p; + SET_FOREACH(p, c.log_filter_allowed_patterns) + assert_se(p && p[0] != '~'); + SET_FOREACH(p, c.log_filter_denied_patterns) + assert_se(p && p[0] != '~'); + } +} + static int intro(void) { if (enter_cgroup_subroot(NULL) == -ENOMEDIUM) return log_tests_skipped("cgroupfs not available"); diff --git a/test/fuzz/fuzz-unit-file/directives-all.service b/test/fuzz/fuzz-unit-file/directives-all.service index b4cfca2814..f8237d74eb 100644 --- a/test/fuzz/fuzz-unit-file/directives-all.service +++ b/test/fuzz/fuzz-unit-file/directives-all.service @@ -846,6 +846,7 @@ LogExtraFields= LogLevelMax= LogRateLimitIntervalSec= LogRateLimitBurst= +LogFilterPatterns= LogsDirectory= LogsDirectoryMode= MACVLAN= -- cgit v1.2.1