libnss_systemd currently uses a thread_local recursion guard to
avoid re-entering nss-systemd during NSS lookups.
Since libnss_systemd.so.2 is loaded lazily by glibc, accessing ELF TLS
may trigger dynamic TLS allocation in __tls_get_addr(). Under allocation
failure conditions, glibc terminates the process from the dynamic loader
instead of allowing the NSS module to return a normal failure.
Replace the recursion guard with POSIX thread-specific data to preserve the
same per-thread semantics while avoiding ELF TLS in the NSS module.
Note that pthread_setspecific() may still allocate internally on first use
per thread. The key improvement is that any such failure is returned
as a normal error code rather than terminating the process from inside
the dynamic loader.
Related: #42559
nss_count_strv() counts trailing NULL pointers in n. The pointer area
then used (n + 1), reserving one slot more than the size check
accounted for.
Drop the + 1 since n already includes the trailing NULLs, unlike the
non-shadow nss_pack_group_record() where n does not.
Fixes: https://github.com/systemd/systemd/issues/41591
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.
The sg_adm and sg_mem fields are supposed to point to a NULL terminated
string array. If these are NULL, some foreign tools like shadow's sg
trigger NULL pointer dereferences (or fortunately their asset() calls).
Let's not leak details from src/shared and src/libsystemd into
src/basic, even though you can't actually do anything useful with
just forward declarations from src/shared.
The sd-forward.h header is put in src/libsystemd/sd-common as we
don't have a directory for shared internal headers for libsystemd
yet.
Let's also rename forward.h to basic-forward.h to keep things
self-explanatory.
Then, also make nss modules parse $SYSTEMD_ASSERT_RETURN_IS_CRITICAL
environment variable.
This also moves nss-util.c and nss-util.h from src/basic/ to src/shared/,
as they are not used by libsystemd.
This moves around the UserDBMatch handling, moves it out of userdbctl
and into generic userdb code, so that it can be passed to the server
side, to allow server side filtering.
This is preparation for one day allowing complex software to do such
filtering server side, and thus reducing the necessary traffic.
Right now no server side actually knows this, hence care is taken to
downgrade to the userdb varlink API as it was in v257 in case the new
options are not understood. This retains compatibility with any
implementation hence.
This fixes builds on Fedora:
../src/nss-systemd/nss-systemd.c:105:18: error: zero as null pointer constant [-Werror=zero-as-null-pointer-constant]
105 | .mutex = PTHREAD_MUTEX_INITIALIZER,
| ^~~~~~~~~~~~~~~~~~~~~~~~~
../src/nss-systemd/nss-systemd.c:105:18: error: zero as null pointer constant [-Werror=zero-as-null-pointer-constant]
../src/nss-systemd/nss-systemd.c:109:18: error: zero as null pointer constant [-Werror=zero-as-null-pointer-constant]
109 | .mutex = PTHREAD_MUTEX_INITIALIZER,
| ^~~~~~~~~~~~~~~~~~~~~~~~~
../src/nss-systemd/nss-systemd.c:109:18: error: zero as null pointer constant [-Werror=zero-as-null-pointer-constant]
../src/nss-systemd/nss-systemd.c:113:18: error: zero as null pointer constant [-Werror=zero-as-null-pointer-constant]
113 | .mutex = PTHREAD_MUTEX_INITIALIZER,
| ^~~~~~~~~~~~~~~~~~~~~~~~~
../src/nss-systemd/nss-systemd.c:113:18: error: zero as null pointer constant [-Werror=zero-as-null-pointer-constant]
../src/nss-systemd/nss-systemd.c:117:18: error: zero as null pointer constant [-Werror=zero-as-null-pointer-constant]
117 | .mutex = PTHREAD_MUTEX_INITIALIZER,
| ^~~~~~~~~~~~~~~~~~~~~~~~~
../src/nss-systemd/nss-systemd.c:117:18: error: zero as null pointer constant [-Werror=zero-as-null-pointer-constant]
cc1: all warnings being treated as errors
The glibc API is behind the wrapper is called "secure_getenv()", hence
our wrapper really should keep the order too, otherwise things are just
too confusing.
Build targets should have a link dependency on the version scripts they
use. This also uses absolute paths in anticipation for meson 1.3
needlessly deprecating file to string conversions.
IN C23, thread_local is a reserved keyword and we shall therefore
do nothing to redefine it. glibc has it defined for older standard
version with the right conditions.
v2 by Yu Watanabe:
Move the definition to missing_threads.h like the way we define e.g.
missing syscalls or missing definitions, and include it by the users.
Co-authored-by: Yu Watanabe <watanabe.yu+github@gmail.com>
/bin/sh as a shell is punishing. There is no good reason to make
the occasional root login unpleasant.
Since /bin/sh is usually /bin/bash in compat mode, i.e. if one is
available, the other will be too, /bin/bash is almost as good as a default.
But to avoid a regression in the situation where /bin/bash (or
DEFAULT_USER_SHELL) is not installed, we check with access() and fall back
to /bin/sh. This should make this change in behaviour less risky.
(FWIW, e.g. Fedora/RHEL use /bin/bash as default for root.)
This is a follow-up of sorts for 53350c7bba,
which added the default-user-shell option, but most likely with the idea
of using /bin/bash less ;)
Fixes#24369.
log_parse_environment() uses should_parse_proc_cmdline() to determine whether
it should parse settings from the kernel command line. But the checks that
should_parse_proc_cmdline() apply to the whole process, and we could get a positive
answer also when log_parse_environment() was called from one of the nss modules.
In case of nss-modules, we don't want to look at the kernel command line.
log_parse_environment_variables() that only looks at the environment variables
is split out and used in the nss modules.
Fixes#22020.
LLVM 13 introduced `-Wunused-but-set-variable` diagnostic flag, which
trips over some intentionally set-but-not-used variables or variables
attached to cleanup handlers with side effects (`_cleanup_umask_`,
`_cleanup_(notify_on_cleanup)`, `_cleanup_(restore_sigsetp)`, etc.):
```
../src/basic/process-util.c:1257:46: error: variable 'saved_ssp' set but not used [-Werror,-Wunused-but-set-variable]
_cleanup_(restore_sigsetp) sigset_t *saved_ssp = NULL;
^
1 error generated.
```
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
getpwnam_r() guarantees that the strings in the struct passwd that it
returns are pointers into the buffer allocated by the application and
passed to getpwnam_r(). This means applications may choose to modify the
strings in place, as long as the length of the strings is not increased.
So it's wrong for us to return a static string here, we really do have
to copy it into the application-provided buffer like we do for all the
other strings.
This is only a theoretical problem since it would be very weird for an
application to modify the pw_passwd field, but I spotted this when
investigating a similar crash caused by glib editing a different field.
See also:
https://gitlab.gnome.org/GNOME/glib/-/merge_requests/2244
This ensures we not only synthesize regular paswd/group records of
userdb records, but shadow records as well. This should make sure that
userdb can be used as comprehensive superset of the classic
passwd/group/shadow/gshadow functionality.
Setting the flags means we won#t try to read the data from /etc/shadow
when reading a user record, thus slightly making conversion quicker and
reducing the chance of generating MAC faults, because we needlessly
access a privileged resource. Previously, passing the flag didn't
matter, when converting our JSON records to NSS since the flag only had
an effect on whether to use NSS getspnam() and related calls or not. But
given that we turn off NSS anyway as backend for this conversion (since
we want to avoid NSS loops, where we turn NSS data to our JSON user
records, and then to NSS forever and ever) it was unnecessary to pass
it.
This changed in one of the previous commits however, where we added
support for reading user definitions from drop-in files, with separate
drop-in files for the shadow data.
Let's use "exclude" for flags that really exclude records from our
lookup. Let's use "avoid" referring to concepts that when flag is set
we'll not use but we have a fallback path for that should yield the same
result. Let' use "suppress" for suppressing partial info, even if we
return the record otherwise.
So far we used "avoid" for all these cases, which was confusing.
Whiel we are at it, let's reassign the bits a bit, leaving some space
for bits follow-up commits are going to add.
When we are queried for membership lists on a system that has exactly
zero, then we'll return ESRCH immediately instead of at EOF. Which is
OK, but we need to handle this in various places, and not get confused
by it.
Let's add three defines for the 3 special cases of passwords.
Some of our tools used different values for the "locked"/"invalid" case,
let's settle on using "!*" which means the password is both locked *and*
invalid.
Other tools like to use "!!" for this case, which however is less than
ideal I think, since the this could also be a considered an entry with
an empty password, that can be enabled again by unlocking it twice.
They both are both short and contain similar parts and various helper will be
shared between both parts of the code so it's easier to use a single file.
Previously we'd used the existance of a specific AF_UNIX socket in the
abstract namespace as lock for disabling lookup recursions. (for
breaking out of the loop: userdb synthesized from nss → nss synthesized
from userdb → userdb synthesized from nss → …)
I did it like that because it promised to work the same both in static
and in dynmically linked environments and is accessible easily from any
programming language.
However, it has a weakness regarding reuse attacks: the socket is
securely hashed (siphash) from the thread ID in combination with the
AT_RANDOM secret. Thus it should not be guessable from an attacker in
advance. That's only true if a thread takes the lock only once and
keeps it forever. However, if a thread takes and releases it multiple
times an attacker might monitor that and quickly take the lock
after the first iteration for follow-up iterations.
It's not a big issue given that userdb (as the primary user for this)
never released the lock and we never made the concept a public
interface, and it was only included in one release so far, but it's
something that deserves fixing. (moreover it's a local DoS only, only
permitting to disable native userdb lookups)
With this rework the libnss_systemd.so.2 module will now export two
additional symbols. These symbols are not used by glibc, but can be used
by arbitrary programs: one can be used to disable nss-systemd, the other
to check if it is currently disabled.
The lock is per-thread. It's slightly less pretty, since it requires
people to manually link against C code via dlopen()/dlsym(), but it
should work safely without the aforementioned weakness.