userdb: replace recursion lock

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.
This commit is contained in:
Lennart Poettering
2020-06-23 17:24:24 +02:00
committed by Zbigniew Jędrzejewski-Szmek
parent 6e78726e20
commit 037b0a47b0
8 changed files with 141 additions and 212 deletions
+2 -3
View File
@@ -659,7 +659,6 @@ static int process_connection(VarlinkServer *server, int fd) {
static int run(int argc, char *argv[]) {
usec_t start_time, listen_idle_usec, last_busy_usec = USEC_INFINITY;
_cleanup_(varlink_server_unrefp) VarlinkServer *server = NULL;
_cleanup_close_ int lock = -1;
unsigned n_iterations = 0;
int m, listen_fd, r;
@@ -696,8 +695,8 @@ static int run(int argc, char *argv[]) {
return log_error_errno(r, "Failed to parse USERDB_FIXED_WORKER: %m");
listen_idle_usec = r ? USEC_INFINITY : LISTEN_IDLE_USEC;
lock = userdb_nss_compat_disable();
if (lock < 0)
r = userdb_block_nss_systemd(true);
if (r < 0)
return log_error_errno(r, "Failed to disable userdb NSS compatibility: %m");
start_time = now(CLOCK_MONOTONIC);