From patchwork Thu Aug 27 18:42:14 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: DJ Delorie X-Patchwork-Id: 1352784 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Authentication-Results: ozlabs.org; spf=pass (sender SPF authorized) smtp.mailfrom=sourceware.org (client-ip=8.43.85.97; helo=sourceware.org; envelope-from=libc-alpha-bounces@sourceware.org; receiver=) Authentication-Results: ozlabs.org; dmarc=pass (p=none dis=none) header.from=sourceware.org Authentication-Results: ozlabs.org; dkim=pass (1024-bit key; secure) header.d=sourceware.org header.i=@sourceware.org header.a=rsa-sha256 header.s=default header.b=F/DfG5yw; dkim-atps=neutral Received: from sourceware.org (server2.sourceware.org [8.43.85.97]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (4096 bits) server-digest SHA256) (No client certificate requested) by ozlabs.org (Postfix) with ESMTPS id 4Bcs6C10Pnz9sPB for ; Fri, 28 Aug 2020 04:42:27 +1000 (AEST) Received: from server2.sourceware.org (localhost [IPv6:::1]) by sourceware.org (Postfix) with ESMTP id 89E92393C86D; Thu, 27 Aug 2020 18:42:22 +0000 (GMT) DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org 89E92393C86D DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=sourceware.org; s=default; t=1598553742; bh=CZe2pDEZwaecT4qq8EnpBysS8lwidQjjR4Kehua8LA8=; h=Date:To:Subject:List-Id:List-Unsubscribe:List-Archive:List-Post: List-Help:List-Subscribe:From:Reply-To:From; b=F/DfG5ywvLASOXXGfm8wbs314xafLjcWgARUqi8Nvbmm0mMNjRpwcWnLS12SsAlnE Q5XFhNYtmvge5NddcnswMGPZvX0yUxZRv2sJ6D0LqJ/4yXebDF9N8Oq+l70e3ah4dx B28zfMNbI1FDnXvYHlyC5fio4XMJg2X3RoiUC8+E= X-Original-To: libc-alpha@sourceware.org Delivered-To: libc-alpha@sourceware.org Received: from us-smtp-delivery-1.mimecast.com (us-smtp-1.mimecast.com [205.139.110.61]) by sourceware.org (Postfix) with ESMTP id 966103870879 for ; Thu, 27 Aug 2020 18:42:19 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.3.2 sourceware.org 966103870879 Received: from mimecast-mx01.redhat.com (mimecast-mx01.redhat.com [209.132.183.4]) (Using TLS) by relay.mimecast.com with ESMTP id us-mta-542-TOp51MnANd-kDXDe7djWHw-1; Thu, 27 Aug 2020 14:42:16 -0400 X-MC-Unique: TOp51MnANd-kDXDe7djWHw-1 Received: from smtp.corp.redhat.com (int-mx06.intmail.prod.int.phx2.redhat.com [10.5.11.16]) (using TLSv1.2 with cipher AECDH-AES256-SHA (256/256 bits)) (No client certificate requested) by mimecast-mx01.redhat.com (Postfix) with ESMTPS id 77DAD189E626 for ; Thu, 27 Aug 2020 18:42:15 +0000 (UTC) Received: from greed.delorie.com (ovpn-112-147.phx2.redhat.com [10.3.112.147]) by smtp.corp.redhat.com (Postfix) with ESMTPS id 3B2E95C1D0 for ; Thu, 27 Aug 2020 18:42:15 +0000 (UTC) Received: from greed.delorie.com.redhat.com (localhost [127.0.0.1]) by greed.delorie.com (8.14.7/8.14.7) with ESMTP id 07RIgEfU020006 for ; Thu, 27 Aug 2020 14:42:14 -0400 Date: Thu, 27 Aug 2020 14:42:14 -0400 Message-Id: To: libc-alpha@sourceware.org Subject: v3 [PATCH 3/4] nss: Implement X-Scanned-By: MIMEDefang 2.79 on 10.5.11.16 X-Mimecast-Spam-Score: 0.003 X-Mimecast-Originator: redhat.com X-Spam-Status: No, score=-12.6 required=5.0 tests=BAYES_00, DKIMWL_WL_HIGH, DKIM_SIGNED, DKIM_VALID, DKIM_VALID_AU, DKIM_VALID_EF, GIT_PATCH_0, KAM_SHORT, RCVD_IN_DNSWL_NONE, RCVD_IN_MSPIKE_H3, RCVD_IN_MSPIKE_WL, SPF_HELO_NONE, SPF_PASS, TXREP autolearn=ham autolearn_force=no version=3.4.2 X-Spam-Checker-Version: SpamAssassin 3.4.2 (2018-09-13) on server2.sourceware.org X-BeenThere: libc-alpha@sourceware.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: Libc-alpha mailing list List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-Patchwork-Original-From: DJ Delorie via Libc-alpha From: DJ Delorie Reply-To: DJ Delorie Errors-To: libc-alpha-bounces@sourceware.org Sender: "Libc-alpha" From 75167ef2c490c209730bede0907a5f94f7730673 Mon Sep 17 00:00:00 2001 From: Florian Weimer Date: Thu, 20 Feb 2020 13:20:32 +0100 Subject: [PATCH 3/4] nss: Implement --- nss/Makefile | 2 +- nss/nss_database.c | 433 +++++++++++++++++++++++++++++++++++++++ nss/nss_database.h | 73 +++++++ sysdeps/mach/hurd/fork.c | 8 + sysdeps/nptl/fork.c | 9 + 5 files changed, 524 insertions(+), 1 deletion(-) create mode 100644 nss/nss_database.c create mode 100644 nss/nss_database.h 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..40162e0bc2 --- /dev/null +++ b/nss/nss_database.c @@ -0,0 +1,433 @@ +/* Mapping NSS services to action lists. + Copyright (C) 1996-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 + . */ + +#include "nss_database.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +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 ` ":"'. */ + 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..a157bbdbb0 --- /dev/null +++ b/nss/nss_database.h @@ -0,0 +1,73 @@ +/* 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 + . */ + +#ifndef _NSS_DATABASE_H +#define _NSS_DATABASE_H + +#include + +#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 32783069ec..1aec951e76 100644 --- a/sysdeps/mach/hurd/fork.c +++ b/sysdeps/mach/hurd/fork.c @@ -28,6 +28,7 @@ #include "hurdmalloc.h" /* XXX */ #include #include +#include #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 #include #include +#include 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. */