[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