diff options
-rw-r--r-- | nss/Makefile | 2 | ||||
-rw-r--r-- | nss/nss_database.c | 433 | ||||
-rw-r--r-- | nss/nss_database.h | 88 | ||||
-rw-r--r-- | sysdeps/mach/hurd/fork.c | 8 | ||||
-rw-r--r-- | sysdeps/nptl/fork.c | 9 |
5 files changed, 539 insertions, 1 deletions
diff --git a/nss/Makefile b/nss/Makefile index f3e555066b..ccd1a6ae4f 100644 --- a/nss/Makefile +++ b/nss/Makefile @@ -31,7 +31,7 @@ routines = nsswitch getnssent getnssent_r digits_dots \ compat-lookup nss_hash nss_files_fopen \ nss_readline nss_parse_line_result \ nss_fgetent_r nss_module nss_action \ - nss_action_parse + nss_action_parse nss_database # 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_database.c b/nss/nss_database.c new file mode 100644 index 0000000000..329bfb1262 --- /dev/null +++ b/nss/nss_database.c @@ -0,0 +1,433 @@ +/* Mapping NSS services to action lists. + 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_database.h" + +#include <allocate_once.h> +#include <array_length.h> +#include <assert.h> +#include <atomic.h> +#include <ctype.h> +#include <file_change_detection.h> +#include <libc-lock.h> +#include <netdb.h> +#include <stdio_ext.h> +#include <string.h> + +struct nss_database_state +{ + struct nss_database_data data; + __libc_lock_define (, lock); +}; + + +/* Global NSS database state. Underlying type is "struct + nss_database_state *" but the allocate_once API requires + "void *". */ +static void *global_database_state; + +/* Allocate and return pointer to nss_database_state object or + on failure return NULL. */ +static void * +global_state_allocate (void *closure) +{ + struct nss_database_state *result = malloc (sizeof (*result)); + if (result != NULL) + { + result->data.nsswitch_conf.size = -1; /* Force reload. */ + memset (result->data.services, 0, sizeof (result->data.services)); + result->data.initialized = true; + result->data.reload_disabled = false; + __libc_lock_init (result->lock); + } + return result; +} + +/* Return pointer to global NSS database state, allocating as + required, or returning NULL on failure. */ +static struct nss_database_state * +nss_database_state_get (void) +{ + return allocate_once (&global_database_state, global_state_allocate, + NULL, NULL); +} + +/* Database default selections. nis/compat mappings get turned into + "files" for !LINK_OBSOLETE_NSL configurations. */ +enum nss_database_default +{ + nss_database_default_defconfig = 0, /* "nis [NOTFOUND=return] files". */ + nss_database_default_compat, /* "compat [NOTFOUND=return] files". */ + nss_database_default_dns, /* "dns [!UNAVAIL=return] files". */ + nss_database_default_files, /* "files". */ + nss_database_default_nis, /* "nis". */ + nss_database_default_nis_nisplus, /* "nis nisplus". */ + nss_database_default_none, /* Empty list. */ + + NSS_DATABASE_DEFAULT_COUNT /* Number of defaults. */ +}; + +/* Databases not listed default to nss_database_default_defconfig. */ +static const char per_database_defaults[NSS_DATABASE_COUNT] = + { + [nss_database_group] = nss_database_default_compat, + [nss_database_gshadow] = nss_database_default_files, + [nss_database_hosts] = nss_database_default_dns, + [nss_database_initgroups] = nss_database_default_none, + [nss_database_networks] = nss_database_default_dns, + [nss_database_passwd] = nss_database_default_compat, + [nss_database_publickey] = nss_database_default_nis_nisplus, + [nss_database_shadow] = nss_database_default_compat, + }; + +struct nss_database_default_cache +{ + nss_action_list caches[NSS_DATABASE_DEFAULT_COUNT]; +}; + +static bool +nss_database_select_default (struct nss_database_default_cache *cache, + enum nss_database db, nss_action_list *result) +{ + enum nss_database_default def = per_database_defaults[db]; + *result = cache->caches[def]; + if (*result != NULL) + return true; + + /* Determine the default line string. */ + const char *line; + switch (def) + { +#ifdef LINK_OBSOLETE_NSL + case nss_database_default_defconfig: + line = "nis [NOTFOUND=return] files"; + break; + case nss_database_default_compat: + line = "compat [NOTFOUND=return] files"; + break; +#endif + + case nss_database_default_dns: + line = "dns [!UNAVAIL=return] files"; + break; + + case nss_database_default_files: +#ifndef LINK_OBSOLETE_NSL + case nss_database_default_defconfig: + case nss_database_default_compat: +#endif + line = "files"; + break; + + case nss_database_default_nis: + line = "nis"; + break; + + case nss_database_default_nis_nisplus: + line = "nis nisplus"; + break; + + case nss_database_default_none: + /* Very special case: Leave *result as NULL. */ + return true; + + case NSS_DATABASE_DEFAULT_COUNT: + __builtin_unreachable (); + } + if (def < 0 || def >= NSS_DATABASE_DEFAULT_COUNT) + /* Tell GCC that line is initialized. */ + __builtin_unreachable (); + + *result = __nss_action_parse (line); + if (*result == NULL) + { + assert (errno == ENOMEM); + return false; + } + else + return true; +} + +/* database_name must be large enough for each individual name plus a + null terminator. */ +typedef char database_name[11]; +#define DEFINE_DATABASE(name) \ + _Static_assert (sizeof (#name) <= sizeof (database_name), #name); +#include "databases.def" +#undef DEFINE_DATABASE + +static const database_name nss_database_name_array[] = + { +#define DEFINE_DATABASE(name) #name, +#include "databases.def" +#undef DEFINE_DATABASE + }; + +static int +name_search (const void *left, const void *right) +{ + return strcmp (left, right); +} + +static int +name_to_database_index (const char *name) +{ + database_name *name_entry = bsearch (name, nss_database_name_array, + array_length (nss_database_name_array), + sizeof (database_name), name_search); + if (name_entry == NULL) + return -1; + return name_entry - nss_database_name_array; +} + +static bool +process_line (struct nss_database_data *data, char *line) +{ + /* Ignore leading white spaces. ATTENTION: this is different from + what is implemented in Solaris. The Solaris man page says a line + beginning with a white space character is ignored. We regard + this as just another misfeature in Solaris. */ + while (isspace (line[0])) + ++line; + + /* Recognize `<database> ":"'. */ + char *name = line; + while (line[0] != '\0' && !isspace (line[0]) && line[0] != ':') + ++line; + if (line[0] == '\0' || name == line) + /* Syntax error. Skip this line. */ + return true; + *line++ = '\0'; + + int db = name_to_database_index (name); + if (db < 0) + /* Not our database e.g. sudoers, automount, etc. */ + return true; + + nss_action_list result = __nss_action_parse (line); + if (result == NULL) + return false; + data->services[db] = result; + return true; +} + +/* Iterate over the lines in FP, parse them, and store them in DATA. + Return false on memory allocation failure, true on success. */ +static bool +nss_database_reload_1 (struct nss_database_data *data, FILE *fp) +{ + char *line = NULL; + size_t line_allocated = 0; + bool result = false; + + while (true) + { + ssize_t ret = __getline (&line, &line_allocated, fp); + if (ferror_unlocked (fp)) + break; + if (feof_unlocked (fp)) + { + result = true; + break; + } + assert (ret > 0); + (void) ret; /* For NDEBUG builds. */ + + if (!process_line (data, line)) + break; + } + + free (line); + return result; +} + +static bool +nss_database_reload (struct nss_database_data *staging, + struct file_change_detection *initial) +{ + FILE *fp = fopen (_PATH_NSSWITCH_CONF, "rce"); + if (fp == NULL) + switch (errno) + { + case EACCES: + case EISDIR: + case ELOOP: + case ENOENT: + case ENOTDIR: + case EPERM: + /* Ignore these errors. They are persistent errors caused + by file system contents. */ + break; + default: + /* Other errors refer to resource allocation problems and + need to be handled by the application. */ + return false; + } + else + /* No other threads have access to fp. */ + __fsetlocking (fp, FSETLOCKING_BYCALLER); + + bool ok = true; + if (fp != NULL) + ok = nss_database_reload_1 (staging, fp); + + /* Apply defaults. */ + if (ok) + { + struct nss_database_default_cache cache = { }; + for (int i = 0; i < NSS_DATABASE_COUNT; ++i) + if (staging->services[i] == NULL) + { + ok = nss_database_select_default (&cache, i, + &staging->services[i]); + if (!ok) + break; + } + } + + if (ok) + ok = __file_change_detection_for_fp (&staging->nsswitch_conf, fp); + + if (fp != NULL) + { + int saved_errno = errno; + fclose (fp); + __set_errno (saved_errno); + } + + if (ok && !__file_is_unchanged (&staging->nsswitch_conf, initial)) + /* Reload is required because the file changed while reading. */ + staging->nsswitch_conf.size = -1; + + return ok; +} + +static bool +nss_database_check_reload_and_get (struct nss_database_state *local, + nss_action_list *result, + enum nss_database database_index) +{ + /* Acquire MO is needed because the thread that sets reload_disabled + may have loaded the configuration first, so synchronize with the + Release MO store there. */ + if (atomic_load_acquire (&local->data.reload_disabled)) + /* No reload, so there is no error. */ + return true; + + struct file_change_detection initial; + if (!__file_change_detection_for_path (&initial, _PATH_NSSWITCH_CONF)) + return false; + + __libc_lock_lock (local->lock); + if (__file_is_unchanged (&initial, &local->data.nsswitch_conf)) + { + /* Configuration is up-to-date. Read it and return it to the + caller. */ + *result = local->data.services[database_index]; + __libc_lock_unlock (local->lock); + return true; + } + __libc_lock_unlock (local->lock); + + /* Avoid overwriting the global configuration until we have loaded + everything successfully. Otherwise, if the file change + information changes back to what is in the global configuration, + the lookups would use the partially-written configuration. */ + struct nss_database_data staging = { .initialized = true, }; + + bool ok = nss_database_reload (&staging, &initial); + + if (ok) + { + __libc_lock_lock (local->lock); + + /* See above for memory order. */ + if (!atomic_load_acquire (&local->data.reload_disabled)) + /* This may go back in time if another thread beats this + thread with the update, but in this case, a reload happens + on the next NSS call. */ + local->data = staging; + + *result = local->data.services[database_index]; + __libc_lock_unlock (local->lock); + } + + return ok; +} + +bool +__nss_database_get (enum nss_database db, nss_action_list *actions) +{ + struct nss_database_state *local = nss_database_state_get (); + return nss_database_check_reload_and_get (local, actions, db); +} + +nss_action_list +__nss_database_get_noreload (enum nss_database db) +{ + /* There must have been a previous __nss_database_get call. */ + struct nss_database_state *local = atomic_load_acquire (&global_database_state); + assert (local != NULL); + + __libc_lock_lock (local->lock); + nss_action_list result = local->data.services[db]; + __libc_lock_unlock (local->lock); + return result; +} + +void __libc_freeres_fn_section +__nss_database_freeres (void) +{ + free (global_database_state); + global_database_state = NULL; +} + +void +__nss_database_fork_prepare_parent (struct nss_database_data *data) +{ + /* Do not use allocate_once to trigger loading unnecessarily. */ + struct nss_database_state *local = atomic_load_acquire (&global_database_state); + if (local == NULL) + data->initialized = false; + else + { + /* Make a copy of the configuration. This approach was chosen + because it avoids acquiring the lock during the actual + fork. */ + __libc_lock_lock (local->lock); + *data = local->data; + __libc_lock_unlock (local->lock); + } +} + +void +__nss_database_fork_subprocess (struct nss_database_data *data) +{ + struct nss_database_state *local = atomic_load_acquire (&global_database_state); + if (data->initialized) + { + /* Restore the state at the point of the fork. */ + assert (local != NULL); + local->data = *data; + __libc_lock_init (local->lock); + } + else if (local != NULL) + /* The NSS configuration was loaded concurrently during fork. We + do not know its state, so we need to discard it. */ + global_database_state = NULL; +} diff --git a/nss/nss_database.h b/nss/nss_database.h new file mode 100644 index 0000000000..7a91b72559 --- /dev/null +++ b/nss/nss_database.h @@ -0,0 +1,88 @@ +/* Mapping NSS services to action lists. + 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_DATABASE_H +#define _NSS_DATABASE_H + +#include <file_change_detection.h> + +/* Each "line" in nsswitch.conf maps a supported database (example: + passwd) to one or more name service providers (example: files dns). + Internally, each name service provider (example: dns) is a + dynamically loadable module (i.e. libnss_dns.so), managed by + nss_module.h. The sequence of providers and rules (example: files + [SUCCESS=RETURN] dns) is mapped by nss_action.h to a cached entry + which encodes the sequence of modules and rules. Keeping track of + all supported databases and their corresponding actions is done + here. + + The key entry is __nss_database_get, which provides a set of + actions which can be used with nss_lookup_function() and + nss_next(). Callers should assume that these functions are fast, + and should not cache the result longer than needed. */ + +#include "nss_action.h" + +/* The enumeration literal in enum nss_database for the database NAME + (e.g., nss_database_hosts for hosts). */ +#define NSS_DATABASE_LITERAL(name) nss_database_##name + +enum nss_database +{ +#define DEFINE_DATABASE(name) NSS_DATABASE_LITERAL (name), +#include "databases.def" +#undef DEFINE_DATABASE + + /* Total number of databases. */ + NSS_DATABASE_COUNT +}; + + +/* Looks up the action list for DB and stores it in *ACTIONS. Returns + true on success or false on failure. Success can mean that + *ACTIONS is NULL. */ +bool __nss_database_get (enum nss_database db, nss_action_list *actions) + attribute_hidden; + +/* Like __nss_database_get, but does not reload /etc/nsswitch.conf + from disk. This assumes that there has been a previous successful + __nss_database_get call (which may not have returned any data). */ +nss_action_list __nss_database_get_noreload (enum nss_database db) + attribute_hidden; + +/* Called from __libc_freeres. */ +void __nss_database_freeres (void) attribute_hidden; + +/* Internal type. Exposed only for fork handling purposes. */ +struct nss_database_data +{ + struct file_change_detection nsswitch_conf; + nss_action_list services[NSS_DATABASE_COUNT]; + int reload_disabled; /* Actually bool; int for atomic access. */ + bool initialized; +}; + +/* Called by fork in the parent process, before forking. */ +void __nss_database_fork_prepare_parent (struct nss_database_data *data) + attribute_hidden; + +/* Called by fork in the new subprocess, after forking. */ +void __nss_database_fork_subprocess (struct nss_database_data *data) + attribute_hidden; + +#endif /* _NSS_DATABASE_H */ diff --git a/sysdeps/mach/hurd/fork.c b/sysdeps/mach/hurd/fork.c index 3767fd9a4c..f296310b58 100644 --- a/sysdeps/mach/hurd/fork.c +++ b/sysdeps/mach/hurd/fork.c @@ -28,6 +28,7 @@ #include "hurdmalloc.h" /* XXX */ #include <tls.h> #include <malloc/malloc-internal.h> +#include <nss/nss_database.h> #undef __fork @@ -68,6 +69,7 @@ __fork (void) size_t i; error_t err; struct hurd_sigstate *volatile ss; + struct nss_database_data nss_database_data; RUN_HOOK (_hurd_atfork_prepare_hook, ()); @@ -109,6 +111,9 @@ __fork (void) /* Run things that prepare for forking before we create the task. */ RUN_HOOK (_hurd_fork_prepare_hook, ()); + call_function_static_weak (__nss_database_fork_prepare_parent, + &nss_database_data); + /* Lock things that want to be locked before we fork. */ { void *const *p; @@ -666,6 +671,9 @@ __fork (void) _hurd_malloc_fork_child (); call_function_static_weak (__malloc_fork_unlock_child); + call_function_static_weak (__nss_database_fork_subprocess, + &nss_database_data); + /* Run things that want to run in the child task to set up. */ RUN_HOOK (_hurd_fork_child_hook, ()); diff --git a/sysdeps/nptl/fork.c b/sysdeps/nptl/fork.c index 5091a000e3..964eb1e5a8 100644 --- a/sysdeps/nptl/fork.c +++ b/sysdeps/nptl/fork.c @@ -32,6 +32,7 @@ #include <arch-fork.h> #include <futex-internal.h> #include <malloc/malloc-internal.h> +#include <nss/nss_database.h> static void fresetlockfiles (void) @@ -57,6 +58,8 @@ __libc_fork (void) __run_fork_handlers (atfork_run_prepare, multiple_threads); + struct nss_database_data nss_database_data; + /* If we are not running multiple threads, we do not have to preserve lock state. If fork runs from a signal handler, only async-signal-safe functions can be used in the child. These data @@ -64,6 +67,9 @@ __libc_fork (void) not matter if fork was called from a signal handler. */ if (multiple_threads) { + call_function_static_weak (__nss_database_fork_prepare_parent, + &nss_database_data); + _IO_list_lock (); /* Acquire malloc locks. This needs to come last because fork @@ -118,6 +124,9 @@ __libc_fork (void) /* Reset locks in the I/O code. */ _IO_list_resetlock (); + + call_function_static_weak (__nss_database_fork_subprocess, + &nss_database_data); } /* Reset the lock the dynamic loader uses to protect its data. */ |