]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
userdb: synthesize stub user records for the foreign UID
authorLennart Poettering <lennart@poettering.net>
Tue, 12 Nov 2024 16:04:11 +0000 (17:04 +0100)
committerLennart Poettering <lennart@poettering.net>
Wed, 8 Jan 2025 20:41:03 +0000 (21:41 +0100)
man/userdbctl.xml
src/nspawn/nspawn-bind-user.c
src/nss-systemd/nss-systemd.c
src/shared/userdb.c
src/shared/userdb.h
src/userdb/userdbctl.c

index 56d0068e7355c2314c43264a4ca7bc307268728d..f7b0c1d9ebb984a9fd563f2bd0176c935436e4df 100644 (file)
         <term><option>--synthesize=<replaceable>BOOL</replaceable></option></term>
 
         <listitem><para>Controls whether to synthesize records for the root and nobody users/groups if they
-        are not defined otherwise. By default (or with <literal>yes</literal>), such records are implicitly
-        synthesized if otherwise missing since they have special significance to the OS. When
-        <literal>no</literal>, this synthesizing is turned off.</para>
+        are not defined otherwise, as well as the user/groups for the "foreign" UID range. By default (or with
+        <literal>yes</literal>), such records are implicitly synthesized if otherwise missing since they have
+        special significance to the OS. When <literal>no</literal>, this synthesizing is turned off.</para>
 
         <xi:include href="version-info.xml" xpointer="v245"/></listitem>
       </varlistentry>
index d64a89f161c3f1346b0b361bb236c82da650c200..749accdce8eb6e60ae6fbacf932e821ebc0aaeae 100644 (file)
@@ -231,7 +231,7 @@ int bind_user_prepare(
                 _cleanup_(group_record_unrefp) GroupRecord *g = NULL, *cg = NULL;
                 _cleanup_free_ char *sm = NULL, *sd = NULL;
 
-                r = userdb_by_name(*n, USERDB_DONT_SYNTHESIZE, &u);
+                r = userdb_by_name(*n, USERDB_DONT_SYNTHESIZE_INTRINSIC|USERDB_DONT_SYNTHESIZE_FOREIGN, &u);
                 if (r < 0)
                         return log_error_errno(r, "Failed to resolve user '%s': %m", *n);
 
@@ -252,7 +252,7 @@ int bind_user_prepare(
                 if (u->uid >= uid_shift && u->uid < uid_shift + uid_range)
                         return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "UID of user '%s' to map is already in container UID range, refusing.", u->user_name);
 
-                r = groupdb_by_gid(u->gid, USERDB_DONT_SYNTHESIZE, &g);
+                r = groupdb_by_gid(u->gid, USERDB_DONT_SYNTHESIZE_INTRINSIC|USERDB_DONT_SYNTHESIZE_FOREIGN, &g);
                 if (r < 0)
                         return log_error_errno(r, "Failed to resolve group of user '%s': %m", u->user_name);
 
index 8e8d4cf1cb6651ca104a4487861b82365d400894..d686d920fc906965c45b36cecb8f72a24b4fea65 100644 (file)
@@ -615,7 +615,7 @@ enum nss_status _nss_systemd_setpwent(int stayopen) {
          * (think: LDAP/NIS type situations), and our synthesizing of root/nobody is a robustness fallback
          * only, which matters for getpwnam()/getpwuid() primarily, which are the main NSS entrypoints to the
          * user database. */
-        r = userdb_all(nss_glue_userdb_flags() | USERDB_DONT_SYNTHESIZE, &getpwent_data.iterator);
+        r = userdb_all(nss_glue_userdb_flags() | USERDB_DONT_SYNTHESIZE_INTRINSIC | USERDB_DONT_SYNTHESIZE_FOREIGN, &getpwent_data.iterator);
         return r < 0 ? NSS_STATUS_UNAVAIL : NSS_STATUS_SUCCESS;
 }
 
@@ -634,8 +634,8 @@ enum nss_status _nss_systemd_setgrent(int stayopen) {
         getgrent_data.iterator = userdb_iterator_free(getgrent_data.iterator);
         getgrent_data.by_membership = false;
 
-        /* See _nss_systemd_setpwent() for an explanation why we use USERDB_DONT_SYNTHESIZE here */
-        r = groupdb_all(nss_glue_userdb_flags() | USERDB_DONT_SYNTHESIZE, &getgrent_data.iterator);
+        /* See _nss_systemd_setpwent() for an explanation why we use USERDB_DONT_SYNTHESIZE_INTRINSIC here */
+        r = groupdb_all(nss_glue_userdb_flags() | USERDB_DONT_SYNTHESIZE_INTRINSIC | USERDB_DONT_SYNTHESIZE_FOREIGN, &getgrent_data.iterator);
         return r < 0 ? NSS_STATUS_UNAVAIL : NSS_STATUS_SUCCESS;
 }
 
@@ -654,8 +654,8 @@ enum nss_status _nss_systemd_setspent(int stayopen) {
         getspent_data.iterator = userdb_iterator_free(getspent_data.iterator);
         getspent_data.by_membership = false;
 
-        /* See _nss_systemd_setpwent() for an explanation why we use USERDB_DONT_SYNTHESIZE here */
-        r = userdb_all(nss_glue_userdb_flags() | USERDB_DONT_SYNTHESIZE, &getspent_data.iterator);
+        /* See _nss_systemd_setpwent() for an explanation why we use USERDB_DONT_SYNTHESIZE_INTRINSIC here */
+        r = userdb_all(nss_glue_userdb_flags() | USERDB_DONT_SYNTHESIZE_INTRINSIC | USERDB_DONT_SYNTHESIZE_FOREIGN, &getspent_data.iterator);
         return r < 0 ? NSS_STATUS_UNAVAIL : NSS_STATUS_SUCCESS;
 }
 
@@ -675,7 +675,7 @@ enum nss_status _nss_systemd_setsgent(int stayopen) {
         getsgent_data.by_membership = false;
 
         /* See _nss_systemd_setpwent() for an explanation why we use USERDB_DONT_SYNTHESIZE here */
-        r = groupdb_all(nss_glue_userdb_flags() | USERDB_DONT_SYNTHESIZE, &getsgent_data.iterator);
+        r = groupdb_all(nss_glue_userdb_flags() | USERDB_DONT_SYNTHESIZE_INTRINSIC | USERDB_DONT_SYNTHESIZE_FOREIGN, &getsgent_data.iterator);
         return r < 0 ? NSS_STATUS_UNAVAIL : NSS_STATUS_SUCCESS;
 }
 
index ff83d4bf902683e0eef6ae2ebc1e884d07c111de..c7334e820d5e2a8e2f41eee03497192353dfcd2b 100644 (file)
 #include "set.h"
 #include "socket-util.h"
 #include "strv.h"
+#include "uid-classification.h"
 #include "user-record-nss.h"
 #include "user-util.h"
-#include "userdb-dropin.h"
 #include "userdb.h"
+#include "userdb-dropin.h"
 
 DEFINE_PRIVATE_HASH_OPS_WITH_VALUE_DESTRUCTOR(link_hash_ops, void, trivial_hash_func, trivial_compare_func, sd_varlink, sd_varlink_unref);
 
@@ -116,8 +117,8 @@ static UserDBIterator* userdb_iterator_new(LookupWhat what, UserDBFlags flags) {
         *i = (UserDBIterator) {
                 .what = what,
                 .flags = flags,
-                .synthesize_root = !FLAGS_SET(flags, USERDB_DONT_SYNTHESIZE),
-                .synthesize_nobody = !FLAGS_SET(flags, USERDB_DONT_SYNTHESIZE),
+                .synthesize_root = !FLAGS_SET(flags, USERDB_DONT_SYNTHESIZE_INTRINSIC),
+                .synthesize_nobody = !FLAGS_SET(flags, USERDB_DONT_SYNTHESIZE_INTRINSIC),
         };
 
         return i;
@@ -434,7 +435,7 @@ static int userdb_start_query(
         }
 
         /* First, let's talk to the multiplexer, if we can */
-        if ((flags & (USERDB_AVOID_MULTIPLEXER|USERDB_EXCLUDE_DYNAMIC_USER|USERDB_EXCLUDE_NSS|USERDB_EXCLUDE_DROPIN|USERDB_DONT_SYNTHESIZE)) == 0 &&
+        if ((flags & (USERDB_AVOID_MULTIPLEXER|USERDB_EXCLUDE_DYNAMIC_USER|USERDB_EXCLUDE_NSS|USERDB_EXCLUDE_DROPIN|USERDB_DONT_SYNTHESIZE_INTRINSIC|USERDB_DONT_SYNTHESIZE_FOREIGN)) == 0 &&
             !strv_contains(except, "io.systemd.Multiplexer") &&
             (!only || strv_contains(only, "io.systemd.Multiplexer"))) {
                 _cleanup_(sd_json_variant_unrefp) sd_json_variant *patched_query = sd_json_variant_ref(query);
@@ -617,6 +618,63 @@ static int synthetic_nobody_user_build(UserRecord **ret) {
                                           SD_JSON_BUILD_PAIR("disposition", JSON_BUILD_CONST_STRING("intrinsic"))));
 }
 
+static int synthetic_foreign_user_build(uid_t foreign_uid, UserRecord **ret) {
+        assert(ret);
+
+        if (!uid_is_valid(foreign_uid))
+                return -ESRCH;
+        if (foreign_uid > 0xFFFF)
+                return -ESRCH;
+
+        _cleanup_free_ char *un = NULL;
+        if (asprintf(&un, "foreign-" UID_FMT, foreign_uid) < 0)
+                return -ENOMEM;
+
+        _cleanup_free_ char *rn = NULL;
+        if (asprintf(&rn, "Foreign System Image UID " UID_FMT, foreign_uid) < 0)
+                return -ENOMEM;
+
+        return user_record_build(
+                        ret,
+                        SD_JSON_BUILD_OBJECT(
+                                        SD_JSON_BUILD_PAIR("userName", SD_JSON_BUILD_STRING(un)),
+                                        SD_JSON_BUILD_PAIR("realName", SD_JSON_BUILD_STRING(rn)),
+                                        SD_JSON_BUILD_PAIR("uid", SD_JSON_BUILD_UNSIGNED(FOREIGN_UID_BASE + foreign_uid)),
+                                        SD_JSON_BUILD_PAIR("gid", SD_JSON_BUILD_UNSIGNED(FOREIGN_UID_BASE + foreign_uid)),
+                                        SD_JSON_BUILD_PAIR("shell", JSON_BUILD_CONST_STRING(NOLOGIN)),
+                                        SD_JSON_BUILD_PAIR("locked", SD_JSON_BUILD_BOOLEAN(true)),
+                                        SD_JSON_BUILD_PAIR("disposition", JSON_BUILD_CONST_STRING("foreign"))));
+}
+
+static int user_name_foreign_extract_uid(const char *name, uid_t *ret_uid) {
+        int r;
+
+        assert(name);
+        assert(ret_uid);
+
+        /* Parses the inner UID from a user name of the foreign UID range, in the form "foreign-NNN". Returns
+         * > 0 if that worked, 0 if it didn't. */
+
+        const char *e = startswith(name, "foreign-");
+        if (!e)
+                goto nomatch;
+
+        uid_t uid;
+        r = parse_uid(e, &uid);
+        if (r < 0)
+                goto nomatch;
+
+        if (uid > 0xFFFF)
+                goto nomatch;
+
+        *ret_uid = uid;
+        return 1;
+
+nomatch:
+        *ret_uid = UID_INVALID;
+        return 0;
+}
+
 int userdb_by_name(const char *name, UserDBFlags flags, UserRecord **ret) {
         _cleanup_(userdb_iterator_freep) UserDBIterator *iterator = NULL;
         _cleanup_(sd_json_variant_unrefp) sd_json_variant *query = NULL;
@@ -658,7 +716,7 @@ int userdb_by_name(const char *name, UserDBFlags flags, UserRecord **ret) {
                 }
         }
 
-        if (!FLAGS_SET(flags, USERDB_DONT_SYNTHESIZE)) {
+        if (!FLAGS_SET(flags, USERDB_DONT_SYNTHESIZE_INTRINSIC)) {
                 if (streq(name, "root"))
                         return synthetic_root_user_build(ret);
 
@@ -666,6 +724,16 @@ int userdb_by_name(const char *name, UserDBFlags flags, UserRecord **ret) {
                         return synthetic_nobody_user_build(ret);
         }
 
+        if (!FLAGS_SET(flags, USERDB_DONT_SYNTHESIZE_FOREIGN)) {
+                uid_t foreign_uid;
+                r = user_name_foreign_extract_uid(name, &foreign_uid);
+                if (r < 0)
+                        return r;
+                if (r > 0)
+                        return synthetic_foreign_user_build(foreign_uid, ret);
+                r = -ESRCH;
+        }
+
         return r;
 }
 
@@ -708,7 +776,7 @@ int userdb_by_uid(uid_t uid, UserDBFlags flags, UserRecord **ret) {
                 }
         }
 
-        if (!FLAGS_SET(flags, USERDB_DONT_SYNTHESIZE)) {
+        if (!FLAGS_SET(flags, USERDB_DONT_SYNTHESIZE_INTRINSIC)) {
                 if (uid == 0)
                         return synthetic_root_user_build(ret);
 
@@ -716,6 +784,9 @@ int userdb_by_uid(uid_t uid, UserDBFlags flags, UserRecord **ret) {
                         return synthetic_nobody_user_build(ret);
         }
 
+        if (!FLAGS_SET(flags, USERDB_DONT_SYNTHESIZE_FOREIGN) && uid_is_foreign(uid))
+                return synthetic_foreign_user_build(uid - FOREIGN_UID_BASE, ret);
+
         return r;
 }
 
@@ -751,6 +822,8 @@ int userdb_all(UserDBFlags flags, UserDBIterator **ret) {
                         log_debug_errno(r, "Failed to find user drop-ins, ignoring: %m");
         }
 
+        /* Note that we do not enumerate the foreign users, since those would be just 64K of noise */
+
         /* propagate IPC error, but only if there are no drop-ins */
         if (qr < 0 &&
             !iterator->nss_iterating &&
@@ -888,6 +961,31 @@ static int synthetic_nobody_group_build(GroupRecord **ret) {
                                           SD_JSON_BUILD_PAIR("disposition", JSON_BUILD_CONST_STRING("intrinsic"))));
 }
 
+static int synthetic_foreign_group_build(gid_t foreign_gid, GroupRecord **ret) {
+        assert(ret);
+
+        if (!gid_is_valid(foreign_gid))
+                return -ESRCH;
+        if (foreign_gid > 0xFFFF)
+                return -ESRCH;
+
+        _cleanup_free_ char *gn = NULL;
+        if (asprintf(&gn, "foreign-" GID_FMT, foreign_gid) < 0)
+                return -ENOMEM;
+
+        _cleanup_free_ char *d = NULL;
+        if (asprintf(&d, "Foreign System Image GID " GID_FMT, foreign_gid) < 0)
+                return -ENOMEM;
+
+        return group_record_build(
+                        ret,
+                        SD_JSON_BUILD_OBJECT(
+                                        SD_JSON_BUILD_PAIR("groupName", SD_JSON_BUILD_STRING(gn)),
+                                        SD_JSON_BUILD_PAIR("description", SD_JSON_BUILD_STRING(d)),
+                                        SD_JSON_BUILD_PAIR("gid", SD_JSON_BUILD_UNSIGNED(FOREIGN_UID_BASE + foreign_gid)),
+                                        SD_JSON_BUILD_PAIR("disposition", JSON_BUILD_CONST_STRING("foreign"))));
+}
+
 int groupdb_by_name(const char *name, UserDBFlags flags, GroupRecord **ret) {
         _cleanup_(userdb_iterator_freep) UserDBIterator *iterator = NULL;
         _cleanup_(sd_json_variant_unrefp) sd_json_variant *query = NULL;
@@ -926,7 +1024,7 @@ int groupdb_by_name(const char *name, UserDBFlags flags, GroupRecord **ret) {
                 }
         }
 
-        if (!FLAGS_SET(flags, USERDB_DONT_SYNTHESIZE)) {
+        if (!FLAGS_SET(flags, USERDB_DONT_SYNTHESIZE_INTRINSIC)) {
                 if (streq(name, "root"))
                         return synthetic_root_group_build(ret);
 
@@ -934,6 +1032,16 @@ int groupdb_by_name(const char *name, UserDBFlags flags, GroupRecord **ret) {
                         return synthetic_nobody_group_build(ret);
         }
 
+        if (!FLAGS_SET(flags, USERDB_DONT_SYNTHESIZE_FOREIGN)) {
+                uid_t foreign_gid;
+                r = user_name_foreign_extract_uid(name, &foreign_gid); /* Same for UID + GID */
+                if (r < 0)
+                        return r;
+                if (r > 0)
+                        return synthetic_foreign_group_build(foreign_gid, ret);
+                r = -ESRCH;
+        }
+
         return r;
 }
 
@@ -975,7 +1083,7 @@ int groupdb_by_gid(gid_t gid, UserDBFlags flags, GroupRecord **ret) {
                 }
         }
 
-        if (!FLAGS_SET(flags, USERDB_DONT_SYNTHESIZE)) {
+        if (!FLAGS_SET(flags, USERDB_DONT_SYNTHESIZE_INTRINSIC)) {
                 if (gid == 0)
                         return synthetic_root_group_build(ret);
 
@@ -983,6 +1091,9 @@ int groupdb_by_gid(gid_t gid, UserDBFlags flags, GroupRecord **ret) {
                         return synthetic_nobody_group_build(ret);
         }
 
+        if (!FLAGS_SET(flags, USERDB_DONT_SYNTHESIZE_FOREIGN) && gid_is_foreign(gid))
+                return synthetic_foreign_group_build(gid - FOREIGN_UID_BASE, ret);
+
         return r;
 }
 
index 75eb4b2dce8b57b291a457d11b00afc5b0231e03..daf87fb5cf5b5c3c017d9001f8c6ad6911ed2383 100644 (file)
@@ -16,19 +16,20 @@ DEFINE_TRIVIAL_CLEANUP_FUNC(UserDBIterator*, userdb_iterator_free);
 
 typedef enum UserDBFlags {
         /* The main sources */
-        USERDB_EXCLUDE_NSS          = 1 << 0,  /* don't do client-side nor server-side NSS */
-        USERDB_EXCLUDE_VARLINK      = 1 << 1,  /* don't talk to any varlink services */
-        USERDB_EXCLUDE_DROPIN       = 1 << 2,  /* don't load drop-in user/group definitions */
+        USERDB_EXCLUDE_NSS               = 1 << 0,  /* don't do client-side nor server-side NSS */
+        USERDB_EXCLUDE_VARLINK           = 1 << 1,  /* don't talk to any varlink services */
+        USERDB_EXCLUDE_DROPIN            = 1 << 2,  /* don't load drop-in user/group definitions */
 
         /* Modifications */
-        USERDB_SUPPRESS_SHADOW      = 1 << 3,  /* don't do client-side shadow calls (server side might happen though) */
-        USERDB_EXCLUDE_DYNAMIC_USER = 1 << 4,  /* exclude looking up in io.systemd.DynamicUser */
-        USERDB_AVOID_MULTIPLEXER    = 1 << 5,  /* exclude looking up via io.systemd.Multiplexer */
-        USERDB_DONT_SYNTHESIZE      = 1 << 6,  /* don't synthesize root/nobody */
+        USERDB_SUPPRESS_SHADOW           = 1 << 3,  /* don't do client-side shadow calls (server side might happen though) */
+        USERDB_EXCLUDE_DYNAMIC_USER      = 1 << 4,  /* exclude looking up in io.systemd.DynamicUser */
+        USERDB_AVOID_MULTIPLEXER         = 1 << 5,  /* exclude looking up via io.systemd.Multiplexer */
+        USERDB_DONT_SYNTHESIZE_INTRINSIC = 1 << 6,  /* don't synthesize root/nobody */
+        USERDB_DONT_SYNTHESIZE_FOREIGN   = 1 << 7,  /* don't synthesize foreign UID records */
 
         /* Combinations */
-        USERDB_NSS_ONLY = USERDB_EXCLUDE_VARLINK|USERDB_EXCLUDE_DROPIN|USERDB_DONT_SYNTHESIZE,
-        USERDB_DROPIN_ONLY = USERDB_EXCLUDE_NSS|USERDB_EXCLUDE_VARLINK|USERDB_DONT_SYNTHESIZE,
+        USERDB_NSS_ONLY = USERDB_EXCLUDE_VARLINK|USERDB_EXCLUDE_DROPIN|USERDB_DONT_SYNTHESIZE_INTRINSIC|USERDB_DONT_SYNTHESIZE_FOREIGN,
+        USERDB_DROPIN_ONLY = USERDB_EXCLUDE_NSS|USERDB_EXCLUDE_VARLINK|USERDB_DONT_SYNTHESIZE_INTRINSIC|USERDB_DONT_SYNTHESIZE_FOREIGN,
 } UserDBFlags;
 
 /* Well-known errors we'll return here:
index 5a0359dccf1b2fd77f5097fff2327821b1642a43..ee6e6c869a02365e9667c4ea5d383d20b02e0152 100644 (file)
@@ -1337,7 +1337,7 @@ static int parse_argv(int argc, char *argv[]) {
                         break;
 
                 case 'N':
-                        arg_userdb_flags |= USERDB_EXCLUDE_NSS|USERDB_DONT_SYNTHESIZE;
+                        arg_userdb_flags |= USERDB_EXCLUDE_NSS|USERDB_DONT_SYNTHESIZE_INTRINSIC|USERDB_DONT_SYNTHESIZE_FOREIGN;
                         break;
 
                 case ARG_WITH_NSS:
@@ -1369,7 +1369,7 @@ static int parse_argv(int argc, char *argv[]) {
                         if (r < 0)
                                 return r;
 
-                        SET_FLAG(arg_userdb_flags, USERDB_DONT_SYNTHESIZE, !r);
+                        SET_FLAG(arg_userdb_flags, USERDB_DONT_SYNTHESIZE_INTRINSIC|USERDB_DONT_SYNTHESIZE_FOREIGN, !r);
                         break;
 
                 case ARG_MULTIPLEXER: