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.
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.
(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".
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.
(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.
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."
(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)
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.
That's what perl already does on modern systems (USE_POSIX_2008_LOCALE).