[PATCH] locale: fix memory leaks in write_locales and write_charmaps
Arjun Shankar
arjun@redhat.com
Mon Aug 17 22:12:21 GMT 2026
Hi Ruslan,
Thank you for working on this.
> Fix multiple memory leaks in the locale program:
>
> 1. PUT(xstrdup(...)) leaks when tsearch finds a duplicate entry,
> since tsearch returns the existing node and the newly allocated
> string is orphaned. Introduce PUT_UNIQUE macro that checks with
> GET (tfind) before inserting, freeing the duplicate if it already
> exists.
>
> 2. String literals "POSIX" and "C" passed to PUT cannot be freed
> by tdestroy. Wrap them in xstrdup so tdestroy(all_data, free) is
> safe.
>
> 3. Add tdestroy(all_data, free) at the end of write_locales and
> write_charmaps to free the search trees.
>
> 4. Free dirents[cnt] entries in the scandir loop (only the dirents
> array pointer was freed, not individual entries).
>
> 5. Free alias_path allocated by argz_create_sep in write_locales.
>
> These leaks were reported by Arjun Shankar via GCC -fanalyzer
> (OpenScanHub/Fedora) and confirmed with valgrind.
>
> Resolves: BZ #33972
> Signed-off-by: Ruslan Valiyev <linuxoid@gmail.com>
This looks mostly good to me. I have a couple of comments below.
Cheers!
> ---
> locale/programs/locale.c | 35 ++++++++++++++++++++++++++---------
> 1 file changed, 26 insertions(+), 9 deletions(-)
>
> diff --git a/locale/programs/locale.c b/locale/programs/locale.c
> index 15f109f3..396821a1 100644
> --- a/locale/programs/locale.c
> +++ b/locale/programs/locale.c
> @@ -429,10 +429,20 @@ write_locales (void)
> #define GET(name) tfind (name, &all_data, \
> (int (*) (const void *, const void *)) strcoll)
>
> + /* Insert NAME into the tree, freeing it if a duplicate exists. */
> +#define PUT_UNIQUE(name) \
> + do {\
> + char *put_name_ = (name);\
> + if (GET (put_name_) != NULL)\
> + free (put_name_);\
> + else\
> + PUT (put_name_);\
> + } while (0)
> +
Since every invocation of this macro uses the result of an xstrdup, I
think it would be cleaner to pass in the un-duplicated string, perform
a GET using it, and then xstrdup only when necessary, right before
PUT. We will eliminate the allocation and subsequent free when
encountering duplicates, and it will also be a bit easier to read.
While at it, there's also a whitespace issue: a missing space before
the "\" at the end of each line.
> /* `POSIX' locale is always available (POSIX.2 4.34.3). */
> - PUT ("POSIX");
> + PUT (xstrdup ("POSIX"));
> /* And so is the "C" locale. */
> - PUT ("C");
> + PUT (xstrdup ("C"));
This is needed so we can call "tdestroy" later.
I see that it's correct to use PUT here since we are just starting,
but it might be worth using PUT_UNIQUE everywhere just for
consistency. I'm OK either way but I just wanted to mention it.
>
> memset (linebuf, '-', sizeof (linebuf) - 1);
> linebuf[sizeof (linebuf) - 1] = '\0';
> @@ -510,8 +520,9 @@ write_locales (void)
>
> /* If the verbose format is not selected we simply
> collect the names. */
> - PUT (xstrdup (dirents[cnt]->d_name));
> + PUT_UNIQUE (xstrdup (dirents[cnt]->d_name));
OK.
> }
> + free (dirents[cnt]);
OK.
> }
> if (ndirents > 0)
> free (dirents);
> @@ -591,7 +602,7 @@ write_locales (void)
>
> /* Add the alias. */
> if (! verbose && GET (value) != NULL)
> - PUT (xstrdup (alias));
> + PUT_UNIQUE (xstrdup (alias));
OK.
> }
> }
>
> @@ -610,10 +621,14 @@ write_locales (void)
> fclose (fp);
> }
>
> + free (alias_path);
> +
OK.
> if (! verbose)
> {
> twalk (all_data, print_names);
> }
> +
> + tdestroy (all_data, free);
OK.
> }
>
>
> @@ -669,7 +684,7 @@ write_archive_locales (void **all_datap, char *linebuf)
> for (cnt = 0; cnt < head->namehash_size; ++cnt)
> if (namehashtab[cnt].locrec_offset != 0)
> {
> - PUT (xstrdup (addr + namehashtab[cnt].name_offset));
> + PUT_UNIQUE (xstrdup (addr + namehashtab[cnt].name_offset));
> ++ret;
> }
> }
> @@ -694,7 +709,7 @@ write_archive_locales (void **all_datap, char *linebuf)
> {
> struct locrecent *locrec;
>
> - PUT (xstrdup (names[cnt].name));
> + PUT_UNIQUE (xstrdup (names[cnt].name));
OK.
>
> if (cnt)
> putchar_unlocked ('\n');
> @@ -744,19 +759,19 @@ write_charmaps (void)
> char **aliases;
> char **p;
>
> - PUT (xstrdup (dirent));
> + PUT_UNIQUE (xstrdup (dirent));
OK.
>
> aliases = charmap_aliases (CHARMAP_PATH, dirent);
>
> #if 0
> /* Add the code_set_name and the aliases. */
> for (p = aliases; *p; p++)
> - PUT (xstrdup (*p));
> + PUT_UNIQUE (xstrdup (*p));
OK.
> #else
> /* Add the code_set_name only. Most aliases are obsolete. */
> p = aliases;
> if (*p)
> - PUT (xstrdup (*p));
> + PUT_UNIQUE (xstrdup (*p));
OK.
I confirmed that apart from the duplication of "POSIX" and "C" at the
start, every other insertion now uses PUT_UNIQUE.
> #endif
>
> charmap_free_aliases (aliases);
> @@ -765,6 +780,8 @@ write_charmaps (void)
> charmap_closedir (dir);
>
> twalk (all_data, print_names);
> +
> + tdestroy (all_data, free);
OK. We can use "free" since everything inside is now the result of an
allocation.
> }
>
> /* Print a properly quoted assignment of NAME with VAL, using double
> --
> 2.43.0
>
More information about the Libc-alpha
mailing list