351 lines
13 KiB
Diff
351 lines
13 KiB
Diff
From 055ba736e12255cf79acc81aac382344129d03c5 Mon Sep 17 00:00:00 2001
|
|
From: Michael Catanzaro <mcatanzaro@redhat.com>
|
|
Date: Wed, 8 Sep 2021 16:51:16 -0500
|
|
Subject: [PATCH] nss-systemd: ensure returned strings point into provided
|
|
buffer
|
|
|
|
Jamie Bainbridge found an issue where glib's g_get_user_database_entry()
|
|
may crash after doing:
|
|
|
|
```
|
|
error = getpwnam_r (logname, &pwd, buffer, bufsize, &pw);
|
|
// ...
|
|
pw->pw_name[0] = g_ascii_toupper (pw->pw_name[0]);
|
|
```
|
|
|
|
in order to uppercase the first letter of the user's real name. This is
|
|
a glib bug, because there is a different codepath that gets the pwd from
|
|
vanilla getpwnam instead of getpwnam_r as shown here. When the pwd
|
|
struct is returned by getpwnam, its fields point to static data owned by
|
|
glibc/NSS, and so it must not be modified by the caller. After much
|
|
debugging, Jamie Bainbridge has fixed this in https://gitlab.gnome.org/GNOME/glib/-/merge_requests/2244
|
|
by making a copy of the data before modifying it, and that resolves all
|
|
problems for glib. Yay!
|
|
|
|
However, glib is crashing even when getpwnam_r is used instead of
|
|
getpwnam! According to getpwnam_r(3), the strings in the pwd struct are
|
|
supposed to be pointers into the buffer passed by the caller, so glib
|
|
should be able to safely edit it directly in this case, so long as it
|
|
doesn't try to increase the size of any of the strings.
|
|
|
|
Problem is various functions throughout nss-systemd.c return synthesized
|
|
records declared at the top of the file. These records are returned
|
|
directly and so contain pointers to static strings owned by
|
|
libsystemd-nss. systemd must instead copy all the strings into the
|
|
provided buffer.
|
|
|
|
This crash is reproducible if nss-systemd is listed first on the passwd
|
|
line in /etc/nsswitch.conf, and the application looks up one of the
|
|
synthesized user accounts "root" or "nobody", and finally the
|
|
application attempts to edit one of the strings in the returned struct.
|
|
All our synthesized records for the other struct types have the same
|
|
problem, so this commit fixes them all at once.
|
|
|
|
Fixes #20679
|
|
|
|
(cherry picked from commit 47fd7fa6c650d7a0ac41bc89747e3b866ffb9534)
|
|
|
|
Conflict:NA
|
|
Reference:https://github.com/systemd/systemd/commit/055ba736e12255cf79acc81aac382344129d03c5
|
|
---
|
|
src/nss-systemd/nss-systemd.c | 204 ++++++++++++++++++++++++++++------
|
|
1 file changed, 168 insertions(+), 36 deletions(-)
|
|
|
|
diff --git a/src/nss-systemd/nss-systemd.c b/src/nss-systemd/nss-systemd.c
|
|
index 1b0866109a..1840a0d508 100644
|
|
--- a/src/nss-systemd/nss-systemd.c
|
|
+++ b/src/nss-systemd/nss-systemd.c
|
|
@@ -2,6 +2,7 @@
|
|
|
|
#include <nss.h>
|
|
#include <pthread.h>
|
|
+#include <string.h>
|
|
|
|
#include "env-util.h"
|
|
#include "errno-util.h"
|
|
@@ -139,6 +140,155 @@ NSS_GRENT_PROTOTYPES(systemd);
|
|
NSS_SGENT_PROTOTYPES(systemd);
|
|
NSS_INITGROUPS_PROTOTYPE(systemd);
|
|
|
|
+/* Since our NSS functions implement reentrant glibc APIs, we have to guarantee
|
|
+ * all the string pointers we return point into the buffer provided by the
|
|
+ * caller, not into our own static memory. */
|
|
+
|
|
+static enum nss_status copy_synthesized_passwd(
|
|
+ struct passwd *dest,
|
|
+ const struct passwd *src,
|
|
+ char *buffer, size_t buflen,
|
|
+ int *errnop) {
|
|
+
|
|
+ size_t required;
|
|
+
|
|
+ assert(dest);
|
|
+ assert(src);
|
|
+ assert(src->pw_name);
|
|
+ assert(src->pw_passwd);
|
|
+ assert(src->pw_gecos);
|
|
+ assert(src->pw_dir);
|
|
+ assert(src->pw_shell);
|
|
+
|
|
+ required = strlen(src->pw_name) + 1;
|
|
+ required += strlen(src->pw_passwd) + 1;
|
|
+ required += strlen(src->pw_gecos) + 1;
|
|
+ required += strlen(src->pw_dir) + 1;
|
|
+ required += strlen(src->pw_shell) + 1;
|
|
+
|
|
+ if (buflen < required) {
|
|
+ *errnop = ERANGE;
|
|
+ return NSS_STATUS_TRYAGAIN;
|
|
+ }
|
|
+
|
|
+ assert(buffer);
|
|
+
|
|
+ *dest = *src;
|
|
+
|
|
+ /* String fields point into the user-provided buffer */
|
|
+ dest->pw_name = buffer;
|
|
+ dest->pw_passwd = stpcpy(dest->pw_name, src->pw_name) + 1;
|
|
+ dest->pw_gecos = stpcpy(dest->pw_passwd, src->pw_passwd) + 1;
|
|
+ dest->pw_dir = stpcpy(dest->pw_gecos, src->pw_gecos) + 1;
|
|
+ dest->pw_shell = stpcpy(dest->pw_dir, src->pw_dir) + 1;
|
|
+ strcpy(dest->pw_shell, src->pw_shell);
|
|
+
|
|
+ return NSS_STATUS_SUCCESS;
|
|
+}
|
|
+
|
|
+static enum nss_status copy_synthesized_spwd(
|
|
+ struct spwd *dest,
|
|
+ const struct spwd *src,
|
|
+ char *buffer, size_t buflen,
|
|
+ int *errnop) {
|
|
+
|
|
+ size_t required;
|
|
+
|
|
+ assert(dest);
|
|
+ assert(src);
|
|
+ assert(src->sp_namp);
|
|
+ assert(src->sp_pwdp);
|
|
+
|
|
+ required = strlen(src->sp_namp) + 1;
|
|
+ required += strlen(src->sp_pwdp) + 1;
|
|
+
|
|
+ if (buflen < required) {
|
|
+ *errnop = ERANGE;
|
|
+ return NSS_STATUS_TRYAGAIN;
|
|
+ }
|
|
+
|
|
+ assert(buffer);
|
|
+
|
|
+ *dest = *src;
|
|
+
|
|
+ /* String fields point into the user-provided buffer */
|
|
+ dest->sp_namp = buffer;
|
|
+ dest->sp_pwdp = stpcpy(dest->sp_namp, src->sp_namp) + 1;
|
|
+ strcpy(dest->sp_pwdp, src->sp_pwdp);
|
|
+
|
|
+ return NSS_STATUS_SUCCESS;
|
|
+}
|
|
+
|
|
+static enum nss_status copy_synthesized_group(
|
|
+ struct group *dest,
|
|
+ const struct group *src,
|
|
+ char *buffer, size_t buflen,
|
|
+ int *errnop) {
|
|
+
|
|
+ size_t required;
|
|
+
|
|
+ assert(dest);
|
|
+ assert(src);
|
|
+ assert(src->gr_name);
|
|
+ assert(src->gr_passwd);
|
|
+ assert(src->gr_mem);
|
|
+ assert(!*src->gr_mem); /* Our synthesized records' gr_mem is always just NULL... */
|
|
+
|
|
+ required = strlen(src->gr_name) + 1;
|
|
+ required += strlen(src->gr_passwd) + 1;
|
|
+ required += 1; /* ...but that NULL still needs to be stored into the buffer! */
|
|
+
|
|
+ if (buflen < required) {
|
|
+ *errnop = ERANGE;
|
|
+ return NSS_STATUS_TRYAGAIN;
|
|
+ }
|
|
+
|
|
+ assert(buffer);
|
|
+
|
|
+ *dest = *src;
|
|
+
|
|
+ /* String fields point into the user-provided buffer */
|
|
+ dest->gr_name = buffer;
|
|
+ dest->gr_passwd = stpcpy(dest->gr_name, src->gr_name) + 1;
|
|
+ dest->gr_mem = (char **) strcpy(dest->gr_passwd, src->gr_passwd) + 1;
|
|
+ *dest->gr_mem = NULL;
|
|
+
|
|
+ return NSS_STATUS_SUCCESS;
|
|
+}
|
|
+
|
|
+static enum nss_status copy_synthesized_sgrp(
|
|
+ struct sgrp *dest,
|
|
+ const struct sgrp *src,
|
|
+ char *buffer, size_t buflen,
|
|
+ int *errnop) {
|
|
+
|
|
+ size_t required;
|
|
+
|
|
+ assert(dest);
|
|
+ assert(src);
|
|
+ assert(src->sg_namp);
|
|
+ assert(src->sg_passwd);
|
|
+
|
|
+ required = strlen(src->sg_namp) + 1;
|
|
+ required += strlen(src->sg_passwd) + 1;
|
|
+
|
|
+ if (buflen < required) {
|
|
+ *errnop = ERANGE;
|
|
+ return NSS_STATUS_TRYAGAIN;
|
|
+ }
|
|
+
|
|
+ assert(buffer);
|
|
+
|
|
+ *dest = *src;
|
|
+
|
|
+ /* String fields point into the user-provided buffer */
|
|
+ dest->sg_namp = buffer;
|
|
+ dest->sg_passwd = stpcpy(dest->sg_namp, src->sg_namp) + 1;
|
|
+ strcpy(dest->sg_passwd, src->sg_passwd);
|
|
+
|
|
+ return NSS_STATUS_SUCCESS;
|
|
+}
|
|
+
|
|
enum nss_status _nss_systemd_getpwnam_r(
|
|
const char *name,
|
|
struct passwd *pwd,
|
|
@@ -164,17 +314,14 @@ enum nss_status _nss_systemd_getpwnam_r(
|
|
/* Synthesize entries for the root and nobody users, in case they are missing in /etc/passwd */
|
|
if (getenv_bool_secure("SYSTEMD_NSS_BYPASS_SYNTHETIC") <= 0) {
|
|
|
|
- if (streq(name, root_passwd.pw_name)) {
|
|
- *pwd = root_passwd;
|
|
- return NSS_STATUS_SUCCESS;
|
|
- }
|
|
+ if (streq(name, root_passwd.pw_name))
|
|
+ return copy_synthesized_passwd(pwd, &root_passwd, buffer, buflen, errnop);
|
|
|
|
if (streq(name, nobody_passwd.pw_name)) {
|
|
if (!synthesize_nobody())
|
|
return NSS_STATUS_NOTFOUND;
|
|
|
|
- *pwd = nobody_passwd;
|
|
- return NSS_STATUS_SUCCESS;
|
|
+ return copy_synthesized_passwd(pwd, &nobody_passwd, buffer, buflen, errnop);
|
|
}
|
|
|
|
} else if (STR_IN_SET(name, root_passwd.pw_name, nobody_passwd.pw_name))
|
|
@@ -211,17 +358,14 @@ enum nss_status _nss_systemd_getpwuid_r(
|
|
/* Synthesize data for the root user and for nobody in case they are missing from /etc/passwd */
|
|
if (getenv_bool_secure("SYSTEMD_NSS_BYPASS_SYNTHETIC") <= 0) {
|
|
|
|
- if (uid == root_passwd.pw_uid) {
|
|
- *pwd = root_passwd;
|
|
- return NSS_STATUS_SUCCESS;
|
|
- }
|
|
+ if (uid == root_passwd.pw_uid)
|
|
+ return copy_synthesized_passwd(pwd, &root_passwd, buffer, buflen, errnop);
|
|
|
|
if (uid == nobody_passwd.pw_uid) {
|
|
if (!synthesize_nobody())
|
|
return NSS_STATUS_NOTFOUND;
|
|
|
|
- *pwd = nobody_passwd;
|
|
- return NSS_STATUS_SUCCESS;
|
|
+ return copy_synthesized_passwd(pwd, &nobody_passwd, buffer, buflen, errnop);
|
|
}
|
|
|
|
} else if (uid == root_passwd.pw_uid || uid == nobody_passwd.pw_uid)
|
|
@@ -259,17 +403,14 @@ enum nss_status _nss_systemd_getspnam_r(
|
|
/* Synthesize entries for the root and nobody users, in case they are missing in /etc/passwd */
|
|
if (getenv_bool_secure("SYSTEMD_NSS_BYPASS_SYNTHETIC") <= 0) {
|
|
|
|
- if (streq(name, root_spwd.sp_namp)) {
|
|
- *spwd = root_spwd;
|
|
- return NSS_STATUS_SUCCESS;
|
|
- }
|
|
+ if (streq(name, root_spwd.sp_namp))
|
|
+ return copy_synthesized_spwd(spwd, &root_spwd, buffer, buflen, errnop);
|
|
|
|
if (streq(name, nobody_spwd.sp_namp)) {
|
|
if (!synthesize_nobody())
|
|
return NSS_STATUS_NOTFOUND;
|
|
|
|
- *spwd = nobody_spwd;
|
|
- return NSS_STATUS_SUCCESS;
|
|
+ return copy_synthesized_spwd(spwd, &nobody_spwd, buffer, buflen, errnop);
|
|
}
|
|
|
|
} else if (STR_IN_SET(name, root_spwd.sp_namp, nobody_spwd.sp_namp))
|
|
@@ -309,17 +450,14 @@ enum nss_status _nss_systemd_getgrnam_r(
|
|
/* Synthesize records for root and nobody, in case they are missing from /etc/group */
|
|
if (getenv_bool_secure("SYSTEMD_NSS_BYPASS_SYNTHETIC") <= 0) {
|
|
|
|
- if (streq(name, root_group.gr_name)) {
|
|
- *gr = root_group;
|
|
- return NSS_STATUS_SUCCESS;
|
|
- }
|
|
+ if (streq(name, root_group.gr_name))
|
|
+ return copy_synthesized_group(gr, &root_group, buffer, buflen, errnop);
|
|
|
|
if (streq(name, nobody_group.gr_name)) {
|
|
if (!synthesize_nobody())
|
|
return NSS_STATUS_NOTFOUND;
|
|
|
|
- *gr = nobody_group;
|
|
- return NSS_STATUS_SUCCESS;
|
|
+ return copy_synthesized_group(gr, &nobody_group, buffer, buflen, errnop);
|
|
}
|
|
|
|
} else if (STR_IN_SET(name, root_group.gr_name, nobody_group.gr_name))
|
|
@@ -356,17 +494,14 @@ enum nss_status _nss_systemd_getgrgid_r(
|
|
/* Synthesize records for root and nobody, in case they are missing from /etc/group */
|
|
if (getenv_bool_secure("SYSTEMD_NSS_BYPASS_SYNTHETIC") <= 0) {
|
|
|
|
- if (gid == root_group.gr_gid) {
|
|
- *gr = root_group;
|
|
- return NSS_STATUS_SUCCESS;
|
|
- }
|
|
+ if (gid == root_group.gr_gid)
|
|
+ return copy_synthesized_group(gr, &root_group, buffer, buflen, errnop);
|
|
|
|
if (gid == nobody_group.gr_gid) {
|
|
if (!synthesize_nobody())
|
|
return NSS_STATUS_NOTFOUND;
|
|
|
|
- *gr = nobody_group;
|
|
- return NSS_STATUS_SUCCESS;
|
|
+ return copy_synthesized_group(gr, &nobody_group, buffer, buflen, errnop);
|
|
}
|
|
|
|
} else if (gid == root_group.gr_gid || gid == nobody_group.gr_gid)
|
|
@@ -404,17 +539,14 @@ enum nss_status _nss_systemd_getsgnam_r(
|
|
/* Synthesize records for root and nobody, in case they are missing from /etc/group */
|
|
if (getenv_bool_secure("SYSTEMD_NSS_BYPASS_SYNTHETIC") <= 0) {
|
|
|
|
- if (streq(name, root_sgrp.sg_namp)) {
|
|
- *sgrp = root_sgrp;
|
|
- return NSS_STATUS_SUCCESS;
|
|
- }
|
|
+ if (streq(name, root_sgrp.sg_namp))
|
|
+ return copy_synthesized_sgrp(sgrp, &root_sgrp, buffer, buflen, errnop);
|
|
|
|
if (streq(name, nobody_sgrp.sg_namp)) {
|
|
if (!synthesize_nobody())
|
|
return NSS_STATUS_NOTFOUND;
|
|
|
|
- *sgrp = nobody_sgrp;
|
|
- return NSS_STATUS_SUCCESS;
|
|
+ return copy_synthesized_sgrp(sgrp, &nobody_sgrp, buffer, buflen, errnop);
|
|
}
|
|
|
|
} else if (STR_IN_SET(name, root_sgrp.sg_namp, nobody_sgrp.sg_namp))
|
|
--
|
|
2.33.0
|
|
|