From patchwork Tue Mar 31 11:11:00 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Gao Xiang X-Patchwork-Id: 132509 Return-Path: X-Original-To: patchwork@sourceware.org Delivered-To: patchwork@sourceware.org Received: from vm01.sourceware.org (localhost [127.0.0.1]) by sourceware.org (Postfix) with ESMTP id 1D7014BB58CA for ; Tue, 31 Mar 2026 11:12:24 +0000 (GMT) DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org 1D7014BB58CA X-Original-To: libc-alpha@sourceware.org Delivered-To: libc-alpha@sourceware.org Received: from mailgw.kylinos.cn (mailgw.kylinos.cn [124.126.103.232]) by sourceware.org (Postfix) with ESMTPS id 9C4FA4BB58BE for ; Tue, 31 Mar 2026 11:11:51 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.4.2 sourceware.org 9C4FA4BB58BE Authentication-Results: sourceware.org; dmarc=none (p=none dis=none) header.from=kylinos.cn Authentication-Results: sourceware.org; spf=pass smtp.mailfrom=kylinos.cn ARC-Filter: OpenARC Filter v1.0.0 sourceware.org 9C4FA4BB58BE Authentication-Results: server2.sourceware.org; arc=none smtp.remote-ip=124.126.103.232 ARC-Seal: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1774955512; cv=none; b=HtJ7siYBA4/uflrYoRuGnmasKCmTOQDGJ9AeVM1FGPMaCpvpNpyrstFUMZqy7NQ/LUshIkMAxOwBY7lZ8lgihCkvhmKldvTljAnreEFFHorF8hvpw4nH81hd+y0AmrHJ3Zq9WvdcGw3DvX7GzNaC+eVeXNjW/5Jor84SwL2isLk= ARC-Message-Signature: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1774955512; c=relaxed/simple; bh=S4sexj6f+q5mhqgqy6t2HHV4653Ij2QFddciAJMYTUY=; h=From:To:Subject:Date:Message-ID:MIME-Version; b=Wdl1dUOlhN1xw0UcuWV6J6Y0eE9xvYgnusj/SR7OGn7EmCwJldyKxMuL6plxPnamcErzu/h93Y34Lxz2BFLUrLssdFHD1NFZEB3WYWbAYIN4LdZWBFX7OqVjZOi1217BABoDVknipM7R9rwiKI85S6/1XpQKR5XZc42FJzot1h4= ARC-Authentication-Results: i=1; server2.sourceware.org DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org 9C4FA4BB58BE X-UUID: 632036482cf211f1aa26b74ffac11d73-20260331 X-CTIC-Tags: HR_CC_AS_FROM, HR_CC_COUNT, HR_CC_DOMAIN_COUNT, HR_CC_NAME, HR_CTE_8B HR_CTT_MISS, HR_DATE_H, HR_DATE_WKD, HR_DATE_ZONE, HR_FROM_NAME HR_SJ_DIGIT_LEN, HR_SJ_LANG, HR_SJ_LEN, HR_SJ_LETTER, HR_SJ_NOR_SYM HR_SJ_PHRASE, HR_SJ_PHRASE_LEN, HR_SJ_WS, HR_TO_COUNT, HR_TO_DOMAIN_COUNT HR_TO_NO_NAME, IP_TRUSTED, SRC_TRUSTED, DN_TRUSTED, SA_TRUSTED SA_EXISTED, SN_TRUSTED, SN_EXISTED, SPF_NOPASS, DKIM_NOPASS DMARC_NOPASS, UD_TRUSTED, CIE_BAD, CIE_GOOD, CIE_GOOD_SPF GTI_FG_BS, GTI_C_CI, GTI_FG_IT, GTI_RG_INFO, GTI_C_BU AMN_GOOD, ABX_BLACK, ABX_EXPLOIT, ABX_MISS_RDNS X-CID-O-RULE: Release_Ham X-CID-RULE: Release_Ham X-CID-O-INFO: VERSION:1.3.12, REQID:bd069261-3a7f-4a4d-9784-db9f54ac9d46, IP:10, URL:0,TC:0,Content:0,EDM:25,RT:0,SF:-5,FILE:0,BULK:0,RULE:Release_Ham,ACTI ON:release,TS:30 X-CID-INFO: VERSION:1.3.12, REQID:bd069261-3a7f-4a4d-9784-db9f54ac9d46, IP:10, UR L:0,TC:0,Content:0,EDM:25,RT:0,SF:-5,FILE:0,BULK:0,RULE:Release_Ham,ACTION :release,TS:30 X-CID-META: VersionHash:e7bac3a, CLOUDID:31393438d19aaf0493ecb2585c407b2c, BulkI D:260331191140CI8XJAMG,BulkQuantity:0,Recheck:0,SF:17|19|66|78|102|127|898 ,TC:nil,Content:0|15|50,EDM:5,IP:-2,URL:99|1,File:nil,RT:nil,Bulk:nil,QS:n il,BEC:nil,COL:0,OSI:0,OSA:0,AV:0,LES:1,SPR:NO,DKR:0,DKP:0,BRR:0,BRE:0,ARC :0 X-CID-BVR: 2,SSN|SDN X-CID-BAS: 2,SSN|SDN,0,_ X-CID-FACTOR: TF_CID_SPAM_FSD,TF_CID_SPAM_ULS,TF_CID_SPAM_SNR,TF_CID_SPAM_FAS X-CID-RHF: D41D8CD98F00B204E9800998ECF8427E X-UUID: 632036482cf211f1aa26b74ffac11d73-20260331 X-User: gaoxiang@kylinos.cn Received: from fedora [(183.242.174.22)] by mailgw.kylinos.cn (envelope-from ) (Generic MTA with TLSv1.3 TLS_AES_256_GCM_SHA384 256/256) with ESMTP id 186504107; Tue, 31 Mar 2026 19:11:38 +0800 From: Gao Xiang To: libc-alpha@sourceware.org Cc: Xiang Gao Subject: [PATCH] libio: Fix wide stream backup buffer leak on fclose [BZ #33999] Date: Tue, 31 Mar 2026 19:11:00 +0800 Message-ID: <20260331111100.246909-1-gaoxiang@kylinos.cn> X-Mailer: git-send-email 2.53.0 MIME-Version: 1.0 X-Spam-Status: No, score=-11.1 required=5.0 tests=BAYES_00, GIT_PATCH_0, KAM_DMARC_STATUS, RCVD_IN_VALIDITY_RPBL_BLOCKED, RCVD_IN_VALIDITY_SAFE_BLOCKED, SPF_HELO_NONE, SPF_PASS, TXREP, UNPARSEABLE_RELAY, URIBL_BLOCKED autolearn=ham autolearn_force=no version=3.4.6 X-Spam-Checker-Version: SpamAssassin 3.4.6 (2021-04-09) on sourceware.org X-BeenThere: libc-alpha@sourceware.org X-Mailman-Version: 2.1.30 Precedence: list List-Id: Libc-alpha mailing list List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: libc-alpha-bounces~patchwork=sourceware.org@sourceware.org From: Xiang Gao Hi, 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_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 cleanup in genops.c for consistency. Tested by: make test t=libio/tst-wbackup-leak [1] https://patchwork.sourceware.org/project/glibc/patch/20260323171742.1039768-1-marocketbd@gmail.com/ Signed-off-by: Xiang Gao --- libio/Makefile | 1 + libio/genops.c | 5 +---- libio/iofclose.c | 7 +++++-- libio/tst-wbackup-leak.c | 45 ++++++++++++++++++++++++++++++++++++++++ libio/wgenops.c | 7 ++----- 5 files changed, 54 insertions(+), 11 deletions(-) create mode 100644 libio/tst-wbackup-leak.c diff --git a/libio/Makefile b/libio/Makefile index 08e1e0ec25..06b20c056e 100644 --- a/libio/Makefile +++ b/libio/Makefile @@ -120,6 +120,7 @@ tests = \ tst-memstream2 \ tst-memstream3 \ tst-memstream4 \ + tst-wbackup-leak \ tst-mmap-eofsync \ tst-mmap-fflushsync \ tst-mmap-offend \ diff --git a/libio/genops.c b/libio/genops.c index cc1684e00a..6cd2be7242 100644 --- a/libio/genops.c +++ b/libio/genops.c @@ -637,10 +637,7 @@ _IO_default_finish (FILE *fp, int dummy) mark->_sbuf = NULL; if (fp->_IO_save_base) - { - _IO_free_backup_buf (fp, fp->_IO_save_base); - fp->_IO_save_base = NULL; - } + _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..846ac042dc 100644 --- a/libio/iofclose.c +++ b/libio/iofclose.c @@ -67,8 +67,11 @@ _IO_new_fclose (FILE *fp) _IO_FINISH (fp); if (fp->_mode > 0) { + if (fp->_wide_data->_IO_save_base) + _IO_free_wbackup_area (fp); + /* This stream has a wide orientation. This means we have to free - the conversion functions. */ + the conversion functions. */ struct _IO_codecvt *cc = fp->_codecvt; __libc_lock_lock (__gconv_lock); @@ -79,7 +82,7 @@ _IO_new_fclose (FILE *fp) else { if (_IO_have_backup (fp)) - _IO_free_backup_area (fp); + _IO_free_backup_area (fp); } _IO_deallocate_file (fp); return status; diff --git a/libio/tst-wbackup-leak.c b/libio/tst-wbackup-leak.c new file mode 100644 index 0000000000..c9b534ca12 --- /dev/null +++ b/libio/tst-wbackup-leak.c @@ -0,0 +1,45 @@ +/* Test _IO_wdefault_finish frees wide backup buffer [BZ #33999]. */ + +#include +#include +#include +#include + +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. */ + 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 diff --git a/libio/wgenops.c b/libio/wgenops.c index 064d71266d..6c07cdfeca 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 (fp->_wide_data->_IO_save_base) + _IO_free_wbackup_area (fp); #ifdef _IO_MTSAFE_IO if (fp->_lock != NULL)