]> git.ipfire.org Git - thirdparty/glibc.git/blobdiff - locale/findlocale.c
Update copyright dates with scripts/update-copyrights.
[thirdparty/glibc.git] / locale / findlocale.c
index 80480113c0cc62ab949e0519e0922bf7cb0bd3f8..872cadb5b9fa5a8844bd7b3f6313bb02d49ce01c 100644 (file)
@@ -1,4 +1,4 @@
-/* Copyright (C) 1996-2001, 2002 Free Software Foundation, Inc.
+/* Copyright (C) 1996-2018 Free Software Foundation, Inc.
    This file is part of the GNU C Library.
    Contributed by Ulrich Drepper <drepper@cygnus.com>, 1996.
 
    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, write to the Free
-   Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
-   02111-1307 USA.  */
+   License along with the GNU C Library; if not, see
+   <http://www.gnu.org/licenses/>.  */
 
 #include <assert.h>
+#include <errno.h>
 #include <locale.h>
 #include <stdlib.h>
 #include <string.h>
 
 #include "localeinfo.h"
 #include "../iconv/gconv_charset.h"
-
-
-#ifndef SHARED
-/* Constant data defined in setlocale.c.  */
-extern struct locale_data *const _nl_C[] attribute_hidden;
+#include "../iconv/gconv_int.h"
+
+
+#ifdef NL_CURRENT_INDIRECT
+# define DEFINE_CATEGORY(category, category_name, items, a) \
+extern struct __locale_data _nl_C_##category; \
+weak_extern (_nl_C_##category)
+# include "categories.def"
+# undef        DEFINE_CATEGORY
+
+/* Array indexed by category of pointers to _nl_C_CATEGORY slots.
+   Elements are zero for categories whose data is never used.  */
+struct __locale_data *const _nl_C[] attribute_hidden =
+  {
+# define DEFINE_CATEGORY(category, category_name, items, a) \
+    [category] = &_nl_C_##category,
+# include "categories.def"
+# undef        DEFINE_CATEGORY
+  };
 #else
 # define _nl_C         (_nl_C_locobj.__locales)
 #endif
@@ -42,17 +56,55 @@ extern struct locale_data *const _nl_C[] attribute_hidden;
    which are somehow addressed.  */
 struct loaded_l10nfile *_nl_locale_file_list[__LC_LAST];
 
-const char _nl_default_locale_path[] attribute_hidden = LOCALEDIR;
+const char _nl_default_locale_path[] attribute_hidden = COMPLOCALEDIR;
 
+/* 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';
+}
 
-struct locale_data *
-internal_function
+/* 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 *
 _nl_find_locale (const char *locale_path, size_t locale_path_len,
                 int category, const char **name)
 {
   int mask;
   /* Name of the locale for this category.  */
-  char *loc_name;
+  const char *cloc_name = *name;
   const char *language;
   const char *modifier;
   const char *territory;
@@ -60,54 +112,75 @@ _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 (cloc_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[category]);
-      if (*name == NULL || (*name)[0] == '\0')
-       *name = getenv ("LANG");
+      cloc_name = getenv ("LC_ALL");
+      if (!name_present (cloc_name))
+       cloc_name = getenv (_nl_category_names.str
+                           + _nl_category_name_idxs[category]);
+      if (!name_present (cloc_name))
+       cloc_name = getenv ("LANG");
+      if (!name_present (cloc_name))
+       cloc_name = _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 (cloc_name, _nl_C_name), 1) == 0
+      || __builtin_expect (strcmp (cloc_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;
+      *name = _nl_C_name;
       return _nl_C[category];
     }
+  else if (!valid_locale_name (cloc_name))
+    {
+      __set_errno (EINVAL);
+      return NULL;
+    }
+
+  *name = cloc_name;
 
   /* We really have to load some data.  First we try the archive,
      but only if there was no LOCPATH environment variable specified.  */
-  if (__builtin_expect (locale_path == NULL, 1))
+  if (__glibc_likely (locale_path == NULL))
     {
-      struct locale_data *data = _nl_load_locale_from_archive (category, name);
-      if (__builtin_expect (data != NULL, 1))
+      struct __locale_data *data
+       = _nl_load_locale_from_archive (category, name);
+      if (__glibc_likely (data != NULL))
        return data;
 
+      /* Nothing in the archive with the given name.  Expanding it as
+        an alias and retry.  */
+      cloc_name = _nl_expand_alias (*name);
+      if (cloc_name != NULL)
+       {
+         data = _nl_load_locale_from_archive (category, &cloc_name);
+         if (__builtin_expect (data != NULL, 1))
+           return data;
+       }
+
       /* Nothing in the archive.  Set the default path to search below.  */
       locale_path = _nl_default_locale_path;
       locale_path_len = sizeof _nl_default_locale_path;
     }
+  else
+    /* We really have to load some data.  First see whether the name is
+       an alias.  Please note that this makes it impossible to have "C"
+       or "POSIX" as aliases.  */
+    cloc_name = _nl_expand_alias (*name);
 
-  /* We really have to load some data.  First see whether the name is
-     an alias.  Please note that this makes it impossible to have "C"
-     or "POSIX" as aliases.  */
-  loc_name = (char *) _nl_expand_alias (*name);
-  if (loc_name == NULL)
+  if (cloc_name == NULL)
     /* It is no alias.  */
-    loc_name = (char *) *name;
+    cloc_name = *name;
 
   /* Make a writable copy of the locale name.  */
-  loc_name = strdupa (loc_name);
+  char *loc_name = strdupa (cloc_name);
 
   /* LOCALE can consist of up to four recognized parts for the XPG syntax:
 
@@ -124,6 +197,9 @@ _nl_find_locale (const char *locale_path, size_t locale_path_len,
    */
   mask = _nl_explode_name (loc_name, &language, &modifier, &territory,
                           &codeset, &normalized_codeset);
+  if (mask == -1)
+    /* Memory allocate problem.  */
+    return NULL;
 
   /* If exactly this locale was already asked for we have an entry with
      the complete name.  */
@@ -131,7 +207,8 @@ _nl_find_locale (const char *locale_path, size_t locale_path_len,
                                    locale_path, locale_path_len, mask,
                                    language, territory, codeset,
                                    normalized_codeset, modifier,
-                                   _nl_category_names[category], 0);
+                                   _nl_category_names.str
+                                   + _nl_category_name_idxs[category], 0);
 
   if (locale_file == NULL)
     {
@@ -141,7 +218,8 @@ _nl_find_locale (const char *locale_path, size_t locale_path_len,
                                        locale_path, locale_path_len, mask,
                                        language, territory, codeset,
                                        normalized_codeset, modifier,
-                                       _nl_category_names[category], 1);
+                                       _nl_category_names.str
+                                       + _nl_category_name_idxs[category], 1);
       if (locale_file == NULL)
        /* This means we are out of core.  */
        return NULL;
@@ -197,12 +275,12 @@ _nl_find_locale (const char *locale_path, size_t locale_path_len,
          [__LC_MEASUREMENT] = _NL_ITEM_INDEX (_NL_MEASUREMENT_CODESET),
          [__LC_IDENTIFICATION] = _NL_ITEM_INDEX (_NL_IDENTIFICATION_CODESET)
        };
-      const struct locale_data *data;
+      const struct __locale_data *data;
       const char *locale_codeset;
       char *clocale_codeset;
       char *ccodeset;
 
-      data = (const struct locale_data *) locale_file->data;
+      data = (const struct __locale_data *) locale_file->data;
       locale_codeset =
        (const char *) data->values[codeset_idx[category]].string;
       assert (locale_codeset != NULL);
@@ -224,7 +302,7 @@ _nl_find_locale (const char *locale_path, size_t locale_path_len,
   /* Determine the locale name for which loading succeeded.  This
      information comes from the file name.  The form is
      <path>/<locale>/LC_foo.  We must extract the <locale> part.  */
-  if (((const struct locale_data *) locale_file->data)->name == NULL)
+  if (((const struct __locale_data *) locale_file->data)->name == NULL)
     {
       char *cp, *endp;
 
@@ -232,65 +310,48 @@ _nl_find_locale (const char *locale_path, size_t locale_path_len,
       cp = endp - 1;
       while (cp[-1] != '/')
        --cp;
-      ((struct locale_data *) locale_file->data)->name = __strndup (cp,
-                                                                   endp - cp);
+      ((struct __locale_data *) locale_file->data)->name
+       = __strndup (cp, endp - cp);
     }
 
   /* Determine whether the user wants transliteration or not.  */
-  if (modifier != NULL && __strcasecmp (modifier, "TRANSLIT") == 0)
-    ((struct locale_data *) locale_file->data)->use_translit = 1;
+  if (modifier != NULL
+      && __strcasecmp_l (modifier, "TRANSLIT", _nl_C_locobj_ptr) == 0)
+    ((struct __locale_data *) locale_file->data)->use_translit = 1;
 
   /* Increment the usage count.  */
-  if (((const struct locale_data *) locale_file->data)->usage_count
+  if (((const struct __locale_data *) locale_file->data)->usage_count
       < MAX_USAGE_COUNT)
-    ++((struct locale_data *) locale_file->data)->usage_count;
+    ++((struct __locale_data *) locale_file->data)->usage_count;
 
-  return (struct locale_data *) locale_file->data;
+  return (struct __locale_data *) locale_file->data;
 }
 
 
 /* Calling this function assumes the lock for handling global locale data
    is acquired.  */
 void
-internal_function
-_nl_remove_locale (int locale, struct locale_data *data)
+_nl_remove_locale (int locale, struct __locale_data *data)
 {
   if (--data->usage_count == 0)
     {
-      /* First search the entry in the list of loaded files.  */
-      struct loaded_l10nfile *ptr = _nl_locale_file_list[locale];
-
-      /* Search for the entry.  It must be in the list.  Otherwise it
-        is a bug and we crash badly.  */
-      while ((struct locale_data *) ptr->data != data)
-       ptr = ptr->next;
-
-      /* Mark the data as not available anymore.  So when the data has
-        to be used again it is reloaded.  */
-      ptr->decided = 0;
-      ptr->data = NULL;
-
-      /* Free the name.  */
-      free ((char *) data->name);
-
-#ifdef _POSIX_MAPPED_FILES
-      /* Really delete the data.  First delete the real data.  */
-      if (__builtin_expect (data->alloc == ld_mapped, 1))
+      if (data->alloc != ld_archive)
        {
-         /* Try to unmap the area.  If this fails we mark the area as
-            permanent.  */
-         if (__munmap ((caddr_t) data->filedata, data->filesize) != 0)
-           {
-             data->usage_count = UNDELETABLE;
-             return;
-           }
+         /* First search the entry in the list of loaded files.  */
+         struct loaded_l10nfile *ptr = _nl_locale_file_list[locale];
+
+         /* Search for the entry.  It must be in the list.  Otherwise it
+            is a bug and we crash badly.  */
+         while ((struct __locale_data *) ptr->data != data)
+           ptr = ptr->next;
+
+         /* Mark the data as not available anymore.  So when the data has
+            to be used again it is reloaded.  */
+         ptr->decided = 0;
+         ptr->data = NULL;
        }
-      else
-#endif /* _POSIX_MAPPED_FILES */
-       /* The memory was malloced.  */
-       free ((void *) data->filedata);
 
-      /* Now free the structure itself.  */
-      free (data);
+      /* This does the real work.  */
+      _nl_unload_locale (data);
     }
 }