about summary refs log tree commit diff
diff options
context:
space:
mode:
authorFlorian Weimer <fweimer@redhat.com>2017-06-19 22:31:04 +0200
committerFlorian Weimer <fweimer@redhat.com>2017-06-19 22:46:18 +0200
commitaab04ca5d359150e17631e6a9b44b65e93bdc467 (patch)
tree90b698c674fd58131664c34e5bc3a8387dd3f8cb
parent87bd4186da10371f46e2f1a7bf7c0a45bb04f1ac (diff)
downloadglibc-aab04ca5d359150e17631e6a9b44b65e93bdc467.tar.gz
glibc-aab04ca5d359150e17631e6a9b44b65e93bdc467.tar.xz
glibc-aab04ca5d359150e17631e6a9b44b65e93bdc467.zip
ld.so: Reject overly long LD_PRELOAD path elements
(cherry picked from commit 6d0ba622891bed9d8394eef1935add53003b12e8)
-rw-r--r--ChangeLog7
-rw-r--r--elf/rtld.c82
2 files changed, 73 insertions, 16 deletions
diff --git a/ChangeLog b/ChangeLog
index e37f14f02a..07c95fe8d0 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,12 @@
 2017-06-19  Florian Weimer  <fweimer@redhat.com>
 
+	* elf/rtld.c (SECURE_NAME_LIMIT, SECURE_PATH_LIMIT): Define.
+	(dso_name_valid_for_suid): New function.
+	(handle_ld_preload): Likewise.
+	(dl_main): Call it.  Remove alloca.
+
+2017-06-19  Florian Weimer  <fweimer@redhat.com>
+
 	[BZ #21624]
 	CVE-2017-1000366
 	* elf/rtld.c (process_envvars): Ignore LD_LIBRARY_PATH for
diff --git a/elf/rtld.c b/elf/rtld.c
index 215a9aec8f..1d8eab9fe2 100644
--- a/elf/rtld.c
+++ b/elf/rtld.c
@@ -99,6 +99,35 @@ uintptr_t __pointer_chk_guard_local
 strong_alias (__pointer_chk_guard_local, __pointer_chk_guard)
 #endif
 
+/* Length limits for names and paths, to protect the dynamic linker,
+   particularly when __libc_enable_secure is active.  */
+#ifdef NAME_MAX
+# define SECURE_NAME_LIMIT NAME_MAX
+#else
+# define SECURE_NAME_LIMIT 255
+#endif
+#ifdef PATH_MAX
+# define SECURE_PATH_LIMIT PATH_MAX
+#else
+# define SECURE_PATH_LIMIT 1024
+#endif
+
+/* Check that AT_SECURE=0, or that the passed name does not contain
+   directories and is not overly long.  Reject empty names
+   unconditionally.  */
+static bool
+dso_name_valid_for_suid (const char *p)
+{
+  if (__glibc_unlikely (__libc_enable_secure))
+    {
+      /* Ignore pathnames with directories for AT_SECURE=1
+	 programs, and also skip overlong names.  */
+      size_t len = strlen (p);
+      if (len >= SECURE_NAME_LIMIT || memchr (p, '/', len) != NULL)
+	return false;
+    }
+  return *p != '\0';
+}
 
 /* List of auditing DSOs.  */
 static struct audit_list
@@ -730,6 +759,42 @@ static const char *preloadlist attribute_relro;
 /* Nonzero if information about versions has to be printed.  */
 static int version_info attribute_relro;
 
+/* The LD_PRELOAD environment variable gives list of libraries
+   separated by white space or colons that are loaded before the
+   executable's dependencies and prepended to the global scope list.
+   (If the binary is running setuid all elements containing a '/' are
+   ignored since it is insecure.)  Return the number of preloads
+   performed.  */
+unsigned int
+handle_ld_preload (const char *preloadlist, struct link_map *main_map)
+{
+  unsigned int npreloads = 0;
+  const char *p = preloadlist;
+  char fname[SECURE_PATH_LIMIT];
+
+  while (*p != '\0')
+    {
+      /* Split preload list at space/colon.  */
+      size_t len = strcspn (p, " :");
+      if (len > 0 && len < sizeof (fname))
+	{
+	  memcpy (fname, p, len);
+	  fname[len] = '\0';
+	}
+      else
+	fname[0] = '\0';
+
+      /* Skip over the substring and the following delimiter.  */
+      p += len;
+      if (*p != '\0')
+	++p;
+
+      if (dso_name_valid_for_suid (fname))
+	npreloads += do_preload (fname, main_map, "LD_PRELOAD");
+    }
+  return npreloads;
+}
+
 static void
 dl_main (const ElfW(Phdr) *phdr,
 	 ElfW(Word) phnum,
@@ -1481,23 +1546,8 @@ ERROR: ld.so: object '%s' cannot be loaded as audit interface: %s; ignored.\n",
 
   if (__glibc_unlikely (preloadlist != NULL))
     {
-      /* The LD_PRELOAD environment variable gives list of libraries
-	 separated by white space or colons that are loaded before the
-	 executable's dependencies and prepended to the global scope
-	 list.  If the binary is running setuid all elements
-	 containing a '/' are ignored since it is insecure.  */
-      char *list = strdupa (preloadlist);
-      char *p;
-
       HP_TIMING_NOW (start);
-
-      /* Prevent optimizing strsep.  Speed is not important here.  */
-      while ((p = (strsep) (&list, " :")) != NULL)
-	if (p[0] != '\0'
-	    && (__builtin_expect (! __libc_enable_secure, 1)
-		|| strchr (p, '/') == NULL))
-	  npreloads += do_preload (p, main_map, "LD_PRELOAD");
-
+      npreloads += handle_ld_preload (preloadlist, main_map);
       HP_TIMING_NOW (stop);
       HP_TIMING_DIFF (diff, start, stop);
       HP_TIMING_ACCUM_NT (load_time, diff);