nss-systemd: avoid ELF TLS for recursion guard
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
This commit is contained in:
committed by
Lennart Poettering
parent
191aa49a8a
commit
19bd80e29a
@@ -4,7 +4,6 @@
|
||||
#include <nss.h>
|
||||
#include <pthread.h>
|
||||
#include <string.h>
|
||||
#include <threads.h>
|
||||
|
||||
#include "alloc-util.h"
|
||||
#include "env-util.h"
|
||||
@@ -1070,28 +1069,72 @@ enum nss_status _nss_systemd_initgroups_dyn(
|
||||
return any ? NSS_STATUS_SUCCESS : NSS_STATUS_NOTFOUND;
|
||||
}
|
||||
|
||||
static thread_local unsigned _blocked = 0;
|
||||
/* Note that we intentionally use POSIX thread-specific data instead of a plain thread_local variable.
|
||||
* A thread_local in this lazily-loaded DSO uses a dynamic TLS model by default and may require
|
||||
* a dynamic TLS allocation. If that allocation fails, glibc calls _exit() from the dynamic linker,
|
||||
* making the failure unrecoverable. Using pthread_key_t avoids ELF TLS entirely and lets any such
|
||||
* failure propagate as a normal error instead of terminating the process. */
|
||||
static pthread_once_t nss_blocked_key_once = PTHREAD_ONCE_INIT;
|
||||
static pthread_key_t nss_blocked_key;
|
||||
static int nss_blocked_key_error;
|
||||
|
||||
static void nss_blocked_key_init(void) {
|
||||
/* NULL destructor: the per-thread value is a plain integer counter encoded as void*,
|
||||
* not a heap allocation, so nothing needs to be freed at thread exit.
|
||||
* No pthread_key_delete: this library is linked with -z nodelete and always opened with
|
||||
* RTLD_NODELETE, so it is never unloaded and the key exists for the process lifetime. */
|
||||
nss_blocked_key_error = pthread_key_create(&nss_blocked_key, NULL);
|
||||
}
|
||||
|
||||
static int nss_blocked_key_ensure(void) {
|
||||
int r;
|
||||
|
||||
r = pthread_once(&nss_blocked_key_once, nss_blocked_key_init);
|
||||
if (r != 0)
|
||||
return -r;
|
||||
|
||||
if (nss_blocked_key_error != 0)
|
||||
return -nss_blocked_key_error;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
_public_ int _nss_systemd_block(bool b) {
|
||||
int r;
|
||||
uintptr_t blocked;
|
||||
|
||||
r = nss_blocked_key_ensure();
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
blocked = (uintptr_t) pthread_getspecific(nss_blocked_key);
|
||||
|
||||
/* This blocks recursively: it's blocked for as many times this function is called with `true` until
|
||||
* it is called an equal time with `false`. */
|
||||
|
||||
if (b) {
|
||||
if (_blocked >= UINT_MAX)
|
||||
if (blocked >= UINTPTR_MAX)
|
||||
return -EOVERFLOW;
|
||||
|
||||
_blocked++;
|
||||
blocked++;
|
||||
} else {
|
||||
if (_blocked <= 0)
|
||||
if (blocked == 0)
|
||||
return -EOVERFLOW;
|
||||
|
||||
_blocked--;
|
||||
blocked--;
|
||||
}
|
||||
|
||||
r = pthread_setspecific(nss_blocked_key, (void*) blocked);
|
||||
/* Ignore failure on the unblock path: callers may assert on it. */
|
||||
if (r != 0 && b)
|
||||
return -r;
|
||||
|
||||
return b; /* Return what is passed in, i.e. the new state from the PoV of the caller */
|
||||
}
|
||||
|
||||
_public_ bool _nss_systemd_is_blocked(void) {
|
||||
return _blocked > 0;
|
||||
if (nss_blocked_key_ensure() < 0)
|
||||
return false;
|
||||
|
||||
return (uintptr_t) pthread_getspecific(nss_blocked_key) > 0;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user