aboutsummaryrefslogtreecommitdiff
path: root/nss/nss_module.c
diff options
context:
space:
mode:
Diffstat (limited to 'nss/nss_module.c')
-rw-r--r--nss/nss_module.c304
1 files changed, 304 insertions, 0 deletions
diff --git a/nss/nss_module.c b/nss/nss_module.c
new file mode 100644
index 0000000000..8de8db09c3
--- /dev/null
+++ b/nss/nss_module.c
@@ -0,0 +1,304 @@
+/* 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>
+
+#ifdef LINK_OBSOLETE_NSL
+# define DEFAULT_CONFIG "compat [NOTFOUND=return] files"
+# define DEFAULT_DEFCONFIG "nis [NOTFOUND=return] files"
+#else
+# define DEFAULT_CONFIG "files"
+# define DEFAULT_DEFCONFIG "files"
+#endif
+
+/* Suffix after .so of NSS service modules. This is a bit of magic,
+ but we assume LIBNSS_FILES_SO looks like "libnss_files.so.2" and we
+ want a pointer to the ".2" part. We have no API to extract this
+ except through the auto-generated lib-names.h and some static
+ pointer manipulation. The "-1" accounts for the trailing NUL
+ included in the sizeof. */
+static const char *const __nss_shlib_revision
+ = LIBNSS_FILES_SO + sizeof("libnss_files.so") - 1;
+
+/* A single-linked list used to implement a mapping from service names
+ to NSS modules. (Most systems only use five or so 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);
+
+#if defined USE_NSCD && (!defined DO_STATIC_NSS || defined SHARED)
+/* Nonzero if this is the nscd process. */
+static bool is_nscd;
+/* The callback passed to the init functions when nscd is used. */
+static void (*nscd_init_cb) (size_t, struct traced_file *);
+#endif
+
+/* Allocate the service NAME with length NAME_LENGTH. If the service
+ is already allocated in the nss_module_list cache then we return a
+ pointer to the struct nss_module, otherwise we try to allocate a
+ new struct nss_module entry and add it to the global
+ nss_modules_list cache. If we fail to allocate the entry we return
+ NULL. Failure to allocate the entry is always transient. */
+struct nss_module *
+__nss_module_allocate (const char *name, size_t name_length)
+{
+ __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 (strncmp (p->name, name, name_length) == 0
+ && p->name[name_length] == '\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. */
+ result = malloc (sizeof (*result) + name_length + 1);
+ if (result != NULL)
+ {
+ result->state = nss_module_uninitialized;
+ memcpy (result->name, name, name_length);
+ result->name[name_length] = '\0';
+ 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 in the
+ nss_function_name_array list below, as getprotobynumber_r is the
+ longest entry in that list. */
+typedef char function_name[sizeof("getprotobynumber_r")];
+
+static const function_name nss_function_name_array[] =
+ {
+#undef DEFINE_NSS_FUNCTION
+#define DEFINE_NSS_FUNCTION(x) #x,
+#include "function.def"
+ };
+
+/* Internal implementation of __nss_module_load. */
+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);
+ }
+
+ /* Failing to load the module can be caused by several different
+ scenarios. One such scenario is that the module has been removed
+ from the disk. In which case the in-memory version is all that
+ we have, and if the module->state indidates it is loaded then we
+ can use it. */
+ 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;
+
+ /* Look up and store locally all the function pointers we may need
+ later. Doing this now means the data will not change in the
+ future. */
+ 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
+ }
+
+# ifdef USE_NSCD
+ if (is_nscd)
+ {
+ /* Call the init function when nscd is used. */
+ size_t initlen = (5 + strlen (module->name)
+ + strlen ("_init") + 1);
+ char init_name[initlen];
+
+ /* Construct the init function name. */
+ __stpcpy (__stpcpy (__stpcpy (init_name,
+ "_nss_"),
+ module->name),
+ "_init");
+
+ /* Find the optional init function. */
+ void (*ifct) (void (*) (size_t, struct traced_file *))
+ = __libc_dlsym (handle, init_name);
+ if (ifct != NULL)
+ {
+ void (*cb) (size_t, struct traced_file *) = nscd_init_cb;
+# ifdef PTR_DEMANGLE
+ PTR_DEMANGLE (cb);
+# endif
+ ifct (cb);
+ }
+ }
+# endif
+
+ /* Install 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;
+ /* Synchronizes with unlocked __nss_module_load atomic_load_acquire. */
+ atomic_store_release (&module->state, nss_module_loaded);
+ break;
+ case nss_module_loaded:
+ /* If the module was already loaded, close our own handle. This
+ does not actually unload the modules, only the reference
+ counter is decremented for the loaded module. */
+ __libc_dlclose (handle);
+ break;
+ }
+ __libc_lock_unlock (nss_module_list_lock);
+ return true;
+}
+
+/* Force the module identified by MODULE to be loaded. We return
+ false if the module could not be loaded, true otherwise. Loading
+ the module requires looking up all the possible interface APIs and
+ caching the results. */
+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);
+}
+
+/* Load module MODULE (if it isn't already) and return a pointer to
+ the module's implementation of NAME, otherwise return NULL on
+ failure or error. */
+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 = name_entry - nss_function_name_array;
+ void *fptr = module->functions.untyped[idx];
+#ifdef PTR_DEMANGLE
+ PTR_DEMANGLE (fptr);
+#endif
+ return fptr;
+}
+
+void __libc_freeres_fn_section
+__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;
+}