mirror of
https://sourceware.org/git/glibc.git
synced 2024-11-21 12:30:06 +00:00
_nl_find_locale: Improve handling of crafted locale names [BZ #17137]
Prevent directory traversal in locale-related environment variables (CVE-2014-0475).
This commit is contained in:
parent
d183645616
commit
4e8f95a0df
@ -1,3 +1,12 @@
|
||||
2014-07-02 Florian Weimer <fweimer@redhat.com>
|
||||
|
||||
[BZ #17137]
|
||||
* locale/findlocale.c (name_present, valid_locale_name): New
|
||||
functions.
|
||||
(_nl_find_locale): Use the loc_name variable to store name
|
||||
candidates. Call name_present and valid_locale_name to check and
|
||||
validate locale names. Return an error if the locale is invalid.
|
||||
|
||||
2014-07-02 Florian Weimer <fweimer@redhat.com>
|
||||
|
||||
* locale/setlocale.c (setlocale): Use strdup for allocating
|
||||
|
12
NEWS
12
NEWS
@ -21,7 +21,8 @@ Version 2.20
|
||||
16882, 16885, 16888, 16890, 16912, 16915, 16916, 16917, 16918, 16922,
|
||||
16927, 16928, 16932, 16943, 16958, 16965, 16966, 16967, 16977, 16978,
|
||||
16984, 16990, 16996, 17009, 17022, 17031, 17042, 17048, 17050, 17058,
|
||||
17061, 17062, 17069, 17075, 17079, 17084, 17086, 17092, 17097, 17125.
|
||||
17061, 17062, 17069, 17075, 17079, 17084, 17086, 17092, 17097, 17125,
|
||||
17137.
|
||||
|
||||
* Optimized strchr implementation for AArch64. Contributed by ARM Ltd.
|
||||
|
||||
@ -70,6 +71,15 @@ Version 2.20
|
||||
On configurations that support it (all Linux configurations), it's now
|
||||
used regardless of the --enable-add-ons switch to configure. It is no
|
||||
longer possible to build such configurations without pthreads support.
|
||||
|
||||
* Locale names, including those obtained from environment variables (LANG
|
||||
and the LC_* variables), are more tightly checked for proper syntax.
|
||||
setlocale will now fail (with EINVAL) for locale names that are overly
|
||||
long, contain slashes without starting with a slash, or contain ".." path
|
||||
components. (CVE-2014-0475) Previously, some valid locale names were
|
||||
silently replaced with the "C" locale when running in AT_SECURE mode
|
||||
(e.g., in a SUID program). This is no longer necessary because of the
|
||||
additional checks.
|
||||
|
||||
Version 2.19
|
||||
|
||||
|
@ -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. */
|
||||
|
@ -1,3 +1,9 @@
|
||||
2014-07-02 Florian Weimer <fweimer@redhat.com>
|
||||
|
||||
* tst-setlocale3.c: New file.
|
||||
* Makefile (tests): Add tst-setlocale3.
|
||||
(tst-setlocale3-ENV): New variable.
|
||||
|
||||
2014-06-20 Stefan Liebler <stli@linux.vnet.ibm.com>
|
||||
|
||||
* Makefile (LOCALES): Add en_GB.UTF-8.
|
||||
|
@ -74,7 +74,8 @@ locale_test_suite := tst_iswalnum tst_iswalpha tst_iswcntrl \
|
||||
tests = $(locale_test_suite) tst-digits tst-setlocale bug-iconv-trans \
|
||||
tst-leaks tst-mbswcs1 tst-mbswcs2 tst-mbswcs3 tst-mbswcs4 tst-mbswcs5 \
|
||||
tst-mbswcs6 tst-xlocale1 tst-xlocale2 bug-usesetlocale \
|
||||
tst-strfmon1 tst-sscanf bug-setlocale1 tst-setlocale2 tst-wctype
|
||||
tst-strfmon1 tst-sscanf bug-setlocale1 tst-setlocale2 tst-setlocale3 \
|
||||
tst-wctype
|
||||
tests-static = bug-setlocale1-static
|
||||
tests += $(tests-static)
|
||||
ifeq (yes,$(build-shared))
|
||||
|
203
localedata/tst-setlocale3.c
Normal file
203
localedata/tst-setlocale3.c
Normal file
@ -0,0 +1,203 @@
|
||||
/* Regression test for setlocale invalid environment variable handling.
|
||||
Copyright (C) 2014 Free Software Foundation, Inc.
|
||||
This file is part of the GNU C Library.
|
||||
|
||||
The GNU C Library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
The GNU C Library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with the GNU C Library; if not, see
|
||||
<http://www.gnu.org/licenses/>. */
|
||||
|
||||
#include <locale.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
/* The result of setlocale may be overwritten by subsequent calls, so
|
||||
this wrapper makes a copy. */
|
||||
static char *
|
||||
setlocale_copy (int category, const char *locale)
|
||||
{
|
||||
const char *result = setlocale (category, locale);
|
||||
if (result == NULL)
|
||||
return NULL;
|
||||
return strdup (result);
|
||||
}
|
||||
|
||||
static char *de_locale;
|
||||
|
||||
static void
|
||||
setlocale_fail (const char *envstring)
|
||||
{
|
||||
setenv ("LC_CTYPE", envstring, 1);
|
||||
if (setlocale (LC_CTYPE, "") != NULL)
|
||||
{
|
||||
printf ("unexpected setlocale success for \"%s\" locale\n", envstring);
|
||||
exit (1);
|
||||
}
|
||||
const char *newloc = setlocale (LC_CTYPE, NULL);
|
||||
if (strcmp (newloc, de_locale) != 0)
|
||||
{
|
||||
printf ("failed setlocale call \"%s\" changed locale to \"%s\"\n",
|
||||
envstring, newloc);
|
||||
exit (1);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
setlocale_success (const char *envstring)
|
||||
{
|
||||
setenv ("LC_CTYPE", envstring, 1);
|
||||
char *newloc = setlocale_copy (LC_CTYPE, "");
|
||||
if (newloc == NULL)
|
||||
{
|
||||
printf ("setlocale for \"%s\": %m\n", envstring);
|
||||
exit (1);
|
||||
}
|
||||
if (strcmp (newloc, de_locale) == 0)
|
||||
{
|
||||
printf ("setlocale with LC_CTYPE=\"%s\" left locale at \"%s\"\n",
|
||||
envstring, de_locale);
|
||||
exit (1);
|
||||
}
|
||||
if (setlocale (LC_CTYPE, de_locale) == NULL)
|
||||
{
|
||||
printf ("restoring locale \"%s\" with LC_CTYPE=\"%s\": %m\n",
|
||||
de_locale, envstring);
|
||||
exit (1);
|
||||
}
|
||||
char *newloc2 = setlocale_copy (LC_CTYPE, newloc);
|
||||
if (newloc2 == NULL)
|
||||
{
|
||||
printf ("restoring locale \"%s\" following \"%s\": %m\n",
|
||||
newloc, envstring);
|
||||
exit (1);
|
||||
}
|
||||
if (strcmp (newloc, newloc2) != 0)
|
||||
{
|
||||
printf ("representation of locale \"%s\" changed from \"%s\" to \"%s\"",
|
||||
envstring, newloc, newloc2);
|
||||
exit (1);
|
||||
}
|
||||
free (newloc);
|
||||
free (newloc2);
|
||||
|
||||
if (setlocale (LC_CTYPE, de_locale) == NULL)
|
||||
{
|
||||
printf ("restoring locale \"%s\" with LC_CTYPE=\"%s\": %m\n",
|
||||
de_locale, envstring);
|
||||
exit (1);
|
||||
}
|
||||
}
|
||||
|
||||
/* Checks that a known-good locale still works if LC_ALL contains a
|
||||
value which should be ignored. */
|
||||
static void
|
||||
setlocale_ignore (const char *to_ignore)
|
||||
{
|
||||
const char *fr_locale = "fr_FR.UTF-8";
|
||||
setenv ("LC_CTYPE", fr_locale, 1);
|
||||
char *expected_locale = setlocale_copy (LC_CTYPE, "");
|
||||
if (expected_locale == NULL)
|
||||
{
|
||||
printf ("setlocale with LC_CTYPE=\"%s\" failed: %m\n", fr_locale);
|
||||
exit (1);
|
||||
}
|
||||
if (setlocale (LC_CTYPE, de_locale) == NULL)
|
||||
{
|
||||
printf ("failed to restore locale: %m\n");
|
||||
exit (1);
|
||||
}
|
||||
unsetenv ("LC_CTYPE");
|
||||
|
||||
setenv ("LC_ALL", to_ignore, 1);
|
||||
setenv ("LC_CTYPE", fr_locale, 1);
|
||||
const char *actual_locale = setlocale (LC_CTYPE, "");
|
||||
if (actual_locale == NULL)
|
||||
{
|
||||
printf ("setlocale with LC_ALL, LC_CTYPE=\"%s\" failed: %m\n",
|
||||
fr_locale);
|
||||
exit (1);
|
||||
}
|
||||
if (strcmp (actual_locale, expected_locale) != 0)
|
||||
{
|
||||
printf ("setlocale under LC_ALL failed: got \"%s\", expected \"%s\"\n",
|
||||
actual_locale, expected_locale);
|
||||
exit (1);
|
||||
}
|
||||
unsetenv ("LC_CTYPE");
|
||||
setlocale_success (fr_locale);
|
||||
unsetenv ("LC_ALL");
|
||||
free (expected_locale);
|
||||
}
|
||||
|
||||
static int
|
||||
do_test (void)
|
||||
{
|
||||
/* The glibc test harness sets this environment variable
|
||||
uncondionally. */
|
||||
unsetenv ("LC_ALL");
|
||||
|
||||
de_locale = setlocale_copy (LC_CTYPE, "de_DE.UTF-8");
|
||||
if (de_locale == NULL)
|
||||
{
|
||||
printf ("setlocale (LC_CTYPE, \"de_DE.UTF-8\"): %m\n");
|
||||
return 1;
|
||||
}
|
||||
setlocale_success ("C");
|
||||
setlocale_success ("en_US.UTF-8");
|
||||
setlocale_success ("/en_US.UTF-8");
|
||||
setlocale_success ("//en_US.UTF-8");
|
||||
setlocale_ignore ("");
|
||||
|
||||
setlocale_fail ("does-not-exist");
|
||||
setlocale_fail ("/");
|
||||
setlocale_fail ("/../localedata/en_US.UTF-8");
|
||||
setlocale_fail ("en_US.UTF-8/");
|
||||
setlocale_fail ("en_US.UTF-8/..");
|
||||
setlocale_fail ("en_US.UTF-8/../en_US.UTF-8");
|
||||
setlocale_fail ("../localedata/en_US.UTF-8");
|
||||
{
|
||||
size_t large_length = 1024;
|
||||
char *large_name = malloc (large_length + 1);
|
||||
if (large_name == NULL)
|
||||
{
|
||||
puts ("malloc failure");
|
||||
return 1;
|
||||
}
|
||||
memset (large_name, '/', large_length);
|
||||
const char *suffix = "en_US.UTF-8";
|
||||
strcpy (large_name + large_length - strlen (suffix), suffix);
|
||||
setlocale_fail (large_name);
|
||||
free (large_name);
|
||||
}
|
||||
{
|
||||
size_t huge_length = 64 * 1024 * 1024;
|
||||
char *huge_name = malloc (huge_length + 1);
|
||||
if (huge_name == NULL)
|
||||
{
|
||||
puts ("malloc failure");
|
||||
return 1;
|
||||
}
|
||||
memset (huge_name, 'X', huge_length);
|
||||
huge_name[huge_length] = '\0';
|
||||
/* Construct a composite locale specification. */
|
||||
const char *prefix = "LC_CTYPE=de_DE.UTF-8;LC_TIME=";
|
||||
memcpy (huge_name, prefix, strlen (prefix));
|
||||
setlocale_fail (huge_name);
|
||||
free (huge_name);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#define TEST_FUNCTION do_test ()
|
||||
#include "../test-skeleton.c"
|
Loading…
Reference in New Issue
Block a user