[glibc] libio: Fix gconv module reference counter overflow in swscanf
Frederic Berat via Glibc-cvs <[email protected]> Tue, 12 May 2026 09:10:51 +0000 (GMT)
| Newsgroups | gmane.comp.lib.glibc.cvs |
|---|---|
| Message-ID | <[email protected]> |
https://sourceware.org/git/gitweb.cgi?p=glibc.git;h=0981c03c2752b5f12ede24e6e696d5a29f7c6396 commit 0981c03c2752b5f12ede24e6e696d5a29f7c6396 Author: Frédéric Bérat <[email protected]> Date: Wed Apr 29 15:53:51 2026 +0200 libio: Fix gconv module reference counter overflow in swscanf The swscanf family of functions creates a wide-oriented FILE stream on the stack. Initialization of this stream invokes `_IO_fwide`, which clones the global locale's gconv transformation steps via `__wcsmbs_clone_conv`. This increments the reference counter (`__counter`) of the gconv module. Because the FILE stream is stack-allocated, `fclose` cannot be called, and so `__gconv_release_step` is never invoked. The counter leaks, eventually hitting the 32-bit integer overflow limit and aborting the process. To resolve this, we introduce `_IO_wstrfile_fclose_stack`, a dedicated cleanup function for stack-allocated FILE streams. This function invokes `_IO_FINISH` and correctly releases the gconv steps via `__gconv_release_step` without attempting to `free` the FILE pointer. This cleanup function is then hooked into all variants of swscanf right before they return. Reviewed-by: Adhemerval Zanella <[email protected]> Diff: --- libio/iofwide.c | 15 +++++++++++++++ libio/iovswscanf.c | 4 +++- libio/libioP.h | 1 + libio/swscanf.c | 2 +- .../ieee754/ldbl-128ibm-compat/ieee128-isoc23_swscanf.c | 2 +- .../ieee754/ldbl-128ibm-compat/ieee128-isoc23_vswscanf.c | 4 +++- .../ieee754/ldbl-128ibm-compat/ieee128-isoc99_swscanf.c | 2 +- .../ieee754/ldbl-128ibm-compat/ieee128-isoc99_vswscanf.c | 4 +++- sysdeps/ieee754/ldbl-128ibm-compat/ieee128-swscanf.c | 2 +- sysdeps/ieee754/ldbl-128ibm-compat/ieee128-vswscanf.c | 4 +++- sysdeps/ieee754/ldbl-opt/nldbl-compat.c | 12 +++++++++--- wcsmbs/isoc23_swscanf.c | 2 +- wcsmbs/isoc23_vswscanf.c | 4 +++- wcsmbs/isoc99_swscanf.c | 2 +- wcsmbs/isoc99_vswscanf.c | 4 +++- 15 files changed, 49 insertions(+), 15 deletions(-) diff --git a/libio/iofwide.c b/libio/iofwide.c index 8e71fdd21b..d016aa33ea 100644 --- a/libio/iofwide.c +++ b/libio/iofwide.c @@ -251,3 +251,18 @@ __libio_codecvt_length (struct _IO_codecvt *codecvt, __mbstate_t *statep, return result; } + +void +_IO_wstrfile_fclose_stack (FILE *fp) +{ + _IO_FINISH (fp); + if (fp->_mode > 0) + { + struct _IO_codecvt *cc = fp->_codecvt; + + __libc_lock_lock (__gconv_lock); + __gconv_release_step (cc->__cd_in.step); + __gconv_release_step (cc->__cd_out.step); + __libc_lock_unlock (__gconv_lock); + } +} diff --git a/libio/iovswscanf.c b/libio/iovswscanf.c index a97f4eed1e..986d94178d 100644 --- a/libio/iovswscanf.c +++ b/libio/iovswscanf.c @@ -38,6 +38,8 @@ __vswscanf (const wchar_t *string, const wchar_t *format, va_list args) _IO_strfile sf; struct _IO_wide_data wd; FILE *f = _IO_strfile_readw (&sf, &wd, string); - return __vfwscanf_internal (f, format, args, 0); + int done = __vfwscanf_internal (f, format, args, 0); + _IO_wstrfile_fclose_stack (f); + return done; } ldbl_weak_alias (__vswscanf, vswscanf) diff --git a/libio/libioP.h b/libio/libioP.h index 1485d22619..1d4217e82a 100644 --- a/libio/libioP.h +++ b/libio/libioP.h @@ -645,6 +645,7 @@ extern FILE* _IO_new_file_fopen (FILE *, const char *, const char *, int); extern void _IO_no_init (FILE *, int, int, struct _IO_wide_data *, const struct _IO_jump_t *) __THROW; +extern void _IO_wstrfile_fclose_stack (FILE *) attribute_hidden; extern void _IO_new_file_init_internal (struct _IO_FILE_plus *) __THROW attribute_hidden; extern FILE* _IO_new_file_setbuf (FILE *, char *, ssize_t); diff --git a/libio/swscanf.c b/libio/swscanf.c index d36ef48258..583253a7dc 100644 --- a/libio/swscanf.c +++ b/libio/swscanf.c @@ -37,7 +37,7 @@ __swscanf (const wchar_t *s, const wchar_t *format, ...) va_start (arg, format); done = __vfwscanf_internal (f, format, arg, 0); va_end (arg); - + _IO_wstrfile_fclose_stack (f); return done; } ldbl_strong_alias (__swscanf, swscanf) diff --git a/sysdeps/ieee754/ldbl-128ibm-compat/ieee128-isoc23_swscanf.c b/sysdeps/ieee754/ldbl-128ibm-compat/ieee128-isoc23_swscanf.c index 6b3915357f..2e95cdf010 100644 --- a/sysdeps/ieee754/ldbl-128ibm-compat/ieee128-isoc23_swscanf.c +++ b/sysdeps/ieee754/ldbl-128ibm-compat/ieee128-isoc23_swscanf.c @@ -35,7 +35,7 @@ ___ieee128_isoc23_swscanf (const wchar_t *string, const wchar_t *format, ...) va_start (ap, format); done = __vfwscanf_internal (fp, format, ap, mode_flags); va_end (ap); - + _IO_wstrfile_fclose_stack (fp); return done; } strong_alias (___ieee128_isoc23_swscanf, __isoc23_swscanfieee128) diff --git a/sysdeps/ieee754/ldbl-128ibm-compat/ieee128-isoc23_vswscanf.c b/sysdeps/ieee754/ldbl-128ibm-compat/ieee128-isoc23_vswscanf.c index 295321147c..857903b2a6 100644 --- a/sysdeps/ieee754/ldbl-128ibm-compat/ieee128-isoc23_vswscanf.c +++ b/sysdeps/ieee754/ldbl-128ibm-compat/ieee128-isoc23_vswscanf.c @@ -28,6 +28,8 @@ ___ieee128_isoc23_vswscanf (wchar_t *string, const wchar_t *format, va_list ap) FILE *fp = _IO_strfile_readw (&sf, &wd, string); int mode_flags = SCANF_ISOC99_A | SCANF_ISOC23_BIN_CST | SCANF_LDBL_USES_FLOAT128; - return __vfwscanf_internal (fp, format, ap, mode_flags); + int done = __vfwscanf_internal (fp, format, ap, mode_flags); + _IO_wstrfile_fclose_stack (fp); + return done; } strong_alias (___ieee128_isoc23_vswscanf, __isoc23_vswscanfieee128) diff --git a/sysdeps/ieee754/ldbl-128ibm-compat/ieee128-isoc99_swscanf.c b/sysdeps/ieee754/ldbl-128ibm-compat/ieee128-isoc99_swscanf.c index 19b71940dd..2280bae1d0 100644 --- a/sysdeps/ieee754/ldbl-128ibm-compat/ieee128-isoc99_swscanf.c +++ b/sysdeps/ieee754/ldbl-128ibm-compat/ieee128-isoc99_swscanf.c @@ -34,7 +34,7 @@ ___ieee128_isoc99_swscanf (const wchar_t *string, const wchar_t *format, ...) va_start (ap, format); done = __vfwscanf_internal (fp, format, ap, mode_flags); va_end (ap); - + _IO_wstrfile_fclose_stack (fp); return done; } strong_alias (___ieee128_isoc99_swscanf, __isoc99_swscanfieee128) diff --git a/sysdeps/ieee754/ldbl-128ibm-compat/ieee128-isoc99_vswscanf.c b/sysdeps/ieee754/ldbl-128ibm-compat/ieee128-isoc99_vswscanf.c index a1b51cb5b6..21cf25610a 100644 --- a/sysdeps/ieee754/ldbl-128ibm-compat/ieee128-isoc99_vswscanf.c +++ b/sysdeps/ieee754/ldbl-128ibm-compat/ieee128-isoc99_vswscanf.c @@ -27,6 +27,8 @@ ___ieee128_isoc99_vswscanf (wchar_t *string, const wchar_t *format, va_list ap) struct _IO_wide_data wd; FILE *fp = _IO_strfile_readw (&sf, &wd, string); int mode_flags = SCANF_ISOC99_A | SCANF_LDBL_USES_FLOAT128; - return __vfwscanf_internal (fp, format, ap, mode_flags); + int done = __vfwscanf_internal (fp, format, ap, mode_flags); + _IO_wstrfile_fclose_stack (fp); + return done; } strong_alias (___ieee128_isoc99_vswscanf, __isoc99_vswscanfieee128) diff --git a/sysdeps/ieee754/ldbl-128ibm-compat/ieee128-swscanf.c b/sysdeps/ieee754/ldbl-128ibm-compat/ieee128-swscanf.c index 7124ecb67c..fdfdf2b65c 100644 --- a/sysdeps/ieee754/ldbl-128ibm-compat/ieee128-swscanf.c +++ b/sysdeps/ieee754/ldbl-128ibm-compat/ieee128-swscanf.c @@ -34,7 +34,7 @@ ___ieee128_swscanf (const wchar_t *string, const wchar_t *format, ...) done = __vfwscanf_internal (fp, format, ap, SCANF_LDBL_USES_FLOAT128); va_end (ap); - + _IO_wstrfile_fclose_stack (fp); return done; } strong_alias (___ieee128_swscanf, __swscanfieee128) diff --git a/sysdeps/ieee754/ldbl-128ibm-compat/ieee128-vswscanf.c b/sysdeps/ieee754/ldbl-128ibm-compat/ieee128-vswscanf.c index 8294582c48..b860a8e87d 100644 --- a/sysdeps/ieee754/ldbl-128ibm-compat/ieee128-vswscanf.c +++ b/sysdeps/ieee754/ldbl-128ibm-compat/ieee128-vswscanf.c @@ -27,6 +27,8 @@ ___ieee128_vswscanf (const wchar_t *string, const wchar_t *format, _IO_strfile sf; struct _IO_wide_data wd; FILE *fp = _IO_strfile_readw (&sf, &wd, string); - return __vfwscanf_internal (fp, format, ap, SCANF_LDBL_USES_FLOAT128); + int done = __vfwscanf_internal (fp, format, ap, SCANF_LDBL_USES_FLOAT128); + _IO_wstrfile_fclose_stack (fp); + return done; } strong_alias (___ieee128_vswscanf, __vswscanfieee128) diff --git a/sysdeps/ieee754/ldbl-opt/nldbl-compat.c b/sysdeps/ieee754/ldbl-opt/nldbl-compat.c index dcd5649200..e239cd5bc5 100644 --- a/sysdeps/ieee754/ldbl-opt/nldbl-compat.c +++ b/sysdeps/ieee754/ldbl-opt/nldbl-compat.c @@ -391,7 +391,9 @@ __nldbl_vswscanf (const wchar_t *s, const wchar_t *fmt, va_list ap) struct _IO_wide_data wd; FILE *f = _IO_strfile_readw (&sf, &wd, s); - return __vfwscanf_internal (f, fmt, ap, SCANF_LDBL_IS_DBL); + int ret = __vfwscanf_internal (f, fmt, ap, SCANF_LDBL_IS_DBL); + _IO_wstrfile_fclose_stack (f); + return ret; } libc_hidden_def (__nldbl_vswscanf) @@ -955,7 +957,9 @@ __nldbl___isoc99_vswscanf (const wchar_t *s, const wchar_t *fmt, va_list ap) struct _IO_wide_data wd; FILE *f = _IO_strfile_readw (&sf, &wd, s); - return __vfwscanf_internal (f, fmt, ap, SCANF_LDBL_IS_DBL | SCANF_ISOC99_A); + int ret = __vfwscanf_internal (f, fmt, ap, SCANF_LDBL_IS_DBL | SCANF_ISOC99_A); + _IO_wstrfile_fclose_stack (f); + return ret; } libc_hidden_def (__nldbl___isoc99_vswscanf) @@ -1115,9 +1119,11 @@ __nldbl___isoc23_vswscanf (const wchar_t *s, const wchar_t *fmt, va_list ap) struct _IO_wide_data wd; FILE *f = _IO_strfile_readw (&sf, &wd, s); - return __vfwscanf_internal (f, fmt, ap, + int ret = __vfwscanf_internal (f, fmt, ap, SCANF_LDBL_IS_DBL | SCANF_ISOC99_A | SCANF_ISOC23_BIN_CST); + _IO_wstrfile_fclose_stack (f); + return ret; } libc_hidden_def (__nldbl___isoc23_vswscanf) diff --git a/wcsmbs/isoc23_swscanf.c b/wcsmbs/isoc23_swscanf.c index 66b5290b3c..568ab138b4 100644 --- a/wcsmbs/isoc23_swscanf.c +++ b/wcsmbs/isoc23_swscanf.c @@ -33,6 +33,6 @@ __isoc23_swscanf (const wchar_t *s, const wchar_t *format, ...) done = __vfwscanf_internal (f, format, arg, SCANF_ISOC99_A | SCANF_ISOC23_BIN_CST); va_end (arg); - + _IO_wstrfile_fclose_stack (f); return done; } diff --git a/wcsmbs/isoc23_vswscanf.c b/wcsmbs/isoc23_vswscanf.c index d84efb9350..89b7241a7a 100644 --- a/wcsmbs/isoc23_vswscanf.c +++ b/wcsmbs/isoc23_vswscanf.c @@ -24,7 +24,9 @@ __isoc23_vswscanf (const wchar_t *string, const wchar_t *format, va_list args) _IO_strfile sf; struct _IO_wide_data wd; FILE *f = _IO_strfile_readw (&sf, &wd, string); - return __vfwscanf_internal (f, format, args, + int done = __vfwscanf_internal (f, format, args, SCANF_ISOC99_A | SCANF_ISOC23_BIN_CST); + _IO_wstrfile_fclose_stack (f); + return done; } libc_hidden_def (__isoc23_vswscanf) diff --git a/wcsmbs/isoc99_swscanf.c b/wcsmbs/isoc99_swscanf.c index 493fe01520..c94f19ca41 100644 --- a/wcsmbs/isoc99_swscanf.c +++ b/wcsmbs/isoc99_swscanf.c @@ -32,6 +32,6 @@ __isoc99_swscanf (const wchar_t *s, const wchar_t *format, ...) va_start (arg, format); done = __vfwscanf_internal (f, format, arg, SCANF_ISOC99_A); va_end (arg); - + _IO_wstrfile_fclose_stack (f); return done; } diff --git a/wcsmbs/isoc99_vswscanf.c b/wcsmbs/isoc99_vswscanf.c index 326f7bfd24..6bc134a807 100644 --- a/wcsmbs/isoc99_vswscanf.c +++ b/wcsmbs/isoc99_vswscanf.c @@ -33,6 +33,8 @@ __isoc99_vswscanf (const wchar_t *string, const wchar_t *format, va_list args) _IO_strfile sf; struct _IO_wide_data wd; FILE *f = _IO_strfile_readw (&sf, &wd, string); - return __vfwscanf_internal (f, format, args, SCANF_ISOC99_A); + int done = __vfwscanf_internal (f, format, args, SCANF_ISOC99_A); + _IO_wstrfile_fclose_stack (f); + return done; } libc_hidden_def (__isoc99_vswscanf)