[PATCH 3/3] ungetc: Fix backup buffer leak on program exit [BZ #27821]

Carlos O'Donell carlos@redhat.com
Thu Aug 15 17:26:10 GMT 2024


On 8/14/24 7:35 PM, Siddhesh Poyarekar wrote:
> If a file descriptor is left unclosed and is cleaned up by _IO_cleanup
> on exit, its backup buffer remains unfreed, registering as a leak in
> valgrind.  This is not strictly an issue since (1) the program should
> ideally be closing the stream once it's not in use and (2) the program
> is about to exit anyway, so keeping the backup buffer around a wee bit
> longer isn't a real problem.  Free it anyway to keep valgrind happy
> when the streams in question are the standard ones, i.e. stdout, stdin
> or stderr.
> 
> Also, the _IO_have_backup macro checks for _IO_save_base,
> which is a roundabout way to check for a backup buffer instead of
> directly looking for _IO_backup_base.  The roundabout check breaks when
> the main get area has not been used and user pushes a char into the
> backup buffer with ungetc.  Fix this to use the _IO_backup_base
> directly.
> 
> Signed-off-by: Siddhesh Poyarekar <siddhesh@sourceware.org>

LGTM, you can keep my RB if you fix tab vs. spaces.

Reviewed-by: Carlos O'Donell <carlos@redhat.com>

> ---
>  libio/genops.c                 |  6 ++++++
>  libio/libioP.h                 |  4 ++--
>  stdio-common/Makefile          |  7 +++++++
>  stdio-common/tst-ungetc-leak.c | 32 ++++++++++++++++++++++++++++++++
>  4 files changed, 47 insertions(+), 2 deletions(-)
>  create mode 100644 stdio-common/tst-ungetc-leak.c
> 
> diff --git a/libio/genops.c b/libio/genops.c
> index b012fa33d2..6ea95c5e68 100644
> --- a/libio/genops.c
> +++ b/libio/genops.c
> @@ -816,6 +816,12 @@ _IO_unbuffer_all (void)
>  	legacy = 1;

OK. In _IO_unbuffer_all.

>  #endif
>  
> +      /* Free up the backup area if it ever was ever allocated.  */
> +      if (_IO_have_backup (fp))
> +        _IO_free_backup_area (fp);

Fix tab vs. spaces.

> +      if (fp->_mode > 0 && _IO_have_wbackup (fp))
> +	_IO_free_wbackup_area (fp);

OK.

> +
>        if (! (fp->_flags & _IO_UNBUFFERED)
>  	  /* Iff stream is un-orientated, it wasn't used. */
>  	  && (legacy || fp->_mode != 0))
> diff --git a/libio/libioP.h b/libio/libioP.h
> index 1af287b19f..616253fcd0 100644
> --- a/libio/libioP.h
> +++ b/libio/libioP.h
> @@ -577,8 +577,8 @@ extern void _IO_old_init (FILE *fp, int flags) __THROW;
>         ((__fp)->_wide_data->_IO_write_base \
>  	= (__fp)->_wide_data->_IO_write_ptr = __p, \
>  	(__fp)->_wide_data->_IO_write_end = (__ep))
> -#define _IO_have_backup(fp) ((fp)->_IO_save_base != NULL)
> -#define _IO_have_wbackup(fp) ((fp)->_wide_data->_IO_save_base != NULL)
> +#define _IO_have_backup(fp) ((fp)->_IO_backup_base != NULL)
> +#define _IO_have_wbackup(fp) ((fp)->_wide_data->_IO_backup_base != NULL)

OK. Agreed.

>  #define _IO_in_backup(fp) ((fp)->_flags & _IO_IN_BACKUP)
>  #define _IO_have_markers(fp) ((fp)->_markers != NULL)
>  #define _IO_blen(fp) ((fp)->_IO_buf_end - (fp)->_IO_buf_base)
> diff --git a/stdio-common/Makefile b/stdio-common/Makefile
> index e4f0146d2c..a91754f52d 100644
> --- a/stdio-common/Makefile
> +++ b/stdio-common/Makefile
> @@ -254,6 +254,7 @@ tests := \
>    tst-swscanf \
>    tst-tmpnam \
>    tst-ungetc \
> +  tst-ungetc-leak \
>    tst-unlockedio \
>    tst-vfprintf-mbs-prec \
>    tst-vfprintf-user-type \
> @@ -316,6 +317,7 @@ tests-special += \
>    $(objpfx)tst-printf-bz25691-mem.out \
>    $(objpfx)tst-printf-fp-free-mem.out \
>    $(objpfx)tst-printf-fp-leak-mem.out \
> +  $(objpfx)tst-ungetc-leak-mem.out \
>    $(objpfx)tst-vfprintf-width-prec-mem.out \
>    # tests-special
>  
> @@ -330,6 +332,8 @@ generated += \
>    tst-printf-fp-leak-mem.out \
>    tst-printf-fp-leak.mtrace \
>    tst-scanf-bz27650.mtrace \
> +  tst-ungetc-leak-mem.out \
> +  tst-ungetc-leak.mtrace \
>    tst-vfprintf-width-prec-mem.out \
>    tst-vfprintf-width-prec.mtrace \
>    # generated
> @@ -424,6 +428,9 @@ tst-printf-fp-leak-ENV = \
>  tst-scanf-bz27650-ENV = \
>    MALLOC_TRACE=$(objpfx)tst-scanf-bz27650.mtrace \
>    LD_PRELOAD=$(common-objpfx)malloc/libc_malloc_debug.so
> +tst-ungetc-leak-ENV = \
> +  MALLOC_TRACE=$(objpfx)tst-ungetc-leak.mtrace \
> +  LD_PRELOAD=$(common-objpfx)malloc/libc_malloc_debug.so

OK.

>  
>  $(objpfx)tst-unbputc.out: tst-unbputc.sh $(objpfx)tst-unbputc
>  	$(SHELL) $< $(common-objpfx) '$(test-program-prefix)'; \
> diff --git a/stdio-common/tst-ungetc-leak.c b/stdio-common/tst-ungetc-leak.c
> new file mode 100644
> index 0000000000..6c5152b43f
> --- /dev/null
> +++ b/stdio-common/tst-ungetc-leak.c
> @@ -0,0 +1,32 @@
> +/* Test for memory leak with ungetc when stream is unused.
> +   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 <stdio.h>
> +#include <mcheck.h>
> +#include <support/check.h>
> +#include <support/support.h>
> +
> +static int
> +do_test (void)
> +{
> +  mtrace ();
> +  TEST_COMPARE (ungetc('y', stdin), 'y');
> +  return 0;
> +}
> +
> +#include <support/test-driver.c>

-- 
Cheers,
Carlos.



More information about the Libc-alpha mailing list