]> git.ipfire.org Git - thirdparty/glibc.git/commitdiff
locale: memory leak in newlocale [BZ #25770] master
authorDmitry Kovalenko <d.kovalenko@postgrespro.ru>
Tue, 28 Apr 2026 18:25:30 +0000 (15:25 -0300)
committerAdhemerval Zanella <adhemerval.zanella@linaro.org>
Tue, 28 Apr 2026 18:25:30 +0000 (15:25 -0300)
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 <d.kovalenko@postgrespro.ru>
Co-authored-by: Florian Weimer <fweimer@redhat.com>
locale/Makefile
locale/newlocale.c
locale/tst-newlocale.c [new file with mode: 0755]

index fc38c02114e6adebb89a1848a87c523595d4a78f..25f9b5bbcebfeadbc20f13b471420e6ddb220b74 100644 (file)
@@ -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)
index 2b24952073393f7763414e70af30a0aa8c1392c7..849c49facfd892f534425cb34e0f6dccd12b6637 100644 (file)
@@ -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 (executable)
index 0000000..5ba3858
--- /dev/null
@@ -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>