[glibc/release/2.41/master] stdio-common: Allow partially-filled %mc buffers [BZ #12701]

Aurelien Jarno via Glibc-cvs <[email protected]> Tue, 23 Jun 2026 04:37:07 +0000 (GMT)
Newsgroups gmane.comp.lib.glibc.cvs
Message-ID <[email protected]>
https://sourceware.org/git/gitweb.cgi?p=glibc.git;h=248f5e876fb695141e6e9a904a8a5ae6844e66a0

commit 248f5e876fb695141e6e9a904a8a5ae6844e66a0
Author: DJ Delorie <[email protected]>
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 <[email protected]>

Diff:
---
 localedata/Makefile             |  1 +
 localedata/tst-bz12701-lc2.c    | 47 +++++++++++++++++++++++++++++++++++++++++
 stdio-common/Makefile           |  1 +
 stdio-common/tst-bz12701-c2.c   | 46 ++++++++++++++++++++++++++++++++++++++++
 stdio-common/vfscanf-internal.c | 16 +++++++++++---
 5 files changed, 108 insertions(+), 3 deletions(-)

diff --git a/localedata/Makefile b/localedata/Makefile
index cafafc809a..d5e9f76899 100644
--- a/localedata/Makefile
+++ b/localedata/Makefile
@@ -237,6 +237,7 @@ tests = \
   bug-setlocale1 \
   bug-usesetlocale \
   tst-bz12701-lc \
+  tst-bz12701-lc2 \
   tst-c-utf8-consistency \
   tst-digits \
   tst-iconv-emojis-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 --git a/stdio-common/Makefile b/stdio-common/Makefile
index 33dc42ef45..ac098dcd8a 100644
--- a/stdio-common/Makefile
+++ b/stdio-common/Makefile
@@ -233,6 +233,7 @@ tests := \
   tst-bz11319 \
   tst-bz11319-fortify2 \
   tst-bz12701-c \
+  tst-bz12701-c2 \
   tst-cookie \
   tst-dprintf-length \
   tst-fdopen \
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 87f23b5845..1ba034d961 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 ();
@@ -892,6 +892,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);
@@ -909,7 +914,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))
@@ -1045,6 +1050,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,