CVE-2012-3406: Stack overflow in vfprintf [BZ #16617]

A larger number of format specifiers coudld cause a stack overflow,
potentially allowing to bypass _FORTIFY_SOURCE format string
protection.
This commit is contained in:
Jeff Law 2014-12-15 10:09:32 +01:00 committed by Florian Weimer
parent 3a12c70f13
commit a5357b7ce2
7 changed files with 207 additions and 8 deletions

View File

@ -1,3 +1,12 @@
2014-12-15 Jeff Law <law@redhat.com>
[BZ #16617]
* stdio-common/vfprintf.c (vfprintf): Allocate large specs array
on the heap. (CVE-2012-3406)
* stdio-common/bug23-2.c, stdio-common/bug23-3.c: New file.
* stdio-common/bug23-4.c: New file. Test case by Joseph Myers.
* stdio-common/Makefile (tests): Add bug23-2, bug23-3, bug23-4.
2014-12-15 Will Newton <will.newton@linaro.org> 2014-12-15 Will Newton <will.newton@linaro.org>
* manual/install.texi: Bump required version of texinfo * manual/install.texi: Bump required version of texinfo

13
NEWS
View File

@ -10,11 +10,11 @@ Version 2.21
* The following bugs are resolved with this release: * The following bugs are resolved with this release:
6652, 10672, 12847, 12926, 13862, 14132, 14138, 14171, 14498, 15215, 6652, 10672, 12847, 12926, 13862, 14132, 14138, 14171, 14498, 15215,
15884, 16469, 16619, 16657, 16740, 16857, 17192, 17266, 17344, 17363, 15884, 16469, 16617, 16619, 16657, 16740, 16857, 17192, 17266, 17344,
17370, 17371, 17411, 17460, 17475, 17485, 17501, 17506, 17508, 17522, 17363, 17370, 17371, 17411, 17460, 17475, 17485, 17501, 17506, 17508,
17555, 17570, 17571, 17572, 17573, 17574, 17581, 17582, 17583, 17584, 17522, 17555, 17570, 17571, 17572, 17573, 17574, 17581, 17582, 17583,
17585, 17589, 17594, 17601, 17608, 17616, 17625, 17633, 17634, 17647, 17584, 17585, 17589, 17594, 17601, 17608, 17616, 17625, 17633, 17634,
17653, 17664, 17665, 17668, 17682. 17647, 17653, 17664, 17665, 17668, 17682.
* CVE-2104-7817 The wordexp function could ignore the WRDE_NOCMD flag * CVE-2104-7817 The wordexp function could ignore the WRDE_NOCMD flag
under certain input conditions resulting in the execution of a shell for under certain input conditions resulting in the execution of a shell for
@ -22,6 +22,9 @@ Version 2.21
implementation now checks WRDE_NOCMD immediately before executing the implementation now checks WRDE_NOCMD immediately before executing the
shell and returns the error WRDE_CMDSUB as expected. shell and returns the error WRDE_CMDSUB as expected.
* CVE-2012-3406 printf-style functions could run into a stack overflow when
processing format strings with a large number of format specifiers.
* The minimum GCC version that can be used to build this version of the GNU * The minimum GCC version that can be used to build this version of the GNU
C Library is GCC 4.6. Older GCC versions, and non-GNU compilers, can C Library is GCC 4.6. Older GCC versions, and non-GNU compilers, can
still be used to compile programs using the GNU C Library. still be used to compile programs using the GNU C Library.

View File

@ -57,7 +57,7 @@ tests := tstscanf test_rdwr test-popen tstgetln test-fseek \
bug19 bug19a tst-popen2 scanf13 scanf14 scanf15 bug20 bug21 bug22 \ bug19 bug19a tst-popen2 scanf13 scanf14 scanf15 bug20 bug21 bug22 \
scanf16 scanf17 tst-setvbuf1 tst-grouping bug23 bug24 \ scanf16 scanf17 tst-setvbuf1 tst-grouping bug23 bug24 \
bug-vfprintf-nargs tst-long-dbl-fphex tst-fphex-wide tst-sprintf3 \ bug-vfprintf-nargs tst-long-dbl-fphex tst-fphex-wide tst-sprintf3 \
bug25 tst-printf-round bug26 bug25 tst-printf-round bug23-2 bug23-3 bug23-4
test-srcs = tst-unbputc tst-printf test-srcs = tst-unbputc tst-printf

70
stdio-common/bug23-2.c Normal file
View File

@ -0,0 +1,70 @@
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
static const char expected[] = "\
\n\
a\n\
abbcd55\
\n\
a\n\
abbcd55\
\n\
a\n\
abbcd55\
\n\
a\n\
abbcd55\
\n\
a\n\
abbcd55\
\n\
a\n\
abbcd55\
\n\
a\n\
abbcd55\
\n\
a\n\
abbcd55\
\n\
a\n\
abbcd55\
\n\
a\n\
abbcd55\
\n\
a\n\
abbcd55\
\n\
a\n\
abbcd55\
\n\
a\n\
abbcd55%%%%%%%%%%%%%%%%%%%%%%%%%%\n";
static int
do_test (void)
{
char *buf = malloc (strlen (expected) + 1);
snprintf (buf, strlen (expected) + 1,
"\n%1$s\n" "%1$s" "%2$s" "%2$s" "%3$s" "%4$s" "%5$d" "%5$d"
"\n%1$s\n" "%1$s" "%2$s" "%2$s" "%3$s" "%4$s" "%5$d" "%5$d"
"\n%1$s\n" "%1$s" "%2$s" "%2$s" "%3$s" "%4$s" "%5$d" "%5$d"
"\n%1$s\n" "%1$s" "%2$s" "%2$s" "%3$s" "%4$s" "%5$d" "%5$d"
"\n%1$s\n" "%1$s" "%2$s" "%2$s" "%3$s" "%4$s" "%5$d" "%5$d"
"\n%1$s\n" "%1$s" "%2$s" "%2$s" "%3$s" "%4$s" "%5$d" "%5$d"
"\n%1$s\n" "%1$s" "%2$s" "%2$s" "%3$s" "%4$s" "%5$d" "%5$d"
"\n%1$s\n" "%1$s" "%2$s" "%2$s" "%3$s" "%4$s" "%5$d" "%5$d"
"\n%1$s\n" "%1$s" "%2$s" "%2$s" "%3$s" "%4$s" "%5$d" "%5$d"
"\n%1$s\n" "%1$s" "%2$s" "%2$s" "%3$s" "%4$s" "%5$d" "%5$d"
"\n%1$s\n" "%1$s" "%2$s" "%2$s" "%3$s" "%4$s" "%5$d" "%5$d"
"\n%1$s\n" "%1$s" "%2$s" "%2$s" "%3$s" "%4$s" "%5$d" "%5$d"
"\n%1$s\n" "%1$s" "%2$s" "%2$s" "%3$s" "%4$s" "%5$d" "%5$d"
"%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%\n",
"a", "b", "c", "d", 5);
return strcmp (buf, expected) != 0;
}
#define TEST_FUNCTION do_test ()
#include "../test-skeleton.c"

50
stdio-common/bug23-3.c Normal file
View File

@ -0,0 +1,50 @@
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
int
do_test (void)
{
size_t instances = 16384;
#define X0 "\n%1$s\n" "%1$s" "%2$s" "%2$s" "%3$s" "%4$s" "%5$d" "%5$d"
const char *item = "\na\nabbcd55";
#define X3 X0 X0 X0 X0 X0 X0 X0 X0
#define X6 X3 X3 X3 X3 X3 X3 X3 X3
#define X9 X6 X6 X6 X6 X6 X6 X6 X6
#define X12 X9 X9 X9 X9 X9 X9 X9 X9
#define X14 X12 X12 X12 X12
#define TRAILER "%%%%%%%%%%%%%%%%%%%%%%%%%%"
#define TRAILER2 TRAILER TRAILER
size_t length = instances * strlen (item) + strlen (TRAILER) + 1;
char *buf = malloc (length + 1);
snprintf (buf, length + 1,
X14 TRAILER2 "\n",
"a", "b", "c", "d", 5);
const char *p = buf;
size_t i;
for (i = 0; i < instances; ++i)
{
const char *expected;
for (expected = item; *expected; ++expected)
{
if (*p != *expected)
{
printf ("mismatch at offset %zu (%zu): expected %d, got %d\n",
(size_t) (p - buf), i, *expected & 0xFF, *p & 0xFF);
return 1;
}
++p;
}
}
if (strcmp (p, TRAILER "\n") != 0)
{
printf ("mismatch at trailer: [%s]\n", p);
return 1;
}
free (buf);
return 0;
}
#define TEST_FUNCTION do_test ()
#include "../test-skeleton.c"

31
stdio-common/bug23-4.c Normal file
View File

@ -0,0 +1,31 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/resource.h>
#define LIMIT 1000000
int
main (void)
{
struct rlimit lim;
getrlimit (RLIMIT_STACK, &lim);
lim.rlim_cur = 1048576;
setrlimit (RLIMIT_STACK, &lim);
char *fmtstr = malloc (4 * LIMIT + 1);
if (fmtstr == NULL)
abort ();
char *output = malloc (LIMIT + 1);
if (output == NULL)
abort ();
for (size_t i = 0; i < LIMIT; i++)
memcpy (fmtstr + 4 * i, "%1$d", 4);
fmtstr[4 * LIMIT] = '\0';
int ret = snprintf (output, LIMIT + 1, fmtstr, 0);
if (ret != LIMIT)
abort ();
for (size_t i = 0; i < LIMIT; i++)
if (output[i] != '0')
abort ();
return 0;
}

View File

@ -263,6 +263,12 @@ vfprintf (FILE *s, const CHAR_T *format, va_list ap)
/* For the argument descriptions, which may be allocated on the heap. */ /* For the argument descriptions, which may be allocated on the heap. */
void *args_malloced = NULL; void *args_malloced = NULL;
/* For positional argument handling. */
struct printf_spec *specs;
/* Track if we malloced the SPECS array and thus must free it. */
bool specs_malloced = false;
/* This table maps a character into a number representing a /* This table maps a character into a number representing a
class. In each step there is a destination label for each class. In each step there is a destination label for each
class. */ class. */
@ -1679,8 +1685,8 @@ do_positional:
size_t nspecs = 0; size_t nspecs = 0;
/* A more or less arbitrary start value. */ /* A more or less arbitrary start value. */
size_t nspecs_size = 32 * sizeof (struct printf_spec); size_t nspecs_size = 32 * sizeof (struct printf_spec);
struct printf_spec *specs = alloca (nspecs_size);
specs = alloca (nspecs_size);
/* The number of arguments the format string requests. This will /* The number of arguments the format string requests. This will
determine the size of the array needed to store the argument determine the size of the array needed to store the argument
attributes. */ attributes. */
@ -1721,11 +1727,39 @@ do_positional:
if (nspecs * sizeof (*specs) >= nspecs_size) if (nspecs * sizeof (*specs) >= nspecs_size)
{ {
/* Extend the array of format specifiers. */ /* Extend the array of format specifiers. */
if (nspecs_size * 2 < nspecs_size)
{
__set_errno (ENOMEM);
done = -1;
goto all_done;
}
struct printf_spec *old = specs; struct printf_spec *old = specs;
if (__libc_use_alloca (2 * nspecs_size))
specs = extend_alloca (specs, nspecs_size, 2 * nspecs_size); specs = extend_alloca (specs, nspecs_size, 2 * nspecs_size);
else
{
nspecs_size *= 2;
specs = malloc (nspecs_size);
if (specs == NULL)
{
__set_errno (ENOMEM);
specs = old;
done = -1;
goto all_done;
}
}
/* Copy the old array's elements to the new space. */ /* Copy the old array's elements to the new space. */
memmove (specs, old, nspecs * sizeof (*specs)); memmove (specs, old, nspecs * sizeof (*specs));
/* If we had previously malloc'd space for SPECS, then
release it after the copy is complete. */
if (specs_malloced)
free (old);
/* Now set SPECS_MALLOCED if needed. */
if (!__libc_use_alloca (nspecs_size))
specs_malloced = true;
} }
/* Parse the format specifier. */ /* Parse the format specifier. */
@ -2046,6 +2080,8 @@ do_positional:
} }
all_done: all_done:
if (specs_malloced)
free (specs);
if (__glibc_unlikely (args_malloced != NULL)) if (__glibc_unlikely (args_malloced != NULL))
free (args_malloced); free (args_malloced);
if (__glibc_unlikely (workstart != NULL)) if (__glibc_unlikely (workstart != NULL))