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

Carlos O'Donell carlos@redhat.com
Mon Jun 1 17:00:04 GMT 2026


On 5/27/26 12:57 PM, DJ Delorie 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.
> 
> 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]

OK. References 2b16c76609 without cherry picked line because this is
a custom backport.

LGTM.

Reviewed-by: Carlos O'Donell <carlos@redhat.com>

> ---
>   localedata/Makefile          |   1 +
>   localedata/tst-bz12701-lc.c  | 218 +++++++++++++++++++++++++++++++++++
>   stdio-common/Makefile        |   1 +
>   stdio-common/tst-bz12701-c.c | 169 +++++++++++++++++++++++++++
>   4 files changed, 389 insertions(+)
>   create mode 100644 localedata/tst-bz12701-lc.c
>   create mode 100644 stdio-common/tst-bz12701-c.c
> 
> diff --git a/localedata/Makefile b/localedata/Makefile
> index 4a23593cca..bff5c0bc71 100644
> --- a/localedata/Makefile
> +++ b/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 \
> 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 --git a/stdio-common/Makefile b/stdio-common/Makefile
> index e52c333808..fdb545242e 100644
> --- a/stdio-common/Makefile
> +++ b/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 \
> 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>


-- 
Cheers,
Carlos.



More information about the Libc-stable mailing list