Picked from upstream commits, but excluding changes to news and tests:
d183645616b0533 and 4e8f95a0df7c2
Also see https://sourceware.org/bugzilla/show_bug.cgi?id=17137
diff --git a/locale/setlocale.c b/locale/setlocale.c
index 9458468..6455b8b 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 (__glibc_unlikely (strchr (locale, ';') != NULL))
{
/* 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;
}
diff --git a/locale/findlocale.c b/locale/findlocale.c
index bbaf708..22e8b53 100644
--- a/locale/findlocale.c
+++ b/locale/findlocale.c
@@ -17,6 +17,7 @@
. */
#include
+#include
#include
#include
#include
@@ -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. */