[glibc] libio: Fix wide stream backup buffer leak on fclose [BZ #33999]
Adhemerval Zanella via Glibc-cvs <[email protected]>
| Newsgroups | gmane.comp.lib.glibc.cvs |
|---|---|
| Message-ID | <[email protected]> |
https://sourceware.org/git/gitweb.cgi?p=glibc.git;h=cd1b1c1035171c9281922acc9670a7a50415f71e commit cd1b1c1035171c9281922acc9670a7a50415f71e Author: Xiang Gao <[email protected]> Date: Thu Apr 2 14:25:44 2026 +0800 libio: Fix wide stream backup buffer leak on fclose [BZ #33999] This patch fixes a memory leak when ungetwc is used on a wide oriented stream. The backup buffer was never freed on fclose, causing a memory leak per ungetwc/fclose call. The leak has two causes: In iofclose.c, for wide streams (fp->mode > 0), _IO_new_fclose never calls _IO_free_wbackup_area. Fixed by adding the missing call. In wgenops.c, _IO_wdefault_finish checks fp->_IO_save_base (the narrow field, always NULL for wide streams) instead of fp->_wide_data->_IO_save_base, and uses a bare free() that leaves _IO_save_end and _IO_backup_base dangling. Replace the hand-rolled cleanup with _IO_have_wbackup/_IO_free_wbackup_area, which handles backup-mode switching and clears all three pointers. This was independently reported by Rocket Ma [1], whose patch corrects the condition but still uses the manual free path. Apply the same _IO_have_backup condition in genops.c for consistency. Tested by: make test t=libio/tst-wbackup-leak [1] https://patchwork.sourceware.org/project/glibc/patch/[email protected]/ Signed-off-by: Xiang Gao <[email protected]> Diff: --- libio/Makefile | 1 + libio/genops.c | 7 ++----- libio/iofclose.c | 2 ++ libio/tst-wbackup-leak.c | 51 ++++++++++++++++++++++++++++++++++++++++++++++++ libio/wgenops.c | 7 ++----- 5 files changed, 58 insertions(+), 10 deletions(-) diff --git a/libio/Makefile b/libio/Makefile index 08e1e0ec25..93656466df 100644 --- a/libio/Makefile +++ b/libio/Makefile @@ -135,6 +135,7 @@ tests = \ tst-swscanf \ tst-ungetwc1 \ tst-ungetwc2 \ + tst-wbackup-leak \ tst-wfile-sync \ tst-wfiledoallocate-static \ tst-widetext \ diff --git a/libio/genops.c b/libio/genops.c index cc1684e00a..90e08e6571 100644 --- a/libio/genops.c +++ b/libio/genops.c @@ -636,11 +636,8 @@ _IO_default_finish (FILE *fp, int dummy) for (mark = fp->_markers; mark != NULL; mark = mark->_next) mark->_sbuf = NULL; - if (fp->_IO_save_base) - { - _IO_free_backup_buf (fp, fp->_IO_save_base); - fp->_IO_save_base = NULL; - } + if (_IO_have_backup (fp)) + _IO_free_backup_area (fp); _IO_un_link ((struct _IO_FILE_plus *) fp); diff --git a/libio/iofclose.c b/libio/iofclose.c index 89782e99d7..0ba85955bf 100644 --- a/libio/iofclose.c +++ b/libio/iofclose.c @@ -67,6 +67,8 @@ _IO_new_fclose (FILE *fp) _IO_FINISH (fp); if (fp->_mode > 0) { + if (_IO_have_wbackup (fp)) + _IO_free_wbackup_area (fp); /* This stream has a wide orientation. This means we have to free the conversion functions. */ struct _IO_codecvt *cc = fp->_codecvt; diff --git a/libio/tst-wbackup-leak.c b/libio/tst-wbackup-leak.c new file mode 100644 index 0000000000..0b28f5df37 --- /dev/null +++ b/libio/tst-wbackup-leak.c @@ -0,0 +1,51 @@ +/* Test _IO_wdefault_finish frees wide backup buffer [BZ #33999]. */ + +#include <malloc.h> +#include <stdio.h> +#include <wchar.h> +#include <support/check.h> + +static void +one_round (void) +{ + wchar_t *buf = NULL; + size_t size = 0; + + FILE *fp = open_wmemstream (&buf, &size); + TEST_VERIFY_EXIT (fp != NULL); + fputwc (L'A', fp); + fflush (fp); + /* Push back without prior read. read_ptr == read_base, so + _IO_wdefault_pbackfail skips the buggy narrow read_ptr access + (BZ #33998) and goes straight to allocating a wide backup + buffer at fp->_wide_data->_IO_save_base. + + Note: this testcase relies on the fact that open_wmemstream + does not set _IO_NO_READS on the stream. If that implementation + is changed, this test would need a different stream type to verify + the leak. */ + rewind (fp); + ungetwc (L'Z', fp); + fclose (fp); + free (buf); +} + +static int +do_test (void) +{ + /* Warm up to stabilize allocator state. */ + one_round (); + + struct mallinfo2 before = mallinfo2 (); + for (int i = 0; i < 1000; i++) + one_round (); + struct mallinfo2 after = mallinfo2 (); + + /* Each leak is 128 * sizeof(wchar_t) = 512 bytes. + * 1000 iterations would leak ~512 KB. Allow 4 KB noise. */ + TEST_VERIFY (after.uordblks - before.uordblks < 4096); + + return 0; +} + +#include <support/test-driver.c> diff --git a/libio/wgenops.c b/libio/wgenops.c index 064d71266d..6829477e0c 100644 --- a/libio/wgenops.c +++ b/libio/wgenops.c @@ -181,11 +181,8 @@ _IO_wdefault_finish (FILE *fp, int dummy) for (mark = fp->_markers; mark != NULL; mark = mark->_next) mark->_sbuf = NULL; - if (fp->_IO_save_base) - { - free (fp->_wide_data->_IO_save_base); - fp->_IO_save_base = NULL; - } + if (_IO_have_wbackup (fp)) + _IO_free_wbackup_area (fp); #ifdef _IO_MTSAFE_IO if (fp->_lock != NULL)