[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