[PATCH v10] locale: memory leak in newlocale [BZ #25770]

Adhemerval Zanella Netto adhemerval.zanella@linaro.org
Wed Aug 27 15:44:02 GMT 2025



On 06/06/25 03:42, Dmitry Kovalenko wrote:
> From 59ae6a06afe6fb4963feb0b82e2bb673fc0ef153 Mon Sep 17 00:00:00 2001
> From: "d.kovalenko" <d.kovalenko@postgrespro.ru>
> Date: Fri, 6 Jun 2025 09:38:52 +0300
> Subject: [PATCH v10] locale: memory leak in newlocale [BZ #25770]
> To: libc-alpha@sourceware.org
> Cc: d.kovalenko@postgrespro.ru,fweimer@redhat.com,schwab@suse.de
> 
> 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>

LGTM, thanks.

Reviewed-by: Adhemerval Zanella  <adhemerval.zanella@linaro.org>

> 
> ---------------- History
> 
> ---- v10
> - __newlocale_1 is updated.
>     - the initial debug checks of locale_path_ptr are removed
>     - the initial initialization of locale_path_ptr with NULL is added
> 
> ---- v9
> * A test is refactored (TEST_COMPARE, TEST_VERIFY_EXIT)
> 
> ---- v8
> * A code style in test is corrected.
> * A error message in test is corrected.
> * A useless code from makefile is removed.
> 
> newlocale.c did not change.
> 
> ---- v7
> * A code style is corrected.
> * Test LOCPATH is "."
> * Test uses setenv instead putenv.
> 
> ---- v6
> * A code style is corrected.
> 
> ---- v5
> * A test is added.
> 
> This test creates locale "POSIX" and works with "/usr/share/locale" folder.
> 
> tst-newlocale-mem.out content:
> OLD: Memory not freed: ... (one block, 0x22 bytes)
> NEW: No memory leaks.
> 
> Test OS is Ubuntu 24.04.
> 
> ---- v4
> * A code style is corrected.
> 
> ---- v3
> * The problems are fixed. Thanks to Florian Weimer.
> 
> ---- v2
> * A simplification (__newlocale_1). An idea of Florian Weimer.
> 
> ---- v1
> * An initial correction.
> ---
>  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 9d9c1a7691..70788df2f4 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
> @@ -184,3 +195,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 d25a6038d3..9a5c2ddea2 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>
> -- 
> 2.43.0
> 



More information about the Libc-alpha mailing list