about summary refs log tree commit diff
path: root/REORG.TODO/elf/dl-tunables.c
diff options
context:
space:
mode:
Diffstat (limited to 'REORG.TODO/elf/dl-tunables.c')
-rw-r--r--REORG.TODO/elf/dl-tunables.c490
1 files changed, 490 insertions, 0 deletions
diff --git a/REORG.TODO/elf/dl-tunables.c b/REORG.TODO/elf/dl-tunables.c
new file mode 100644
index 0000000000..76e8c5cae1
--- /dev/null
+++ b/REORG.TODO/elf/dl-tunables.c
@@ -0,0 +1,490 @@
+/* The tunable framework.  See the README.tunables to know how to use the
+   tunable in a glibc module.
+
+   Copyright (C) 2016-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 <stdint.h>
+#include <stdbool.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <sysdep.h>
+#include <fcntl.h>
+#include <ldsodefs.h>
+
+#define TUNABLES_INTERNAL 1
+#include "dl-tunables.h"
+
+#if TUNABLES_FRONTEND == TUNABLES_FRONTEND_valstring
+# define GLIBC_TUNABLES "GLIBC_TUNABLES"
+#endif
+
+/* Compare environment or tunable names, bounded by the name hardcoded in
+   glibc.  */
+static bool
+is_name (const char *orig, const char *envname)
+{
+  for (;*orig != '\0' && *envname != '\0'; envname++, orig++)
+    if (*orig != *envname)
+      break;
+
+  /* The ENVNAME is immediately followed by a value.  */
+  if (*orig == '\0' && *envname == '=')
+    return true;
+  else
+    return false;
+}
+
+#if TUNABLES_FRONTEND == TUNABLES_FRONTEND_valstring
+static char *
+tunables_strdup (const char *in)
+{
+  size_t i = 0;
+
+  while (in[i++] != '\0');
+  char *out = __sbrk (i);
+
+  /* FIXME: In reality if the allocation fails, __sbrk will crash attempting to
+     set the thread-local errno since the TCB has not yet been set up.  This
+     needs to be fixed with an __sbrk implementation that does not set
+     errno.  */
+  if (out == (void *)-1)
+    return NULL;
+
+  i--;
+
+  while (i-- > 0)
+    out[i] = in[i];
+
+  return out;
+}
+#endif
+
+static char **
+get_next_env (char **envp, char **name, size_t *namelen, char **val,
+	      char ***prev_envp)
+{
+  while (envp != NULL && *envp != NULL)
+    {
+      char **prev = envp;
+      char *envline = *envp++;
+      int len = 0;
+
+      while (envline[len] != '\0' && envline[len] != '=')
+	len++;
+
+      /* Just the name and no value, go to the next one.  */
+      if (envline[len] == '\0')
+	continue;
+
+      *name = envline;
+      *namelen = len;
+      *val = &envline[len + 1];
+      *prev_envp = prev;
+
+      return envp;
+    }
+
+  return NULL;
+}
+
+/* A stripped down strtoul-like implementation for very early use.  It does not
+   set errno if the result is outside bounds because it gets called before
+   errno may have been set up.  */
+static uint64_t
+tunables_strtoul (const char *nptr)
+{
+  uint64_t result = 0;
+  long int sign = 1;
+  unsigned max_digit;
+
+  while (*nptr == ' ' || *nptr == '\t')
+    ++nptr;
+
+  if (*nptr == '-')
+    {
+      sign = -1;
+      ++nptr;
+    }
+  else if (*nptr == '+')
+    ++nptr;
+
+  if (*nptr < '0' || *nptr > '9')
+    return 0UL;
+
+  int base = 10;
+  max_digit = 9;
+  if (*nptr == '0')
+    {
+      if (nptr[1] == 'x' || nptr[1] == 'X')
+	{
+	  base = 16;
+	  nptr += 2;
+	}
+      else
+	{
+	  base = 8;
+	  max_digit = 7;
+	}
+    }
+
+  while (1)
+    {
+      int digval;
+      if (*nptr >= '0' && *nptr <= '0' + max_digit)
+        digval = *nptr - '0';
+      else if (base == 16)
+        {
+	  if (*nptr >= 'a' && *nptr <= 'f')
+	    digval = *nptr - 'a' + 10;
+	  else if (*nptr >= 'A' && *nptr <= 'F')
+	    digval = *nptr - 'A' + 10;
+	  else
+	    break;
+	}
+      else
+        break;
+
+      if (result >= (UINT64_MAX - digval) / base)
+	return UINT64_MAX;
+      result *= base;
+      result += digval;
+      ++nptr;
+    }
+
+  return result * sign;
+}
+
+#define TUNABLE_SET_VAL_IF_VALID_RANGE(__cur, __val, __type, __default_min, \
+				       __default_max)			      \
+({									      \
+  __type min = (__cur)->type.min;					      \
+  __type max = (__cur)->type.max;					      \
+									      \
+  if (min == max)							      \
+    {									      \
+      min = __default_min;						      \
+      max = __default_max;						      \
+    }									      \
+									      \
+  if ((__type) (__val) >= min && (__type) (val) <= max)			      \
+    {									      \
+      (__cur)->val.numval = val;					      \
+      (__cur)->initialized = true;					      \
+    }									      \
+})
+
+static void
+do_tunable_update_val (tunable_t *cur, const void *valp)
+{
+  uint64_t val;
+
+  if (cur->type.type_code != TUNABLE_TYPE_STRING)
+    val = *((int64_t *) valp);
+
+  switch (cur->type.type_code)
+    {
+    case TUNABLE_TYPE_INT_32:
+	{
+	  TUNABLE_SET_VAL_IF_VALID_RANGE (cur, val, int64_t, INT32_MIN, INT32_MAX);
+	  break;
+	}
+    case TUNABLE_TYPE_UINT_64:
+	{
+	  TUNABLE_SET_VAL_IF_VALID_RANGE (cur, val, uint64_t, 0, UINT64_MAX);
+	  break;
+	}
+    case TUNABLE_TYPE_SIZE_T:
+	{
+	  TUNABLE_SET_VAL_IF_VALID_RANGE (cur, val, uint64_t, 0, SIZE_MAX);
+	  break;
+	}
+    case TUNABLE_TYPE_STRING:
+	{
+	  cur->val.strval = valp;
+	  break;
+	}
+    default:
+      __builtin_unreachable ();
+    }
+}
+
+/* Validate range of the input value and initialize the tunable CUR if it looks
+   good.  */
+static void
+tunable_initialize (tunable_t *cur, const char *strval)
+{
+  uint64_t val;
+  const void *valp;
+
+  if (cur->type.type_code != TUNABLE_TYPE_STRING)
+    {
+      val = tunables_strtoul (strval);
+      valp = &val;
+    }
+  else
+    {
+      cur->initialized = true;
+      valp = strval;
+    }
+  do_tunable_update_val (cur, valp);
+}
+
+void
+__tunable_set_val (tunable_id_t id, void *valp)
+{
+  tunable_t *cur = &tunable_list[id];
+
+  do_tunable_update_val (cur, valp);
+}
+
+#if TUNABLES_FRONTEND == TUNABLES_FRONTEND_valstring
+/* Parse the tunable string TUNESTR and adjust it to drop any tunables that may
+   be unsafe for AT_SECURE processes so that it can be used as the new
+   environment variable value for GLIBC_TUNABLES.  VALSTRING is the original
+   environment variable string which we use to make NULL terminated values so
+   that we don't have to allocate memory again for it.  */
+static void
+parse_tunables (char *tunestr, char *valstring)
+{
+  if (tunestr == NULL || *tunestr == '\0')
+    return;
+
+  char *p = tunestr;
+
+  while (true)
+    {
+      char *name = p;
+      size_t len = 0;
+
+      /* First, find where the name ends.  */
+      while (p[len] != '=' && p[len] != ':' && p[len] != '\0')
+	len++;
+
+      /* If we reach the end of the string before getting a valid name-value
+	 pair, bail out.  */
+      if (p[len] == '\0')
+	return;
+
+      /* We did not find a valid name-value pair before encountering the
+	 colon.  */
+      if (p[len]== ':')
+	{
+	  p += len + 1;
+	  continue;
+	}
+
+      p += len + 1;
+
+      /* Take the value from the valstring since we need to NULL terminate it.  */
+      char *value = &valstring[p - tunestr];
+      len = 0;
+
+      while (p[len] != ':' && p[len] != '\0')
+	len++;
+
+      /* Add the tunable if it exists.  */
+      for (size_t i = 0; i < sizeof (tunable_list) / sizeof (tunable_t); i++)
+	{
+	  tunable_t *cur = &tunable_list[i];
+
+	  if (is_name (cur->name, name))
+	    {
+	      /* If we are in a secure context (AT_SECURE) then ignore the tunable
+		 unless it is explicitly marked as secure.  Tunable values take
+		 precendence over their envvar aliases.  */
+	      if (__libc_enable_secure)
+		{
+		  if (cur->security_level == TUNABLE_SECLEVEL_SXID_ERASE)
+		    {
+		      if (p[len] == '\0')
+			{
+			  /* Last tunable in the valstring.  Null-terminate and
+			     return.  */
+			  *name = '\0';
+			  return;
+			}
+		      else
+			{
+			  /* Remove the current tunable from the string.  We do
+			     this by overwriting the string starting from NAME
+			     (which is where the current tunable begins) with
+			     the remainder of the string.  We then have P point
+			     to NAME so that we continue in the correct
+			     position in the valstring.  */
+			  char *q = &p[len + 1];
+			  p = name;
+			  while (*q != '\0')
+			    *name++ = *q++;
+			  name[0] = '\0';
+			  len = 0;
+			}
+		    }
+
+		  if (cur->security_level != TUNABLE_SECLEVEL_NONE)
+		    break;
+		}
+
+	      value[len] = '\0';
+	      tunable_initialize (cur, value);
+	      break;
+	    }
+	}
+
+      if (p[len] == '\0')
+	return;
+      else
+	p += len + 1;
+    }
+}
+#endif
+
+/* Enable the glibc.malloc.check tunable in SETUID/SETGID programs only when
+   the system administrator has created the /etc/suid-debug file.  This is a
+   special case where we want to conditionally enable/disable a tunable even
+   for setuid binaries.  We use the special version of access() to avoid
+   setting ERRNO, which is a TLS variable since TLS has not yet been set
+   up.  */
+static inline void
+__always_inline
+maybe_enable_malloc_check (void)
+{
+  tunable_id_t id = TUNABLE_ENUM_NAME (glibc, malloc, check);
+  if (__libc_enable_secure && __access_noerrno ("/etc/suid-debug", F_OK) == 0)
+    tunable_list[id].security_level = TUNABLE_SECLEVEL_NONE;
+}
+
+/* Initialize the tunables list from the environment.  For now we only use the
+   ENV_ALIAS to find values.  Later we will also use the tunable names to find
+   values.  */
+void
+__tunables_init (char **envp)
+{
+  char *envname = NULL;
+  char *envval = NULL;
+  size_t len = 0;
+  char **prev_envp = envp;
+
+  maybe_enable_malloc_check ();
+
+  while ((envp = get_next_env (envp, &envname, &len, &envval,
+			       &prev_envp)) != NULL)
+    {
+#if TUNABLES_FRONTEND == TUNABLES_FRONTEND_valstring
+      if (is_name (GLIBC_TUNABLES, envname))
+	{
+	  char *new_env = tunables_strdup (envname);
+	  if (new_env != NULL)
+	    parse_tunables (new_env + len + 1, envval);
+	  /* Put in the updated envval.  */
+	  *prev_envp = new_env;
+	  continue;
+	}
+#endif
+
+      for (int i = 0; i < sizeof (tunable_list) / sizeof (tunable_t); i++)
+	{
+	  tunable_t *cur = &tunable_list[i];
+
+	  /* Skip over tunables that have either been set already or should be
+	     skipped.  */
+	  if (cur->initialized || cur->env_alias == NULL)
+	    continue;
+
+	  const char *name = cur->env_alias;
+
+	  /* We have a match.  Initialize and move on to the next line.  */
+	  if (is_name (name, envname))
+	    {
+	      /* For AT_SECURE binaries, we need to check the security settings of
+		 the tunable and decide whether we read the value and also whether
+		 we erase the value so that child processes don't inherit them in
+		 the environment.  */
+	      if (__libc_enable_secure)
+		{
+		  if (cur->security_level == TUNABLE_SECLEVEL_SXID_ERASE)
+		    {
+		      /* Erase the environment variable.  */
+		      char **ep = prev_envp;
+
+		      while (*ep != NULL)
+			{
+			  if (is_name (name, *ep))
+			    {
+			      char **dp = ep;
+
+			      do
+				dp[0] = dp[1];
+			      while (*dp++);
+			    }
+			  else
+			    ++ep;
+			}
+		      /* Reset the iterator so that we read the environment again
+			 from the point we erased.  */
+		      envp = prev_envp;
+		    }
+
+		  if (cur->security_level != TUNABLE_SECLEVEL_NONE)
+		    continue;
+		}
+
+	      tunable_initialize (cur, envval);
+	      break;
+	    }
+	}
+    }
+}
+
+/* Set the tunable value.  This is called by the module that the tunable exists
+   in. */
+void
+__tunable_get_val (tunable_id_t id, void *valp, tunable_callback_t callback)
+{
+  tunable_t *cur = &tunable_list[id];
+
+  switch (cur->type.type_code)
+    {
+    case TUNABLE_TYPE_UINT_64:
+	{
+	  *((uint64_t *) valp) = (uint64_t) cur->val.numval;
+	  break;
+	}
+    case TUNABLE_TYPE_INT_32:
+	{
+	  *((int32_t *) valp) = (int32_t) cur->val.numval;
+	  break;
+	}
+    case TUNABLE_TYPE_SIZE_T:
+	{
+	  *((size_t *) valp) = (size_t) cur->val.numval;
+	  break;
+	}
+    case TUNABLE_TYPE_STRING:
+	{
+	  *((const char **)valp) = cur->val.strval;
+	  break;
+	}
+    default:
+      __builtin_unreachable ();
+    }
+
+  if (cur->initialized && callback != NULL)
+    callback (&cur->val);
+}
+
+rtld_hidden_def (__tunable_get_val)