summary refs log tree commit diff
path: root/resolv/resolv_conf.c
diff options
context:
space:
mode:
authorFlorian Weimer <fweimer@redhat.com>2017-07-03 20:31:23 +0200
committerFlorian Weimer <fweimer@redhat.com>2017-07-03 20:57:28 +0200
commitf30a54b21b83f254533c59ca72ad17af5249c6be (patch)
tree174c6e8b77d8fc1514f4108e3290b91d19d838a6 /resolv/resolv_conf.c
parent352f4ff9a268b81ef5d4b2413f582565806e4790 (diff)
downloadglibc-f30a54b21b83f254533c59ca72ad17af5249c6be.tar.gz
glibc-f30a54b21b83f254533c59ca72ad17af5249c6be.tar.xz
glibc-f30a54b21b83f254533c59ca72ad17af5249c6be.zip
resolv: Introduce struct resolv_conf with extended resolver state
This change provides additional resolver configuration state which
is not exposed through the _res ABI.  It reuses the existing
initstamp field in the supposedly-private part of _res.  Some effort
is undertaken to avoid memory safety issues introduced by applications
which directly patch the _res object.

With this commit, only the initstamp field is moved into struct
resolv_conf.  Additional members will be added later, eventually
migrating the entire resolver configuration.
Diffstat (limited to 'resolv/resolv_conf.c')
-rw-r--r--resolv/resolv_conf.c322
1 files changed, 322 insertions, 0 deletions
diff --git a/resolv/resolv_conf.c b/resolv/resolv_conf.c
new file mode 100644
index 0000000000..cabe69cf1b
--- /dev/null
+++ b/resolv/resolv_conf.c
@@ -0,0 +1,322 @@
+/* Extended resolver state separate from struct __res_state.
+   Copyright (C) 2017 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
+   <http://www.gnu.org/licenses/>.  */
+
+#include <resolv_conf.h>
+
+#include <alloc_buffer.h>
+#include <assert.h>
+#include <libc-lock.h>
+#include <resolv-internal.h>
+
+/* _res._u._ext.__glibc_extension_index is used as an index into a
+   struct resolv_conf_array object.  The intent of this construction
+   is to make reasonably sure that even if struct __res_state objects
+   are copied around and patched by applications, we can still detect
+   accesses to stale extended resolver state.  */
+#define DYNARRAY_STRUCT resolv_conf_array
+#define DYNARRAY_ELEMENT struct resolv_conf *
+#define DYNARRAY_PREFIX resolv_conf_array_
+#define DYNARRAY_INITIAL_SIZE 0
+#include <malloc/dynarray-skeleton.c>
+
+/* A magic constant for XORing the extension index
+   (_res._u._ext.__glibc_extension_index).  This makes it less likely
+   that a valid index is created by accident.  In particular, a zero
+   value leads to an invalid index.  */
+#define INDEX_MAGIC 0x26a8fa5e48af8061ULL
+
+/* Global resolv.conf-related state.  */
+struct resolv_conf_global
+{
+  /* struct __res_state objects contain the extension index
+     (_res._u._ext.__glibc_extension_index ^ INDEX_MAGIC), which
+     refers to an element of this array.  When a struct resolv_conf
+     object (extended resolver state) is associated with a struct
+     __res_state object (legacy resolver state), its reference count
+     is increased and added to this array.  Conversely, if the
+     extended state is detached from the basic state (during
+     reinitialization or deallocation), the index is decremented, and
+     the array element is overwritten with NULL.  */
+  struct resolv_conf_array array;
+
+};
+
+/* Lazily allocated storage for struct resolv_conf_global.  */
+static struct resolv_conf_global *global;
+
+/* The lock synchronizes access to global and *global.  It also
+   protects the __refcount member of struct resolv_conf.  */
+__libc_lock_define_initialized (static, lock);
+
+/* Ensure that GLOBAL is allocated and lock it.  Return NULL if
+   memory allocation failes.  */
+static struct resolv_conf_global *
+get_locked_global (void)
+{
+  __libc_lock_lock (lock);
+  /* Use relaxed MO through because of load outside the lock in
+     __resolv_conf_detach.  */
+  struct resolv_conf_global *global_copy = atomic_load_relaxed (&global);
+  if (global_copy == NULL)
+    {
+      global_copy = calloc (1, sizeof (*global));
+      if (global_copy == NULL)
+        return NULL;
+      atomic_store_relaxed (&global, global_copy);
+      resolv_conf_array_init (&global_copy->array);
+    }
+  return global_copy;
+}
+
+/* Relinquish the lock acquired by get_locked_global.  */
+static void
+put_locked_global (struct resolv_conf_global *global_copy)
+{
+  __libc_lock_unlock (lock);
+}
+
+/* Decrement the reference counter.  The caller must acquire the lock
+   around the function call.  */
+static void
+conf_decrement (struct resolv_conf *conf)
+{
+  assert (conf->__refcount > 0);
+  if (--conf->__refcount == 0)
+    free (conf);
+}
+
+/* Internal implementation of __resolv_conf_get, without validation
+   against *RESP.  */
+static struct resolv_conf *
+resolv_conf_get_1 (const struct __res_state *resp)
+{
+  /* Not initialized, and therefore no assoicated context.  */
+  if (!(resp->options & RES_INIT))
+    return NULL;
+
+  struct resolv_conf_global *global_copy = get_locked_global ();
+  if (global_copy == NULL)
+    /* A memory allocation failure here means that no associated
+       contexts exists, so returning NULL is correct.  */
+    return NULL;
+  size_t index = resp->_u._ext.__glibc_extension_index ^ INDEX_MAGIC;
+  struct resolv_conf *conf;
+  if (index < resolv_conf_array_size (&global_copy->array))
+    {
+      conf = *resolv_conf_array_at (&global_copy->array, index);
+      assert (conf->__refcount > 0);
+      ++conf->__refcount;
+    }
+  else
+    conf = NULL;
+  put_locked_global (global_copy);
+  return conf;
+}
+
+/* Check that *RESP and CONF match.  Used by __resolv_conf_get.  */
+static bool
+resolv_conf_matches (const struct __res_state *resp,
+                     const struct resolv_conf *conf)
+{
+  return true;
+}
+
+struct resolv_conf *
+__resolv_conf_get (struct __res_state *resp)
+{
+  struct resolv_conf *conf = resolv_conf_get_1 (resp);
+  if (conf == NULL)
+    return NULL;
+  if (resolv_conf_matches (resp, conf))
+    return conf;
+  __resolv_conf_put (conf);
+  return NULL;
+}
+
+void
+__resolv_conf_put (struct resolv_conf *conf)
+{
+  if (conf == NULL)
+    return;
+
+  __libc_lock_lock (lock);
+  conf_decrement (conf);
+  __libc_lock_unlock (lock);
+}
+
+struct resolv_conf *
+__resolv_conf_allocate (const struct resolv_conf *init)
+{
+  /* Allocate the buffer.  */
+  void *ptr;
+  struct alloc_buffer buffer = alloc_buffer_allocate
+    (sizeof (struct resolv_conf),
+     &ptr);
+  struct resolv_conf *conf
+    = alloc_buffer_alloc (&buffer, struct resolv_conf);
+  if (conf == NULL)
+    /* Memory allocation failure.  */
+    return NULL;
+  assert (conf == ptr);
+
+  /* Initialize the contents.  */
+  conf->__refcount = 1;
+  conf->initstamp = __res_initstamp;
+
+  assert (!alloc_buffer_has_failed (&buffer));
+  return conf;
+}
+
+/* Update *RESP from the extended state.  */
+static __attribute__ ((nonnull (1, 2), warn_unused_result)) bool
+update_from_conf (struct __res_state *resp, const struct resolv_conf *conf)
+{
+  /* The overlapping parts of both configurations should agree after
+     initialization.  */
+  assert (resolv_conf_matches (resp, conf));
+  return true;
+}
+
+/* Decrement the configuration object at INDEX and free it if the
+   reference counter reaches 0.  *GLOBAL_COPY must be locked and
+   remains so.  */
+static void
+decrement_at_index (struct resolv_conf_global *global_copy, size_t index)
+{
+  if (index < resolv_conf_array_size (&global_copy->array))
+    {
+      /* Index found.  Deallocate the struct resolv_conf object once
+         the reference counter reaches.  Free the array slot.  */
+      struct resolv_conf **slot
+        = resolv_conf_array_at (&global_copy->array, index);
+      struct resolv_conf *conf = *slot;
+      if (conf != NULL)
+        {
+          conf_decrement (conf);
+          /* Clear the slot even if the reference count is positive.
+             Slots are not shared.  */
+          *slot = NULL;
+        }
+    }
+}
+
+bool
+__resolv_conf_attach (struct __res_state *resp, struct resolv_conf *conf)
+{
+  assert (conf->__refcount > 0);
+
+  struct resolv_conf_global *global_copy = get_locked_global ();
+  if (global_copy == NULL)
+    {
+      free (conf);
+      return false;
+    }
+
+  /* Try to find an unused index in the array.  */
+  size_t index;
+  {
+    size_t size = resolv_conf_array_size (&global_copy->array);
+    bool found = false;
+    for (index = 0; index < size; ++index)
+      {
+        struct resolv_conf **p
+          = resolv_conf_array_at (&global_copy->array, index);
+        if (*p == NULL)
+          {
+            *p = conf;
+            found = true;
+            break;
+          }
+      }
+
+    if (!found)
+      {
+        /* No usable index found.  Increase the array size.  */
+        resolv_conf_array_add (&global_copy->array, conf);
+        if (resolv_conf_array_has_failed (&global_copy->array))
+          {
+            put_locked_global (global_copy);
+            __set_errno (ENOMEM);
+            return false;
+          }
+        /* The new array element was added at the end.  */
+        index = size;
+      }
+  }
+
+  /* We have added a new reference to the object.  */
+  ++conf->__refcount;
+  assert (conf->__refcount > 0);
+  put_locked_global (global_copy);
+
+  if (!update_from_conf (resp, conf))
+    {
+      /* Drop the reference we acquired.  Reacquire the lock.  The
+         object has already been allocated, so it cannot be NULL this
+         time.  */
+      global_copy = get_locked_global ();
+      decrement_at_index (global_copy, index);
+      put_locked_global (global_copy);
+      return false;
+    }
+  resp->_u._ext.__glibc_extension_index = index ^ INDEX_MAGIC;
+
+  return true;
+}
+
+void
+__resolv_conf_detach (struct __res_state *resp)
+{
+  if (atomic_load_relaxed (&global) == NULL)
+    /* Detach operation after a shutdown, or without any prior
+       attachment.  We cannot free the data (and there might not be
+       anything to free anyway).  */
+    return;
+
+  struct resolv_conf_global *global_copy = get_locked_global ();
+  size_t index = resp->_u._ext.__glibc_extension_index ^ INDEX_MAGIC;
+  decrement_at_index (global_copy, index);
+
+  /* Clear the index field, so that accidental reuse is less
+     likely.  */
+  resp->_u._ext.__glibc_extension_index = 0;
+
+  put_locked_global (global_copy);
+}
+
+/* Deallocate the global data.  */
+static void __attribute__ ((section ("__libc_thread_freeres_fn")))
+freeres (void)
+{
+  /* No locking because this function is supposed to be called when
+     the process has turned single-threaded.  */
+  if (global == NULL)
+    return;
+
+  /* Note that this frees only the array itself.  The pointed-to
+     configuration objects should have been deallocated by res_nclose
+     and per-thread cleanup functions.  */
+  resolv_conf_array_free (&global->array);
+
+  free (global);
+
+  /* Stop potential future __resolv_conf_detach calls from accessing
+     deallocated memory.  */
+  global = NULL;
+}
+text_set_element (__libc_subfreeres, freeres);