import CS git glibc-2.34-272.el9_8

This commit is contained in:
AlmaLinux RelEng Bot 2026-06-30 05:18:11 -04:00
parent bd103df304
commit d39fb886f1
11 changed files with 3637 additions and 2094 deletions

View File

@ -0,0 +1,206 @@
commit 0981c03c2752b5f12ede24e6e696d5a29f7c6396
Author: Frédéric Bérat <fberat@redhat.com>
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 <adhemerval.zanella@linaro.org>
Conflicts:
sysdeps/ieee754/ldbl-opt/nldbl-compat.c
(isoc23 variants not present downstream)
sysdeps/ieee754/ldbl-128ibm-compat/ieee128-isoc23_swscanf.c
(not present downstream)
sysdeps/ieee754/ldbl-128ibm-compat/ieee128-isoc23_vswscanf.c
(not present downstream)
wcsmbs/isoc23_swscanf.c
(not present downstream)
wcsmbs/isoc23_vswscanf.c
(not present downstream)
diff --git a/libio/iofwide.c b/libio/iofwide.c
index a7f29fa0b9693cc3..972f154365e88bb3 100644
--- a/libio/iofwide.c
+++ b/libio/iofwide.c
@@ -257,3 +257,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 35bddc0daeca917f..284c61615ebddd47 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 50570f89de5a7010..76fd7853dc087bf9 100644
--- a/libio/libioP.h
+++ b/libio/libioP.h
@@ -594,6 +594,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 88a19144342cdc38..cb3795599c6f70c6 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-isoc99_swscanf.c b/sysdeps/ieee754/ldbl-128ibm-compat/ieee128-isoc99_swscanf.c
index ef8c1317dcd7f5e5..96bf2ac3559186e7 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 f77bb64638a4717f..8fa10ee2e22e238f 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 04ee5419200298b1..509eaa663d0a9a33 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 7aebc5f1c12939b6..18aa6deaacdde682 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 a6c5c49ecb151bf5..81d53a4cf30acb89 100644
--- a/sysdeps/ieee754/ldbl-opt/nldbl-compat.c
+++ b/sysdeps/ieee754/ldbl-opt/nldbl-compat.c
@@ -388,7 +388,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)
@@ -952,7 +954,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)
diff --git a/wcsmbs/isoc99_swscanf.c b/wcsmbs/isoc99_swscanf.c
index bb6d8f2035d8e4a4..3fda1f7be695b31e 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 3cd0a28c21ffa36f..1df90438416d3bb7 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)

View File

@ -0,0 +1,57 @@
libio: Fix gconv module reference counter overflow in vswprintf
The vswprintf family of functions (swprintf, vswprintf and their
fortified/ldbl-compat wrappers) creates a wide-oriented FILE stream
on the stack via _IO_no_init with _IO_wstrn_jumps, then calls
_IO_fwide which clones the gconv transformation steps via
__wcsmbs_clone_conv, incrementing the __counter reference counter.
Because the FILE is stack-allocated, fclose is never called, and the
counter is never decremented. In a long-running process calling
swprintf repeatedly with a non-builtin locale (e.g. en_US.UTF-8),
the counter eventually overflows, triggering a fatal abort.
This is the same class of bug fixed upstream for swscanf in commit
0981c03c2752b5f12ede24e6e696d5a29f7c6396. Upstream resolved the
swprintf path architecturally by reworking the printf subsystem to
use struct __wprintf_buffer instead of FILE * (commit 118816de3383),
eliminating the _IO_fwide call entirely. That rework is too large
to backport to glibc 2.34.
Instead, apply the same pattern as the swscanf fix: call
_IO_wstrfile_fclose_stack after __vfwprintf_internal returns to
release the gconv steps before the stack FILE goes out of scope.
All ldbl-compat wrappers (ieee128, nldbl) call __vswprintf_internal,
so they are covered by this single change.
Note: _IO_wstrfile_fclose_stack calls _IO_FINISH which invokes
_IO_wstr_finish, and that NULLs out _IO_buf_base. Therefore the
cleanup must happen after the overflow check (which compares
_IO_buf_base against overflow_buf) and after string termination
(which dereferences _IO_write_ptr).
diff --git a/libio/vswprintf.c b/libio/vswprintf.c
index b7036f1480733ac6..accbb8516f860680 100644
--- a/libio/vswprintf.c
+++ b/libio/vswprintf.c
@@ -111,13 +111,17 @@ __vswprintf_internal (wchar_t *string, size_t maxlen, const wchar_t *format,
ret = __vfwprintf_internal ((FILE *) &sf.f._sbf, format, args, mode_flags);
if (sf.f._sbf._f._wide_data->_IO_buf_base == sf.overflow_buf)
- /* ISO C99 requires swprintf/vswprintf to return an error if the
- output does not fit in the provided buffer. */
- return -1;
+ {
+ /* ISO C99 requires swprintf/vswprintf to return an error if the
+ output does not fit in the provided buffer. */
+ _IO_wstrfile_fclose_stack ((FILE *) &sf.f._sbf);
+ return -1;
+ }
/* Terminate the string. */
*sf.f._sbf._f._wide_data->_IO_write_ptr = '\0';
+ _IO_wstrfile_fclose_stack ((FILE *) &sf.f._sbf);
return ret;
}

View File

@ -0,0 +1,123 @@
commit 1cc165baed20b6589ef2f2c8c0e0415139bbb151
Author: Frédéric Bérat <fberat@redhat.com>
Date: Wed Apr 29 13:26:38 2026 +0200
test: Add gconv refcount leak test for swscanf
Add a new internal test, `tst-wcsmbs-clone-overflow`, to verify correct
gconv module reference counting. The Makefile is updated to include this
test in the `tests-internal` list and ensure it runs with generated locales.
This test specifically checks that the `__counter` for `gconv_fcts->towc`
does not leak references when `swscanf` is used with a stack-allocated
wide character stream. It ensures that `_IO_wstrfile_fclose_stack`
properly decrements the module reference counter, preventing a module
from staying loaded indefinitely due to unreleased references.
Assisted-by: LLM
Reviewed-by: Adhemerval Zanella <adhemerval.zanella@linaro.org>
Conflicts:
wcsmbs/Makefile
(test-c8rtomb, test-mbrtoc8, tst-wscanf-to_inpunct not present
downstream)
wcsmbs/tst-wcsmbs-clone-overflow.c
(struct lc_ctype_data not present downstream; use
loc->private.ctype directly instead)
diff --git a/wcsmbs/Makefile b/wcsmbs/Makefile
index 0e5fe909a83c7a13..f74b06d9749eaa02 100644
--- a/wcsmbs/Makefile
+++ b/wcsmbs/Makefile
@@ -59,6 +59,12 @@ tests := tst-wcstof wcsmbs-tst1 tst-wcsnlen tst-btowc tst-mbrtowc \
# This test runs for a long time.
xtests += test-wcsncmp-nonarray
+tests-internal += \
+ tst-wcsmbs-clone-overflow
+
+tests-static += \
+ tst-wcsmbs-clone-overflow
+
include ../Rules
@@ -77,6 +83,7 @@ $(objpfx)tst-wcstol-locale.out: $(gen-locales)
$(objpfx)tst-wcstod-nan-locale.out: $(gen-locales)
$(objpfx)tst-c16-surrogate.out: $(gen-locales)
$(objpfx)tst-c32-state.out: $(gen-locales)
+$(objpfx)tst-wcsmbs-clone-overflow.out: $(gen-locales)
endif
$(objpfx)tst-wcstod-round: $(libm)
diff --git a/wcsmbs/tst-wcsmbs-clone-overflow.c b/wcsmbs/tst-wcsmbs-clone-overflow.c
new file mode 100644
index 0000000000000000..412e1c54085c68d5
--- /dev/null
+++ b/wcsmbs/tst-wcsmbs-clone-overflow.c
@@ -0,0 +1,65 @@
+/* Test for gconv module reference counter leak.
+ Copyright (C) 2026 Free Software Foundation, Inc.
+ 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 <locale.h>
+#include <stdio.h>
+#include <wchar.h>
+#include <support/check.h>
+#include <support/support.h>
+
+/* Internal headers for accessing the gconv structures. */
+#include <locale/localeinfo.h>
+#include <iconv/gconv_int.h>
+#include <wcsmbs/wcsmbsload.h>
+
+static int
+do_test (void)
+{
+ if (setlocale (LC_ALL, "de_DE.ISO-8859-1") == NULL)
+ FAIL_EXIT1 ("setlocale failed, check if de_DE.ISO-8859-1 is generated");
+
+ wchar_t buf[32] = L"123";
+ int j;
+
+ /* First iteration initializes the gconv functions internally. */
+ if (swscanf (buf, L"%d", &j) < 1)
+ FAIL_EXIT1 ("swscanf failed");
+
+ /* Retrieve the current gconv_fcts from the LC_CTYPE locale data. */
+ struct __locale_data *loc = _NL_CURRENT_DATA (LC_CTYPE);
+ const struct gconv_fcts *fcts = loc->private.ctype;
+
+ TEST_VERIFY_EXIT (fcts != NULL);
+ TEST_VERIFY_EXIT (fcts->towc != NULL);
+
+ /* Capture the reference counter. */
+ int initial_counter = fcts->towc->__counter;
+
+ /* Perform a second iteration of swscanf. If the stack-allocated FILE
+ leaks the gconv reference, the counter will increment. */
+ if (swscanf (buf, L"%d", &j) < 1)
+ FAIL_EXIT1 ("swscanf failed");
+
+ /* The counter should be unchanged, as _IO_wstrfile_fclose_stack should
+ have decremented it correctly. */
+ TEST_COMPARE (fcts->towc->__counter, initial_counter);
+
+ return 0;
+}
+
+#include <support/test-driver.c>

View File

@ -0,0 +1,146 @@
commit 9ef37798fa7dc2b10926742e3f3bd304a17a9ad1
Author: Frédéric Bérat <fberat@redhat.com>
Date: Tue May 26 13:29:57 2026 +0200
test: Fix and stabilize tst-wcsmbs-clone-overflow test
The test tst-wcsmbs-clone-overflow was initially added to tests-static.
However, this causes the test to be unstable because gconv modules
dynamically load libc.so. Any discrepancy between the statically linked
version and the dynamically loaded one can lead to a crash.
By removing the test from tests-static, it relies on dynamic linking,
safely bypassing the dlopen crash. Since the test is now dynamically
linked, it cannot use the internal thread-local symbol
_NL_CURRENT_DATA(LC_CTYPE) because _nl_current_LC_CTYPE is hidden in
libc.so, leading to undefined references. Thus, the test now uses
newlocale and uselocale, safely extracting the locale data from the
returned locale_t object.
Furthermore, using newlocale requires the gconv-modules configuration to
be built and available so that the ISO8859-1.so module can be
dynamically loaded. Otherwise, glibc falls back to the built-in C locale
conversions, leaving __shlib_handle as NULL and silently bypassing the
reference counter increment.
A new Makefile fragment, gen-gconv-modules.mk, is introduced to ensure
the gconv-modules are built before the test runs, and an explicit check
for __shlib_handle != NULL is added to the test.
Reviewed-by: Carlos O'Donell <carlos@redhat.com>
Conflicts:
wcsmbs/Makefile
(test-c8rtomb, test-mbrtoc8, tst-wscanf-to_inpunct not present
downstream)
wcsmbs/tst-wcsmbs-clone-overflow.c
(struct lc_ctype_data not present downstream; use
loc->private.ctype directly instead)
diff --git a/gen-gconv-modules.mk b/gen-gconv-modules.mk
new file mode 100644
index 0000000000000000..046721a7a7bf460c
--- /dev/null
+++ b/gen-gconv-modules.mk
@@ -0,0 +1,6 @@
+# defines target $(gen-gconv-modules) that ensures gconv-modules are available
+
+gen-gconv-modules := $(common-objpfx)iconvdata/gconv-modules
+
+$(gen-gconv-modules):
+ $(MAKE) -C ../iconvdata subdir=iconvdata $@
diff --git a/localedata/Makefile b/localedata/Makefile
index 56de42e9262decd3..0cc02c430d96d804 100644
--- a/localedata/Makefile
+++ b/localedata/Makefile
@@ -204,7 +204,7 @@ install-others := $(addprefix $(inst_i18ndir)/, \
$(locales))
endif
-tests: $(objdir)/iconvdata/gconv-modules
+tests: $(gen-gconv-modules)
tests-static += tst-langinfo-newlocale-static tst-langinfo-setlocale-static
@@ -315,6 +315,7 @@ LOCALES := \
$(NULL)
include ../gen-locales.mk
+include ../gen-gconv-modules.mk
$(objpfx)tst-iconv-math-trans.out: $(gen-locales)
endif
@@ -486,6 +487,3 @@ $(objpfx)mtrace-tst-leaks.out: $(objpfx)tst-leaks.out
bug-setlocale1-ENV-only = LOCPATH=$(objpfx) LC_CTYPE=de_DE.UTF-8
bug-setlocale1-static-ENV-only = $(bug-setlocale1-ENV-only)
-
-$(objdir)/iconvdata/gconv-modules:
- $(MAKE) -C ../iconvdata subdir=iconvdata $@
diff --git a/wcsmbs/Makefile b/wcsmbs/Makefile
index f74b06d9749eaa02..bb03d92576700a99 100644
--- a/wcsmbs/Makefile
+++ b/wcsmbs/Makefile
@@ -62,16 +62,13 @@ xtests += test-wcsncmp-nonarray
tests-internal += \
tst-wcsmbs-clone-overflow
-tests-static += \
- tst-wcsmbs-clone-overflow
-
-
include ../Rules
ifeq ($(run-built-tests),yes)
LOCALES := de_DE.ISO-8859-1 de_DE.UTF-8 en_US.ANSI_X3.4-1968 hr_HR.ISO-8859-2 \
ja_JP.EUC-JP zh_TW.EUC-TW tr_TR.UTF-8 tr_TR.ISO-8859-9
include ../gen-locales.mk
+include ../gen-gconv-modules.mk
$(objpfx)tst-btowc.out: $(gen-locales)
$(objpfx)tst-c16c32-1.out: $(gen-locales)
@@ -83,7 +80,7 @@ $(objpfx)tst-wcstol-locale.out: $(gen-locales)
$(objpfx)tst-wcstod-nan-locale.out: $(gen-locales)
$(objpfx)tst-c16-surrogate.out: $(gen-locales)
$(objpfx)tst-c32-state.out: $(gen-locales)
-$(objpfx)tst-wcsmbs-clone-overflow.out: $(gen-locales)
+$(objpfx)tst-wcsmbs-clone-overflow.out: $(gen-locales) $(gen-gconv-modules)
endif
$(objpfx)tst-wcstod-round: $(libm)
diff --git a/wcsmbs/tst-wcsmbs-clone-overflow.c b/wcsmbs/tst-wcsmbs-clone-overflow.c
index 412e1c54085c68d5..6d8b643f48aa7775 100644
--- a/wcsmbs/tst-wcsmbs-clone-overflow.c
+++ b/wcsmbs/tst-wcsmbs-clone-overflow.c
@@ -30,8 +30,11 @@
static int
do_test (void)
{
- if (setlocale (LC_ALL, "de_DE.ISO-8859-1") == NULL)
- FAIL_EXIT1 ("setlocale failed, check if de_DE.ISO-8859-1 is generated");
+ locale_t loc_obj = newlocale (LC_ALL_MASK, "de_DE.ISO-8859-1", NULL);
+ if (loc_obj == NULL)
+ FAIL_EXIT1 ("newlocale failed, check if de_DE.ISO-8859-1 is generated");
+
+ uselocale (loc_obj);
wchar_t buf[32] = L"123";
int j;
@@ -41,7 +44,7 @@ do_test (void)
FAIL_EXIT1 ("swscanf failed");
/* Retrieve the current gconv_fcts from the LC_CTYPE locale data. */
- struct __locale_data *loc = _NL_CURRENT_DATA (LC_CTYPE);
+ struct __locale_data *loc = loc_obj->__locales[LC_CTYPE];
const struct gconv_fcts *fcts = loc->private.ctype;
TEST_VERIFY_EXIT (fcts != NULL);
@@ -50,6 +53,9 @@ do_test (void)
/* Capture the reference counter. */
int initial_counter = fcts->towc->__counter;
+ if (fcts->towc->__shlib_handle == NULL)
+ FAIL_EXIT1 ("__shlib_handle is NULL!");
+
/* Perform a second iteration of swscanf. If the stack-allocated FILE
leaks the gconv reference, the counter will increment. */
if (swscanf (buf, L"%d", &j) < 1)

View File

@ -0,0 +1,111 @@
test: Add gconv refcount leak test for swprintf
Add a new internal test, tst-wcsmbs-clone-overflow-swprintf, to verify
correct gconv module reference counting for the swprintf path.
This is the swprintf counterpart to tst-wcsmbs-clone-overflow (which
tests swscanf). The vswprintf/swprintf family creates a wide-oriented
stack-allocated FILE stream via _IO_no_init and _IO_fwide, which clones
the gconv transformation steps. This test verifies that the __counter
for gconv_fcts->towc does not leak references after swprintf returns,
confirming that _IO_wstrfile_fclose_stack properly releases the gconv
steps.
diff --git a/wcsmbs/Makefile b/wcsmbs/Makefile
index bb03d92576700a99..252630457146de35 100644
--- a/wcsmbs/Makefile
+++ b/wcsmbs/Makefile
@@ -60,7 +60,8 @@ tests := tst-wcstof wcsmbs-tst1 tst-wcsnlen tst-btowc tst-mbrtowc \
xtests += test-wcsncmp-nonarray
tests-internal += \
- tst-wcsmbs-clone-overflow
+ tst-wcsmbs-clone-overflow \
+ tst-wcsmbs-clone-overflow-swprintf
include ../Rules
@@ -81,6 +82,7 @@ $(objpfx)tst-wcstod-nan-locale.out: $(gen-locales)
$(objpfx)tst-c16-surrogate.out: $(gen-locales)
$(objpfx)tst-c32-state.out: $(gen-locales)
$(objpfx)tst-wcsmbs-clone-overflow.out: $(gen-locales) $(gen-gconv-modules)
+$(objpfx)tst-wcsmbs-clone-overflow-swprintf.out: $(gen-locales) $(gen-gconv-modules)
endif
$(objpfx)tst-wcstod-round: $(libm)
diff --git a/wcsmbs/tst-wcsmbs-clone-overflow-swprintf.c b/wcsmbs/tst-wcsmbs-clone-overflow-swprintf.c
new file mode 100644
index 0000000000000000..daccb08e6352a943
--- /dev/null
+++ b/wcsmbs/tst-wcsmbs-clone-overflow-swprintf.c
@@ -0,0 +1,70 @@
+/* Test for gconv module reference counter leak in swprintf.
+ Copyright (C) 2026 Free Software Foundation, Inc.
+ 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 <locale.h>
+#include <stdio.h>
+#include <wchar.h>
+#include <support/check.h>
+#include <support/support.h>
+
+/* Internal headers for accessing the gconv structures. */
+#include <locale/localeinfo.h>
+#include <iconv/gconv_int.h>
+#include <wcsmbs/wcsmbsload.h>
+
+static int
+do_test (void)
+{
+ locale_t loc_obj = newlocale (LC_ALL_MASK, "de_DE.ISO-8859-1", NULL);
+ if (loc_obj == NULL)
+ FAIL_EXIT1 ("newlocale failed, check if de_DE.ISO-8859-1 is generated");
+
+ uselocale (loc_obj);
+
+ wchar_t buf[32];
+
+ /* First iteration initializes the gconv functions internally. */
+ if (swprintf (buf, sizeof (buf) / sizeof (buf[0]), L"%d", 123) < 0)
+ FAIL_EXIT1 ("swprintf failed");
+
+ /* Retrieve the current gconv_fcts from the LC_CTYPE locale data. */
+ struct __locale_data *loc = loc_obj->__locales[LC_CTYPE];
+ const struct gconv_fcts *fcts = loc->private.ctype;
+
+ TEST_VERIFY_EXIT (fcts != NULL);
+ TEST_VERIFY_EXIT (fcts->towc != NULL);
+
+ /* Capture the reference counter. */
+ int initial_counter = fcts->towc->__counter;
+
+ if (fcts->towc->__shlib_handle == NULL)
+ FAIL_EXIT1 ("__shlib_handle is NULL!");
+
+ /* Perform a second iteration of swprintf. If the stack-allocated FILE
+ leaks the gconv reference, the counter will increment. */
+ if (swprintf (buf, sizeof (buf) / sizeof (buf[0]), L"%d", 456) < 0)
+ FAIL_EXIT1 ("swprintf failed");
+
+ /* The counter should be unchanged, as _IO_wstrfile_fclose_stack should
+ have decremented it correctly. */
+ TEST_COMPARE (fcts->towc->__counter, initial_counter);
+
+ return 0;
+}
+
+#include <support/test-driver.c>

View File

@ -0,0 +1,126 @@
commit 839898777226a3ed88c0859f25ffe712519b4ead
Author: Rocket Ma <marocketbd@gmail.com>
Date: Fri Apr 17 23:48:41 2026 -0700
stdio-common: Fix buffer overflow in scanf %mc [BZ #34008]
* stdio-common/vfscanf-internal.c: When enlarging allocated buffer with
format %mc or %mC, glibc allocates one byte less, leading to
user-controlled one byte overflow. This commit fixes BZ #34008, or
CVE-2026-5450.
Reviewed-by: Carlos O'Donell <carlos@redhat.com>
Signed-off-by: Rocket Ma <marocketbd@gmail.com>
Reviewed-by: H.J. Lu <hjl.tools@gmail.com>
Conflicts:
stdio-common/Makefile
(usual test differences)
diff -Nrup a/stdio-common/Makefile b/stdio-common/Makefile
--- a/stdio-common/Makefile 2026-06-16 08:24:01.348860456 -0400
+++ b/stdio-common/Makefile 2026-06-16 08:18:22.791126117 -0400
@@ -290,6 +290,7 @@ tests := \
tst-vfprintf-mbs-prec \
tst-vfprintf-user-type \
tst-vfprintf-width-prec-alloc \
+ tst-vfscanf-bz34008 \
tst-wc-printf \
tstdiomisc \
tstgetln \
@@ -441,6 +442,9 @@ tst-printf-bz18872-ENV = MALLOC_TRACE=$(
tst-vfprintf-width-prec-ENV = \
MALLOC_TRACE=$(objpfx)tst-vfprintf-width-prec.mtrace \
LD_PRELOAD=$(common-objpfx)/malloc/libc_malloc_debug.so
+tst-vfscanf-bz34008-ENV = \
+ MALLOC_CHECK_=3 \
+ LD_PRELOAD=$(common-objpfx)/malloc/libc_malloc_debug.so
tst-printf-bz25691-ENV = \
MALLOC_TRACE=$(objpfx)tst-printf-bz25691.mtrace \
LD_PRELOAD=$(common-objpfx)/malloc/libc_malloc_debug.so
diff --git a/stdio-common/tst-vfscanf-bz34008.c b/stdio-common/tst-vfscanf-bz34008.c
new file mode 100644
index 0000000000..48371c8a3d
--- /dev/null
+++ b/stdio-common/tst-vfscanf-bz34008.c
@@ -0,0 +1,48 @@
+/* Regression test for vfscanf %Nmc out-of-bound write (BZ #34008)
+ Copyright (C) 2026 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 "malloc/mcheck.h"
+#include <stddef.h>
+#include <stdio.h>
+#include <string.h>
+#include <wchar.h>
+#include <stdlib.h>
+#include <malloc.h>
+#include <support/check.h>
+
+#define WIDTH 0x410
+#define SCANFSTR "%1040mc"
+static int
+do_test (void)
+{
+ mcheck_pedantic (NULL);
+ char *input = malloc (WIDTH + 1);
+ TEST_VERIFY (input != NULL);
+ memset (input, 'A', WIDTH);
+ input[WIDTH] = '\0';
+
+ char *buf = NULL;
+ TEST_VERIFY (sscanf (input, SCANFSTR, &buf) != -1);
+ TEST_VERIFY (buf != NULL);
+
+ free (buf);
+ free (input);
+ return 0;
+}
+
+#include <support/test-driver.c>
diff --git a/stdio-common/vfscanf-internal.c b/stdio-common/vfscanf-internal.c
index 59fc8208aa..3d11ac261e 100644
--- a/stdio-common/vfscanf-internal.c
+++ b/stdio-common/vfscanf-internal.c
@@ -855,8 +855,7 @@ __vfscanf_internal (FILE *s, const char *format, va_list argptr,
{
/* Enlarge the buffer. */
size_t newsize
- = strsize
- + (strsize >= width ? width - 1 : strsize);
+ = strsize + (strsize >= width ? width : strsize);
str = (char *) realloc (*strptr, newsize);
if (str == NULL)
@@ -929,7 +928,7 @@ __vfscanf_internal (FILE *s, const char *format, va_list argptr,
&& wstr == (wchar_t *) *strptr + strsize)
{
size_t newsize
- = strsize + (strsize > width ? width - 1 : strsize);
+ = strsize + (strsize >= width ? width : strsize);
/* Enlarge the buffer. */
wstr = (wchar_t *) realloc (*strptr,
newsize * sizeof (wchar_t));
@@ -984,7 +983,7 @@ __vfscanf_internal (FILE *s, const char *format, va_list argptr,
&& wstr == (wchar_t *) *strptr + strsize)
{
size_t newsize
- = strsize + (strsize > width ? width - 1 : strsize);
+ = strsize + (strsize >= width ? width : strsize);
/* Enlarge the buffer. */
wstr = (wchar_t *) realloc (*strptr,
newsize * sizeof (wchar_t));

View File

@ -0,0 +1,80 @@
commit b866ef29773b22a1343ff9084374775114350b78
Author: Maciej W. Rozycki <macro@redhat.com>
Date: Wed May 27 12:57:10 2026 -0400
support: Implement 'xfmemopen' for seamless 'fmemopen' use
Add 'xfmemopen' wrapper for seamless 'fmemopen' use in tests, following
'xfopen', 'xfclose', etc., and providing a standardized error reporting
facility.
Reviewed-by: Florian Weimer <fweimer@redhat.com>
(cherry picked from commit fe709cc24578ecfd2ff5b07e10e3829fcb55075b)
Reviewed-by: Carlos O'Donell <carlos@redhat.com>
Conflicts:
support/Makefile
(usual test differences)
support/xstdio.h
(adjust for downstream context difference)
diff -Nrup a/support/Makefile b/support/Makefile
--- a/support/Makefile 2026-06-16 10:34:13.241874132 -0400
+++ b/support/Makefile 2026-06-16 10:37:06.085172353 -0400
@@ -135,6 +135,7 @@ libsupport-routines = \
xfchmod \
xfclose \
xfdopendir \
+ xfmemopen \
xfopen \
xfork \
xfread \
diff --git a/support/xfmemopen.c b/support/xfmemopen.c
new file mode 100644
index 0000000000..f1dbc72c67
--- /dev/null
+++ b/support/xfmemopen.c
@@ -0,0 +1,31 @@
+/* fmemopen with error checking.
+ Copyright (C) 2025 Free Software Foundation, Inc.
+ 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 <support/xstdio.h>
+
+#include <support/check.h>
+#include <stdlib.h>
+
+FILE *
+xfmemopen (void *mem, size_t len, const char *mode)
+{
+ FILE *fp = fmemopen (mem, len, mode);
+ if (fp == NULL)
+ FAIL_EXIT1 ("fmemopen (mode \"%s\"): %m", mode);
+ return fp;
+}
diff -Nrup a/support/xstdio.h b/support/xstdio.h
--- a/support/xstdio.h 2026-06-16 10:34:10.450856660 -0400
+++ b/support/xstdio.h 2026-06-16 10:39:05.792825780 -0400
@@ -26,6 +26,7 @@ __BEGIN_DECLS
FILE *xfopen (const char *path, const char *mode);
void xfclose (FILE *);
+FILE *xfmemopen (void *mem, size_t len, const char *mode);
void xfread (void *ptr, size_t size, size_t nmemb, FILE *stream);
/* Read a line from FP, using getline. *BUFFER must be NULL, or a

View File

@ -0,0 +1,457 @@
commit 97926e9017f3faeaacce9337f1288460f5e6ec7d
Author: Maciej W. Rozycki <macro@redhat.com>
Date: Wed May 27 12:57:10 2026 -0400
stdio-common: Reject insufficient character data in scanf [BZ #12701]
Reject invalid formatted scanf character data with the 'c' conversion
where there is not enough input available to satisfy the field width
requested. It is required by ISO C that this conversion matches a
sequence of characters of exactly the number specified by the field
width and it is also already documented as such in our own manual:
"It reads precisely the next N characters, and fails if it cannot get
that many."
Currently a matching success is instead incorrectly produced where the
EOF condition is encountered before the required number of characters
has been retrieved, and the characters actually obtained are stored in
the buffer provided.
Add test cases accordingly and remove placeholders from 'c' conversion
input data for the existing scanf tests.
Reviewed-by: Adhemerval Zanella <adhemerval.zanella@linaro.org>
[This is a modified version of commit 2b16c76609, which tests for the
old behavior and only includes the test cases, for older branches
and downstream backports - DJ]
Reviewed-by: Carlos O'Donell <carlos@redhat.com>
Conflicts:
localedata/Makefile
stdio-common/Makefile
(usual test differences)
diff -Nrup a/localedata/Makefile b/localedata/Makefile
--- a/localedata/Makefile 2026-06-16 09:15:19.295653341 -0400
+++ b/localedata/Makefile 2026-06-16 09:11:51.111414458 -0400
@@ -160,6 +160,7 @@ tests = \
bug-iconv-trans \
bug-setlocale1 \
bug-usesetlocale \
+ tst-bz12701-lc \
tst-c-utf8-consistency \
tst-digits \
tst-iconv-math-trans \
diff --git a/localedata/tst-bz12701-lc.c b/localedata/tst-bz12701-lc.c
new file mode 100644
index 0000000000..23c2ab7d2a
--- /dev/null
+++ b/localedata/tst-bz12701-lc.c
@@ -0,0 +1,218 @@
+/* Verify scanf field width handling with the 'lc' conversion (BZ #12701).
+ Copyright (C) 2025-2026 Free Software Foundation, Inc.
+ 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 <locale.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <string.h>
+#include <wchar.h>
+
+#include <libc-diag.h>
+#include <support/check.h>
+#include <support/next_to_fault.h>
+#include <support/xstdio.h>
+
+/* Compare character-wise the initial part of the wide character object
+ pointed to by WS corresponding to wide characters obtained by the
+ conversion of first N bytes of the multibyte character object pointed
+ to by S. */
+
+static int
+tst_bz12701_lc_memcmp (const wchar_t *ds, const char *s, size_t n)
+{
+ size_t nc = mbsnrtowcs (NULL, &s, n, 0, NULL);
+
+ struct support_next_to_fault ntf;
+ ntf = support_next_to_fault_allocate (nc * sizeof (wchar_t));
+ wchar_t *ss = (wchar_t *) ntf.buffer;
+
+ mbsnrtowcs (ss, &s, n, nc, NULL);
+ int r = wmemcmp (ds, ss, nc);
+
+ support_next_to_fault_free (&ntf);
+
+ return r;
+}
+
+/* Verify various aspects of field width handling, including the data
+ obtained, the number of bytes consumed, and the stream position. */
+
+static int
+do_test (void)
+{
+ if (setlocale (LC_ALL, "pl_PL.UTF-8") == NULL)
+ FAIL_EXIT1 ("setlocale (LC_ALL, \"pl_PL.UTF-8\")");
+
+ /* Part of a tongue-twister in Polish, which says:
+ "On a rainy morning cuckoos and warblers, rather than starting
+ on earthworms, stuffed themselves fasted with the flesh of cress." */
+ static const char s[126] = "Dżdżystym rankiem gżegżółki i piegże, "
+ "zamiast wziąć się za dżdżownice, "
+ "nażarły się na czczo miąższu rzeżuchy";
+
+ const char *sp = s;
+ size_t nc;
+ TEST_VERIFY_EXIT ((nc = mbsnrtowcs (NULL, &sp, sizeof (s), 0, NULL)) == 108);
+
+ struct support_next_to_fault ntfo, ntfi;
+ ntfo = support_next_to_fault_allocate (nc * sizeof (wchar_t));
+ ntfi = support_next_to_fault_allocate (sizeof (s));
+ wchar_t *e = (wchar_t *) ntfo.buffer + nc;
+ char *b = ntfi.buffer;
+
+ wchar_t *c;
+ FILE *f;
+ int ic;
+ int n;
+ int i;
+
+ memcpy (ntfi.buffer, s, sizeof (s));
+
+ ic = i = 0;
+ f = xfmemopen (b, sizeof (s), "r");
+
+ c = e - 1;
+ TEST_VERIFY_EXIT (ftell (f) == i);
+ /* Avoid: "warning: zero width in gnu_scanf format [-Werror=format=]". */
+ DIAG_PUSH_NEEDS_COMMENT;
+ DIAG_IGNORE_NEEDS_COMMENT (4.9, "-Wformat");
+ TEST_VERIFY_EXIT (fscanf (f, "%0lc%n", c, &n) == 1);
+ DIAG_POP_NEEDS_COMMENT;
+ TEST_VERIFY_EXIT (n == 1);
+ TEST_VERIFY_EXIT (tst_bz12701_lc_memcmp (c, s + i, n) == 0);
+ ic += 1;
+ i += n;
+
+ c = e - 1;
+ TEST_VERIFY_EXIT (ftell (f) == i);
+ TEST_VERIFY_EXIT (fscanf (f, "%lc%n", c, &n) == 1);
+ TEST_VERIFY_EXIT (n == 2);
+ TEST_VERIFY_EXIT (tst_bz12701_lc_memcmp (c, s + i, n) == 0);
+ ic += 1;
+ i += n;
+
+ c = e - 1;
+ TEST_VERIFY_EXIT (ftell (f) == i);
+ TEST_VERIFY_EXIT (fscanf (f, "%1lc%n", c, &n) == 1);
+ TEST_VERIFY_EXIT (n == 1);
+ TEST_VERIFY_EXIT (tst_bz12701_lc_memcmp (c, s + i, n) == 0);
+ ic += 1;
+ i += n;
+
+ c = e - 2;
+ TEST_VERIFY_EXIT (ftell (f) == i);
+ TEST_VERIFY_EXIT (fscanf (f, "%2lc%n", c, &n) == 1);
+ TEST_VERIFY_EXIT (n == 3);
+ TEST_VERIFY_EXIT (tst_bz12701_lc_memcmp (c, s + i, n) == 0);
+ ic += 2;
+ i += n;
+
+ c = e - 4;
+ TEST_VERIFY_EXIT (ftell (f) == i);
+ TEST_VERIFY_EXIT (fscanf (f, "%4lc%n", c, &n) == 1);
+ TEST_VERIFY_EXIT (n == 4);
+ TEST_VERIFY_EXIT (tst_bz12701_lc_memcmp (c, s + i, n) == 0);
+ ic += 4;
+ i += n;
+
+ c = e - 8;
+ TEST_VERIFY_EXIT (ftell (f) == i);
+ TEST_VERIFY_EXIT (fscanf (f, "%8lc%n", c, &n) == 1);
+ TEST_VERIFY_EXIT (n == 8);
+ TEST_VERIFY_EXIT (tst_bz12701_lc_memcmp (c, s + i, n) == 0);
+ ic += 8;
+ i += n;
+
+ c = e - 16;
+ TEST_VERIFY_EXIT (ftell (f) == i);
+ TEST_VERIFY_EXIT (fscanf (f, "%16lc%n", c, &n) == 1);
+ TEST_VERIFY_EXIT (n == 20);
+ TEST_VERIFY_EXIT (tst_bz12701_lc_memcmp (c, s + i, n) == 0);
+ ic += 16;
+ i += n;
+
+ c = e - 32;
+ TEST_VERIFY_EXIT (ftell (f) == i);
+ TEST_VERIFY_EXIT (fscanf (f, "%32lc%n", c, &n) == 1);
+ TEST_VERIFY_EXIT (n == 38);
+ TEST_VERIFY_EXIT (tst_bz12701_lc_memcmp (c, s + i, n) == 0);
+ ic += 32;
+ i += n;
+
+ c = e - (nc - ic);
+ TEST_VERIFY_EXIT (ftell (f) == i);
+ TEST_COMPARE (fscanf (f, "%64lc%n", c, &n), 1);
+ TEST_COMPARE (n , 49);
+ TEST_VERIFY_EXIT (tst_bz12701_lc_memcmp (c, s + i, sizeof (s) - i) == 0);
+
+ TEST_VERIFY_EXIT (ftell (f) == sizeof (s));
+ TEST_VERIFY_EXIT (feof (f) != 0);
+
+ xfclose (f);
+
+ ic = i = 0;
+ f = xfmemopen (b, 3, "r");
+
+ c = e - 2;
+ TEST_VERIFY_EXIT (ftell (f) == i);
+ TEST_VERIFY_EXIT (fscanf (f, "%2lc%n", c, &n) == 1);
+ TEST_VERIFY_EXIT (n == 3);
+ TEST_VERIFY_EXIT (tst_bz12701_lc_memcmp (c, s + i, n) == 0);
+ ic += 2;
+ i += n;
+
+ c = e - (nc - ic);
+ TEST_VERIFY_EXIT (feof (f) == 0);
+ TEST_VERIFY_EXIT (ftell (f) == i);
+ TEST_VERIFY_EXIT (fscanf (f, "%2lc%n", c, &n) == EOF);
+ TEST_VERIFY_EXIT (n == 3);
+
+ TEST_VERIFY_EXIT (ftell (f) == 3);
+ TEST_VERIFY_EXIT (feof (f) != 0);
+
+ xfclose (f);
+
+ ic = i = 0;
+ f = xfmemopen (b, 3, "r");
+
+ c = e - 1;
+ TEST_VERIFY_EXIT (ftell (f) == i);
+ TEST_VERIFY_EXIT (fscanf (f, "%lc%n", c, &n) == 1);
+ TEST_VERIFY_EXIT (n == 1);
+ TEST_VERIFY_EXIT (tst_bz12701_lc_memcmp (c, s + i, n) == 0);
+ ic += 1;
+ i += n;
+
+ c = e - (nc - ic);
+ TEST_VERIFY_EXIT (ftell (f) == i);
+ TEST_VERIFY_EXIT (fscanf (f, "%2lc%n", c, &n) == 1);
+ TEST_VERIFY_EXIT (n == 2);
+ TEST_VERIFY_EXIT (tst_bz12701_lc_memcmp (c, s + i, 3 - i) == 0);
+
+ TEST_VERIFY_EXIT (ftell (f) == 3);
+ TEST_VERIFY_EXIT (feof (f) != 0);
+
+ xfclose (f);
+
+ support_next_to_fault_free (&ntfi);
+ support_next_to_fault_free (&ntfo);
+
+ return 0;
+}
+
+#include <support/test-driver.c>
diff -Nrup a/stdio-common/Makefile b/stdio-common/Makefile
--- a/stdio-common/Makefile 2026-06-16 09:15:19.320447732 -0400
+++ b/stdio-common/Makefile 2026-06-16 09:14:09.814624522 -0400
@@ -217,6 +217,7 @@ tests := \
tllformat \
tst-bz11319 \
tst-bz11319-fortify2 \
+ tst-bz12701-c \
tst-cookie \
tst-fclose-devzero \
tst-fclose-offset \
diff --git a/stdio-common/tst-bz12701-c.c b/stdio-common/tst-bz12701-c.c
new file mode 100644
index 0000000000..4f3616fbfd
--- /dev/null
+++ b/stdio-common/tst-bz12701-c.c
@@ -0,0 +1,169 @@
+/* Verify scanf field width handling with the 'c' conversion (BZ #12701).
+ Copyright (C) 2025-2026 Free Software Foundation, Inc.
+ 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 <string.h>
+
+#include <libc-diag.h>
+#include <support/check.h>
+#include <support/next_to_fault.h>
+#include <support/xstdio.h>
+
+/* Verify various aspects of field width handling, including the data
+ obtained, the number of bytes consumed, and the stream position. */
+
+static int
+do_test (void)
+{
+ static const char s[43] = "The quick brown fox jumps over the lazy dog";
+ struct support_next_to_fault ntfo, ntfi;
+ ntfo = support_next_to_fault_allocate (sizeof (s));
+ ntfi = support_next_to_fault_allocate (sizeof (s));
+ char *e = ntfo.buffer + sizeof (s);
+ char *b = ntfi.buffer;
+
+ char *c;
+ FILE *f;
+ int n;
+ int i;
+
+ memcpy (ntfi.buffer, s, sizeof (s));
+
+ i = 0;
+ f = xfmemopen (b, sizeof (s), "r");
+
+ c = e - 1;
+ TEST_VERIFY_EXIT (ftell (f) == i);
+ /* Avoid: "warning: zero width in gnu_scanf format [-Werror=format=]". */
+ DIAG_PUSH_NEEDS_COMMENT;
+ DIAG_IGNORE_NEEDS_COMMENT (4.9, "-Wformat");
+ TEST_VERIFY_EXIT (fscanf (f, "%0c%n", c, &n) == 1);
+ DIAG_POP_NEEDS_COMMENT;
+ TEST_VERIFY_EXIT (n == 1);
+ TEST_VERIFY_EXIT (memcmp (c, s + i, n) == 0);
+ i += n;
+
+ c = e - 1;
+ TEST_VERIFY_EXIT (ftell (f) == i);
+ TEST_VERIFY_EXIT (fscanf (f, "%c%n", c, &n) == 1);
+ TEST_VERIFY_EXIT (n == 1);
+ TEST_VERIFY_EXIT (memcmp (c, s + i, n) == 0);
+ i += n;
+
+ c = e - 1;
+ TEST_VERIFY_EXIT (ftell (f) == i);
+ TEST_VERIFY_EXIT (fscanf (f, "%1c%n", c, &n) == 1);
+ TEST_VERIFY_EXIT (n == 1);
+ TEST_VERIFY_EXIT (memcmp (c, s + i, n) == 0);
+ i += n;
+
+ c = e - 2;
+ TEST_VERIFY_EXIT (ftell (f) == i);
+ TEST_VERIFY_EXIT (fscanf (f, "%2c%n", c, &n) == 1);
+ TEST_VERIFY_EXIT (n == 2);
+ TEST_VERIFY_EXIT (memcmp (c, s + i, n) == 0);
+ i += n;
+
+ c = e - 4;
+ TEST_VERIFY_EXIT (ftell (f) == i);
+ TEST_VERIFY_EXIT (fscanf (f, "%4c%n", c, &n) == 1);
+ TEST_VERIFY_EXIT (n == 4);
+ TEST_VERIFY_EXIT (memcmp (c, s + i, n) == 0);
+ i += n;
+
+ c = e - 8;
+ TEST_VERIFY_EXIT (ftell (f) == i);
+ TEST_VERIFY_EXIT (fscanf (f, "%8c%n", c, &n) == 1);
+ TEST_VERIFY_EXIT (n == 8);
+ TEST_VERIFY_EXIT (memcmp (c, s + i, n) == 0);
+ i += n;
+
+ c = e - 16;
+ TEST_VERIFY_EXIT (ftell (f) == i);
+ TEST_VERIFY_EXIT (fscanf (f, "%16c%n", c, &n) == 1);
+ TEST_VERIFY_EXIT (n == 16);
+ TEST_VERIFY_EXIT (memcmp (c, s + i, n) == 0);
+ i += n;
+
+ c = e - (sizeof (s) - i);
+ TEST_VERIFY_EXIT (ftell (f) == i);
+ TEST_VERIFY_EXIT (fscanf (f, "%32c%n", c, &n) == 1);
+ TEST_VERIFY_EXIT (n == 10);
+ TEST_VERIFY_EXIT (memcmp (c, s + i, sizeof (s) - i) == 0);
+
+ TEST_VERIFY_EXIT (ftell (f) == sizeof (s));
+ TEST_VERIFY_EXIT (feof (f) != 0);
+
+ xfclose (f);
+
+ i = 0;
+ f = xfmemopen (b, 3, "r");
+
+ c = e - 1;
+ TEST_VERIFY_EXIT (ftell (f) == i);
+ TEST_VERIFY_EXIT (fscanf (f, "%c%n", c, &n) == 1);
+ TEST_VERIFY_EXIT (n == 1);
+ TEST_VERIFY_EXIT (memcmp (c, s + i, n) == 0);
+ i += n;
+
+ c = e - 2;
+ TEST_VERIFY_EXIT (ftell (f) == i);
+ TEST_VERIFY_EXIT (fscanf (f, "%2c%n", c, &n) == 1);
+ TEST_VERIFY_EXIT (n == 2);
+ TEST_VERIFY_EXIT (memcmp (c, s + i, n) == 0);
+ i += n;
+
+ c = e - (3 - i);
+ TEST_VERIFY_EXIT (feof (f) == 0);
+ TEST_VERIFY_EXIT (ftell (f) == i);
+ TEST_VERIFY_EXIT (fscanf (f, "%2c%n", c, &n) == EOF);
+ TEST_VERIFY_EXIT (n == 2);
+
+ TEST_VERIFY_EXIT (ftell (f) == i);
+ TEST_VERIFY_EXIT (feof (f) != 0);
+
+ xfclose (f);
+
+ i = 0;
+ f = xfmemopen (b, 3, "r");
+
+ c = e - 2;
+ TEST_VERIFY_EXIT (ftell (f) == i);
+ TEST_VERIFY_EXIT (fscanf (f, "%2c%n", c, &n) == 1);
+ TEST_VERIFY_EXIT (n == 2);
+ TEST_VERIFY_EXIT (memcmp (c, s + i, n) == 0);
+ i += n;
+
+ c = e - (3 - i);
+ TEST_VERIFY_EXIT (ftell (f) == i);
+ TEST_VERIFY_EXIT (fscanf (f, "%2c%n", c, &n) == 1);
+ TEST_VERIFY_EXIT (n == 1);
+ TEST_VERIFY_EXIT (memcmp (c, s + i, 3 - i) == 0);
+
+ TEST_VERIFY_EXIT (ftell (f) == 3);
+ TEST_VERIFY_EXIT (feof (f) != 0);
+
+ xfclose (f);
+
+ support_next_to_fault_free (&ntfi);
+ support_next_to_fault_free (&ntfo);
+
+ return 0;
+}
+
+#include <support/test-driver.c>

View File

@ -0,0 +1,193 @@
commit 6cebb0b80fd783e442a8ad27c3f52cde52a9cac7
Author: DJ Delorie <dj@redhat.com>
Date: Wed May 27 12:57:10 2026 -0400
stdio-common: Allow partially-filled %mc buffers [BZ #12701]
This is a backwards-compatible alternative to the main solution to
the %mc part of 12701. The allocated buffer is expanded to the
requested size and NUL padded, but truncated reads are allowed.
Reviewed-by: Carlos O'Donell <carlos@redhat.com>
Conflicts:
localedata/Makefile
stdio-common/Makefile
(usual test differences)
diff -Nrup a/localedata/Makefile b/localedata/Makefile
--- a/localedata/Makefile 2026-06-16 10:00:37.802495527 -0400
+++ b/localedata/Makefile 2026-06-16 09:59:09.766677507 -0400
@@ -161,6 +161,7 @@ tests = \
bug-setlocale1 \
bug-usesetlocale \
tst-bz12701-lc \
+ tst-bz12701-lc2 \
tst-c-utf8-consistency \
tst-digits \
tst-iconv-math-trans \
diff --git a/localedata/tst-bz12701-lc2.c b/localedata/tst-bz12701-lc2.c
new file mode 100644
index 0000000000..b24e86df0b
--- /dev/null
+++ b/localedata/tst-bz12701-lc2.c
@@ -0,0 +1,47 @@
+/* Verify scanf memory handling with the 'c' conversion (BZ #12701).
+ Copyright (C) 2026 Free Software Foundation, Inc.
+ 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 <malloc.h>
+#include <string.h>
+
+#include <libc-diag.h>
+#include <support/check.h>
+#include <support/next_to_fault.h>
+#include <support/xstdio.h>
+
+static int
+do_test (void)
+{
+ wchar_t *c = NULL;
+ int i;
+
+ TEST_VERIFY (sscanf ("1234", "%30mlc", &c) == 1);
+
+ TEST_VERIFY (c != NULL);
+ TEST_COMPARE_BLOB (c, 5 * sizeof (wchar_t),
+ L"1234\0", 5 * sizeof (wchar_t));
+ for (i = 5; i < 30; i ++)
+ TEST_VERIFY (c[i] == L'\0');
+
+ TEST_VERIFY (malloc_usable_size (c) >= 30 * sizeof(wchar_t));
+
+ return 0;
+}
+
+#include <support/test-driver.c>
diff -Nrup a/stdio-common/Makefile b/stdio-common/Makefile
--- a/stdio-common/Makefile 2026-06-16 10:00:37.803143281 -0400
+++ b/stdio-common/Makefile 2026-06-16 09:59:54.974421920 -0400
@@ -218,6 +218,7 @@ tests := \
tst-bz11319 \
tst-bz11319-fortify2 \
tst-bz12701-c \
+ tst-bz12701-c2 \
tst-cookie \
tst-fclose-devzero \
tst-fclose-offset \
diff --git a/stdio-common/tst-bz12701-c2.c b/stdio-common/tst-bz12701-c2.c
new file mode 100644
index 0000000000..5f9ca7c592
--- /dev/null
+++ b/stdio-common/tst-bz12701-c2.c
@@ -0,0 +1,46 @@
+/* Verify scanf memory handling with the 'c' conversion (BZ #12701).
+ Copyright (C) 2026 Free Software Foundation, Inc.
+ 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 <malloc.h>
+#include <string.h>
+
+#include <libc-diag.h>
+#include <support/check.h>
+#include <support/next_to_fault.h>
+#include <support/xstdio.h>
+
+static int
+do_test (void)
+{
+ char *c = NULL;
+ int i;
+
+ TEST_VERIFY (sscanf ("1234", "%30mc", &c) == 1);
+
+ TEST_VERIFY (c != NULL);
+ TEST_COMPARE_BLOB (c, 5, "1234\0", 5);
+ for (i = 5; i < 30; i ++)
+ TEST_VERIFY (c[i] == '\0');
+
+ TEST_VERIFY (malloc_usable_size (c) >= 30);
+
+ return 0;
+}
+
+#include <support/test-driver.c>
diff --git a/stdio-common/vfscanf-internal.c b/stdio-common/vfscanf-internal.c
index 17b5565d0f..90a1886951 100644
--- a/stdio-common/vfscanf-internal.c
+++ b/stdio-common/vfscanf-internal.c
@@ -780,9 +780,9 @@ __vfscanf_internal (FILE *s, const char *format, va_list argptr,
conv_error (); \
} while (0)
#ifdef COMPILE_WSCANF
- STRING_ARG (str, char, 100);
+ STRING_ARG (str, char, (width > 0 ? width : 1));
#else
- STRING_ARG (str, char, (width > 1024 ? 1024 : width));
+ STRING_ARG (str, char, (width > 0 ? width : 1));
#endif
c = inchar ();
@@ -891,6 +891,11 @@ __vfscanf_internal (FILE *s, const char *format, va_list argptr,
if (!(flags & SUPPRESS))
{
+ /* If the buffer isn't completely filled, pad it with NULs. */
+ if (flags & MALLOC)
+ while (width-- > 0)
+ *str++ = '\0';
+
if ((flags & MALLOC) && str - *strptr != strsize)
{
char *cp = (char *) realloc (*strptr, str - *strptr);
@@ -908,7 +913,7 @@ __vfscanf_internal (FILE *s, const char *format, va_list argptr,
if (width == -1)
width = 1;
- STRING_ARG (wstr, wchar_t, (width > 1024 ? 1024 : width));
+ STRING_ARG (wstr, wchar_t, (width > 0 ? width : 1));
c = inchar ();
if (__glibc_unlikely (c == EOF))
@@ -1044,6 +1049,11 @@ __vfscanf_internal (FILE *s, const char *format, va_list argptr,
if (!(flags & SUPPRESS))
{
+ /* If the buffer isn't completely filled, pad it with NULs. */
+ if (flags & MALLOC)
+ while (width-- > 0)
+ *wstr++ = L'\0';
+
if ((flags & MALLOC) && wstr - (wchar_t *) *strptr != strsize)
{
wchar_t *cp = (wchar_t *) realloc (*strptr,

View File

@ -1,2 +1,2 @@
381936891b4e421cd9a03b682bcb5a66039fd230
ec99e297ec21a794af038ae0b8ad9eae6a5b7a54
v1

File diff suppressed because it is too large Load Diff