diff options
-rw-r--r-- | malloc/set-freeres.c | 3 | ||||
-rw-r--r-- | nss/Makefile | 2 | ||||
-rw-r--r-- | nss/nss_module.c | 299 | ||||
-rw-r--r-- | nss/nss_module.h | 151 |
4 files changed, 454 insertions, 1 deletions
diff --git a/malloc/set-freeres.c b/malloc/set-freeres.c index aa80eb64b8..b328cca7c6 100644 --- a/malloc/set-freeres.c +++ b/malloc/set-freeres.c @@ -20,6 +20,7 @@ #include <set-hooks.h> #include <libc-internal.h> +#include "../nss/nss_module.h" #include "../libio/libioP.h" DEFINE_HOOK (__libc_subfreeres, (void)); @@ -41,6 +42,8 @@ __libc_freeres (void) { void *const *p; + call_function_static_weak (__nss_module_freeres); + _IO_cleanup (); /* We run the resource freeing after IO cleanup. */ diff --git a/nss/Makefile b/nss/Makefile index 97bab5bb75..5d357eb51e 100644 --- a/nss/Makefile +++ b/nss/Makefile @@ -28,7 +28,7 @@ headers := nss.h routines = nsswitch getnssent getnssent_r digits_dots \ valid_field valid_list_field rewrite_field \ $(addsuffix -lookup,$(databases)) \ - compat-lookup nss_hash + compat-lookup nss_hash nss_module # These are the databases that go through nss dispatch. # Caution: if you add a database here, you must add its real name diff --git a/nss/nss_module.c b/nss/nss_module.c new file mode 100644 index 0000000000..292094a6a5 --- /dev/null +++ b/nss/nss_module.c @@ -0,0 +1,299 @@ +/* Global list of NSS service modules. + Copyright (c) 2020 Free Software Foundation, Inc. + This file is part of the GNU C Library. + + The GNU C Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + The GNU C Library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with the GNU C Library; if not, see + <https://www.gnu.org/licenses/>. */ + +#include <nss_module.h> + +#include <array_length.h> +#include <assert.h> +#include <atomic.h> +#include <dlfcn.h> +#include <gnu/lib-names.h> +#include <libc-lock.h> +#include <stddef.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +/* Suffix after .so of NSS service modules. */ +static const char *const __nss_shlib_revision = LIBNSS_FILES_SO + 15; + +/* A single-linked list used to implement a mapping from names to NSS + modules. (Most systems only use five or so service modules, so a + list is sufficient here.) Elements of this list are never freed + during normal operation. */ +static struct nss_module *nss_module_list; + +/* Covers the list and also loading of individual NSS service + modules. */ +__libc_lock_define (static, nss_module_list_lock); + +struct nss_module * +__nss_module_allocate (const char *name) +{ + __libc_lock_lock (nss_module_list_lock); + + struct nss_module *result = NULL; + for (struct nss_module *p = nss_module_list; p != NULL; p = p->next) + if (strcmp (p->name, name) == 0) + { + /* Return the previously existing object. */ + result = p; + break; + } + + if (result == NULL) + { + /* Allocate a new list entry if the name was not found in the + list. */ + size_t name_length = strlen (name); + result = malloc (sizeof (*result) + name_length + 1); + if (result != NULL) + { + result->state = nss_module_uninitialized; + memcpy (result->name, name, name_length + 1); + result->handle = NULL; + result->next = nss_module_list; + nss_module_list = result; + } + } + + __libc_lock_unlock (nss_module_list_lock); + return result; +} + +/* Long enough to store the name of any function. */ +typedef char function_name[18]; + +/* This must be lexicographically sorted and match struct + nss_module_functions. */ +static const function_name nss_function_name_array[] = + { + "endaliasent", + "endetherent", + "endgrent", + "endhostent", + "endnetent", + "endnetgrent", + "endprotoent", + "endpwent", + "endrpcent", + "endservent", + "endsgent", + "endspent", + "getaliasbyname_r", + "getaliasent_r", + "getcanonname_r", + "getetherent_r", + "getgrent_r", + "getgrgid_r", + "getgrnam_r", + "gethostbyaddr2_r", + "gethostbyaddr_r", + "gethostbyname2_r", + "gethostbyname3_r", + "gethostbyname4_r", + "gethostbyname_r", + "gethostent_r", + "gethostton_r", + "getnetbyaddr_r", + "getnetbyname_r", + "getnetent_r", + "getnetgrent_r", + "getntohost_r", + "getprotobyname_r", + "getprotobynumber_r", + "getprotoent_r", + "getpublickey", + "getpwent_r", + "getpwnam_r", + "getpwuid_r", + "getrpcbyname_r", + "getrpcbynumber_r", + "getrpcent_r", + "getsecretkey", + "getservbyname_r", + "getservbyport_r", + "getservent_r", + "getsgent_r", + "getsgnam_r", + "getspent_r", + "getspnam_r", + "initgroups_dyn", + "netname2user", + "setaliasent", + "setetherent", + "setgrent", + "sethostent", + "setnetent", + "setnetgrent", + "setprotoent", + "setpwent", + "setrpcent", + "setservent", + "setsgent", + "setspent", + }; + +_Static_assert ((array_length (nss_function_name_array) * sizeof (void *)) + == sizeof (struct nss_module_functions), + "length of nss_module_name_array"); +_Static_assert (array_length (nss_function_name_array) + == array_length ((struct nss_module) { 0 }.functions.untyped), + "length of nss_module_name_array"); + +static bool +module_load (struct nss_module *module) +{ + void *handle; + { + char *shlib_name; + if (asprintf (&shlib_name, "libnss_%s.so%s", + module->name, __nss_shlib_revision) < 0) + /* This is definitely a temporary failure. Do not update + module->state. This will trigger another attempt at the next + call. */ + return false; + + handle = __libc_dlopen (shlib_name); + free (shlib_name); + } + + if (handle == NULL) + { + /* dlopen failure. We do not know if this a temporary or + permanent error. See bug 22041. Update the state using the + double-checked locking idiom. */ + + __libc_lock_lock (nss_module_list_lock); + bool result = result; + switch ((enum nss_module_state) atomic_load_acquire (&module->state)) + { + case nss_module_uninitialized: + atomic_store_release (&module->state, nss_module_failed); + result = false; + break; + case nss_module_loaded: + result = true; + break; + case nss_module_failed: + result = false; + break; + } + __libc_lock_unlock (nss_module_list_lock); + return result; + } + + nss_module_functions_untyped pointers; + + for (size_t idx = 0; idx < array_length (nss_function_name_array); ++idx) + { + char *function_name; + if (asprintf (&function_name, "_nss_%s_%s", + module->name, nss_function_name_array[idx]) < 0) + { + /* Definitely a temporary error. */ + __libc_dlclose (handle); + return false; + } + pointers[idx] = __libc_dlsym (handle, function_name); + free (function_name); +#ifdef PTR_MANGLE + PTR_MANGLE (pointers[idx]); +#endif + } + + /* Intall the function pointers, following the double-checked + locking idiom. Delay this after all processing, in case loading + the module triggers unwinding. */ + __libc_lock_lock (nss_module_list_lock); + switch ((enum nss_module_state) atomic_load_acquire (&module->state)) + { + case nss_module_uninitialized: + case nss_module_failed: + memcpy (module->functions.untyped, pointers, + sizeof (module->functions.untyped)); + module->handle = handle; + atomic_store_release (&module->state, nss_module_loaded); + break; + case nss_module_loaded: + /* This does not actually unload the module, only the reference + counter is decremented. */ + __libc_dlclose (handle); + break; + } + __libc_lock_unlock (nss_module_list_lock); + return true; +} + +/* Ensures that MODULE is in a loaded or failed state. */ +bool +__nss_module_load (struct nss_module *module) +{ + switch ((enum nss_module_state) atomic_load_acquire (&module->state)) + { + case nss_module_uninitialized: + return module_load (module); + case nss_module_loaded: + /* Loading has already succeeded. */ + return true; + case nss_module_failed: + /* Loading previously failed. */ + return false; + } + __builtin_unreachable (); +} + +static int +name_search (const void *left, const void *right) +{ + return strcmp (left, right); +} + +void * +__nss_module_get_function (struct nss_module *module, const char *name) +{ + if (!__nss_module_load (module)) + return NULL; + + function_name *name_entry = bsearch (name, nss_function_name_array, + array_length (nss_function_name_array), + sizeof (function_name), name_search); + assert (name_entry != NULL); + size_t idx = nss_function_name_array - name_entry; + void *fptr = module->functions.untyped[idx]; +#ifdef PTR_DEMANGLE + PTR_DEMANGLE (fptr); +#endif + return fptr; +} + +void +__nss_module_freeres (void) +{ + struct nss_module *current = nss_module_list; + while (current != NULL) + { + if (current->state == nss_module_loaded) + __libc_dlclose (current->handle); + + struct nss_module *next = current->next; + free (current); + current = next; + } + nss_module_list = NULL; +} diff --git a/nss/nss_module.h b/nss/nss_module.h new file mode 100644 index 0000000000..838e99e878 --- /dev/null +++ b/nss/nss_module.h @@ -0,0 +1,151 @@ +/* Global list of NSS service modules. + Copyright (c) 2020 Free Software Foundation, Inc. + This file is part of the GNU C Library. + + The GNU C Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + The GNU C Library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with the GNU C Library; if not, see + <https://www.gnu.org/licenses/>. */ + +#ifndef _NSS_MODULE_H +#define _NSS_MODULE_H + +#include <nss.h> +#include <stdbool.h> + +/* Typed function pointers for all functions that can be defined by a + service module. */ +struct nss_module_functions +{ + nss_endaliasent *endaliasent; + nss_endetherent *endetherent; + nss_endgrent *endgrent; + nss_endhostent *endhostent; + nss_endnetent *endnetent; + nss_endnetgrent *endnetgrent; + nss_endprotoent *endprotoent; + nss_endpwent *endpwent; + nss_endrpcent *endrpcent; + nss_endservent *endservent; + nss_endsgent *endsgent; + nss_endspent *endspent; + nss_getaliasbyname_r *getaliasbyname_r; + nss_getaliasent_r *getaliasent_r; + nss_getcanonname_r *getcanonname_r; + nss_getetherent_r *getetherent_r; + nss_getgrent_r *getgrent_r; + nss_getgrgid_r *getgrgid_r; + nss_getgrnam_r *getgrnam_r; + nss_gethostbyaddr2_r *gethostbyaddr2_r; + nss_gethostbyaddr_r *gethostbyaddr_r; + nss_gethostbyname2_r *gethostbyname2_r; + nss_gethostbyname3_r *gethostbyname3_r; + nss_gethostbyname4_r *gethostbyname4_r; + nss_gethostbyname_r *gethostbyname_r; + nss_gethostent_r *gethostent_r; + nss_gethostton_r *gethostton_r; + nss_getnetbyaddr_r *getnetbyaddr_r; + nss_getnetbyname_r *getnetbyname_r; + nss_getnetent_r *getnetent_r; + nss_getnetgrent_r *getnetgrent_r; + nss_getntohost_r *getntohost_r; + nss_getprotobyname_r *getprotobyname_r; + nss_getprotobynumber_r *getprotobynumber_r; + nss_getprotoent_r *getprotoent_r; + nss_getpublickey *getpublickey; + nss_getpwent_r *getpwent_r; + nss_getpwnam_r *getpwnam_r; + nss_getpwuid_r *getpwuid_r; + nss_getrpcbyname_r *getrpcbyname_r; + nss_getrpcbynumber_r *getrpcbynumber_r; + nss_getrpcent_r *getrpcent_r; + nss_getsecretkey *getsecretkey; + nss_getservbyname_r *getservbyname_r; + nss_getservbyport_r *getservbyport_r; + nss_getservent_r *getservent_r; + nss_getsgent_r *getsgent_r; + nss_getsgnam_r *getsgnam_r; + nss_getspent_r *getspent_r; + nss_getspnam_r *getspnam_r; + nss_initgroups_dyn *initgroups_dyn; + nss_netname2user *netname2user; + nss_setaliasent *setaliasent; + nss_setetherent *setetherent; + nss_setgrent *setgrent; + nss_sethostent *sethostent; + nss_setnetent *setnetent; + nss_setnetgrent *setnetgrent; + nss_setprotoent *setprotoent; + nss_setpwent *setpwent; + nss_setrpcent *setrpcent; + nss_setservent *setservent; + nss_setsgent *setsgent; + nss_setspent *setspent; +}; + +/* Untyped version of struct nss_module_functions, for consistent + processing purposes. */ +typedef void *nss_module_functions_untyped[sizeof (struct nss_module_functions) + / sizeof (void *)]; + +/* Initialization state of a NSS module. */ +enum nss_module_state +{ + nss_module_uninitialized, + nss_module_loaded, + nss_module_failed, +}; + +/* A NSS service module (potentially unloaded). Client code should + use the functions below. */ +struct nss_module +{ + /* Actual type is enum nss_module_state. Use int due to atomic + access. Used in a double-checked locking idiom. */ + int state; + + /* The function pointers in the module. */ + union + { + struct nss_module_functions typed; + nss_module_functions_untyped untyped; + } functions; + + /* Only used for __libc_freeres unloading. */ + void *handle; + + /* The next module in the list. */ + struct nss_module *next; + + /* The name of the module (as it appears in /etc/nsswitch.conf). */ + char name[]; +}; + +/* Allocates the NSS module NAME and places it into the global list. + If it already exists in the list, return the pre-existing module. + This does not actually load the module. Returns NULL on memory + allocation failure. */ +struct nss_module *__nss_module_allocate (const char *name) attribute_hidden; + +/* Ensures that MODULE is in a loaded or failed state. */ +bool __nss_module_load (struct nss_module *module) attribute_hidden; + +/* Ensures that MODULE is loaded and returns a pointer to the function + NAME defined in it. Returns NULL if MODULE could not be loaded, or + if the function NAME is not defined in the module. */ +void *__nss_module_get_function (struct nss_module *module, const char *name) + attribute_hidden; + +/* Called from __libc_freeres. */ +void __nss_module_freeres (void) attribute_hidden; + +#endif /* NSS_MODULE_H */ |