[PATCH v2 06/14] stdio-common: Reject insufficient character data in scanf [BZ #12701]

Adhemerval Zanella Netto adhemerval.zanella@linaro.org
Fri Aug 15 13:01:26 GMT 2025



On 05/06/25 14:58, Maciej W. Rozycki wrote:
> From: Maciej W. Rozycki <macro@redhat.com>
> 
> 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.
> ---
> I believe this concludes fixes for BZ #12701 and the bug can be closed 
> now.
> 
> No change from v1.

Just some nits wtr to tests. 

LGTM with those fixed.

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

> ---
>  localedata/Makefile                     |    1 
>  localedata/tst-bz12701-lc.c             |  217 ++++++++++++++++++++++++++++++++
>  stdio-common/Makefile                   |    1 
>  stdio-common/tst-bz12701-c.c            |  168 ++++++++++++++++++++++++
>  stdio-common/tst-scanf-format-c-c.input |   22 ---
>  stdio-common/vfscanf-internal.c         |    4 
>  6 files changed, 391 insertions(+), 22 deletions(-)
> 
> glibc-scanf-bz12701-char-width.diff
> Index: glibc/localedata/Makefile
> ===================================================================
> --- glibc.orig/localedata/Makefile
> +++ glibc/localedata/Makefile
> @@ -236,6 +236,7 @@ tests = \
>    bug-iconv-trans \
>    bug-setlocale1 \
>    bug-usesetlocale \
> +  tst-bz12701-lc \
>    tst-bz13988 \
>    tst-c-utf8-consistency \
>    tst-digits \
> Index: glibc/localedata/tst-bz12701-lc.c
> ===================================================================
> --- /dev/null
> +++ glibc/localedata/tst-bz12701-lc.c
> @@ -0,0 +1,217 @@
> +/* Verify scanf field width handling with the 'lc' conversion (BZ #12701).
> +   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 <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>
> +
> +/* 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 = fmemopen (b, sizeof (s), "r");

Add a TEST_VERIFY_EXIT (f != NULL);

> +
> +  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_VERIFY_EXIT (fscanf (f, "%64lc%n", c, &n) == EOF);
> +  TEST_VERIFY_EXIT (n == 38);
> +  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);
> +
> +  fclose (f);
> +
> +  ic = i = 0;
> +  f = fmemopen (b, 3, "r");

Ditto.

> +
> +  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);
> +
> +  fclose (f);

Use xfclose.

> +
> +  ic = i = 0;
> +  f = fmemopen (b, 3, "r");

Ditto.

> +
> +  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) == EOF);
> +  TEST_VERIFY_EXIT (n == 1);
> +  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);
> +
> +  fclose (f);
> +
> +  support_next_to_fault_free (&ntfi);
> +  support_next_to_fault_free (&ntfo);
> +
> +  return 0;
> +}
> +
> +#include <support/test-driver.c>
> Index: glibc/stdio-common/Makefile
> ===================================================================
> --- glibc.orig/stdio-common/Makefile
> +++ glibc/stdio-common/Makefile
> @@ -260,6 +260,7 @@ tests := \
>    tllformat \
>    tst-bz11319 \
>    tst-bz11319-fortify2 \
> +  tst-bz12701-c \
>    tst-cookie \
>    tst-dprintf-length \
>    tst-fclose-devzero \
> Index: glibc/stdio-common/tst-bz12701-c.c
> ===================================================================
> --- /dev/null
> +++ glibc/stdio-common/tst-bz12701-c.c
> @@ -0,0 +1,168 @@
> +/* Verify scanf field width handling with the 'c' conversion (BZ #12701).
> +   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 <stdio.h>
> +#include <string.h>
> +
> +#include <libc-diag.h>
> +#include <support/check.h>
> +#include <support/next_to_fault.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 = fmemopen (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) == EOF);
> +  TEST_VERIFY_EXIT (n == 16);
> +  TEST_VERIFY_EXIT (memcmp (c, s + i, sizeof (s) - i) == 0);
> +
> +  TEST_VERIFY_EXIT (ftell (f) == sizeof (s));
> +  TEST_VERIFY_EXIT (feof (f) != 0);
> +
> +  fclose (f);
> +
> +  i = 0;
> +  f = fmemopen (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);
> +
> +  fclose (f);
> +
> +  i = 0;
> +  f = fmemopen (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) == EOF);
> +  TEST_VERIFY_EXIT (n == 2);
> +  TEST_VERIFY_EXIT (memcmp (c, s + i, 3 - i) == 0);
> +
> +  TEST_VERIFY_EXIT (ftell (f) == 3);
> +  TEST_VERIFY_EXIT (feof (f) != 0);
> +
> +  fclose (f);
> +
> +  support_next_to_fault_free (&ntfi);
> +  support_next_to_fault_free (&ntfo);
> +
> +  return 0;
> +}
> +
> +#include <support/test-driver.c>
> Index: glibc/stdio-common/tst-scanf-format-c-c.input
> ===================================================================
> --- glibc.orig/stdio-common/tst-scanf-format-c-c.input
> +++ glibc/stdio-common/tst-scanf-format-c-c.input
> @@ -22,30 +22,14 @@
>  %*2c:brown fox:0:2:
>  %2c:jumps over the lazy dog:1:2:ju:
>  %*2c:jumps over the lazy dog:0:2:
> -# BZ12701 %5c:The:0:-1:
> -# BZ12701 %*5c:The:0:-1:
>  %5c:quick:1:5:quick:
>  %*5c:quick:0:5:
>  %5c:brown fox:1:5:brown:
>  %*5c:brown fox:0:5:
>  %5c:jumps over the lazy dog:1:5:jumps:
>  %*5c:jumps over the lazy dog:0:5:
> -# BZ12701 %10c:The:0:-1:
> -# BZ12701 %*10c:The:0:-1:
> -# BZ12701 %10c:quick:0:-1:
> -# BZ12701 %*10c:quick:0:-1:
> -# BZ12701 %10c:brown fox:0:-1:
> -# BZ12701 %*10c:brown fox:0:-1:
>  %10c:jumps over the lazy dog:1:10:jumps over:
>  %*10c:jumps over the lazy dog:0:10:
> -# BZ12701 %25c:The:0:-1:
> -# BZ12701 %*25c:The:0:-1:
> -# BZ12701 %25c:quick:0:-1:
> -# BZ12701 %*25c:quick:0:-1:
> -# BZ12701 %25c:brown fox:0:-1:
> -# BZ12701 %*25c:brown fox:0:-1:
> -# BZ12701 %25c:jumps over the lazy dog:0:-1:
> -# BZ12701 %*25c:jumps over the lazy dog:0:-1:
>  %5c: The :1:5: The :
>  %*5c: The :0:5:
>  %5c: quick :1:5: quic:
> @@ -54,11 +38,5 @@
>  %*5c: brown fox :0:5:
>  %5c: jumps over the lazy dog :1:5: jump:
>  %*5c: jumps over the lazy dog :0:5:
> -# BZ12701 %25c: The :0:-1:
> -# BZ12701 %*25c: The :0:-1:
> -# BZ12701 %25c: quick :0:-1:
> -# BZ12701 %*25c: quick :0:-1:
> -# BZ12701 %25c: brown fox :0:-1:
> -# BZ12701 %*25c: brown fox :0:-1:
>  %25c: jumps over the lazy dog :1:25: jumps over the lazy dog :
>  %*25c: jumps over the lazy dog :0:25:
> Index: glibc/stdio-common/vfscanf-internal.c
> ===================================================================
> --- glibc.orig/stdio-common/vfscanf-internal.c
> +++ glibc/stdio-common/vfscanf-internal.c
> @@ -898,6 +898,8 @@ __vfscanf_internal (FILE *s, const char
>  	      else
>  		while (--width > 0 && inchar () != EOF);
>  #endif
> +	      if (width > 0)
> +		input_error ();
>  
>  	      if (!(flags & SUPPRESS))
>  		{
> @@ -1051,6 +1053,8 @@ __vfscanf_internal (FILE *s, const char
>  	    while (--width > 0 && inchar () != EOF);
>  	  }
>  #endif
> +	  if (width > 0)
> +	    input_error ();
>  
>  	  if (!(flags & SUPPRESS))
>  	    {



More information about the Libc-alpha mailing list