]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
nss-systemd: set sg_adm/sg_mem for all groups
authorTobias Stoeckmann <tobias@stoeckmann.org>
Sat, 27 Dec 2025 14:27:05 +0000 (15:27 +0100)
committerTobias Stoeckmann <tobias@stoeckmann.org>
Sun, 4 Jan 2026 17:01:36 +0000 (18:01 +0100)
Fill sg_adm and sg_mem in nss_pack_group_record_shadow to stay
compatible with other NSS getsgnam implementations which set these
members to NULL terminated string arrays.

Tools like shadow's sg would trigger a NULL pointer dereference with
groups only found through nss-systemd otherwise.

src/nss-systemd/userdb-glue.c

index 70f0ecfbd5f4c1c878309ce754512ba0d5a9cdd6..1d5e311ce86530fcbafb910fe4636b7e00f7b65d 100644 (file)
@@ -411,6 +411,40 @@ enum nss_status userdb_getgrgid(
         return NSS_STATUS_SUCCESS;
 }
 
+/* Counts string pointers (including terminating NULL element) of given
+ * string vector strv and stores amount of pointers in n and total
+ * length of all contained strings including NUL bytes in len. */
+static void nss_count_strv(char * const *strv, size_t *n, size_t *len) {
+        STRV_FOREACH(str, strv) {
+                (*len) += sizeof(char*);  /* space for array entry */
+                (*len) += strlen(*str) + 1;
+                (*n)++;
+        }
+        (*len) += sizeof(char*); /* trailing NULL in array entry */
+        (*n)++;
+}
+
+/* Performs deep copy of given string vector src and stores content
+ * of contained strings into buf with references to these strings
+ * in dst. At dst location, a new NULL-terminated string vector is
+ * created. The dst and buf locations are updated to point just behind
+ * the last pointer or char respectively. Returns total amount of
+ * pointers in newly created string vector in dst, including the
+ * terminating NULL element. */
+static size_t nss_deep_copy_strv(char * const *src, char ***dst, char **buf) {
+        char *p = *buf;
+        size_t i = 0;
+
+        STRV_FOREACH(str, src) {
+                (*dst)[i++] = p;
+                p = stpcpy(p, *str) + 1;
+        }
+        (*dst)[i++] = NULL;
+        *dst += i;
+        *buf = p;
+        return i;
+}
+
 int nss_pack_group_record_shadow(
                 GroupRecord *hr,
                 struct sgrp *sgrp,
@@ -418,7 +452,8 @@ int nss_pack_group_record_shadow(
                 size_t buflen) {
 
         const char *hashed;
-        size_t required;
+        char **array = NULL, *p;
+        size_t i = 0, n = 0, required;
 
         assert(hr);
         assert(sgrp);
@@ -429,15 +464,26 @@ int nss_pack_group_record_shadow(
         assert_se(hashed = strv_isempty(hr->hashed_password) ? PASSWORD_LOCKED_AND_INVALID : hr->hashed_password[0]);
         required += strlen(hashed) + 1;
 
+        nss_count_strv(hr->administrators, &n, &required);
+        nss_count_strv(hr->members, &n, &required);
+
         if (buflen < required)
                 return -ERANGE;
 
-        *sgrp = (struct sgrp) {
-                .sg_namp = buffer,
-        };
-
         assert(buffer);
 
+        p = buffer + sizeof(void*) * (n + 1); /* place member strings right after the ptr array */
+        array = (char**) buffer; /* place ptr array at beginning of buffer, under assumption buffer is aligned */
+
+        sgrp->sg_mem = array;
+        i += nss_deep_copy_strv(hr->members, &array, &p);
+
+        sgrp->sg_adm = array;
+        i += nss_deep_copy_strv(hr->administrators, &array, &p);
+
+        assert_se(i == n);
+
+        sgrp->sg_namp = p;
         sgrp->sg_passwd = stpcpy(sgrp->sg_namp, hr->group_name) + 1;
         strcpy(sgrp->sg_passwd, hashed);