[PATCH v2] locale: fix memory leaks in write_locales and write_charmaps

Arjun Shankar arjun@redhat.com
Mon Sep 7 13:07:53 GMT 2026


Hi Ruslan,

> 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, which looks the name up
>    with GET first and only allocates when it is actually inserted.
>
> 2. String literals "POSIX" and "C" passed to PUT cannot be freed by
>    tdestroy.  They now go through PUT_UNIQUE, which duplicates them,
>    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 the individual entries).
>
> 5. Free alias_path allocated by argz_create_sep in write_locales.
>
> Before this change "locale -a" leaked 74 bytes in 3 blocks directly
> and 835 bytes in 49 blocks indirectly, and "locale -m" leaked 2190
> bytes in 227 blocks.  Both are valgrind-clean afterwards.
>
> These leaks were reported by Arjun Shankar via GCC -fanalyzer
> (OpenScanHub/Fedora).
>
> Resolves: BZ #33972
> Signed-off-by: Ruslan Valiyev <linuxoid@gmail.com>

Thanks for the v2! This looks good to me.
Reviewed-by: Arjun Shankar <arjun@redhat.com>

> ---
>  locale/programs/locale.c | 36 +++++++++++++++++++++++++++---------
>  1 file changed, 27 insertions(+), 9 deletions(-)
>
> diff --git a/locale/programs/locale.c b/locale/programs/locale.c
> index 15f109f3..2e47b14f 100644
> --- a/locale/programs/locale.c
> +++ b/locale/programs/locale.c
> @@ -429,10 +429,21 @@ write_locales (void)
>  #define GET(name) tfind (name, &all_data, \
>                            (int (*) (const void *, const void *)) strcoll)
>
> +/* Insert a copy of NAME into the tree, unless an equal string is already
> +   present.  Only allocates when the name is actually new.  */
> +#define PUT_UNIQUE(name) \
> +  do \
> +    { \
> +      const char *put_name_ = (name); \
> +      if (GET (put_name_) == NULL) \
> +       PUT (xstrdup (put_name_)); \
> +    } \
> +  while (0)

OK. Do the xstrdup inside. I need to check below if every invocation
now *skips* the dup.

> +
>    /* `POSIX' locale is always available (POSIX.2 4.34.3).  */
> -  PUT ("POSIX");
> +  PUT_UNIQUE ("POSIX");
>    /* And so is the "C" locale.  */
> -  PUT ("C");
> +  PUT_UNIQUE ("C");

OK. This will dup the static strings, but the destructor will also
correctly deallocate the dupe.

>
>    memset (linebuf, '-', sizeof (linebuf) - 1);
>    linebuf[sizeof (linebuf) - 1] = '\0';
> @@ -510,8 +521,9 @@ write_locales (void)
>
>           /* If the verbose format is not selected we simply
>              collect the names.  */
> -         PUT (xstrdup (dirents[cnt]->d_name));
> +         PUT_UNIQUE (dirents[cnt]->d_name);
>         }
> +      free (dirents[cnt]);

OK.

>      }
>    if (ndirents > 0)
>      free (dirents);
> @@ -591,7 +603,7 @@ write_locales (void)
>
>                   /* Add the alias.  */
>                   if (! verbose && GET (value) != NULL)
> -                   PUT (xstrdup (alias));
> +                   PUT_UNIQUE (alias);
>                 }
>             }
>
> @@ -610,10 +622,14 @@ write_locales (void)
>        fclose (fp);
>      }
>
> +  free (alias_path);
> +

OK.

>    if (! verbose)
>      {
>        twalk (all_data, print_names);
>      }
> +
> +  tdestroy (all_data, free);

OK.

>  }
>
>
> @@ -669,7 +685,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 (addr + namehashtab[cnt].name_offset);
>             ++ret;
>           }
>      }
> @@ -694,7 +710,7 @@ write_archive_locales (void **all_datap, char *linebuf)
>         {
>           struct locrecent *locrec;
>
> -         PUT (xstrdup (names[cnt].name));
> +         PUT_UNIQUE (names[cnt].name);
>
>           if (cnt)
>             putchar_unlocked ('\n');
> @@ -744,19 +760,19 @@ write_charmaps (void)
>        char **aliases;
>        char **p;
>
> -      PUT (xstrdup (dirent));
> +      PUT_UNIQUE (dirent);
>
>        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 (*p);
>  #else
>        /* Add the code_set_name only.  Most aliases are obsolete.  */
>        p = aliases;
>        if (*p)
> -       PUT (xstrdup (*p));
> +       PUT_UNIQUE (*p);
>  #endif
>
>        charmap_free_aliases (aliases);
> @@ -765,6 +781,8 @@ write_charmaps (void)
>    charmap_closedir (dir);
>
>    twalk (all_data, print_names);
> +
> +  tdestroy (all_data, free);

OK.

All PUT calls replaced by PUT_UNIQUE.

>  }
>
>  /* Print a properly quoted assignment of NAME with VAL, using double
>
> base-commit: a1e333203ac2d9f90bf9fbc09b4f43f1d16efef5
> --
> 2.43.0
>


-- 
Arjun Shankar
he/him/his



More information about the Libc-alpha mailing list