about summary refs log tree commit diff
diff options
context:
space:
mode:
authorStan Shebs <stanshebs@google.com>2016-06-27 09:36:28 -0700
committerStan Shebs <stanshebs@google.com>2016-06-27 09:36:28 -0700
commitbdf9cbb2db3493470a6c56ed5e5e1589abaeca8c (patch)
tree9cc4859dd3709c8ede5dff6aaf0f0a1a921b19aa
parenta39e9f7a233799f28a79c0a562b66c250e2cd4b2 (diff)
downloadglibc-bdf9cbb2db3493470a6c56ed5e5e1589abaeca8c.tar.gz
glibc-bdf9cbb2db3493470a6c56ed5e5e1589abaeca8c.tar.xz
glibc-bdf9cbb2db3493470a6c56ed5e5e1589abaeca8c.zip
Backport checking of locale environment handling
-rw-r--r--README.google7
-rw-r--r--locale/findlocale.c74
-rw-r--r--locale/setlocale.c14
3 files changed, 80 insertions, 15 deletions
diff --git a/README.google b/README.google
index b65b4eef00..49ab22c708 100644
--- a/README.google
+++ b/README.google
@@ -581,3 +581,10 @@ resolv/res_send.c
   under high load (BZ15946, CVE-2013-7423)
   https://sourceware.org/git/gitweb.cgi?p=glibc.git;h=f9d2d03254a58d92635a311a42253eeed5a40a47
   (stanshebs, backport)
+
+locale/findlocale.c
+locale/setlocale.c
+  For b/29130905, fix locale vulnerabilities (BZ1737, CVE-2014-0475)
+  https://sourceware.org/git/gitweb.cgi?p=glibc.git;h=4e8f95a0df7c2300b830ec12c0ae1e161bc8a8a3
+  https://sourceware.org/git/gitweb.cgi?p=glibc.git;h=d183645616b0533b3acee28f1a95570bffbdf50f
+  (stanshebs, backport)
diff --git a/locale/findlocale.c b/locale/findlocale.c
index 0c42b99251..faeee61bcb 100644
--- a/locale/findlocale.c
+++ b/locale/findlocale.c
@@ -17,6 +17,7 @@
    <http://www.gnu.org/licenses/>.  */
 
 #include <assert.h>
+#include <errno.h>
 #include <locale.h>
 #include <stdlib.h>
 #include <string.h>
@@ -57,6 +58,45 @@ struct loaded_l10nfile *_nl_locale_file_list[__LC_LAST];
 
 const char _nl_default_locale_path[] attribute_hidden = LOCALEDIR;
 
+/* Checks if the name is actually present, that is, not NULL and not
+   empty.  */
+static inline int
+name_present (const char *name)
+{
+  return name != NULL && name[0] != '\0';
+}
+
+/* Checks that the locale name neither extremely long, nor contains a
+   ".." path component (to prevent directory traversal).  */
+static inline int
+valid_locale_name (const char *name)
+{
+  /* Not set.  */
+  size_t namelen = strlen (name);
+  /* Name too long.  The limit is arbitrary and prevents stack overflow
+     issues later.  */
+  if (__glibc_unlikely (namelen > 255))
+    return 0;
+  /* Directory traversal attempt.  */
+  static const char slashdot[4] = {'/', '.', '.', '/'};
+  if (__glibc_unlikely (memmem (name, namelen,
+				slashdot, sizeof (slashdot)) != NULL))
+    return 0;
+  if (namelen == 2 && __glibc_unlikely (name[0] == '.' && name [1] == '.'))
+    return 0;
+  if (namelen >= 3
+      && __glibc_unlikely (((name[0] == '.'
+			     && name[1] == '.'
+			     && name[2] == '/')
+			    || (name[namelen - 3] == '/'
+				&& name[namelen - 2] == '.'
+				&& name[namelen - 1] == '.'))))
+    return 0;
+  /* If there is a slash in the name, it must start with one.  */
+  if (__glibc_unlikely (memchr (name, '/', namelen) != NULL) && name[0] != '/')
+    return 0;
+  return 1;
+}
 
 struct __locale_data *
 internal_function
@@ -65,7 +105,7 @@ _nl_find_locale (const char *locale_path, size_t locale_path_len,
 {
   int mask;
   /* Name of the locale for this category.  */
-  char *loc_name;
+  char *loc_name = (char *) *name;
   const char *language;
   const char *modifier;
   const char *territory;
@@ -73,31 +113,39 @@ _nl_find_locale (const char *locale_path, size_t locale_path_len,
   const char *normalized_codeset;
   struct loaded_l10nfile *locale_file;
 
-  if ((*name)[0] == '\0')
+  if (loc_name[0] == '\0')
     {
       /* The user decides which locale to use by setting environment
 	 variables.  */
-      *name = getenv ("LC_ALL");
-      if (*name == NULL || (*name)[0] == '\0')
-	*name = getenv (_nl_category_names.str
+      loc_name = getenv ("LC_ALL");
+      if (!name_present (loc_name))
+	loc_name = getenv (_nl_category_names.str
 			+ _nl_category_name_idxs[category]);
-      if (*name == NULL || (*name)[0] == '\0')
-	*name = getenv ("LANG");
+      if (!name_present (loc_name))
+	loc_name = getenv ("LANG");
+      if (!name_present (loc_name))
+	loc_name = (char *) _nl_C_name;
     }
 
-  if (*name == NULL || (*name)[0] == '\0'
-      || (__builtin_expect (__libc_enable_secure, 0)
-	  && strchr (*name, '/') != NULL))
-    *name = (char *) _nl_C_name;
+  /* We used to fall back to the C locale if the name contains a slash
+     character '/', but we now check for directory traversal in
+     valid_locale_name, so this is no longer necessary.  */
 
-  if (__builtin_expect (strcmp (*name, _nl_C_name), 1) == 0
-      || __builtin_expect (strcmp (*name, _nl_POSIX_name), 1) == 0)
+  if (__builtin_expect (strcmp (loc_name, _nl_C_name), 1) == 0
+      || __builtin_expect (strcmp (loc_name, _nl_POSIX_name), 1) == 0)
     {
       /* We need not load anything.  The needed data is contained in
 	 the library itself.  */
       *name = (char *) _nl_C_name;
       return _nl_C[category];
     }
+  else if (!valid_locale_name (loc_name))
+    {
+      __set_errno (EINVAL);
+      return NULL;
+    }
+
+  *name = loc_name;
 
   /* We really have to load some data.  First we try the archive,
      but only if there was no LOCPATH environment variable specified.  */
diff --git a/locale/setlocale.c b/locale/setlocale.c
index b70fa6cbce..a4c59832ca 100644
--- a/locale/setlocale.c
+++ b/locale/setlocale.c
@@ -272,6 +272,8 @@ setlocale (int category, const char *locale)
 	 of entries of the form `CATEGORY=VALUE'.  */
       const char *newnames[__LC_LAST];
       struct __locale_data *newdata[__LC_LAST];
+      /* Copy of the locale argument, for in-place splitting.  */
+      char *locale_copy = NULL;
 
       /* Set all name pointers to the argument name.  */
       for (category = 0; category < __LC_LAST; ++category)
@@ -281,7 +283,13 @@ setlocale (int category, const char *locale)
       if (__builtin_expect (strchr (locale, ';') != NULL, 0))
 	{
 	  /* This is a composite name.  Make a copy and split it up.  */
-	  char *np = strdupa (locale);
+	  locale_copy = strdup (locale);
+	  if (__glibc_unlikely (locale_copy == NULL))
+	    {
+	      __libc_rwlock_unlock (__libc_setlocale_lock);
+	      return NULL;
+	    }
+	  char *np = locale_copy;
 	  char *cp;
 	  int cnt;
 
@@ -299,6 +307,7 @@ setlocale (int category, const char *locale)
 		{
 		error_return:
 		  __libc_rwlock_unlock (__libc_setlocale_lock);
+		  free (locale_copy);
 
 		  /* Bogus category name.  */
 		  ERROR_RETURN;
@@ -391,8 +400,9 @@ setlocale (int category, const char *locale)
       /* Critical section left.  */
       __libc_rwlock_unlock (__libc_setlocale_lock);
 
-      /* Free the resources (the locale path variable).  */
+      /* Free the resources.  */
       free (locale_path);
+      free (locale_copy);
 
       return composite;
     }