55f6e7
commit c6fad4fa149485a307207f707e5851216f190fc8
55f6e7
Author: Florian Weimer <fweimer@redhat.com>
55f6e7
Date:   Thu Mar 19 18:32:28 2020 -0300
55f6e7
55f6e7
    stdio: Remove memory leak from multibyte convertion [BZ#25691]
55f6e7
    
55f6e7
    This is an updated version of a previous patch [1] with the
55f6e7
    following changes:
55f6e7
    
55f6e7
      - Use compiler overflow builtins on done_add_func function.
55f6e7
      - Define the scratch +utstring_converted_wide_string using
55f6e7
        CHAR_T.
55f6e7
      - Added a testcase and mention the bug report.
55f6e7
    
55f6e7
    Both default and wide printf functions might leak memory when
55f6e7
    manipulate multibyte characters conversion depending of the size
55f6e7
    of the input (whether __libc_use_alloca trigger or not the fallback
55f6e7
    heap allocation).
55f6e7
    
55f6e7
    This patch fixes it by removing the extra memory allocation on
55f6e7
    string formatting with conversion parts.
55f6e7
    
55f6e7
    The testcase uses input argument size that trigger memory leaks
55f6e7
    on unpatched code (using a scratch buffer the threashold to use
55f6e7
    heap allocation is lower).
55f6e7
    
55f6e7
    Checked on x86_64-linux-gnu and i686-linux-gnu.
55f6e7
    
55f6e7
    Reviewed-by: Adhemerval Zanella  <adhemerval.zanella@linaro.org>
55f6e7
    
55f6e7
    [1] https://sourceware.org/pipermail/libc-alpha/2017-June/082098.html
55f6e7
    
55f6e7
    (cherry picked from commit 3cc4a8367c23582b7db14cf4e150e4068b7fd461)
55f6e7
55f6e7
diff --git a/stdio-common/vfprintf.c b/stdio-common/vfprintf.c
55f6e7
index ae412e4b8444aea2..dab56b6ba2c7bdbe 100644
55f6e7
--- a/stdio-common/vfprintf.c
55f6e7
+++ b/stdio-common/vfprintf.c
55f6e7
@@ -31,6 +31,7 @@
55f6e7
 #include <locale/localeinfo.h>
55f6e7
 #include <stdio.h>
55f6e7
 #include <scratch_buffer.h>
55f6e7
+#include <intprops.h>
55f6e7
 
55f6e7
 /* This code is shared between the standard stdio implementation found
55f6e7
    in GNU C library and the libio implementation originally found in
55f6e7
@@ -64,23 +65,40 @@
55f6e7
     } while (0)
55f6e7
 #define UNBUFFERED_P(S) ((S)->_flags & _IO_UNBUFFERED)
55f6e7
 
55f6e7
-#define done_add(val) \
55f6e7
-  do {									      \
55f6e7
-    unsigned int _val = val;						      \
55f6e7
-    assert ((unsigned int) done < (unsigned int) INT_MAX);		      \
55f6e7
-    if (__glibc_unlikely (INT_MAX - done < _val))			      \
55f6e7
-      {									      \
55f6e7
-	done = -1;							      \
55f6e7
-	 __set_errno (EOVERFLOW);					      \
55f6e7
-	goto all_done;							      \
55f6e7
-      }									      \
55f6e7
-    done += _val;							      \
55f6e7
-  } while (0)
55f6e7
+/* Add LENGTH to DONE.  Return the new value of DONE, or -1 on
55f6e7
+   overflow (and set errno accordingly).  */
55f6e7
+static inline int
55f6e7
+done_add_func (size_t length, int done)
55f6e7
+{
55f6e7
+  if (done < 0)
55f6e7
+    return done;
55f6e7
+  int ret;
55f6e7
+  if (INT_ADD_WRAPV (done, length, &ret))
55f6e7
+    {
55f6e7
+      __set_errno (EOVERFLOW);
55f6e7
+      return -1;
55f6e7
+    }
55f6e7
+  return ret;
55f6e7
+}
55f6e7
+
55f6e7
+#define done_add(val)							\
55f6e7
+  do									\
55f6e7
+    {									\
55f6e7
+      /* Ensure that VAL has a type similar to int.  */			\
55f6e7
+      _Static_assert (sizeof (val) == sizeof (int), "value int size");	\
55f6e7
+      _Static_assert ((__typeof__ (val)) -1 < 0, "value signed");	\
55f6e7
+      done = done_add_func ((val), done);				\
55f6e7
+      if (done < 0)							\
55f6e7
+	goto all_done;							\
55f6e7
+    }									\
55f6e7
+  while (0)
55f6e7
 
55f6e7
 #ifndef COMPILE_WPRINTF
55f6e7
 # define vfprintf	_IO_vfprintf_internal
55f6e7
 # define CHAR_T		char
55f6e7
+# define CHAR_T		char
55f6e7
 # define UCHAR_T	unsigned char
55f6e7
+# define OTHER_CHAR_T   wchar_t
55f6e7
 # define INT_T		int
55f6e7
 typedef const char *THOUSANDS_SEP_T;
55f6e7
 # define L_(Str)	Str
55f6e7
@@ -88,22 +106,10 @@ typedef const char *THOUSANDS_SEP_T;
55f6e7
 # define STR_LEN(Str)	strlen (Str)
55f6e7
 
55f6e7
 # define PUT(F, S, N)	_IO_sputn ((F), (S), (N))
55f6e7
-# define PAD(Padchar) \
55f6e7
-  do {									      \
55f6e7
-    if (width > 0)							      \
55f6e7
-      {									      \
55f6e7
-	ssize_t written = _IO_padn (s, (Padchar), width);		      \
55f6e7
-	if (__glibc_unlikely (written != width))			      \
55f6e7
-	  {								      \
55f6e7
-	    done = -1;							      \
55f6e7
-	    goto all_done;						      \
55f6e7
-	  }								      \
55f6e7
-	done_add (written);						      \
55f6e7
-      }									      \
55f6e7
-  } while (0)
55f6e7
 # define PUTC(C, F)	_IO_putc_unlocked (C, F)
55f6e7
 # define ORIENT		if (_IO_vtable_offset (s) == 0 && _IO_fwide (s, -1) != -1)\
55f6e7
 			  return -1
55f6e7
+# define CONVERT_FROM_OTHER_STRING __wcsrtombs
55f6e7
 #else
55f6e7
 # define vfprintf	_IO_vfwprintf
55f6e7
 # define CHAR_T		wchar_t
55f6e7
@@ -118,21 +124,11 @@ typedef wchar_t THOUSANDS_SEP_T;
55f6e7
 # include <_itowa.h>
55f6e7
 
55f6e7
 # define PUT(F, S, N)	_IO_sputn ((F), (S), (N))
55f6e7
-# define PAD(Padchar) \
55f6e7
-  do {									      \
55f6e7
-    if (width > 0)							      \
55f6e7
-      {									      \
55f6e7
-	ssize_t written = _IO_wpadn (s, (Padchar), width);		      \
55f6e7
-	if (__glibc_unlikely (written != width))			      \
55f6e7
-	  {								      \
55f6e7
-	    done = -1;							      \
55f6e7
-	    goto all_done;						      \
55f6e7
-	  }								      \
55f6e7
-	done_add (written);						      \
55f6e7
-      }									      \
55f6e7
-  } while (0)
55f6e7
 # define PUTC(C, F)	_IO_putwc_unlocked (C, F)
55f6e7
 # define ORIENT		if (_IO_fwide (s, 1) != 1) return -1
55f6e7
+# define CONVERT_FROM_OTHER_STRING __mbsrtowcs
55f6e7
+# define CHAR_T		wchar_t
55f6e7
+# define OTHER_CHAR_T   char
55f6e7
 
55f6e7
 # undef _itoa
55f6e7
 # define _itoa(Val, Buf, Base, Case) _itowa (Val, Buf, Base, Case)
55f6e7
@@ -141,6 +137,33 @@ typedef wchar_t THOUSANDS_SEP_T;
55f6e7
 # define EOF WEOF
55f6e7
 #endif
55f6e7
 
55f6e7
+static inline int
55f6e7
+pad_func (FILE *s, CHAR_T padchar, int width, int done)
55f6e7
+{
55f6e7
+  if (width > 0)
55f6e7
+    {
55f6e7
+      ssize_t written;
55f6e7
+#ifndef COMPILE_WPRINTF
55f6e7
+      written = _IO_padn (s, padchar, width);
55f6e7
+#else
55f6e7
+      written = _IO_wpadn (s, padchar, width);
55f6e7
+#endif
55f6e7
+      if (__glibc_unlikely (written != width))
55f6e7
+	return -1;
55f6e7
+      return done_add_func (width, done);
55f6e7
+    }
55f6e7
+  return done;
55f6e7
+}
55f6e7
+
55f6e7
+#define PAD(Padchar)							\
55f6e7
+  do									\
55f6e7
+    {									\
55f6e7
+      done = pad_func (s, (Padchar), width, done);			\
55f6e7
+      if (done < 0)							\
55f6e7
+	goto all_done;							\
55f6e7
+    }									\
55f6e7
+  while (0)
55f6e7
+
55f6e7
 #include "_i18n_number.h"
55f6e7
 
55f6e7
 /* Include the shared code for parsing the format string.  */
55f6e7
@@ -160,24 +183,115 @@ typedef wchar_t THOUSANDS_SEP_T;
55f6e7
     }									      \
55f6e7
   while (0)
55f6e7
 
55f6e7
-#define outstring(String, Len)						      \
55f6e7
-  do									      \
55f6e7
-    {									      \
55f6e7
-      assert ((size_t) done <= (size_t) INT_MAX);			      \
55f6e7
-      if ((size_t) PUT (s, (String), (Len)) != (size_t) (Len))		      \
55f6e7
-	{								      \
55f6e7
-	  done = -1;							      \
55f6e7
-	  goto all_done;						      \
55f6e7
-	}								      \
55f6e7
-      if (__glibc_unlikely (INT_MAX - done < (Len)))			      \
55f6e7
-      {									      \
55f6e7
-	done = -1;							      \
55f6e7
-	 __set_errno (EOVERFLOW);					      \
55f6e7
-	goto all_done;							      \
55f6e7
-      }									      \
55f6e7
-      done += (Len);							      \
55f6e7
-    }									      \
55f6e7
-  while (0)
55f6e7
+static inline int
55f6e7
+outstring_func (FILE *s, const UCHAR_T *string, size_t length, int done)
55f6e7
+{
55f6e7
+  assert ((size_t) done <= (size_t) INT_MAX);
55f6e7
+  if ((size_t) PUT (s, string, length) != (size_t) (length))
55f6e7
+    return -1;
55f6e7
+  return done_add_func (length, done);
55f6e7
+}
55f6e7
+
55f6e7
+#define outstring(String, Len)						\
55f6e7
+  do									\
55f6e7
+    {									\
55f6e7
+      const void *string_ = (String);					\
55f6e7
+      done = outstring_func (s, string_, (Len), done);			\
55f6e7
+      if (done < 0)							\
55f6e7
+	goto all_done;							\
55f6e7
+    }									\
55f6e7
+   while (0)
55f6e7
+
55f6e7
+/* Write the string SRC to S.  If PREC is non-negative, write at most
55f6e7
+   PREC bytes.  If LEFT is true, perform left justification.  */
55f6e7
+static int
55f6e7
+outstring_converted_wide_string (FILE *s, const OTHER_CHAR_T *src, int prec,
55f6e7
+				 int width, bool left, int done)
55f6e7
+{
55f6e7
+  /* Use a small buffer to combine processing of multiple characters.
55f6e7
+     CONVERT_FROM_OTHER_STRING expects the buffer size in (wide)
55f6e7
+     characters, and buf_length counts that.  */
55f6e7
+  enum { buf_length = 256 / sizeof (CHAR_T) };
55f6e7
+  CHAR_T buf[buf_length];
55f6e7
+  _Static_assert (sizeof (buf) > MB_LEN_MAX,
55f6e7
+		  "buffer is large enough for a single multi-byte character");
55f6e7
+
55f6e7
+  /* Add the initial padding if needed.  */
55f6e7
+  if (width > 0 && !left)
55f6e7
+    {
55f6e7
+      /* Make a first pass to find the output width, so that we can
55f6e7
+	 add the required padding.  */
55f6e7
+      mbstate_t mbstate = { 0 };
55f6e7
+      const OTHER_CHAR_T *src_copy = src;
55f6e7
+      size_t total_written;
55f6e7
+      if (prec < 0)
55f6e7
+	total_written = CONVERT_FROM_OTHER_STRING
55f6e7
+	  (NULL, &src_copy, 0, &mbstate);
55f6e7
+      else
55f6e7
+	{
55f6e7
+	  /* The source might not be null-terminated.  Enforce the
55f6e7
+	     limit manually, based on the output length.  */
55f6e7
+	  total_written = 0;
55f6e7
+	  size_t limit = prec;
55f6e7
+	  while (limit > 0 && src_copy != NULL)
55f6e7
+	    {
55f6e7
+	      size_t write_limit = buf_length;
55f6e7
+	      if (write_limit > limit)
55f6e7
+		write_limit = limit;
55f6e7
+	      size_t written = CONVERT_FROM_OTHER_STRING
55f6e7
+		(buf, &src_copy, write_limit, &mbstate);
55f6e7
+	      if (written == (size_t) -1)
55f6e7
+		return -1;
55f6e7
+	      if (written == 0)
55f6e7
+		break;
55f6e7
+	      total_written += written;
55f6e7
+	      limit -= written;
55f6e7
+	    }
55f6e7
+	}
55f6e7
+
55f6e7
+      /* Output initial padding.  */
55f6e7
+      if (total_written < width)
55f6e7
+	{
55f6e7
+	  done = pad_func (s, L_(' '), width - total_written, done);
55f6e7
+	  if (done < 0)
55f6e7
+	    return done;
55f6e7
+	}
55f6e7
+    }
55f6e7
+
55f6e7
+  /* Convert the input string, piece by piece.  */
55f6e7
+  size_t total_written = 0;
55f6e7
+  {
55f6e7
+    mbstate_t mbstate = { 0 };
55f6e7
+    /* If prec is negative, remaining is not decremented, otherwise,
55f6e7
+      it serves as the write limit.  */
55f6e7
+    size_t remaining = -1;
55f6e7
+    if (prec >= 0)
55f6e7
+      remaining = prec;
55f6e7
+    while (remaining > 0 && src != NULL)
55f6e7
+      {
55f6e7
+	size_t write_limit = buf_length;
55f6e7
+	if (remaining < write_limit)
55f6e7
+	  write_limit = remaining;
55f6e7
+	size_t written = CONVERT_FROM_OTHER_STRING
55f6e7
+	  (buf, &src, write_limit, &mbstate);
55f6e7
+	if (written == (size_t) -1)
55f6e7
+	  return -1;
55f6e7
+	if (written == 0)
55f6e7
+	  break;
55f6e7
+	done = outstring_func (s, (const UCHAR_T *) buf, written, done);
55f6e7
+	if (done < 0)
55f6e7
+	  return done;
55f6e7
+	total_written += written;
55f6e7
+	if (prec >= 0)
55f6e7
+	  remaining -= written;
55f6e7
+      }
55f6e7
+  }
55f6e7
+
55f6e7
+  /* Add final padding.  */
55f6e7
+  if (width > 0 && left && total_written < width)
55f6e7
+    return pad_func (s, L_(' '), width - total_written, done);
55f6e7
+  return done;
55f6e7
+}
55f6e7
 
55f6e7
 /* For handling long_double and longlong we use the same flag.  If
55f6e7
    `long' and `long long' are effectively the same type define it to
55f6e7
@@ -975,7 +1089,6 @@ static const uint8_t jump_table[] =
55f6e7
     LABEL (form_string):						      \
55f6e7
       {									      \
55f6e7
 	size_t len;							      \
55f6e7
-	int string_malloced;						      \
55f6e7
 									      \
55f6e7
 	/* The string argument could in fact be `char *' or `wchar_t *'.      \
55f6e7
 	   But this should not make a difference here.  */		      \
55f6e7
@@ -987,7 +1100,6 @@ static const uint8_t jump_table[] =
55f6e7
 	/* Entry point for printing other strings.  */			      \
55f6e7
       LABEL (print_string):						      \
55f6e7
 									      \
55f6e7
-	string_malloced = 0;						      \
55f6e7
 	if (string == NULL)						      \
55f6e7
 	  {								      \
55f6e7
 	    /* Write "(null)" if there's space.  */			      \
55f6e7
@@ -1004,41 +1116,12 @@ static const uint8_t jump_table[] =
55f6e7
 	  }								      \
55f6e7
 	else if (!is_long && spec != L_('S'))				      \
55f6e7
 	  {								      \
55f6e7
-	    /* This is complicated.  We have to transform the multibyte	      \
55f6e7
-	       string into a wide character string.  */			      \
55f6e7
-	    const char *mbs = (const char *) string;			      \
55f6e7
-	    mbstate_t mbstate;						      \
55f6e7
-									      \
55f6e7
-	    len = prec != -1 ? __strnlen (mbs, (size_t) prec) : strlen (mbs); \
55f6e7
-									      \
55f6e7
-	    /* Allocate dynamically an array which definitely is long	      \
55f6e7
-	       enough for the wide character version.  Each byte in the	      \
55f6e7
-	       multi-byte string can produce at most one wide character.  */  \
55f6e7
-	    if (__glibc_unlikely (len > SIZE_MAX / sizeof (wchar_t)))	      \
55f6e7
-	      {								      \
55f6e7
-		__set_errno (EOVERFLOW);				      \
55f6e7
-		done = -1;						      \
55f6e7
-		goto all_done;						      \
55f6e7
-	      }								      \
55f6e7
-	    else if (__libc_use_alloca (len * sizeof (wchar_t)))	      \
55f6e7
-	      string = (CHAR_T *) alloca (len * sizeof (wchar_t));	      \
55f6e7
-	    else if ((string = (CHAR_T *) malloc (len * sizeof (wchar_t)))    \
55f6e7
-		     == NULL)						      \
55f6e7
-	      {								      \
55f6e7
-		done = -1;						      \
55f6e7
-		goto all_done;						      \
55f6e7
-	      }								      \
55f6e7
-	    else							      \
55f6e7
-	      string_malloced = 1;					      \
55f6e7
-									      \
55f6e7
-	    memset (&mbstate, '\0', sizeof (mbstate_t));		      \
55f6e7
-	    len = __mbsrtowcs (string, &mbs, len, &mbstate);		      \
55f6e7
-	    if (len == (size_t) -1)					      \
55f6e7
-	      {								      \
55f6e7
-		/* Illegal multibyte character.  */			      \
55f6e7
-		done = -1;						      \
55f6e7
-		goto all_done;						      \
55f6e7
-	      }								      \
55f6e7
+	    done = outstring_converted_wide_string			      \
55f6e7
+	      (s, (const char *) string, prec, width, left, done);	      \
55f6e7
+	    if (done < 0)						      \
55f6e7
+	      goto all_done;						      \
55f6e7
+	    /* The padding has already been written.  */		      \
55f6e7
+	    break;							      \
55f6e7
 	  }								      \
55f6e7
 	else								      \
55f6e7
 	  {								      \
55f6e7
@@ -1061,8 +1144,6 @@ static const uint8_t jump_table[] =
55f6e7
 	outstring (string, len);					      \
55f6e7
 	if (left)							      \
55f6e7
 	  PAD (L' ');							      \
55f6e7
-	if (__glibc_unlikely (string_malloced))				      \
55f6e7
-	  free (string);						      \
55f6e7
       }									      \
55f6e7
       break;
55f6e7
 #else
55f6e7
@@ -1111,7 +1192,6 @@ static const uint8_t jump_table[] =
55f6e7
     LABEL (form_string):						      \
55f6e7
       {									      \
55f6e7
 	size_t len;							      \
55f6e7
-	int string_malloced;						      \
55f6e7
 									      \
55f6e7
 	/* The string argument could in fact be `char *' or `wchar_t *'.      \
55f6e7
 	   But this should not make a difference here.  */		      \
55f6e7
@@ -1123,7 +1203,6 @@ static const uint8_t jump_table[] =
55f6e7
 	/* Entry point for printing other strings.  */			      \
55f6e7
       LABEL (print_string):						      \
55f6e7
 									      \
55f6e7
-	string_malloced = 0;						      \
55f6e7
 	if (string == NULL)						      \
55f6e7
 	  {								      \
55f6e7
 	    /* Write "(null)" if there's space.  */			      \
55f6e7
@@ -1149,51 +1228,12 @@ static const uint8_t jump_table[] =
55f6e7
 	  }								      \
55f6e7
 	else								      \
55f6e7
 	  {								      \
55f6e7
-	    const wchar_t *s2 = (const wchar_t *) string;		      \
55f6e7
-	    mbstate_t mbstate;						      \
55f6e7
-									      \
55f6e7
-	    memset (&mbstate, '\0', sizeof (mbstate_t));		      \
55f6e7
-									      \
55f6e7
-	    if (prec >= 0)						      \
55f6e7
-	      {								      \
55f6e7
-		/* The string `s2' might not be NUL terminated.  */	      \
55f6e7
-		if (__libc_use_alloca (prec))				      \
55f6e7
-		  string = (char *) alloca (prec);			      \
55f6e7
-		else if ((string = (char *) malloc (prec)) == NULL)	      \
55f6e7
-		  {							      \
55f6e7
-		    done = -1;						      \
55f6e7
-		    goto all_done;					      \
55f6e7
-		  }							      \
55f6e7
-		else							      \
55f6e7
-		  string_malloced = 1;					      \
55f6e7
-		len = __wcsrtombs (string, &s2, prec, &mbstate);	      \
55f6e7
-	      }								      \
55f6e7
-	    else							      \
55f6e7
-	      {								      \
55f6e7
-		len = __wcsrtombs (NULL, &s2, 0, &mbstate);		      \
55f6e7
-		if (len != (size_t) -1)					      \
55f6e7
-		  {							      \
55f6e7
-		    assert (__mbsinit (&mbstate));			      \
55f6e7
-		    s2 = (const wchar_t *) string;			      \
55f6e7
-		    if (__libc_use_alloca (len + 1))			      \
55f6e7
-		      string = (char *) alloca (len + 1);		      \
55f6e7
-		    else if ((string = (char *) malloc (len + 1)) == NULL)    \
55f6e7
-		      {							      \
55f6e7
-			done = -1;					      \
55f6e7
-			goto all_done;					      \
55f6e7
-		      }							      \
55f6e7
-		    else						      \
55f6e7
-		      string_malloced = 1;				      \
55f6e7
-		    (void) __wcsrtombs (string, &s2, len + 1, &mbstate);      \
55f6e7
-		  }							      \
55f6e7
-	      }								      \
55f6e7
-									      \
55f6e7
-	    if (len == (size_t) -1)					      \
55f6e7
-	      {								      \
55f6e7
-		/* Illegal wide-character string.  */			      \
55f6e7
-		done = -1;						      \
55f6e7
-		goto all_done;						      \
55f6e7
-	      }								      \
55f6e7
+	    done = outstring_converted_wide_string			      \
55f6e7
+	      (s, (const wchar_t *) string, prec, width, left, done);	      \
55f6e7
+	    if (done < 0)						      \
55f6e7
+	      goto all_done;						      \
55f6e7
+	    /* The padding has already been written.  */		      \
55f6e7
+	    break;							      \
55f6e7
 	  }								      \
55f6e7
 									      \
55f6e7
 	if ((width -= len) < 0)						      \
55f6e7
@@ -1207,8 +1247,6 @@ static const uint8_t jump_table[] =
55f6e7
 	outstring (string, len);					      \
55f6e7
 	if (left)							      \
55f6e7
 	  PAD (' ');							      \
55f6e7
-	if (__glibc_unlikely (string_malloced))			              \
55f6e7
-	  free (string);						      \
55f6e7
       }									      \
55f6e7
       break;
55f6e7
 #endif