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