Bug 25123 - Heap use-after-free in setlocale
Summary: Heap use-after-free in setlocale
Status: RESOLVED INVALID
Alias: None
Product: glibc
Classification: Unclassified
Component: locale (show other bugs)
Version: 2.27
: P2 normal
Target Milestone: ---
Assignee: Not yet assigned to anyone
URL:
Keywords:
Depends on:
Blocks:
 
Reported: 2019-10-21 12:12 UTC by Nguyễn Đức Mạnh
Modified: 2019-10-22 08:55 UTC (History)
1 user (show)

See Also:
Host:
Target:
Build:
Last reconfirmed:
fweimer: security-


Attachments

Note You need to log in before you can comment on or make changes to this bug.
Description Nguyễn Đức Mạnh 2019-10-21 12:12:26 UTC
As analyzing glibc source code (https://code.woboq.org/userspace/glibc/locale/setlocale.c.html), I see that when a new locale is set up for a category, setlocale allocates a memory block to save the new name, and then links it to a structure:
Line setlocale.c:434 (allocate memory to hold the locale name):
---------------------------------------
newname[0] = __strdup (newname[0]);
---------------------------------------
Line setlocale.c:455 (save the new name):
---------------------------------------  
setname (category, newname[0]);
setname (LC_ALL, composite);
---------------------------------------
Line setlocale.c:469 (return the new name as return value of setlocale):
---------------------------------------
return (char *) newname[0];
---------------------------------------
Line setlocale.c:195, setname source:
---------------------------------------
setname (int category, const char *name)
{
  if (_nl_global_locale.__names[category] == name)
    return;
  if (_nl_global_locale.__names[category] != _nl_C_name)
    free ((char *) _nl_global_locale.__names[category]);
  _nl_global_locale.__names[category] = name;
}
---------------------------------------
In setname function, the allocated memory chunk is saved in  _nl_global_locale.__names. The old chunk of the same category in _nl_global_locale.__names is freed. So there should be use-after-free bug here.

Here is the Proof of Concepts:
--------------------------------------
root@pen:~/fuzz# cat test_setlocale.c
#include <stdlib.h>
#include <stdio.h>
#include <locale.h>
#include <string.h>
int main()
{
  char* oldLocale = setlocale(LC_NUMERIC, "C.UTF-8");
  printf("oldLocale = %s\n", oldLocale);
  setlocale(LC_NUMERIC, "en_US.UTF-8");
  char* foo = strdup("fakename");
  printf("oldLocale = %s\n", oldLocale);
  setlocale(LC_NUMERIC, oldLocale);
  return 0;
}
root@pen:~/fuzz# gcc test_setlocale.c -O0 -m32 -ggdb -o test_setlocale
root@pen:~/fuzz# ./test_setlocale 
oldLocale = C.UTF-8
oldLocale = fakename
root@pen:~/fuzz#
--------------------------------------
As you can see, oldLocale is overwritten with some fake data.
Comment 1 Florian Weimer 2019-10-21 12:22:11 UTC
The reproducer is invalid.  The return value of setlocale is only valid until the next setlocale call.  This is clearly specified in ISO C and POSIX, and the manual also warns about that.
Comment 2 Nguyễn Đức Mạnh 2019-10-21 15:12:54 UTC
(In reply to Florian Weimer from comment #1)
> The reproducer is invalid.  The return value of setlocale is only valid
> until the next setlocale call.  This is clearly specified in ISO C and
> POSIX, and the manual also warns about that.

So what is the return value used for?
Let's look at the following code of vutil.c of Perl (https://sourceware.org/bugzilla/show_bug.cgi?id=25123):
============================================================
 631 #  ifndef USE_POSIX_2008_LOCALE
 632 
 633             const char * locale_name_on_entry;
 634 
 635             LC_NUMERIC_LOCK(0);    /* Start critical section */
 636 
 637             locale_name_on_entry = setlocale(LC_NUMERIC, NULL);
 638             if (   strNE(locale_name_on_entry, "C")
 639                 && strNE(locale_name_on_entry, "POSIX"))
 640             {
 641                 setlocale(LC_NUMERIC, "C");
 642             }
 .............
 711 #  ifndef USE_POSIX_2008_LOCALE
 710 
 711             if (locale_name_on_entry) {
 712                 setlocale(LC_NUMERIC, locale_name_on_entry);
 713             }
============================================================
Perl uses the code above to recover locale_name_on_entry after set it to "C".
Comment 3 Florian Weimer 2019-10-21 15:17:45 UTC
The Perl coded looks invalid, particularly for multi-threaded code.  On POSIX, it should use uselocale to temporarily change the locale for the current thread.

That being said, you can make a copy of the returned string using strdup, and use that in the second setlocale call.
Comment 4 Nguyễn Đức Mạnh 2019-10-21 16:11:37 UTC
(In reply to Florian Weimer from comment #3)
> The Perl coded looks invalid, particularly for multi-threaded code.  On
> POSIX, it should use uselocale to temporarily change the locale for the
> current thread.
> 
> That being said, you can make a copy of the returned string using strdup,
> and use that in the second setlocale call.

Please explain the usage of the return value of setlocale. As https://linux.die.net/man/3/setlocale says, "The string returned is such that a subsequent call with that string and its associated category will restore that part of the process's locale." I see Perl follows this. Usage of strdup is not mentioned anywhere in documents.
Comment 5 Andreas Schwab 2019-10-21 16:19:41 UTC
https://pubs.opengroup.org/onlinepubs/9699919799/functions/setlocale.html

"The string returned by setlocale() is such that a subsequent call with that string and its associated category shall restore that part of the global locale. The application shall not modify the string returned. The returned string pointer might be invalidated or the string content might be overwritten by a subsequent call to setlocale(). The returned pointer might also be invalidated if the calling thread is terminated."
Comment 6 Nguyễn Đức Mạnh 2019-10-21 23:10:48 UTC
(In reply to Andreas Schwab from comment #5)
> https://pubs.opengroup.org/onlinepubs/9699919799/functions/setlocale.html
> 
> "The string returned by setlocale() is such that a subsequent call with that
> string and its associated category shall restore that part of the global
> locale. The application shall not modify the string returned. The returned
> string pointer might be invalidated or the string content might be
> overwritten by a subsequent call to setlocale(). The returned pointer might
> also be invalidated if the calling thread is terminated."

Thanks. It seems that we should the return value is useless, except in setlocale(cat, NULL)
Comment 7 Florian Weimer 2019-10-22 08:48:50 UTC
You can make a copy of the return value before the next call to setlocale.

However, with glibc, you should really be using uselocale for this construct.
Comment 8 Andreas Schwab 2019-10-22 08:55:19 UTC
That's what perl already does on modern systems (USE_POSIX_2008_LOCALE).