stdio-common: Reject insufficient character data in scanf [BZ #12701]

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 commit is contained in:
Maciej W. Rozycki 2025-08-23 01:02:10 +01:00
parent e377a7a8ec
commit 2b16c76609
6 changed files with 405 additions and 22 deletions

View File

@ -236,6 +236,7 @@ tests = \
bug-iconv-trans \
bug-setlocale1 \
bug-usesetlocale \
tst-bz12701-lc \
tst-bz13988 \
tst-c-utf8-consistency \
tst-digits \

224
localedata/tst-bz12701-lc.c Normal file
View File

@ -0,0 +1,224 @@
/* 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>
#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 = fmemopen (b, sizeof (s), "r");
if (f == NULL)
FAIL_EXIT1 ("fmemopen: %m");
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);
xfclose (f);
ic = i = 0;
f = fmemopen (b, 3, "r");
if (f == NULL)
FAIL_EXIT1 ("fmemopen: %m");
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 = fmemopen (b, 3, "r");
if (f == NULL)
FAIL_EXIT1 ("fmemopen: %m");
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);
xfclose (f);
support_next_to_fault_free (&ntfi);
support_next_to_fault_free (&ntfo);
return 0;
}
#include <support/test-driver.c>

View File

@ -260,6 +260,7 @@ tests := \
tllformat \
tst-bz11319 \
tst-bz11319-fortify2 \
tst-bz12701-c \
tst-cookie \
tst-dprintf-length \
tst-fclose-devzero \

View File

@ -0,0 +1,175 @@
/* 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>
#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 = fmemopen (b, sizeof (s), "r");
if (f == NULL)
FAIL_EXIT1 ("fmemopen: %m");
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);
xfclose (f);
i = 0;
f = fmemopen (b, 3, "r");
if (f == NULL)
FAIL_EXIT1 ("fmemopen: %m");
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 = fmemopen (b, 3, "r");
if (f == NULL)
FAIL_EXIT1 ("fmemopen: %m");
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);
xfclose (f);
support_next_to_fault_free (&ntfi);
support_next_to_fault_free (&ntfo);
return 0;
}
#include <support/test-driver.c>

View File

@ -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:

View File

@ -898,6 +898,8 @@ __vfscanf_internal (FILE *s, const char *format, va_list argptr,
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 *format, va_list argptr,
while (--width > 0 && inchar () != EOF);
}
#endif
if (width > 0)
input_error ();
if (!(flags & SUPPRESS))
{