]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
udev: use userdb_by_name()/groupdb_by_name() 37304/head
authorYu Watanabe <watanabe.yu+github@gmail.com>
Wed, 30 Apr 2025 11:43:44 +0000 (20:43 +0900)
committerYu Watanabe <watanabe.yu+github@gmail.com>
Fri, 9 May 2025 03:10:28 +0000 (12:10 +0900)
Prompted by https://github.com/systemd/systemd/pull/37294#discussion_r2068141968.

src/udev/udev-rules.c
test/units/TEST-17-UDEV.verify.sh

index 506b49aa217cf8a49cb443fb47aecae93e1ec861..4555219765c10f4bc224d21cca8b63ef7d0be95b 100644 (file)
@@ -44,8 +44,7 @@
 #include "udev-trace.h"
 #include "udev-util.h"
 #include "udev-worker.h"
-#include "uid-classification.h"
-#include "user-util.h"
+#include "userdb.h"
 #include "virt.h"
 
 #define RULES_DIRS ((const char* const*) CONF_PATHS_STRV("udev/rules.d"))
@@ -490,83 +489,71 @@ UdevRules* udev_rules_free(UdevRules *rules) {
 
 static int rule_resolve_user(UdevRuleLine *rule_line, const char *name, uid_t *ret) {
         Hashmap **known_users = &LINE_GET_RULES(rule_line)->known_users;
-        _cleanup_free_ char *n = NULL;
-        uid_t uid;
-        void *val;
         int r;
 
         assert(name);
         assert(ret);
 
-        val = hashmap_get(*known_users, name);
+        void *val = hashmap_get(*known_users, name);
         if (val) {
                 *ret = PTR_TO_UID(val);
                 return 0;
         }
 
-        r = get_user_creds(
-                        &name,
-                        &uid,
-                        /* ret_gid = */ NULL,
-                        /* ret_home = */ NULL,
-                        /* ret_shell = */ NULL,
-                        USER_CREDS_ALLOW_MISSING);
+        _cleanup_(user_record_unrefp) UserRecord *ur = NULL;
+        r = userdb_by_name(name, &USERDB_MATCH_ROOT_AND_SYSTEM, USERDB_PARSE_NUMERIC | USERDB_SYNTHESIZE_NUMERIC, &ur);
         if (r == -ESRCH)
                 return log_line_error_errno(rule_line, r, "Unknown user '%s', ignoring.", name);
+        if (r == -ENOEXEC)
+                return log_line_error_errno(rule_line, r, "User '%s' is not a system user, ignoring.", name);
         if (r < 0)
                 return log_line_error_errno(rule_line, r, "Failed to resolve user '%s', ignoring: %m", name);
-        if (!uid_is_system(uid))
-                return log_line_error_errno(rule_line, SYNTHETIC_ERRNO(EINVAL),
-                                            "User '%s' is not a system user (UID="UID_FMT"), ignoring.", name, uid);
 
-        n = strdup(name);
+        _cleanup_free_ char *n = strdup(name);
         if (!n)
                 return log_oom();
 
-        r = hashmap_ensure_put(known_users, &string_hash_ops_free, n, UID_TO_PTR(uid));
+        r = hashmap_ensure_put(known_users, &string_hash_ops_free, n, UID_TO_PTR(ur->uid));
         if (r < 0)
                 return log_oom();
 
         TAKE_PTR(n);
-        *ret = uid;
+        *ret = ur->uid;
         return 0;
 }
 
 static int rule_resolve_group(UdevRuleLine *rule_line, const char *name, gid_t *ret) {
         Hashmap **known_groups = &LINE_GET_RULES(rule_line)->known_groups;
-        _cleanup_free_ char *n = NULL;
-        gid_t gid;
-        void *val;
         int r;
 
         assert(name);
         assert(ret);
 
-        val = hashmap_get(*known_groups, name);
+        void *val = hashmap_get(*known_groups, name);
         if (val) {
                 *ret = PTR_TO_GID(val);
                 return 0;
         }
 
-        r = get_group_creds(&name, &gid, USER_CREDS_ALLOW_MISSING);
+        _cleanup_(group_record_unrefp) GroupRecord *gr = NULL;
+        r = groupdb_by_name(name, &USERDB_MATCH_ROOT_AND_SYSTEM, USERDB_PARSE_NUMERIC | USERDB_SYNTHESIZE_NUMERIC, &gr);
         if (r == -ESRCH)
                 return log_line_error_errno(rule_line, r, "Unknown group '%s', ignoring.", name);
+        if (r == -ENOEXEC)
+                return log_line_error_errno(rule_line, r, "Group '%s' is not a system group, ignoring.", name);
         if (r < 0)
                 return log_line_error_errno(rule_line, r, "Failed to resolve group '%s', ignoring: %m", name);
-        if (!gid_is_system(gid))
-                return log_line_error_errno(rule_line, SYNTHETIC_ERRNO(EINVAL),
-                                            "Group '%s' is not a system group (GID="GID_FMT"), ignoring.", name, gid);
 
-        n = strdup(name);
+        _cleanup_free_ char *n = strdup(name);
         if (!n)
                 return log_oom();
 
-        r = hashmap_ensure_put(known_groups, &string_hash_ops_free, n, GID_TO_PTR(gid));
+        r = hashmap_ensure_put(known_groups, &string_hash_ops_free, n, GID_TO_PTR(gr->gid));
         if (r < 0)
                 return log_oom();
 
         TAKE_PTR(n);
-        *ret = gid;
+        *ret = gr->gid;
         return 0;
 }
 
@@ -1048,8 +1035,6 @@ static int parse_token(
                         return 0;
                 }
         } else if (streq(key, "OWNER")) {
-                uid_t uid;
-
                 if (attr)
                         return log_line_invalid_attr(rule_line, key);
                 if (is_match || op == OP_REMOVE)
@@ -1059,18 +1044,15 @@ static int parse_token(
                         op = OP_ASSIGN;
                 }
 
-                if (parse_uid(value, &uid) >= 0) {
-                        if (!uid_is_system(uid))
-                                return log_line_error_errno(rule_line, SYNTHETIC_ERRNO(EINVAL),
-                                                            "UID="UID_FMT" is not in the system user range, ignoring.", uid);
-
-                        r = rule_line_add_token(rule_line, TK_A_OWNER_ID, op, NULL, UID_TO_PTR(uid), /* is_case_insensitive = */ false, token_str);
-                } else if (resolve_name_timing == RESOLVE_NAME_EARLY &&
-                           rule_get_substitution_type(value) == SUBST_TYPE_PLAIN) {
+                if (in_charset(value, DIGITS) ||
+                    (resolve_name_timing == RESOLVE_NAME_EARLY &&
+                     rule_get_substitution_type(value) == SUBST_TYPE_PLAIN)) {
+                        uid_t uid = UID_INVALID;  /* avoid false maybe-uninitialized warning */
 
                         r = rule_resolve_user(rule_line, value, &uid);
                         if (r < 0)
                                 return r;
+                        assert(uid_is_valid(uid));
 
                         r = rule_line_add_token(rule_line, TK_A_OWNER_ID, op, NULL, UID_TO_PTR(uid), /* is_case_insensitive = */ false, token_str);
                 } else if (resolve_name_timing != RESOLVE_NAME_NEVER) {
@@ -1081,8 +1063,6 @@ static int parse_token(
                         return 0;
                 }
         } else if (streq(key, "GROUP")) {
-                gid_t gid;
-
                 if (attr)
                         return log_line_invalid_attr(rule_line, key);
                 if (is_match || op == OP_REMOVE)
@@ -1092,18 +1072,15 @@ static int parse_token(
                         op = OP_ASSIGN;
                 }
 
-                if (parse_gid(value, &gid) >= 0) {
-                        if (!gid_is_system(gid))
-                                return log_line_error_errno(rule_line, SYNTHETIC_ERRNO(EINVAL),
-                                                            "GID="GID_FMT" is not in the system group range, ignoring.", gid);
-
-                        r = rule_line_add_token(rule_line, TK_A_GROUP_ID, op, NULL, GID_TO_PTR(gid), /* is_case_insensitive = */ false, token_str);
-                } else if (resolve_name_timing == RESOLVE_NAME_EARLY &&
-                           rule_get_substitution_type(value) == SUBST_TYPE_PLAIN) {
+                if (in_charset(value, DIGITS) ||
+                    (resolve_name_timing == RESOLVE_NAME_EARLY &&
+                     rule_get_substitution_type(value) == SUBST_TYPE_PLAIN)) {
+                        gid_t gid = GID_INVALID;  /* avoid false maybe-uninitialized warning */
 
                         r = rule_resolve_group(rule_line, value, &gid);
                         if (r < 0)
                                 return r;
+                        assert(gid_is_valid(gid));
 
                         r = rule_line_add_token(rule_line, TK_A_GROUP_ID, op, NULL, GID_TO_PTR(gid), /* is_case_insensitive = */ false, token_str);
                 } else if (resolve_name_timing != RESOLVE_NAME_NEVER) {
@@ -2678,7 +2655,6 @@ static int udev_rule_apply_token_to_event(
         }
         case TK_A_OWNER: {
                 char owner[UDEV_NAME_SIZE];
-                const char *ow = owner;
 
                 if (event->owner_final)
                         return log_event_final_set(event, token);
@@ -2689,29 +2665,22 @@ static int udev_rule_apply_token_to_event(
                 if (!apply_format_value(event, token, owner, sizeof(owner), "user name"))
                         return true;
 
-                uid_t uid;
-                r = get_user_creds(
-                                &ow,
-                                &uid,
-                                /* ret_gid = */ NULL,
-                                /* ret_home = */ NULL,
-                                /* ret_shell = */ NULL,
-                                USER_CREDS_ALLOW_MISSING);
+                _cleanup_(user_record_unrefp) UserRecord *ur = NULL;
+                r = userdb_by_name(owner, &USERDB_MATCH_ROOT_AND_SYSTEM, USERDB_PARSE_NUMERIC | USERDB_SYNTHESIZE_NUMERIC, &ur);
                 if (r == -ESRCH)
                         log_event_error_errno(event, token, r, "Unknown user \"%s\", ignoring.", owner);
+                else if (r == -ENOEXEC)
+                        log_event_error(event, token, "User \"%s\" is not a system user, ignoring.", owner);
                 else if (r < 0)
                         log_event_error_errno(event, token, r, "Failed to resolve user \"%s\", ignoring: %m", owner);
-                else if (!uid_is_system(uid))
-                        log_event_error(event, token, "User \"%s\" is not a system user (UID="UID_FMT"), ignoring.", owner, uid);
                 else {
-                        event->uid = uid;
-                        log_event_debug(event, token, "Set owner: %s(%u)", owner, event->uid);
+                        event->uid = ur->uid;
+                        log_event_debug(event, token, "Set owner: %s("UID_FMT")", owner, event->uid);
                 }
                 return true;
         }
         case TK_A_GROUP: {
                 char group[UDEV_NAME_SIZE];
-                const char *gr = group;
 
                 if (event->group_final)
                         return log_event_final_set(event, token);
@@ -2722,17 +2691,17 @@ static int udev_rule_apply_token_to_event(
                 if (!apply_format_value(event, token, group, sizeof(group), "group name"))
                         return true;
 
-                gid_t gid;
-                r = get_group_creds(&gr, &gid, USER_CREDS_ALLOW_MISSING);
+                _cleanup_(group_record_unrefp) GroupRecord *gr = NULL;
+                r = groupdb_by_name(group, &USERDB_MATCH_ROOT_AND_SYSTEM, USERDB_PARSE_NUMERIC | USERDB_SYNTHESIZE_NUMERIC, &gr);
                 if (r == -ESRCH)
                         log_event_error_errno(event, token, r, "Unknown group \"%s\", ignoring.", group);
+                else if (r == -ENOEXEC)
+                        log_event_error(event, token, "Group \"%s\" is not a system group, ignoring.", group);
                 else if (r < 0)
                         log_event_error_errno(event, token, r, "Failed to resolve group \"%s\", ignoring: %m", group);
-                else if (!gid_is_system(gid))
-                        log_event_error(event, token, "Group \"%s\" is not a system group (GID="GID_FMT"), ignoring.", group, gid);
                 else {
-                        event->gid = gid;
-                        log_event_debug(event, token, "Set group: %s(%u)", group, event->gid);
+                        event->gid = gr->gid;
+                        log_event_debug(event, token, "Set group: %s("GID_FMT")", group, event->gid);
                 }
                 return true;
         }
index b0750f0aa44397111bcffc0343af98edaf705711..3117bc86be0c28f8932e8b7f18e9e4d94bb17db2 100755 (executable)
@@ -291,16 +291,52 @@ test_syntax_error 'OWNER{a}="b"' 'Invalid attribute for OWNER.'
 test_syntax_error 'OWNER-="b"' 'Invalid operator for OWNER.'
 test_syntax_error 'OWNER!="b"' 'Invalid operator for OWNER.'
 test_syntax_error 'OWNER+="0"' "OWNER key takes '=' or ':=' operator, assuming '='."
-test_syntax_error 'OWNER=":nosuchuser:"' "Unknown user ':nosuchuser:', ignoring."
-test_syntax_error 'OWNER="testuser"' "User 'testuser' is not a system user (UID=$(id -u testuser 2>/dev/null)), ignoring."
-test_syntax_error 'OWNER="12345"' "UID=12345 is not in the system user range, ignoring."
+# numeric system UID is valid even if it does not exist
+SYS_UID_MAX=999
+if [[ -e /etc/login.defs ]]; then
+    SYS_UID_MAX=$(awk '$1 == "SYS_UID_MAX" { print $2 }' /etc/login.defs)
+fi
+for ((i=0;i<=SYS_UID_MAX;i++)); do
+    echo "OWNER=\"$i\""
+done >"${rules}"
+assert_0 "${rules}"
+# invalid user name
+test_syntax_error 'OWNER=":nosuchuser:"' "Failed to resolve user ':nosuchuser:', ignoring: Invalid argument"
+# nonexistent user
+if ! getent passwd nosuchuser >/dev/null; then
+    test_syntax_error 'OWNER="nosuchuser"' "Unknown user 'nosuchuser', ignoring."
+fi
+if ! getent passwd 12345 >/dev/null; then
+    test_syntax_error 'OWNER="12345"' "Unknown user '12345', ignoring."
+fi
+# regular user
+test_syntax_error 'OWNER="testuser"' "User 'testuser' is not a system user, ignoring."
+test_syntax_error "OWNER=\"$(id -u testuser)\"" "User '$(id -u testuser)' is not a system user, ignoring."
 test_syntax_error 'GROUP{a}="b"' 'Invalid attribute for GROUP.'
 test_syntax_error 'GROUP-="b"' 'Invalid operator for GROUP.'
 test_syntax_error 'GROUP!="b"' 'Invalid operator for GROUP.'
 test_syntax_error 'GROUP+="0"' "GROUP key takes '=' or ':=' operator, assuming '='."
-test_syntax_error 'GROUP=":nosuchgroup:"' "Unknown group ':nosuchgroup:', ignoring."
-test_syntax_error 'GROUP="testuser"' "Group 'testuser' is not a system group (GID=$(id -u testuser 2>/dev/null)), ignoring."
-test_syntax_error 'GROUP="12345"' "GID=12345 is not in the system group range, ignoring."
+# numeric system GID is valid even if it does not exist
+SYS_GID_MAX=999
+if [[ -e /etc/login.defs ]]; then
+    SYS_GID_MAX=$(awk '$1 == "SYS_GID_MAX" { print $2 }' /etc/login.defs)
+fi
+for ((i=0;i<=SYS_GID_MAX;i++)); do
+    echo "GROUP=\"$i\""
+done >"${rules}"
+assert_0 "${rules}"
+# invalid group name
+test_syntax_error 'GROUP=":nosuchgroup:"' "Failed to resolve group ':nosuchgroup:', ignoring: Invalid argument"
+# nonexistent group
+if ! getent group nosuchgroup >/dev/null; then
+    test_syntax_error 'GROUP="nosuchgroup"' "Unknown group 'nosuchgroup', ignoring."
+fi
+if ! getent group 12345 >/dev/null; then
+    test_syntax_error 'GROUP="12345"' "Unknown group '12345', ignoring."
+fi
+# regular group
+test_syntax_error 'GROUP="testuser"' "Group 'testuser' is not a system group, ignoring."
+test_syntax_error "GROUP=\"$(id -g testuser)\"" "Group '$(id -g testuser)' is not a system group, ignoring."
 test_syntax_error 'MODE{a}="b"' 'Invalid attribute for MODE.'
 test_syntax_error 'MODE-="b"' 'Invalid operator for MODE.'
 test_syntax_error 'MODE!="b"' 'Invalid operator for MODE.'