e38cb5
commit e1c0c00cc2bdd147bfcf362ada1443bee90465ec
e38cb5
Author: Joseph Myers <joseph@codesourcery.com>
e38cb5
Date:   Tue Jul 7 14:54:12 2020 +0000
e38cb5
e38cb5
    Remove most vfprintf width/precision-dependent allocations (bug 14231, bug 26211).
e38cb5
    
e38cb5
    The vfprintf implementation (used for all printf-family functions)
e38cb5
    contains complicated logic to allocate internal buffers of a size
e38cb5
    depending on the width and precision used for a format, using either
e38cb5
    malloc or alloca depending on that size, and with consequent checks
e38cb5
    for size overflow and allocation failure.
e38cb5
    
e38cb5
    As noted in bug 26211, the version of that logic used when '$' plus
e38cb5
    argument number formats are in use is missing the overflow checks,
e38cb5
    which can result in segfaults (quite possibly exploitable, I didn't
e38cb5
    try to work that out) when the width or precision is in the range
e38cb5
    0x7fffffe0 through 0x7fffffff (maybe smaller values as well in the
e38cb5
    wprintf case on 32-bit systems, when the multiplication by sizeof
e38cb5
    (CHAR_T) can overflow).
e38cb5
    
e38cb5
    All that complicated logic in fact appears to be useless.  As far as I
e38cb5
    can tell, there has been no need (outside the floating-point printf
e38cb5
    code, which does its own allocations) for allocations depending on
e38cb5
    width or precision since commit
e38cb5
    3e95f6602b226e0de06aaff686dc47b282d7cc16 ("Remove limitation on size
e38cb5
    of precision for integers", Sun Sep 12 21:23:32 1999 +0000).  Thus,
e38cb5
    this patch removes that logic completely, thereby fixing both problems
e38cb5
    with excessive allocations for large width and precision for
e38cb5
    non-floating-point formats, and the problem with missing overflow
e38cb5
    checks with such allocations.  Note that this does have the
e38cb5
    consequence that width and precision up to INT_MAX are now allowed
e38cb5
    where previously INT_MAX / sizeof (CHAR_T) - EXTSIZ or more would have
e38cb5
    been rejected, so could potentially expose any other overflows where
e38cb5
    the value would previously have been rejected by those removed checks.
e38cb5
    
e38cb5
    I believe this completely fixes bugs 14231 and 26211.
e38cb5
    
e38cb5
    Excessive allocations are still possible in the floating-point case
e38cb5
    (bug 21127), as are other integer or buffer overflows (see bug 26201).
e38cb5
    This does not address the cases where a precision larger than INT_MAX
e38cb5
    (embedded in the format string) would be meaningful without printf's
e38cb5
    return value overflowing (when it's used with a string format, or %g
e38cb5
    without the '#' flag, so the actual output will be much smaller), as
e38cb5
    mentioned in bug 17829 comment 8; using size_t internally for
e38cb5
    precision to handle that case would be complicated by struct
e38cb5
    printf_info being a public ABI.  Nor does it address the matter of an
e38cb5
    INT_MIN width being negated (bug 17829 comment 7; the same logic
e38cb5
    appears a second time in the file as well, in the form of multiplying
e38cb5
    by -1).  There may be other sources of memory allocations with malloc
e38cb5
    in printf functions as well (bug 24988, bug 16060).  From inspection,
e38cb5
    I think there are also integer overflows in two copies of "if ((width
e38cb5
    -= len) < 0)" logic (where width is int, len is size_t and a very long
e38cb5
    string could result in spurious padding being output on a 32-bit
e38cb5
    system before printf overflows the count of output characters).
e38cb5
    
e38cb5
    Tested for x86-64 and x86.
e38cb5
    
e38cb5
    (cherry picked from commit 6caddd34bd7ffb5ac4f36c8e036eee100c2cc535)
e38cb5
e38cb5
diff --git a/stdio-common/Makefile b/stdio-common/Makefile
e38cb5
index 51062a7dbf698931..d76b47bd5f932f69 100644
e38cb5
--- a/stdio-common/Makefile
e38cb5
+++ b/stdio-common/Makefile
e38cb5
@@ -64,6 +64,7 @@ tests := tstscanf test_rdwr test-popen tstgetln test-fseek \
e38cb5
 	 tst-scanf-round \
e38cb5
 	 tst-renameat2 \
e38cb5
 	 tst-printf-bz25691 \
e38cb5
+	 tst-vfprintf-width-prec-alloc
e38cb5
 
e38cb5
 test-srcs = tst-unbputc tst-printf tst-printfsz-islongdouble
e38cb5
 
e38cb5
diff --git a/stdio-common/bug22.c b/stdio-common/bug22.c
e38cb5
index b26399acb7dfc775..e12b01731e1b4ac8 100644
e38cb5
--- a/stdio-common/bug22.c
e38cb5
+++ b/stdio-common/bug22.c
e38cb5
@@ -42,7 +42,7 @@ do_test (void)
e38cb5
 
e38cb5
   ret = fprintf (fp, "%." SN3 "d", 1);
e38cb5
   printf ("ret = %d\n", ret);
e38cb5
-  if (ret != -1 || errno != EOVERFLOW)
e38cb5
+  if (ret != N3)
e38cb5
 	  return 1;
e38cb5
 
e38cb5
   ret = fprintf (fp, "%" SN2 "d%" SN2 "d", 1, 1);
e38cb5
diff --git a/stdio-common/tst-vfprintf-width-prec-alloc.c b/stdio-common/tst-vfprintf-width-prec-alloc.c
e38cb5
new file mode 100644
e38cb5
index 0000000000000000..0a74b53a3389d699
e38cb5
--- /dev/null
e38cb5
+++ b/stdio-common/tst-vfprintf-width-prec-alloc.c
e38cb5
@@ -0,0 +1,41 @@
e38cb5
+/* Test large width or precision does not involve large allocation.
e38cb5
+   Copyright (C) 2020 Free Software Foundation, Inc.
e38cb5
+   This file is part of the GNU C Library.
e38cb5
+
e38cb5
+   The GNU C Library is free software; you can redistribute it and/or
e38cb5
+   modify it under the terms of the GNU Lesser General Public
e38cb5
+   License as published by the Free Software Foundation; either
e38cb5
+   version 2.1 of the License, or (at your option) any later version.
e38cb5
+
e38cb5
+   The GNU C Library is distributed in the hope that it will be useful,
e38cb5
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
e38cb5
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
e38cb5
+   Lesser General Public License for more details.
e38cb5
+
e38cb5
+   You should have received a copy of the GNU Lesser General Public
e38cb5
+   License along with the GNU C Library; if not, see
e38cb5
+   <https://www.gnu.org/licenses/>.  */
e38cb5
+
e38cb5
+#include <stdio.h>
e38cb5
+#include <sys/resource.h>
e38cb5
+#include <support/check.h>
e38cb5
+
e38cb5
+char test_string[] = "test";
e38cb5
+
e38cb5
+static int
e38cb5
+do_test (void)
e38cb5
+{
e38cb5
+  struct rlimit limit;
e38cb5
+  TEST_VERIFY_EXIT (getrlimit (RLIMIT_AS, &limit) == 0);
e38cb5
+  limit.rlim_cur = 200 * 1024 * 1024;
e38cb5
+  TEST_VERIFY_EXIT (setrlimit (RLIMIT_AS, &limit) == 0);
e38cb5
+  FILE *fp = fopen ("/dev/null", "w");
e38cb5
+  TEST_VERIFY_EXIT (fp != NULL);
e38cb5
+  TEST_COMPARE (fprintf (fp, "%1000000000d", 1), 1000000000);
e38cb5
+  TEST_COMPARE (fprintf (fp, "%.1000000000s", test_string), 4);
e38cb5
+  TEST_COMPARE (fprintf (fp, "%1000000000d %1000000000d", 1, 2), 2000000001);
e38cb5
+  TEST_COMPARE (fprintf (fp, "%2$.*1$s", 0x7fffffff, test_string), 4);
e38cb5
+  return 0;
e38cb5
+}
e38cb5
+
e38cb5
+#include <support/test-driver.c>
e38cb5
diff --git a/stdio-common/vfprintf.c b/stdio-common/vfprintf.c
e38cb5
index dab56b6ba2c7bdbe..6b83ba91a12cdcd5 100644
e38cb5
--- a/stdio-common/vfprintf.c
e38cb5
+++ b/stdio-common/vfprintf.c
e38cb5
@@ -42,10 +42,6 @@
e38cb5
 
e38cb5
 #include <libioP.h>
e38cb5
 
e38cb5
-/* In some cases we need extra space for all the output which is not
e38cb5
-   counted in the width of the string. We assume 32 characters is
e38cb5
-   enough.  */
e38cb5
-#define EXTSIZ		32
e38cb5
 #define ARGCHECK(S, Format) \
e38cb5
   do									      \
e38cb5
     {									      \
e38cb5
@@ -1295,7 +1291,6 @@ vfprintf (FILE *s, const CHAR_T *format, va_list ap)
e38cb5
 
e38cb5
   /* Buffer intermediate results.  */
e38cb5
   CHAR_T work_buffer[WORK_BUFFER_SIZE];
e38cb5
-  CHAR_T *workstart = NULL;
e38cb5
   CHAR_T *workend;
e38cb5
 
e38cb5
   /* We have to save the original argument pointer.  */
e38cb5
@@ -1404,7 +1399,6 @@ vfprintf (FILE *s, const CHAR_T *format, va_list ap)
e38cb5
       UCHAR_T pad = L_(' ');/* Padding character.  */
e38cb5
       CHAR_T spec;
e38cb5
 
e38cb5
-      workstart = NULL;
e38cb5
       workend = work_buffer + WORK_BUFFER_SIZE;
e38cb5
 
e38cb5
       /* Get current character in format string.  */
e38cb5
@@ -1496,31 +1490,6 @@ vfprintf (FILE *s, const CHAR_T *format, va_list ap)
e38cb5
 	    pad = L_(' ');
e38cb5
 	    left = 1;
e38cb5
 	  }
e38cb5
-
e38cb5
-	if (__glibc_unlikely (width >= INT_MAX / sizeof (CHAR_T) - EXTSIZ))
e38cb5
-	  {
e38cb5
-	    __set_errno (EOVERFLOW);
e38cb5
-	    done = -1;
e38cb5
-	    goto all_done;
e38cb5
-	  }
e38cb5
-
e38cb5
-	if (width >= WORK_BUFFER_SIZE - EXTSIZ)
e38cb5
-	  {
e38cb5
-	    /* We have to use a special buffer.  */
e38cb5
-	    size_t needed = ((size_t) width + EXTSIZ) * sizeof (CHAR_T);
e38cb5
-	    if (__libc_use_alloca (needed))
e38cb5
-	      workend = (CHAR_T *) alloca (needed) + width + EXTSIZ;
e38cb5
-	    else
e38cb5
-	      {
e38cb5
-		workstart = (CHAR_T *) malloc (needed);
e38cb5
-		if (workstart == NULL)
e38cb5
-		  {
e38cb5
-		    done = -1;
e38cb5
-		    goto all_done;
e38cb5
-		  }
e38cb5
-		workend = workstart + width + EXTSIZ;
e38cb5
-	      }
e38cb5
-	  }
e38cb5
       }
e38cb5
       JUMP (*f, step1_jumps);
e38cb5
 
e38cb5
@@ -1528,31 +1497,13 @@ vfprintf (FILE *s, const CHAR_T *format, va_list ap)
e38cb5
     LABEL (width):
e38cb5
       width = read_int (&f);
e38cb5
 
e38cb5
-      if (__glibc_unlikely (width == -1
e38cb5
-			    || width >= INT_MAX / sizeof (CHAR_T) - EXTSIZ))
e38cb5
+      if (__glibc_unlikely (width == -1))
e38cb5
 	{
e38cb5
 	  __set_errno (EOVERFLOW);
e38cb5
 	  done = -1;
e38cb5
 	  goto all_done;
e38cb5
 	}
e38cb5
 
e38cb5
-      if (width >= WORK_BUFFER_SIZE - EXTSIZ)
e38cb5
-	{
e38cb5
-	  /* We have to use a special buffer.  */
e38cb5
-	  size_t needed = ((size_t) width + EXTSIZ) * sizeof (CHAR_T);
e38cb5
-	  if (__libc_use_alloca (needed))
e38cb5
-	    workend = (CHAR_T *) alloca (needed) + width + EXTSIZ;
e38cb5
-	  else
e38cb5
-	    {
e38cb5
-	      workstart = (CHAR_T *) malloc (needed);
e38cb5
-	      if (workstart == NULL)
e38cb5
-		{
e38cb5
-		  done = -1;
e38cb5
-		  goto all_done;
e38cb5
-		}
e38cb5
-	      workend = workstart + width + EXTSIZ;
e38cb5
-	    }
e38cb5
-	}
e38cb5
       if (*f == L_('$'))
e38cb5
 	/* Oh, oh.  The argument comes from a positional parameter.  */
e38cb5
 	goto do_positional;
e38cb5
@@ -1601,34 +1552,6 @@ vfprintf (FILE *s, const CHAR_T *format, va_list ap)
e38cb5
 	}
e38cb5
       else
e38cb5
 	prec = 0;
e38cb5
-      if (prec > width && prec > WORK_BUFFER_SIZE - EXTSIZ)
e38cb5
-	{
e38cb5
-	  /* Deallocate any previously allocated buffer because it is
e38cb5
-	     too small.  */
e38cb5
-	  if (__glibc_unlikely (workstart != NULL))
e38cb5
-	    free (workstart);
e38cb5
-	  workstart = NULL;
e38cb5
-	  if (__glibc_unlikely (prec >= INT_MAX / sizeof (CHAR_T) - EXTSIZ))
e38cb5
-	    {
e38cb5
-	      __set_errno (EOVERFLOW);
e38cb5
-	      done = -1;
e38cb5
-	      goto all_done;
e38cb5
-	    }
e38cb5
-	  size_t needed = ((size_t) prec + EXTSIZ) * sizeof (CHAR_T);
e38cb5
-
e38cb5
-	  if (__libc_use_alloca (needed))
e38cb5
-	    workend = (CHAR_T *) alloca (needed) + prec + EXTSIZ;
e38cb5
-	  else
e38cb5
-	    {
e38cb5
-	      workstart = (CHAR_T *) malloc (needed);
e38cb5
-	      if (workstart == NULL)
e38cb5
-		{
e38cb5
-		  done = -1;
e38cb5
-		  goto all_done;
e38cb5
-		}
e38cb5
-	      workend = workstart + prec + EXTSIZ;
e38cb5
-	    }
e38cb5
-	}
e38cb5
       JUMP (*f, step2_jumps);
e38cb5
 
e38cb5
       /* Process 'h' modifier.  There might another 'h' following.  */
e38cb5
@@ -1692,10 +1615,6 @@ vfprintf (FILE *s, const CHAR_T *format, va_list ap)
e38cb5
       /* The format is correctly handled.  */
e38cb5
       ++nspecs_done;
e38cb5
 
e38cb5
-      if (__glibc_unlikely (workstart != NULL))
e38cb5
-	free (workstart);
e38cb5
-      workstart = NULL;
e38cb5
-
e38cb5
       /* Look for next format specifier.  */
e38cb5
 #ifdef COMPILE_WPRINTF
e38cb5
       f = __find_specwc ((end_of_spec = ++f));
e38cb5
@@ -1713,18 +1632,11 @@ vfprintf (FILE *s, const CHAR_T *format, va_list ap)
e38cb5
 
e38cb5
   /* Hand off processing for positional parameters.  */
e38cb5
 do_positional:
e38cb5
-  if (__glibc_unlikely (workstart != NULL))
e38cb5
-    {
e38cb5
-      free (workstart);
e38cb5
-      workstart = NULL;
e38cb5
-    }
e38cb5
   done = printf_positional (s, format, readonly_format, ap, &ap_save,
e38cb5
 			    done, nspecs_done, lead_str_end, work_buffer,
e38cb5
 			    save_errno, grouping, thousands_sep);
e38cb5
 
e38cb5
  all_done:
e38cb5
-  if (__glibc_unlikely (workstart != NULL))
e38cb5
-    free (workstart);
e38cb5
   /* Unlock the stream.  */
e38cb5
   _IO_funlockfile (s);
e38cb5
   _IO_cleanup_region_end (0);
e38cb5
@@ -1767,8 +1679,6 @@ printf_positional (FILE *s, const CHAR_T *format, int readonly_format,
e38cb5
   /* Just a counter.  */
e38cb5
   size_t cnt;
e38cb5
 
e38cb5
-  CHAR_T *workstart = NULL;
e38cb5
-
e38cb5
   if (grouping == (const char *) -1)
e38cb5
     {
e38cb5
 #ifdef COMPILE_WPRINTF
e38cb5
@@ -1957,7 +1867,6 @@ printf_positional (FILE *s, const CHAR_T *format, int readonly_format,
e38cb5
       char pad = specs[nspecs_done].info.pad;
e38cb5
       CHAR_T spec = specs[nspecs_done].info.spec;
e38cb5
 
e38cb5
-      workstart = NULL;
e38cb5
       CHAR_T *workend = work_buffer + WORK_BUFFER_SIZE;
e38cb5
 
e38cb5
       /* Fill in last information.  */
e38cb5
@@ -1991,27 +1900,6 @@ printf_positional (FILE *s, const CHAR_T *format, int readonly_format,
e38cb5
 	  prec = specs[nspecs_done].info.prec;
e38cb5
 	}
e38cb5
 
e38cb5
-      /* Maybe the buffer is too small.  */
e38cb5
-      if (MAX (prec, width) + EXTSIZ > WORK_BUFFER_SIZE)
e38cb5
-	{
e38cb5
-	  if (__libc_use_alloca ((MAX (prec, width) + EXTSIZ)
e38cb5
-				 * sizeof (CHAR_T)))
e38cb5
-	    workend = ((CHAR_T *) alloca ((MAX (prec, width) + EXTSIZ)
e38cb5
-					  * sizeof (CHAR_T))
e38cb5
-		       + (MAX (prec, width) + EXTSIZ));
e38cb5
-	  else
e38cb5
-	    {
e38cb5
-	      workstart = (CHAR_T *) malloc ((MAX (prec, width) + EXTSIZ)
e38cb5
-					     * sizeof (CHAR_T));
e38cb5
-	      if (workstart == NULL)
e38cb5
-		{
e38cb5
-		  done = -1;
e38cb5
-		  goto all_done;
e38cb5
-		}
e38cb5
-	      workend = workstart + (MAX (prec, width) + EXTSIZ);
e38cb5
-	    }
e38cb5
-	}
e38cb5
-
e38cb5
       /* Process format specifiers.  */
e38cb5
       while (1)
e38cb5
 	{
e38cb5
@@ -2085,18 +1973,12 @@ printf_positional (FILE *s, const CHAR_T *format, int readonly_format,
e38cb5
 	  break;
e38cb5
 	}
e38cb5
 
e38cb5
-      if (__glibc_unlikely (workstart != NULL))
e38cb5
-	free (workstart);
e38cb5
-      workstart = NULL;
e38cb5
-
e38cb5
       /* Write the following constant string.  */
e38cb5
       outstring (specs[nspecs_done].end_of_fmt,
e38cb5
 		 specs[nspecs_done].next_fmt
e38cb5
 		 - specs[nspecs_done].end_of_fmt);
e38cb5
     }
e38cb5
  all_done:
e38cb5
-  if (__glibc_unlikely (workstart != NULL))
e38cb5
-    free (workstart);
e38cb5
   scratch_buffer_free (&argsbuf);
e38cb5
   scratch_buffer_free (&specsbuf);
e38cb5
   return done;