[PATCH v10] locale: memory leak in newlocale [BZ #25770]
Dmitry Kovalenko
d.kovalenko@postgrespro.ru
Fri Jun 6 06:42:59 GMT 2025
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>
---------------- 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