]> git.ipfire.org Git - thirdparty/systemd.git/blobdiff - src/udev/udev-rules.c
nulstr-util: Declare NULSTR_FOREACH() iterator inline
[thirdparty/systemd.git] / src / udev / udev-rules.c
index 1a384d6b38451787d5c1f6b24721d00460984ac9..3b0cdda74e0bfdc106faed9a0b37f15722360e73 100644 (file)
@@ -5,11 +5,11 @@
 #include "alloc-util.h"
 #include "architecture.h"
 #include "conf-files.h"
-#include "def.h"
+#include "conf-parser.h"
+#include "constants.h"
 #include "device-private.h"
 #include "device-util.h"
 #include "dirent-util.h"
-#include "escape.h"
 #include "fd-util.h"
 #include "fileio.h"
 #include "format-util.h"
@@ -29,7 +29,7 @@
 #include "syslog-util.h"
 #include "udev-builtin.h"
 #include "udev-event.h"
-#include "udev-netlink.h"
+#include "udev-node.h"
 #include "udev-rules.h"
 #include "udev-util.h"
 #include "user-util.h"
@@ -177,10 +177,10 @@ struct UdevRuleFile {
 };
 
 struct UdevRules {
-        usec_t dirs_ts_usec;
         ResolveNameTiming resolve_name_timing;
         Hashmap *known_users;
         Hashmap *known_groups;
+        Hashmap *stats_by_path;
         UdevRuleFile *current_file;
         LIST_HEAD(UdevRuleFile, rule_files);
 };
@@ -268,11 +268,9 @@ static void udev_rule_token_free(UdevRuleToken *token) {
 }
 
 static void udev_rule_line_clear_tokens(UdevRuleLine *rule_line) {
-        UdevRuleToken *i, *next;
-
         assert(rule_line);
 
-        LIST_FOREACH_SAFE(tokens, i, next, rule_line->tokens)
+        LIST_FOREACH(tokens, i, rule_line->tokens)
                 udev_rule_token_free(i);
 
         rule_line->tokens = NULL;
@@ -298,12 +296,10 @@ static UdevRuleLine* udev_rule_line_free(UdevRuleLine *rule_line) {
 DEFINE_TRIVIAL_CLEANUP_FUNC(UdevRuleLine*, udev_rule_line_free);
 
 static void udev_rule_file_free(UdevRuleFile *rule_file) {
-        UdevRuleLine *i, *next;
-
         if (!rule_file)
                 return;
 
-        LIST_FOREACH_SAFE(rule_lines, i, next, rule_file->rule_lines)
+        LIST_FOREACH(rule_lines, i, rule_file->rule_lines)
                 udev_rule_line_free(i);
 
         free(rule_file->filename);
@@ -311,16 +307,15 @@ static void udev_rule_file_free(UdevRuleFile *rule_file) {
 }
 
 UdevRules *udev_rules_free(UdevRules *rules) {
-        UdevRuleFile *i, *next;
-
         if (!rules)
                 return NULL;
 
-        LIST_FOREACH_SAFE(rule_files, i, next, rules->rule_files)
+        LIST_FOREACH(rule_files, i, rules->rule_files)
                 udev_rule_file_free(i);
 
         hashmap_free_free_key(rules->known_users);
         hashmap_free_free_key(rules->known_groups);
+        hashmap_free(rules->stats_by_path);
         return mfree(rules);
 }
 
@@ -1062,21 +1057,19 @@ static int parse_line(char **line, char **ret_key, char **ret_attr, UdevRuleOper
 }
 
 static void sort_tokens(UdevRuleLine *rule_line) {
-        UdevRuleToken *head_old;
-
         assert(rule_line);
 
-        head_old = TAKE_PTR(rule_line->tokens);
+        UdevRuleToken *old_tokens = TAKE_PTR(rule_line->tokens);
         rule_line->current_token = NULL;
 
-        while (!LIST_IS_EMPTY(head_old)) {
-                UdevRuleToken *t, *min_token = NULL;
+        while (old_tokens) {
+                UdevRuleToken *min_token = NULL;
 
-                LIST_FOREACH(tokens, t, head_old)
+                LIST_FOREACH(tokens, t, old_tokens)
                         if (!min_token || min_token->type > t->type)
                                 min_token = t;
 
-                LIST_REMOVE(tokens, head_old, min_token);
+                LIST_REMOVE(tokens, old_tokens, min_token);
                 rule_line_append_token(rule_line, min_token);
         }
 }
@@ -1146,12 +1139,10 @@ static int rule_add_line(UdevRules *rules, const char *line_str, unsigned line_n
 }
 
 static void rule_resolve_goto(UdevRuleFile *rule_file) {
-        UdevRuleLine *line, *line_next, *i;
-
         assert(rule_file);
 
         /* link GOTOs to LABEL rules in this file to be able to fast-forward */
-        LIST_FOREACH_SAFE(rule_lines, line, line_next, rule_file->rule_lines) {
+        LIST_FOREACH(rule_lines, line, rule_file->rule_lines) {
                 if (!FLAGS_SET(line->type, LINE_HAS_GOTO))
                         continue;
 
@@ -1186,6 +1177,7 @@ int udev_rules_parse_file(UdevRules *rules, const char *filename) {
         UdevRuleFile *rule_file;
         bool ignore_line = false;
         unsigned line_nr = 0;
+        struct stat st;
         int r;
 
         f = fopen(filename, "re");
@@ -1193,16 +1185,23 @@ int udev_rules_parse_file(UdevRules *rules, const char *filename) {
                 if (errno == ENOENT)
                         return 0;
 
-                return -errno;
+                return log_warning_errno(errno, "Failed to open %s, ignoring: %m", filename);
         }
 
-        (void) fd_warn_permissions(filename, fileno(f));
+        if (fstat(fileno(f), &st) < 0)
+                return log_warning_errno(errno, "Failed to stat %s, ignoring: %m", filename);
 
-        if (null_or_empty_fd(fileno(f))) {
+        if (null_or_empty(&st)) {
                 log_debug("Skipping empty file: %s", filename);
                 return 0;
         }
 
+        r = hashmap_put_stats_by_path(&rules->stats_by_path, filename, &st);
+        if (r < 0)
+                return log_warning_errno(errno, "Failed to save stat for %s, ignoring: %m", filename);
+
+        (void) fd_warn_permissions(filename, fileno(f));
+
         log_debug("Reading rules file: %s", filename);
 
         name = strdup(filename);
@@ -1300,15 +1299,12 @@ UdevRules* udev_rules_new(ResolveNameTiming resolve_name_timing) {
 int udev_rules_load(UdevRules **ret_rules, ResolveNameTiming resolve_name_timing) {
         _cleanup_(udev_rules_freep) UdevRules *rules = NULL;
         _cleanup_strv_free_ char **files = NULL;
-        char **f;
         int r;
 
         rules = udev_rules_new(resolve_name_timing);
         if (!rules)
                 return -ENOMEM;
 
-        (void) udev_rules_check_timestamp(rules);
-
         r = conf_files_list_strv(&files, ".rules", NULL, 0, RULES_DIRS);
         if (r < 0)
                 return log_debug_errno(r, "Failed to enumerate rules files: %m");
@@ -1323,15 +1319,29 @@ int udev_rules_load(UdevRules **ret_rules, ResolveNameTiming resolve_name_timing
         return 0;
 }
 
-bool udev_rules_check_timestamp(UdevRules *rules) {
+bool udev_rules_should_reload(UdevRules *rules) {
+        _cleanup_hashmap_free_ Hashmap *stats_by_path = NULL;
+        int r;
+
         if (!rules)
-                return false;
+                return true;
 
-        return paths_check_timestamp(RULES_DIRS, &rules->dirs_ts_usec, true);
+        r = config_get_stats_by_path(".rules", NULL, 0, RULES_DIRS, /* check_dropins = */ false, &stats_by_path);
+        if (r < 0) {
+                log_warning_errno(r, "Failed to get stats of udev rules, ignoring: %m");
+                return true;
+        }
+
+        if (!stats_by_path_equal(rules->stats_by_path, stats_by_path)) {
+                log_debug("Udev rules need reloading");
+                return true;
+        }
+
+        return false;
 }
 
 static bool token_match_string(UdevRuleToken *token, const char *str) {
-        const char *i, *value;
+        const char *value;
         bool match = false;
 
         assert(token);
@@ -1381,11 +1391,14 @@ static bool token_match_string(UdevRuleToken *token, const char *str) {
         return token->op == (match ? OP_MATCH : OP_NOMATCH);
 }
 
-static bool token_match_attr(UdevRuleToken *token, sd_device *dev, UdevEvent *event) {
+static bool token_match_attr(UdevRules *rules, UdevRuleToken *token, sd_device *dev, UdevEvent *event) {
         char nbuf[UDEV_NAME_SIZE], vbuf[UDEV_NAME_SIZE];
         const char *name, *value;
+        bool truncated;
 
+        assert(rules);
         assert(token);
+        assert(IN_SET(token->type, TK_M_ATTR, TK_M_PARENTS_ATTR));
         assert(dev);
         assert(event);
 
@@ -1393,11 +1406,19 @@ static bool token_match_attr(UdevRuleToken *token, sd_device *dev, UdevEvent *ev
 
         switch (token->attr_subst_type) {
         case SUBST_TYPE_FORMAT:
-                (void) udev_event_apply_format(event, name, nbuf, sizeof(nbuf), false);
+                (void) udev_event_apply_format(event, name, nbuf, sizeof(nbuf), false, &truncated);
+                if (truncated) {
+                        log_rule_debug(dev, rules,
+                                       "The sysfs attribute name '%s' is truncated while substituting into '%s', "
+                                       "assuming the %s key does not match.", nbuf, name,
+                                       token->type == TK_M_ATTR ? "ATTR" : "ATTRS");
+                        return false;
+                }
+
                 name = nbuf;
                 _fallthrough_;
         case SUBST_TYPE_PLAIN:
-                if (device_get_sysattr_value_maybe_from_netlink(dev, &event->rtnl, name, &value) < 0)
+                if (sd_device_get_sysattr_value(dev, name, &value) < 0)
                         return false;
                 break;
         case SUBST_TYPE_SUBSYS:
@@ -1497,19 +1518,22 @@ static int attr_subst_subdir(char attr[static UDEV_PATH_SIZE]) {
         char buf[UDEV_PATH_SIZE], *p;
         const char *tail;
         size_t len, size;
+        bool truncated;
 
         assert(attr);
 
         tail = strstr(attr, "/*/");
         if (!tail)
-            return 0;
+                return 0;
 
         len = tail - attr + 1; /* include slash at the end */
         tail += 2; /* include slash at the beginning */
 
         p = buf;
         size = sizeof(buf);
-        size -= strnpcpy(&p, size, attr, len);
+        size -= strnpcpy_full(&p, size, attr, len, &truncated);
+        if (truncated)
+                return -ENOENT;
 
         dir = opendir(buf);
         if (!dir)
@@ -1519,7 +1543,10 @@ static int attr_subst_subdir(char attr[static UDEV_PATH_SIZE]) {
                 if (de->d_name[0] == '.')
                         continue;
 
-                strscpyl(p, size, de->d_name, tail, NULL);
+                strscpyl_full(p, size, &truncated, de->d_name, tail, NULL);
+                if (truncated)
+                        continue;
+
                 if (faccessat(dirfd(dir), p, F_OK, 0) < 0)
                         continue;
 
@@ -1645,12 +1672,19 @@ static int udev_rule_apply_token_to_event(
         }
         case TK_M_ATTR:
         case TK_M_PARENTS_ATTR:
-                return token_match_attr(token, dev, event);
+                return token_match_attr(rules, token, dev, event);
         case TK_M_SYSCTL: {
                 _cleanup_free_ char *value = NULL;
                 char buf[UDEV_PATH_SIZE];
+                bool truncated;
+
+                (void) udev_event_apply_format(event, token->data, buf, sizeof(buf), false, &truncated);
+                if (truncated) {
+                        log_rule_debug(dev, rules, "The sysctl entry name '%s' is truncated while substituting into '%s', "
+                                       "assuming the SYSCTL key does not match.", buf, (const char*) token->data);
+                        return false;
+                }
 
-                (void) udev_event_apply_format(event, token->data, buf, sizeof(buf), false);
                 r = sysctl_read(sysctl_normalize(buf), &value);
                 if (r < 0 && r != -ENOENT)
                         return log_rule_error_errno(dev, rules, r, "Failed to read sysctl '%s': %m", buf);
@@ -1661,9 +1695,15 @@ static int udev_rule_apply_token_to_event(
                 mode_t mode = PTR_TO_MODE(token->data);
                 char buf[UDEV_PATH_SIZE];
                 struct stat statbuf;
-                bool match;
+                bool match, truncated;
+
+                (void) udev_event_apply_format(event, token->value, buf, sizeof(buf), false, &truncated);
+                if (truncated) {
+                        log_rule_debug(dev, rules, "The file name '%s' is truncated while substituting into '%s', "
+                                       "assuming the TEST key does not match", buf, token->value);
+                        return false;
+                }
 
-                (void) udev_event_apply_format(event, token->value, buf, sizeof(buf), false);
                 if (!path_is_absolute(buf) &&
                     udev_resolve_subsys_kernel(buf, buf, sizeof(buf), false) < 0) {
                         char tmp[UDEV_PATH_SIZE];
@@ -1673,8 +1713,11 @@ static int udev_rule_apply_token_to_event(
                         if (r < 0)
                                 return log_rule_error_errno(dev, rules, r, "Failed to get syspath: %m");
 
-                        strscpy(tmp, sizeof(tmp), buf);
-                        strscpyl(buf, sizeof(buf), val, "/", tmp, NULL);
+                        strscpy_full(tmp, sizeof(tmp), buf, &truncated);
+                        assert(!truncated);
+                        strscpyl_full(buf, sizeof(buf), &truncated, val, "/", tmp, NULL);
+                        if (truncated)
+                                return false;
                 }
 
                 r = attr_subst_subdir(buf);
@@ -1693,14 +1736,21 @@ static int udev_rule_apply_token_to_event(
                 return token->op == (match ? OP_MATCH : OP_NOMATCH);
         }
         case TK_M_PROGRAM: {
-                char buf[UDEV_PATH_SIZE], result[UDEV_LINE_SIZE];
+                char buf[UDEV_LINE_SIZE], result[UDEV_LINE_SIZE];
+                bool truncated;
                 size_t count;
 
                 event->program_result = mfree(event->program_result);
-                (void) udev_event_apply_format(event, token->value, buf, sizeof(buf), false);
+                (void) udev_event_apply_format(event, token->value, buf, sizeof(buf), false, &truncated);
+                if (truncated) {
+                        log_rule_debug(dev, rules, "The command '%s' is truncated while substituting into '%s', "
+                                       "assuming the PROGRAM key does not match.", buf, token->value);
+                        return false;
+                }
+
                 log_rule_debug(dev, rules, "Running PROGRAM '%s'", buf);
 
-                r = udev_event_spawn(event, timeout_usec, timeout_signal, true, buf, result, sizeof(result));
+                r = udev_event_spawn(event, timeout_usec, timeout_signal, true, buf, result, sizeof(result), NULL);
                 if (r != 0) {
                         if (r < 0)
                                 log_rule_warning_errno(dev, rules, r, "Failed to execute \"%s\": %m", buf);
@@ -1721,8 +1771,15 @@ static int udev_rule_apply_token_to_event(
         case TK_M_IMPORT_FILE: {
                 _cleanup_fclose_ FILE *f = NULL;
                 char buf[UDEV_PATH_SIZE];
+                bool truncated;
+
+                (void) udev_event_apply_format(event, token->value, buf, sizeof(buf), false, &truncated);
+                if (truncated) {
+                        log_rule_debug(dev, rules, "The file name '%s' to be imported is truncated while substituting into '%s', "
+                                       "assuming the IMPORT key does not match.", buf, token->value);
+                        return false;
+                }
 
-                (void) udev_event_apply_format(event, token->value, buf, sizeof(buf), false);
                 log_rule_debug(dev, rules, "Importing properties from '%s'", buf);
 
                 f = fopen(buf, "re");
@@ -1767,12 +1824,19 @@ static int udev_rule_apply_token_to_event(
         }
         case TK_M_IMPORT_PROGRAM: {
                 _cleanup_strv_free_ char **lines = NULL;
-                char buf[UDEV_PATH_SIZE], result[UDEV_LINE_SIZE], **line;
+                char buf[UDEV_LINE_SIZE], result[UDEV_LINE_SIZE];
+                bool truncated;
+
+                (void) udev_event_apply_format(event, token->value, buf, sizeof(buf), false, &truncated);
+                if (truncated) {
+                        log_rule_debug(dev, rules, "The command '%s' is truncated while substituting into '%s', "
+                                       "assuming the IMPORT key does not match.", buf, token->value);
+                        return false;
+                }
 
-                (void) udev_event_apply_format(event, token->value, buf, sizeof(buf), false);
                 log_rule_debug(dev, rules, "Importing properties from results of '%s'", buf);
 
-                r = udev_event_spawn(event, timeout_usec, timeout_signal, true, buf, result, sizeof result);
+                r = udev_event_spawn(event, timeout_usec, timeout_signal, true, buf, result, sizeof result, &truncated);
                 if (r != 0) {
                         if (r < 0)
                                 log_rule_warning_errno(dev, rules, r, "Failed to execute '%s', ignoring: %m", buf);
@@ -1781,10 +1845,26 @@ static int udev_rule_apply_token_to_event(
                         return token->op == OP_NOMATCH;
                 }
 
+                if (truncated) {
+                        bool found = false;
+
+                        /* Drop the last line. */
+                        for (char *p = PTR_SUB1(buf + strlen(buf), buf); p; p = PTR_SUB1(p, buf))
+                                if (strchr(NEWLINE, *p)) {
+                                        *p = '\0';
+                                        found = true;
+                                } else if (found)
+                                        break;
+                }
+
                 r = strv_split_newlines_full(&lines, result, EXTRACT_RETAIN_ESCAPE);
-                if (r < 0)
+                if (r == -ENOMEM)
+                        return log_oom();
+                if (r < 0) {
                         log_rule_warning_errno(dev, rules, r,
                                                "Failed to extract lines from result of command \"%s\", ignoring: %m", buf);
+                        return false;
+                }
 
                 STRV_FOREACH(line, lines) {
                         char *key, *value;
@@ -1812,7 +1892,8 @@ static int udev_rule_apply_token_to_event(
                 UdevBuiltinCommand cmd = PTR_TO_UDEV_BUILTIN_CMD(token->data);
                 assert(cmd >= 0 && cmd < _UDEV_BUILTIN_MAX);
                 unsigned mask = 1U << (int) cmd;
-                char buf[UDEV_PATH_SIZE];
+                char buf[UDEV_LINE_SIZE];
+                bool truncated;
 
                 if (udev_builtin_run_once(cmd)) {
                         /* check if we ran already */
@@ -1826,7 +1907,13 @@ static int udev_rule_apply_token_to_event(
                         event->builtin_run |= mask;
                 }
 
-                (void) udev_event_apply_format(event, token->value, buf, sizeof(buf), false);
+                (void) udev_event_apply_format(event, token->value, buf, sizeof(buf), false, &truncated);
+                if (truncated) {
+                        log_rule_debug(dev, rules, "The builtin command '%s' is truncated while substituting into '%s', "
+                                       "assuming the IMPORT key does not match", buf, token->value);
+                        return false;
+                }
+
                 log_rule_debug(dev, rules, "Importing properties from results of builtin command '%s'", buf);
 
                 r = udev_builtin_run(dev, &event->rtnl, cmd, buf, false);
@@ -1875,8 +1962,15 @@ static int udev_rule_apply_token_to_event(
         }
         case TK_M_IMPORT_PARENT: {
                 char buf[UDEV_PATH_SIZE];
+                bool truncated;
+
+                (void) udev_event_apply_format(event, token->value, buf, sizeof(buf), false, &truncated);
+                if (truncated) {
+                        log_rule_debug(dev, rules, "The property name '%s' is truncated while substituting into '%s', "
+                                       "assuming the IMPORT key does not match.", buf, token->value);
+                        return false;
+                }
 
-                (void) udev_event_apply_format(event, token->value, buf, sizeof(buf), false);
                 r = import_parent_into_properties(dev, buf);
                 if (r < 0)
                         return log_rule_error_errno(dev, rules, r,
@@ -1925,13 +2019,20 @@ static int udev_rule_apply_token_to_event(
         case TK_A_OWNER: {
                 char owner[UDEV_NAME_SIZE];
                 const char *ow = owner;
+                bool truncated;
 
                 if (event->owner_final)
                         break;
                 if (token->op == OP_ASSIGN_FINAL)
                         event->owner_final = true;
 
-                (void) udev_event_apply_format(event, token->value, owner, sizeof(owner), false);
+                (void) udev_event_apply_format(event, token->value, owner, sizeof(owner), false, &truncated);
+                if (truncated) {
+                        log_rule_warning(dev, rules, "The user name '%s' is truncated while substituting into '%s', "
+                                         "refusing to apply the OWNER key.", owner, token->value);
+                        break;
+                }
+
                 r = get_user_creds(&ow, &event->uid, NULL, NULL, NULL, USER_CREDS_ALLOW_MISSING);
                 if (r < 0)
                         log_unknown_owner(dev, rules, r, "user", owner);
@@ -1942,13 +2043,20 @@ static int udev_rule_apply_token_to_event(
         case TK_A_GROUP: {
                 char group[UDEV_NAME_SIZE];
                 const char *gr = group;
+                bool truncated;
 
                 if (event->group_final)
                         break;
                 if (token->op == OP_ASSIGN_FINAL)
                         event->group_final = true;
 
-                (void) udev_event_apply_format(event, token->value, group, sizeof(group), false);
+                (void) udev_event_apply_format(event, token->value, group, sizeof(group), false, &truncated);
+                if (truncated) {
+                        log_rule_warning(dev, rules, "The group name '%s' is truncated while substituting into '%s', "
+                                         "refusing to apply the GROUP key.", group, token->value);
+                        break;
+                }
+
                 r = get_group_creds(&gr, &event->gid, USER_CREDS_ALLOW_MISSING);
                 if (r < 0)
                         log_unknown_owner(dev, rules, r, "group", group);
@@ -1958,13 +2066,20 @@ static int udev_rule_apply_token_to_event(
         }
         case TK_A_MODE: {
                 char mode_str[UDEV_NAME_SIZE];
+                bool truncated;
 
                 if (event->mode_final)
                         break;
                 if (token->op == OP_ASSIGN_FINAL)
                         event->mode_final = true;
 
-                (void) udev_event_apply_format(event, token->value, mode_str, sizeof(mode_str), false);
+                (void) udev_event_apply_format(event, token->value, mode_str, sizeof(mode_str), false, &truncated);
+                if (truncated) {
+                        log_rule_warning(dev, rules, "The mode '%s' is truncated while substituting into %s, "
+                                         "refusing to apply the MODE key.", mode_str, token->value);
+                        break;
+                }
+
                 r = parse_mode(mode_str, &event->mode);
                 if (r < 0)
                         log_rule_error_errno(dev, rules, r, "Failed to parse mode '%s', ignoring: %m", mode_str);
@@ -2005,12 +2120,19 @@ static int udev_rule_apply_token_to_event(
         case TK_A_SECLABEL: {
                 _cleanup_free_ char *name = NULL, *label = NULL;
                 char label_str[UDEV_LINE_SIZE] = {};
+                bool truncated;
 
                 name = strdup(token->data);
                 if (!name)
                         return log_oom();
 
-                (void) udev_event_apply_format(event, token->value, label_str, sizeof(label_str), false);
+                (void) udev_event_apply_format(event, token->value, label_str, sizeof(label_str), false, &truncated);
+                if (truncated) {
+                        log_rule_warning(dev, rules, "The security label '%s' is truncated while substituting into '%s', "
+                                         "refusing to apply the SECLABEL key.", label_str, token->value);
+                        break;
+                }
+
                 if (!isempty(label_str))
                         label = strdup(label_str);
                 else
@@ -2025,7 +2147,7 @@ static int udev_rule_apply_token_to_event(
                 if (r == -ENOMEM)
                         return log_oom();
                 if (r < 0)
-                        return log_rule_error_errno(dev, rules, r, "Failed to store SECLABEL{%s}='%s': %m", name, label);;
+                        return log_rule_error_errno(dev, rules, r, "Failed to store SECLABEL{%s}='%s': %m", name, label);
 
                 log_rule_debug(dev, rules, "SECLABEL{%s}='%s'", name, label);
 
@@ -2037,6 +2159,7 @@ static int udev_rule_apply_token_to_event(
                 const char *val, *name = token->data;
                 char value_new[UDEV_NAME_SIZE], *p = value_new;
                 size_t count, l = sizeof(value_new);
+                bool truncated;
 
                 if (isempty(token->value)) {
                         if (token->op == OP_ADD)
@@ -2048,10 +2171,22 @@ static int udev_rule_apply_token_to_event(
                 }
 
                 if (token->op == OP_ADD &&
-                    sd_device_get_property_value(dev, name, &val) >= 0)
-                        l = strpcpyl(&p, l, val, " ", NULL);
+                    sd_device_get_property_value(dev, name, &val) >= 0) {
+                        l = strpcpyl_full(&p, l, &truncated, val, " ", NULL);
+                        if (truncated) {
+                                log_rule_warning(dev, rules, "The buffer for the property '%s' is full, "
+                                                 "refusing to append the new value '%s'.", name, token->value);
+                                break;
+                        }
+                }
+
+                (void) udev_event_apply_format(event, token->value, p, l, false, &truncated);
+                if (truncated) {
+                        log_rule_warning(dev, rules, "The property value '%s' is truncated while substituting into '%s', "
+                                         "refusing to add property '%s'.", p, token->value, name);
+                        break;
+                }
 
-                (void) udev_event_apply_format(event, token->value, p, l, false);
                 if (event->esc == ESCAPE_REPLACE) {
                         count = udev_replace_chars(p, NULL);
                         if (count > 0)
@@ -2066,8 +2201,16 @@ static int udev_rule_apply_token_to_event(
         }
         case TK_A_TAG: {
                 char buf[UDEV_PATH_SIZE];
+                bool truncated;
+
+                (void) udev_event_apply_format(event, token->value, buf, sizeof(buf), false, &truncated);
+                if (truncated) {
+                        log_rule_warning(dev, rules, "The tag name '%s' is truncated while substituting into '%s',"
+                                         "refusing to %s the tag.", buf, token->value,
+                                         token->op == OP_REMOVE ? "remove" : "add");
+                        break;
+                }
 
-                (void) udev_event_apply_format(event, token->value, buf, sizeof(buf), false);
                 if (token->op == OP_ASSIGN)
                         device_cleanup_tags(dev);
 
@@ -2086,6 +2229,7 @@ static int udev_rule_apply_token_to_event(
         }
         case TK_A_NAME: {
                 char buf[UDEV_PATH_SIZE];
+                bool truncated;
                 size_t count;
 
                 if (event->name_final)
@@ -2100,7 +2244,13 @@ static int udev_rule_apply_token_to_event(
                         break;
                 }
 
-                (void) udev_event_apply_format(event, token->value, buf, sizeof(buf), false);
+                (void) udev_event_apply_format(event, token->value, buf, sizeof(buf), false, &truncated);
+                if (truncated) {
+                        log_rule_warning(dev, rules, "The network interface name '%s' is truncated while substituting into '%s', "
+                                         "refusing to set the name.", buf, token->value);
+                        break;
+                }
+
                 if (IN_SET(event->esc, ESCAPE_UNSET, ESCAPE_REPLACE)) {
                         if (naming_scheme_has(NAMING_REPLACE_STRICTLY))
                                 count = udev_replace_ifname(buf);
@@ -2119,6 +2269,7 @@ static int udev_rule_apply_token_to_event(
         }
         case TK_A_DEVLINK: {
                 char buf[UDEV_PATH_SIZE], *p;
+                bool truncated;
                 size_t count;
 
                 if (event->devlink_final)
@@ -2131,7 +2282,13 @@ static int udev_rule_apply_token_to_event(
                         device_cleanup_devlinks(dev);
 
                 /* allow multiple symlinks separated by spaces */
-                (void) udev_event_apply_format(event, token->value, buf, sizeof(buf), event->esc != ESCAPE_NONE);
+                (void) udev_event_apply_format(event, token->value, buf, sizeof(buf), event->esc != ESCAPE_NONE, &truncated);
+                if (truncated) {
+                        log_rule_warning(dev, rules, "The symbolic link path '%s' is truncated while substituting into '%s', "
+                                         "refusing to add the device symbolic link.", buf, token->value);
+                        break;
+                }
+
                 if (event->esc == ESCAPE_UNSET)
                         count = udev_replace_chars(buf, "/ ");
                 else if (event->esc == ESCAPE_REPLACE)
@@ -2152,7 +2309,10 @@ static int udev_rule_apply_token_to_event(
                                 next = skip_leading_chars(next, NULL);
                         }
 
-                        strscpyl(filename, sizeof(filename), "/dev/", p, NULL);
+                        strscpyl_full(filename, sizeof(filename), &truncated, "/dev/", p, NULL);
+                        if (truncated)
+                                continue;
+
                         r = device_add_devlink(dev, filename);
                         if (r < 0)
                                 return log_rule_error_errno(dev, rules, r, "Failed to add devlink '%s': %m", filename);
@@ -2165,17 +2325,30 @@ static int udev_rule_apply_token_to_event(
         case TK_A_ATTR: {
                 char buf[UDEV_PATH_SIZE], value[UDEV_NAME_SIZE];
                 const char *val, *key_name = token->data;
+                bool truncated;
 
                 if (udev_resolve_subsys_kernel(key_name, buf, sizeof(buf), false) < 0 &&
-                    sd_device_get_syspath(dev, &val) >= 0)
-                        strscpyl(buf, sizeof(buf), val, "/", key_name, NULL);
+                    sd_device_get_syspath(dev, &val) >= 0) {
+                        strscpyl_full(buf, sizeof(buf), &truncated, val, "/", key_name, NULL);
+                        if (truncated) {
+                                log_rule_warning(dev, rules,
+                                                 "The path to the attribute '%s/%s' is too long, refusing to set the attribute.",
+                                                 val, key_name);
+                                break;
+                        }
+                }
 
                 r = attr_subst_subdir(buf);
                 if (r < 0) {
                         log_rule_error_errno(dev, rules, r, "Could not find file matches '%s', ignoring: %m", buf);
                         break;
                 }
-                (void) udev_event_apply_format(event, token->value, value, sizeof(value), false);
+                (void) udev_event_apply_format(event, token->value, value, sizeof(value), false, &truncated);
+                if (truncated) {
+                        log_rule_warning(dev, rules, "The attribute value '%s' is truncated while substituting into '%s', "
+                                         "refusing to set the attribute '%s'", value, token->value, buf);
+                        break;
+                }
 
                 log_rule_debug(dev, rules, "ATTR '%s' writing '%s'", buf, value);
                 r = write_string_file(buf, value,
@@ -2189,9 +2362,22 @@ static int udev_rule_apply_token_to_event(
         }
         case TK_A_SYSCTL: {
                 char buf[UDEV_PATH_SIZE], value[UDEV_NAME_SIZE];
+                bool truncated;
+
+                (void) udev_event_apply_format(event, token->data, buf, sizeof(buf), false, &truncated);
+                if (truncated) {
+                        log_rule_warning(dev, rules, "The sysctl entry name '%s' is truncated while substituting into '%s', "
+                                         "refusing to set the sysctl entry.", buf, (const char*) token->data);
+                        break;
+                }
+
+                (void) udev_event_apply_format(event, token->value, value, sizeof(value), false, &truncated);
+                if (truncated) {
+                        log_rule_warning(dev, rules, "The sysctl value '%s' is truncated while substituting into '%s', "
+                                         "refusing to set the sysctl entry '%s'", value, token->value, buf);
+                        break;
+                }
 
-                (void) udev_event_apply_format(event, token->data, buf, sizeof(buf), false);
-                (void) udev_event_apply_format(event, token->value, value, sizeof(value), false);
                 sysctl_normalize(buf);
                 log_rule_debug(dev, rules, "SYSCTL '%s' writing '%s'", buf, value);
                 r = sysctl_write(buf, value);
@@ -2202,7 +2388,8 @@ static int udev_rule_apply_token_to_event(
         case TK_A_RUN_BUILTIN:
         case TK_A_RUN_PROGRAM: {
                 _cleanup_free_ char *cmd = NULL;
-                char buf[UDEV_PATH_SIZE];
+                char buf[UDEV_LINE_SIZE];
+                bool truncated;
 
                 if (event->run_final)
                         break;
@@ -2212,7 +2399,12 @@ static int udev_rule_apply_token_to_event(
                 if (IN_SET(token->op, OP_ASSIGN, OP_ASSIGN_FINAL))
                         ordered_hashmap_clear_free_key(event->run_list);
 
-                (void) udev_event_apply_format(event, token->value, buf, sizeof(buf), false);
+                (void) udev_event_apply_format(event, token->value, buf, sizeof(buf), false, &truncated);
+                if (truncated) {
+                        log_rule_warning(dev, rules, "The command '%s' is truncated while substituting into '%s', "
+                                         "refusing to invoke the command.", buf, token->value);
+                        break;
+                }
 
                 cmd = strdup(buf);
                 if (!cmd)
@@ -2252,21 +2444,28 @@ static int udev_rule_apply_parent_token_to_event(
         UdevRuleToken *head;
         int r;
 
-        line = rules->current_file->current_line;
-        head = rules->current_file->current_line->current_token;
-        event->dev_parent = event->dev;
+        assert(rules);
+        assert(rules->current_file);
+        assert(event);
+
+        line = ASSERT_PTR(rules->current_file->current_line);
+        head = ASSERT_PTR(rules->current_file->current_line->current_token);
+        event->dev_parent = ASSERT_PTR(event->dev);
+
         for (;;) {
-                LIST_FOREACH(tokens, line->current_token, head) {
-                        if (!token_is_for_parents(line->current_token))
+                LIST_FOREACH(tokens, token, head) {
+                        if (!token_is_for_parents(token))
                                 return true; /* All parent tokens match. */
+
+                        line->current_token = token;
                         r = udev_rule_apply_token_to_event(rules, event->dev_parent, event, 0, timeout_signal, NULL);
                         if (r < 0)
                                 return r;
                         if (r == 0)
                                 break;
                 }
-                if (!line->current_token)
-                        /* All parent tokens match. But no assign tokens in the line. Hmm... */
+                if (r > 0)
+                        /* All parent tokens match, and no more token (except for GOTO) in the line. */
                         return true;
 
                 if (sd_device_get_parent(event->dev_parent, &event->dev_parent) < 0) {
@@ -2286,7 +2485,6 @@ static int udev_rule_apply_line_to_event(
 
         UdevRuleLine *line = rules->current_file->current_line;
         UdevRuleLineType mask = LINE_HAS_GOTO | LINE_UPDATE_SOMETHING;
-        UdevRuleToken *token, *next_token;
         bool parents_done = false;
         sd_device_action_t action;
         int r;
@@ -2310,7 +2508,7 @@ static int udev_rule_apply_line_to_event(
 
         DEVICE_TRACE_POINT(rules_apply_line, event->dev, line->rule_file->filename, line->line_number);
 
-        LIST_FOREACH_SAFE(tokens, token, next_token, line->tokens) {
+        LIST_FOREACH(tokens, token, line->tokens) {
                 line->current_token = token;
 
                 if (token_is_for_parents(token)) {
@@ -2343,8 +2541,6 @@ int udev_rules_apply_to_event(
                 int timeout_signal,
                 Hashmap *properties_list) {
 
-        UdevRuleFile *file;
-        UdevRuleLine *next_line;
         int r;
 
         assert(rules);
@@ -2352,7 +2548,8 @@ int udev_rules_apply_to_event(
 
         LIST_FOREACH(rule_files, file, rules->rule_files) {
                 rules->current_file = file;
-                LIST_FOREACH_SAFE(rule_lines, file->current_line, next_line, file->rule_lines) {
+                LIST_FOREACH_WITH_NEXT(rule_lines, line, next_line, file->rule_lines) {
+                        file->current_line = line;
                         r = udev_rule_apply_line_to_event(rules, event, timeout_usec, timeout_signal, properties_list, &next_line);
                         if (r < 0)
                                 return r;
@@ -2362,75 +2559,7 @@ int udev_rules_apply_to_event(
         return 0;
 }
 
-static int apply_static_dev_perms(const char *devnode, uid_t uid, gid_t gid, mode_t mode, char **tags) {
-        char device_node[UDEV_PATH_SIZE], tags_dir[UDEV_PATH_SIZE], tag_symlink[UDEV_PATH_SIZE];
-        _cleanup_free_ char *unescaped_filename = NULL;
-        struct stat stats;
-        char **t;
-        int r;
-
-        assert(devnode);
-
-        if (uid == UID_INVALID && gid == GID_INVALID && mode == MODE_INVALID && !tags)
-                return 0;
-
-        strscpyl(device_node, sizeof(device_node), "/dev/", devnode, NULL);
-        if (stat(device_node, &stats) < 0) {
-                if (errno != ENOENT)
-                        return log_error_errno(errno, "Failed to stat %s: %m", device_node);
-                return 0;
-        }
-
-        if (!S_ISBLK(stats.st_mode) && !S_ISCHR(stats.st_mode)) {
-                log_warning("%s is neither block nor character device, ignoring.", device_node);
-                return 0;
-        }
-
-        if (!strv_isempty(tags)) {
-                unescaped_filename = xescape(devnode, "/.");
-                if (!unescaped_filename)
-                        return log_oom();
-        }
-
-        /* export the tags to a directory as symlinks, allowing otherwise dead nodes to be tagged */
-        STRV_FOREACH(t, tags) {
-                strscpyl(tags_dir, sizeof(tags_dir), "/run/udev/static_node-tags/", *t, "/", NULL);
-                r = mkdir_p(tags_dir, 0755);
-                if (r < 0)
-                        return log_error_errno(r, "Failed to create %s: %m", tags_dir);
-
-                strscpyl(tag_symlink, sizeof(tag_symlink), tags_dir, unescaped_filename, NULL);
-                r = symlink(device_node, tag_symlink);
-                if (r < 0 && errno != EEXIST)
-                        return log_error_errno(errno, "Failed to create symlink %s -> %s: %m",
-                                               tag_symlink, device_node);
-        }
-
-        /* don't touch the permissions if only the tags were set */
-        if (uid == UID_INVALID && gid == GID_INVALID && mode == MODE_INVALID)
-                return 0;
-
-        if (mode == MODE_INVALID)
-                mode = gid_is_valid(gid) ? 0660 : 0600;
-        if (!uid_is_valid(uid))
-                uid = 0;
-        if (!gid_is_valid(gid))
-                gid = 0;
-
-        r = chmod_and_chown(device_node, mode, uid, gid);
-        if (r == -ENOENT)
-                return 0;
-        if (r < 0)
-                return log_error_errno(r, "Failed to chown '%s' %u %u: %m", device_node, uid, gid);
-        else
-                log_debug("chown '%s' %u:%u with mode %#o", device_node, uid, gid, mode);
-
-        (void) utimensat(AT_FDCWD, device_node, NULL, 0);
-        return 0;
-}
-
 static int udev_rule_line_apply_static_dev_perms(UdevRuleLine *rule_line) {
-        UdevRuleToken *token;
         _cleanup_strv_free_ char **tags = NULL;
         uid_t uid = UID_INVALID;
         gid_t gid = GID_INVALID;
@@ -2454,7 +2583,7 @@ static int udev_rule_line_apply_static_dev_perms(UdevRuleLine *rule_line) {
                         if (r < 0)
                                 return log_oom();
                 } else if (token->type == TK_A_OPTIONS_STATIC_NODE) {
-                        r = apply_static_dev_perms(token->value, uid, gid, mode, tags);
+                        r = static_node_apply_permissions(token->value, mode, uid, gid, tags);
                         if (r < 0)
                                 return r;
                 }
@@ -2463,8 +2592,6 @@ static int udev_rule_line_apply_static_dev_perms(UdevRuleLine *rule_line) {
 }
 
 int udev_rules_apply_static_dev_perms(UdevRules *rules) {
-        UdevRuleFile *file;
-        UdevRuleLine *line;
         int r;
 
         assert(rules);