[PATCH 1/2] libio: Fix gconv module reference counter overflow in swscanf

Adhemerval Zanella Netto adhemerval.zanella@linaro.org
Thu Apr 30 18:42:36 GMT 2026



On 29/04/26 11:59, Frédéric Bérat wrote:
> 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.

LGTM, thanks.

Reviewed-by: Adhemerval Zanella  <adhemerval.zanella@linaro.org>

> ---
>  libio/iofwide.c                                   | 15 +++++++++++++++
>  libio/iovswscanf.c                                |  4 +++-
>  libio/libioP.h                                    |  1 +
>  libio/swscanf.c                                   |  2 +-
>  .../ldbl-128ibm-compat/ieee128-isoc23_swscanf.c   |  2 +-
>  .../ldbl-128ibm-compat/ieee128-isoc23_vswscanf.c  |  4 +++-
>  .../ldbl-128ibm-compat/ieee128-isoc99_swscanf.c   |  2 +-
>  .../ldbl-128ibm-compat/ieee128-isoc99_vswscanf.c  |  4 +++-
>  .../ieee754/ldbl-128ibm-compat/ieee128-swscanf.c  |  2 +-
>  .../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)



More information about the Libc-alpha mailing list