170 lines
5.6 KiB
Diff
170 lines
5.6 KiB
Diff
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 @@
|
|
<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. */
|