25845f
commit 5aad5f617892e75d91d4c8fb7594ff35b610c042
25845f
Author: Carlos O'Donell <carlos@redhat.com>
25845f
Date:   Tue Jun 5 23:55:17 2018 -0400
25845f
25845f
    Improve DST handling (Bug 23102, Bug 21942, Bug 18018, Bug 23259).
25845f
    
25845f
    This commit improves DST handling significantly in the following
25845f
    ways: firstly is_dst () is overhauled to correctly process DST
25845f
    sequences that would be accepted given the ELF gABI.  This means that
25845f
    we actually now accept slightly more sequences than before.  Now we
25845f
    accept $ORIGIN$ORIGIN, but in the past we accepted only $ORIGIN\0 or
25845f
    $ORIGIN/..., but this kind of behaviour results in unexpected
25845f
    and uninterpreted DST sequences being used as literal search paths
25845f
    leading to security defects.  Therefore the first step in correcting
25845f
    this defect is making is_dst () properly account for all DSTs
25845f
    and making the function context free in the sense that it counts
25845f
    DSTs without knowledge of path, or AT_SECURE.  Next, _dl_dst_count ()
25845f
    is also simplified to count all DSTs regardless of context.
25845f
    Then in _dl_dst_substitute () we reintroduce context-dependent
25845f
    processing for such things as AT_SECURE handling.  At the level of
25845f
    _dl_dst_substitute we can have access to things like the true start
25845f
    of the string sequence to validate $ORIGIN-based paths rooted in
25845f
    trusted directories.  Lastly, we tighten up the accepted sequences
25845f
    in AT_SECURE, and avoid leaving known unexpanded DSTs, this is
25845f
    noted in the NEWS entry.
25845f
    
25845f
    Verified with a sequence of 68 tests on x86_64 that cover
25845f
    non-AT_SECURE and AT_SECURE testing using a sysroot (requires root
25845f
    to run).  The tests cover cases for bug 23102, bug 21942, bug 18018,
25845f
    and bug 23259.  These tests are not yet appropriate for the glibc
25845f
    regression testsuite, but with the upcoming test-in-container testing
25845f
    framework it should be possible to include these tests upstream soon.
25845f
    
25845f
    See the mailing list for the tests:
25845f
    https://www.sourceware.org/ml/libc-alpha/2018-06/msg00251.html
25845f
25845f
Index: glibc-2.17-c758a686/elf/dl-deps.c
25845f
===================================================================
25845f
--- glibc-2.17-c758a686.orig/elf/dl-deps.c
25845f
+++ glibc-2.17-c758a686/elf/dl-deps.c
25845f
@@ -101,7 +101,7 @@ struct list
25845f
   ({									      \
25845f
     const char *__str = (str);						      \
25845f
     const char *__result = __str;					      \
25845f
-    size_t __dst_cnt = DL_DST_COUNT (__str);				      \
25845f
+    size_t __dst_cnt = _dl_dst_count (__str);				      \
25845f
 									      \
25845f
     if (__dst_cnt != 0)							      \
25845f
       {									      \
25845f
Index: glibc-2.17-c758a686/elf/dl-dst.h
25845f
===================================================================
25845f
--- glibc-2.17-c758a686.orig/elf/dl-dst.h
25845f
+++ glibc-2.17-c758a686/elf/dl-dst.h
25845f
@@ -18,19 +18,6 @@
25845f
 
25845f
 #include "trusted-dirs.h"
25845f
 
25845f
-/* Determine the number of DST elements in the name.  Only if IS_PATH is
25845f
-   nonzero paths are recognized (i.e., multiple, ':' separated filenames).  */
25845f
-#define DL_DST_COUNT(name) \
25845f
-  ({									      \
25845f
-    size_t __cnt = 0;							      \
25845f
-    const char *__sf = strchr (name, '$');				      \
25845f
-									      \
25845f
-    if (__builtin_expect (__sf != NULL, 0))				      \
25845f
-      __cnt = _dl_dst_count (__sf);					      \
25845f
-									      \
25845f
-    __cnt; })
25845f
-
25845f
-
25845f
 #ifdef SHARED
25845f
 # define IS_RTLD(l) (l) == &GL(dl_rtld_map)
25845f
 #else
25845f
Index: glibc-2.17-c758a686/elf/dl-load.c
25845f
===================================================================
25845f
--- glibc-2.17-c758a686.orig/elf/dl-load.c
25845f
+++ glibc-2.17-c758a686/elf/dl-load.c
25845f
@@ -174,12 +174,6 @@ is_trusted_path_normalize (const char *p
25845f
   if (len == 0)
25845f
     return false;
25845f
 
25845f
-  if (*path == ':')
25845f
-    {
25845f
-      ++path;
25845f
-      --len;
25845f
-    }
25845f
-
25845f
   char *npath = (char *) alloca (len + 2);
25845f
   char *wnp = npath;
25845f
   while (*path != '\0')
25845f
@@ -230,120 +224,172 @@ is_trusted_path_normalize (const char *p
25845f
   return false;
25845f
 }
25845f
 
25845f
+/* Given a substring starting at INPUT, just after the DST '$' start
25845f
+   token, determine if INPUT contains DST token REF, following the
25845f
+   ELF gABI rules for DSTs:
25845f
+
25845f
+   * Longest possible sequence using the rules (greedy).
25845f
+
25845f
+   * Must start with a $ (enforced by caller).
25845f
+
25845f
+   * Must follow $ with one underscore or ASCII [A-Za-z] (caller
25845f
+     follows these rules for REF) or '{' (start curly quoted name).
25845f
+
25845f
+   * Must follow first two characters with zero or more [A-Za-z0-9_]
25845f
+     (enforced by caller) or '}' (end curly quoted name).
25845f
 
25845f
+   If the sequence is a DST matching REF then the length of the DST
25845f
+   (excluding the $ sign but including curly braces, if any) is
25845f
+   returned, otherwise 0.  */
25845f
 static size_t
25845f
-is_dst (const char *start, const char *name, const char *str, int secure)
25845f
+is_dst (const char *input, const char *ref)
25845f
 {
25845f
-  size_t len;
25845f
   bool is_curly = false;
25845f
 
25845f
-  if (name[0] == '{')
25845f
+  /* Is a ${...} input sequence?  */
25845f
+  if (input[0] == '{')
25845f
     {
25845f
       is_curly = true;
25845f
-      ++name;
25845f
+      ++input;
25845f
     }
25845f
 
25845f
-  len = 0;
25845f
-  while (name[len] == str[len] && name[len] != '\0')
25845f
-    ++len;
25845f
-
25845f
-  if (is_curly)
25845f
-    {
25845f
-      if (name[len] != '}')
25845f
-	return 0;
25845f
-
25845f
-      /* Point again at the beginning of the name.  */
25845f
-      --name;
25845f
-      /* Skip over closing curly brace and adjust for the --name.  */
25845f
-      len += 2;
25845f
-    }
25845f
-  else if (name[len] != '\0' && name[len] != '/')
25845f
-    return 0;
25845f
-
25845f
-  if (__builtin_expect (secure, 0)
25845f
-      && ((name[len] != '\0' && name[len] != '/')
25845f
-	  || (name != start + 1)))
25845f
+  /* Check for matching name, following closing curly brace (if
25845f
+     required), or trailing characters which are part of an
25845f
+     identifier.  */
25845f
+  size_t rlen = strlen (ref);
25845f
+  if (strncmp (input, ref, rlen) != 0
25845f
+      || (is_curly && input[rlen] != '}')
25845f
+      || ((input[rlen] >= 'A' && input[rlen] <= 'Z')
25845f
+	  || (input[rlen] >= 'a' && input[rlen] <= 'z')
25845f
+	  || (input[rlen] >= '0' && input[rlen] <= '9')
25845f
+	  || (input[rlen] == '_')))
25845f
     return 0;
25845f
 
25845f
-  return len;
25845f
+  if (is_curly)
25845f
+    /* Count the two curly braces.  */
25845f
+    return rlen + 2;
25845f
+  else
25845f
+    return rlen;
25845f
 }
25845f
 
25845f
-
25845f
+/* INPUT is the start of a DST sequence at the first '$' occurrence.
25845f
+   If there is a DST we call into _dl_dst_count to count the number of
25845f
+   DSTs.  We count all known DSTs regardless of __libc_enable_secure;
25845f
+   the caller is responsible for enforcing the security of the
25845f
+   substitution rules (usually _dl_dst_substitute).  */
25845f
 size_t
25845f
-_dl_dst_count (const char *name)
25845f
+_dl_dst_count (const char *input)
25845f
 {
25845f
-  const char *const start = name;
25845f
   size_t cnt = 0;
25845f
 
25845f
+  input = strchr (input, '$');
25845f
+
25845f
+  /* Most likely there is no DST.  */
25845f
+  if (__glibc_likely (input == NULL))
25845f
+    return 0;
25845f
+
25845f
   do
25845f
     {
25845f
       size_t len;
25845f
 
25845f
-      /* $ORIGIN is not expanded for SUID/GUID programs (except if it
25845f
-	 is $ORIGIN alone) and it must always appear first in path.  */
25845f
-      ++name;
25845f
-      if ((len = is_dst (start, name, "ORIGIN", INTUSE(__libc_enable_secure))) != 0
25845f
-	  || (len = is_dst (start, name, "PLATFORM", 0)) != 0
25845f
-	  || (len = is_dst (start, name, "LIB", 0)) != 0)
25845f
+      ++input;
25845f
+      /* All DSTs must follow ELF gABI rules, see is_dst ().  */
25845f
+      if ((len = is_dst (input, "ORIGIN")) != 0
25845f
+	  || (len = is_dst (input, "PLATFORM")) != 0
25845f
+	  || (len = is_dst (input, "LIB")) != 0)
25845f
 	++cnt;
25845f
 
25845f
-      name = strchr (name + len, '$');
25845f
+      /* There may be more than one DST in the input.  */
25845f
+      input = strchr (input + len, '$');
25845f
     }
25845f
-  while (name != NULL);
25845f
+  while (input != NULL);
25845f
 
25845f
   return cnt;
25845f
 }
25845f
 
25845f
-
25845f
+/* Process INPUT for DSTs and store in RESULT using the information
25845f
+   from link map L to resolve the DSTs. This function only handles one
25845f
+   path at a time and does not handle colon-separated path lists (see
25845f
+   fillin_rpath ()).  Lastly the size of result in bytes should be at
25845f
+   least equal to the value returned by DL_DST_REQUIRED.  Note that it
25845f
+   is possible for a DT_NEEDED, DT_AUXILIARY, and DT_FILTER entries to
25845f
+   have colons, but we treat those as literal colons here, not as path
25845f
+   list delimeters.  */
25845f
 char *
25845f
-_dl_dst_substitute (struct link_map *l, const char *name, char *result)
25845f
+_dl_dst_substitute (struct link_map *l, const char *input, char *result)
25845f
 {
25845f
-  const char *const start = name;
25845f
-
25845f
-  /* Now fill the result path.  While copying over the string we keep
25845f
-     track of the start of the last path element.  When we come accross
25845f
-     a DST we copy over the value or (if the value is not available)
25845f
-     leave the entire path element out.  */
25845f
+  /* Copy character-by-character from input into the working pointer
25845f
+     looking for any DSTs.  We track the start of input and if we are
25845f
+     going to check for trusted paths, all of which are part of $ORIGIN
25845f
+     handling in SUID/SGID cases (see below).  In some cases, like when
25845f
+     a DST cannot be replaced, we may set result to an empty string and
25845f
+     return.  */
25845f
   char *wp = result;
25845f
-  char *last_elem = result;
25845f
+  const char *start = input;
25845f
   bool check_for_trusted = false;
25845f
 
25845f
   do
25845f
     {
25845f
-      if (__builtin_expect (*name == '$', 0))
25845f
+      if (__glibc_unlikely (*input == '$'))
25845f
 	{
25845f
 	  const char *repl = NULL;
25845f
 	  size_t len;
25845f
 
25845f
-	  ++name;
25845f
-	  if ((len = is_dst (start, name, "ORIGIN", INTUSE(__libc_enable_secure))) != 0)
25845f
+	  ++input;
25845f
+	  if ((len = is_dst (input, "ORIGIN")) != 0)
25845f
 	    {
25845f
-#ifndef SHARED
25845f
-	      if (l == NULL)
25845f
-		repl = _dl_get_origin ();
25845f
+	      /* For SUID/GUID programs we normally ignore the path with
25845f
+		 $ORIGIN in DT_RUNPATH, or DT_RPATH.  However, there is
25845f
+		 one exception to this rule, and it is:
25845f
+
25845f
+		   * $ORIGIN appears as the first path element, and is
25845f
+		     the only string in the path or is immediately
25845f
+		     followed by a path separator and the rest of the
25845f
+		     path.
25845f
+
25845f
+		   * The path is rooted in a trusted directory.
25845f
+
25845f
+		 This exception allows such programs to reference
25845f
+		 shared libraries in subdirectories of trusted
25845f
+		 directories.  The use case is one of general
25845f
+		 organization and deployment flexibility.
25845f
+		 Trusted directories are usually such paths as "/lib64"
25845f
+		 or "/usr/lib64", and the usual RPATHs take the form of
25845f
+		 [$ORIGIN/../$LIB/somedir].  */
25845f
+	      if (__glibc_unlikely (__libc_enable_secure)
25845f
+		  && !(input == start + 1
25845f
+		       && (input[len] == '\0' || input[len] == '/')))
25845f
+		repl = (const char *) -1;
25845f
 	      else
25845f
+		{
25845f
+#ifndef SHARED
25845f
+		  if (l == NULL)
25845f
+		    repl = _dl_get_origin ();
25845f
+		  else
25845f
 #endif
25845f
-		repl = l->l_origin;
25845f
+		    repl = l->l_origin;
25845f
+		}
25845f
 
25845f
 	      check_for_trusted = (INTUSE(__libc_enable_secure)
25845f
 				   && l->l_type == lt_executable);
25845f
 	    }
25845f
-	  else if ((len = is_dst (start, name, "PLATFORM", 0)) != 0)
25845f
+	  else if ((len = is_dst (input, "PLATFORM")) != 0)
25845f
 	    repl = GLRO(dl_platform);
25845f
-	  else if ((len = is_dst (start, name, "LIB", 0)) != 0)
25845f
+	  else if ((len = is_dst (input, "LIB")) != 0)
25845f
 	    repl = DL_DST_LIB;
25845f
 
25845f
 	  if (repl != NULL && repl != (const char *) -1)
25845f
 	    {
25845f
 	      wp = __stpcpy (wp, repl);
25845f
-	      name += len;
25845f
+	      input += len;
25845f
 	    }
25845f
-	  else if (len > 1)
25845f
+	  else if (len != 0)
25845f
 	    {
25845f
-	      /* We cannot use this path element, the value of the
25845f
-		 replacement is unknown.  */
25845f
-	      wp = last_elem;
25845f
-	      break;
25845f
+	      /* We found a valid DST that we know about, but we could
25845f
+	         not find a replacement value for it, therefore we
25845f
+		 cannot use this path and discard it.  */
25845f
+	      *result = '\0';
25845f
+	      return result;
25845f
 	    }
25845f
 	  else
25845f
 	    /* No DST we recognize.  */
25845f
@@ -351,16 +397,26 @@ _dl_dst_substitute (struct link_map *l,
25845f
 	}
25845f
       else
25845f
 	{
25845f
-	  *wp++ = *name++;
25845f
+	  *wp++ = *input++;
25845f
 	}
25845f
     }
25845f
-  while (*name != '\0');
25845f
+  while (*input != '\0');
25845f
 
25845f
   /* In SUID/SGID programs, after $ORIGIN expansion the normalized
25845f
-     path must be rooted in one of the trusted directories.  */
25845f
-  if (__builtin_expect (check_for_trusted, false)
25845f
-      && !is_trusted_path_normalize (last_elem, wp - last_elem))
25845f
-    wp = last_elem;
25845f
+     path must be rooted in one of the trusted directories.  The $LIB
25845f
+     and $PLATFORM DST cannot in any way be manipulated by the caller
25845f
+     because they are fixed values that are set by the dynamic loader
25845f
+     and therefore any paths using just $LIB or $PLATFORM need not be
25845f
+     checked for trust, the authors of the binaries themselves are
25845f
+     trusted to have designed this correctly.  Only $ORIGIN is tested in
25845f
+     this way because it may be manipulated in some ways with hard
25845f
+     links.  */
25845f
+  if (__glibc_unlikely (check_for_trusted)
25845f
+      && !is_trusted_path_normalize (result, wp - result))
25845f
+    {
25845f
+      *result = '\0';
25845f
+      return result;
25845f
+    }
25845f
 
25845f
   *wp = '\0';
25845f
 
25845f
@@ -368,13 +424,13 @@ _dl_dst_substitute (struct link_map *l,
25845f
 }
25845f
 
25845f
 
25845f
-/* Return copy of argument with all recognized dynamic string tokens
25845f
-   ($ORIGIN and $PLATFORM for now) replaced.  On some platforms it
25845f
-   might not be possible to determine the path from which the object
25845f
-   belonging to the map is loaded.  In this case the path element
25845f
-   containing $ORIGIN is left out.  */
25845f
+/* Return a malloc allocated copy of INPUT with all recognized DSTs
25845f
+   replaced. On some platforms it might not be possible to determine the
25845f
+   path from which the object belonging to the map is loaded.  In this
25845f
+   case the path containing the DST is left out.  On error NULL
25845f
+   is returned.  */
25845f
 static char *
25845f
-expand_dynamic_string_token (struct link_map *l, const char *s)
25845f
+expand_dynamic_string_token (struct link_map *l, const char *input)
25845f
 {
25845f
   /* We make two runs over the string.  First we determine how large the
25845f
      resulting string is and then we copy it over.  Since this is no
25845f
@@ -384,22 +440,22 @@ expand_dynamic_string_token (struct link
25845f
   size_t total;
25845f
   char *result;
25845f
 
25845f
-  /* Determine the number of DST elements.  */
25845f
-  cnt = DL_DST_COUNT (s);
25845f
+  /* Determine the number of DSTs.  */
25845f
+  cnt = _dl_dst_count (input);
25845f
 
25845f
   /* If we do not have to replace anything simply copy the string.  */
25845f
   if (__builtin_expect (cnt, 0) == 0)
25845f
-    return local_strdup (s);
25845f
+    return local_strdup (input);
25845f
 
25845f
   /* Determine the length of the substituted string.  */
25845f
-  total = DL_DST_REQUIRED (l, s, strlen (s), cnt);
25845f
+  total = DL_DST_REQUIRED (l, input, strlen (input), cnt);
25845f
 
25845f
   /* Allocate the necessary memory.  */
25845f
   result = (char *) malloc (total + 1);
25845f
   if (result == NULL)
25845f
     return NULL;
25845f
 
25845f
-  return _dl_dst_substitute (l, s, result);
25845f
+  return _dl_dst_substitute (l, input, result);
25845f
 }
25845f
 
25845f