[glibc] locale: memory leak in newlocale [BZ #25770]

Adhemerval Zanella via Glibc-cvs <[email protected]>
Newsgroups gmane.comp.lib.glibc.cvs
Message-ID <[email protected]>
https://sourceware.org/git/gitweb.cgi?p=glibc.git;h=ddc825d1b3279754068264a9d274bf21c97ec680

commit ddc825d1b3279754068264a9d274bf21c97ec680
Author: Dmitry Kovalenko <[email protected]>
Date:   Tue Apr 28 15:25:30 2026 -0300

    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 <my-application>, all is OK.
    
    Signed-off-by: Dmitry Kovalenko <[email protected]>
    
    Co-authored-by: Florian Weimer <[email protected]>

Diff:
---
 locale/Makefile        | 18 ++++++++++++++++++
 locale/newlocale.c     | 32 +++++++++++++++++++++++++-------
 locale/tst-newlocale.c | 44 ++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 87 insertions(+), 7 deletions(-)

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 <locale.h>
 #include <stdlib.h>
 #include <string.h>
+#include <assert.h>
 
 #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
+   <https://www.gnu.org/licenses/>.  */
+
+#include <support/check.h>
+#include <mcheck.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <locale.h>
+
+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 <support/test-driver.c>
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.