From ddc825d1b3279754068264a9d274bf21c97ec680 Mon Sep 17 00:00:00 2001 From: Dmitry Kovalenko Date: Tue, 28 Apr 2026 15:25:30 -0300 Subject: [PATCH] locale: memory leak in newlocale [BZ #25770] It is a fix for memory leak in the function 'newlocale' (the real name is __newlocale). There is not released a memory for the local buffer 'locale_path'. https://sourceware.org/bugzilla/show_bug.cgi?id=25770 --- Patch was tested on Ubuntu 24.04. My 'LOCPATH' environment variable is "/snap/code/193/usr/lib/locale". With an original (system) libc I have the following problem: #0 0x7d5c3aafc778 in realloc ../../../../src/libsanitizer/asan/asan_malloc_linux.cpp:85 #1 0x7d5c394b05f2 in __argz_add_sep string/argz-addsep.c:34 #2 0x7d5c39439bdb in __newlocale locale/newlocale.c:111 #3 0x7d5c370e58fc (/lib/x86_64-linux-gnu/libp11-kit.so.0+0x1098fc) (BuildId: 1cbcd6ac7e0ff0259eb3acc14d556d2c1ec00cdc) #4 0x7d5c3b14571e in call_init elf/dl-init.c:74 #5 0x7d5c3b145823 in call_init elf/dl-init.c:120 #6 0x7d5c3b145823 in _dl_init elf/dl-init.c:121 #7 0x7d5c3b15f59f (/lib64/ld-linux-x86-64.so.2+0x1f59f) (BuildId: 1c8db5f83bba514f8fd5f1fb6d7be975be1bb855) SUMMARY: AddressSanitizer: 46 byte(s) leaked in 1 allocation(s). When I run with my corrected libc, through runtest.sh , all is OK. Signed-off-by: Dmitry Kovalenko Co-authored-by: Florian Weimer --- locale/Makefile | 18 +++++++++++++++++ locale/newlocale.c | 32 +++++++++++++++++++++++------- locale/tst-newlocale.c | 44 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 87 insertions(+), 7 deletions(-) create mode 100755 locale/tst-newlocale.c diff --git a/locale/Makefile b/locale/Makefile index fc38c02114..25f9b5bbce 100644 --- a/locale/Makefile +++ b/locale/Makefile @@ -47,6 +47,7 @@ tests = \ tst-C-locale \ tst-duplocale \ tst-locname \ + tst-newlocale \ # tests tests-container = \ tst-localedef-path-norm \ @@ -133,6 +134,16 @@ GPERFFLAGS = -acCgopt -k1,2,5,9,$$ -L ANSI-C ifeq ($(run-built-tests),yes) tests-special += $(objpfx)tst-locale-locpath.out +ifeq ($(build-shared),yes) +ifneq ($(PERL),no) +generated += \ + tst-newlocale.mtrace \ + # generated +tests-special += \ + $(objpfx)tst-newlocale-mem.out \ + # tests-special +endif # $(PERL) == yes +endif # $(build-shared) == yes endif include ../Rules @@ -185,3 +196,10 @@ $(objpfx)tst-locale-locpath.out : tst-locale-locpath.sh $(objpfx)locale $(evaluate-test) $(objpfx)tst-localedef-path-norm: $(shared-thread-library) + +LDLIBS-tst-newlocale = $(shared-thread-library) +tst-newlocale-ENV = MALLOC_TRACE=$(objpfx)tst-newlocale.mtrace \ + LD_PRELOAD=$(common-objpfx)/malloc/libc_malloc_debug.so +$(objpfx)tst-newlocale-mem.out: $(objpfx)tst-newlocale.out + $(common-objpfx)malloc/mtrace $(objpfx)tst-newlocale.mtrace > $@; \ + $(evaluate-test) diff --git a/locale/newlocale.c b/locale/newlocale.c index 2b24952073..849c49facf 100644 --- a/locale/newlocale.c +++ b/locale/newlocale.c @@ -22,6 +22,7 @@ #include #include #include +#include #include "localeinfo.h" @@ -38,19 +39,21 @@ __libc_rwlock_define (extern , __libc_setlocale_lock attribute_hidden) } while (0) -locale_t -__newlocale (int category_mask, const char *locale, locale_t base) +static locale_t +__newlocale_1 (int category_mask, const char *locale, locale_t base, + char ** const locale_path_ptr) { /* Intermediate memory for result. */ const char *newnames[__LC_LAST]; struct __locale_struct result; locale_t result_ptr; - char *locale_path; size_t locale_path_len; const char *locpath_var; int cnt; size_t names_len; + *locale_path_ptr = NULL; + /* We treat LC_ALL in the same way as if all bits were set. */ if (category_mask == 1 << LC_ALL) category_mask = (1 << __LC_LAST) - 1 - (1 << LC_ALL); @@ -98,19 +101,20 @@ __newlocale (int category_mask, const char *locale, locale_t base) `LOCPATH' must only be used when the binary has no SUID or SGID bit set. If using the default path, we tell _nl_find_locale by passing null and it can check the canonical locale archive. */ - locale_path = NULL; locale_path_len = 0; locpath_var = getenv ("LOCPATH"); if (locpath_var != NULL && locpath_var[0] != '\0') { if (__argz_create_sep (locpath_var, ':', - &locale_path, &locale_path_len) != 0) + locale_path_ptr, &locale_path_len) != 0) return NULL; - if (__argz_add_sep (&locale_path, &locale_path_len, + if (__argz_add_sep (locale_path_ptr, &locale_path_len, _nl_default_locale_path, ':') != 0) return NULL; + + assert (*locale_path_ptr != NULL); } /* Get the names for the locales we are interested in. We either @@ -166,7 +170,7 @@ __newlocale (int category_mask, const char *locale, locale_t base) { if ((category_mask & 1 << cnt) != 0) { - result.__locales[cnt] = _nl_find_locale (locale_path, + result.__locales[cnt] = _nl_find_locale (*locale_path_ptr, locale_path_len, cnt, &newnames[cnt]); if (result.__locales[cnt] == NULL) @@ -275,4 +279,18 @@ __newlocale (int category_mask, const char *locale, locale_t base) return result_ptr; } + +locale_t +__newlocale (int category_mask, const char *locale, locale_t base) +{ + char *tmp_buffer = NULL; + + const locale_t result = __newlocale_1 (category_mask, locale, + base, &tmp_buffer); + + free (tmp_buffer); + + return result; +} + weak_alias (__newlocale, newlocale) diff --git a/locale/tst-newlocale.c b/locale/tst-newlocale.c new file mode 100755 index 0000000000..5ba38586ff --- /dev/null +++ b/locale/tst-newlocale.c @@ -0,0 +1,44 @@ +/* This test checks a memory leak in newlocal function [BZ #25770]. + Copyright The GNU Toolchain Authors. + 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 + . */ + +#include +#include +#include +#include +#include + +static int +do_test (void) +{ + mtrace (); + + /* We can use an any valid path here. + If setenv fails, the next part of test should still run okay. */ + TEST_COMPARE (setenv ("LOCPATH", ".", 1), 0); + + { + locale_t const l = newlocale (1 << LC_CTYPE, "POSIX", NULL); + TEST_VERIFY_EXIT (l != NULL); + + freelocale (l); + } + + return 0; +} + +#include -- 2.47.3