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

Arjun Shankar <[email protected]>
Newsgroups gmane.comp.lib.glibc.alpha
Message-ID <CAG_osabny=NUZr1dnRhFqBQqm_RmuObMi6La9o=aoi0VoUytyA@mail.gmail.com>
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 <[email protected]>

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
>
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.