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