summary refs log tree commit diff
path: root/stdio-common
diff options
context:
space:
mode:
authorFlorian Weimer <fweimer@redhat.com>2015-10-15 16:37:48 +0200
committerFlorian Weimer <fweimer@redhat.com>2015-10-15 17:18:51 +0200
commit95e83974812f5c6de0483690ef47787965bb817a (patch)
treec86080b681b554ae4466370a385b4fe23237e8fd /stdio-common
parentb994fd793799590f70ceb9a96f135bc2390bb4f3 (diff)
downloadglibc-95e83974812f5c6de0483690ef47787965bb817a.tar.gz
glibc-95e83974812f5c6de0483690ef47787965bb817a.tar.xz
glibc-95e83974812f5c6de0483690ef47787965bb817a.zip
vfscanf: Use struct scratch_buffer instead of extend_alloca
A custom character buffer is added in this commit, in the form of
struct char_buffer.  The char_buffer_add function replaces the
ADDW macro (which has grown with each successive security fix).
The char_buffer_add slow path is moved out-of-line, reducing
code size.

	* stdio-common/vfscanf.c (MEMCPY): Remove macro.
	(struct char_buffer): New type.
	(char_buffer_start, char_buffer_size, char_buffer_error)
	(char_buffer_rewind, char_buffer_add): New functions.
	(ADDW): Remove macro, replaced by the char_buffer_add function.
	(_IO_vfscanf_internal): Rewrite using struct char_buffer instead
	of extend_alloca.  Make control flow more explicit.
Diffstat (limited to 'stdio-common')
-rw-r--r--stdio-common/vfscanf.c356
1 files changed, 231 insertions, 125 deletions
diff --git a/stdio-common/vfscanf.c b/stdio-common/vfscanf.c
index 9d9ff2094c..1382eb513d 100644
--- a/stdio-common/vfscanf.c
+++ b/stdio-common/vfscanf.c
@@ -29,6 +29,7 @@
 #include <wctype.h>
 #include <libc-lock.h>
 #include <locale/localeinfo.h>
+#include <scratch_buffer.h>
 
 #ifdef	__GNUC__
 # define HAVE_LONGLONG
@@ -87,7 +88,6 @@
 				    ? ++read_in				      \
 				    : (size_t) (inchar_errno = errno)), c))
 
-# define MEMCPY(d, s, n) __wmemcpy (d, s, n)
 # define ISSPACE(Ch)	  iswspace (Ch)
 # define ISDIGIT(Ch)	  iswdigit (Ch)
 # define ISXDIGIT(Ch)	  iswxdigit (Ch)
@@ -118,7 +118,6 @@
 			    (void) (c != EOF				      \
 				    ? ++read_in				      \
 				    : (size_t) (inchar_errno = errno)), c))
-# define MEMCPY(d, s, n) memcpy (d, s, n)
 # define ISSPACE(Ch)	  __isspace_l (Ch, loc)
 # define ISDIGIT(Ch)	  __isdigit_l (Ch, loc)
 # define ISXDIGIT(Ch)	  __isxdigit_l (Ch, loc)
@@ -192,6 +191,78 @@ struct ptrs_to_free
   char **ptrs[32];
 };
 
+struct char_buffer {
+  CHAR_T *current;
+  CHAR_T *end;
+  struct scratch_buffer scratch;
+};
+
+/* Returns a pointer to the first CHAR_T object in the buffer.  Only
+   valid if char_buffer_add (BUFFER, CH) has been called and
+   char_buffer_error (BUFFER) is false.  */
+static inline CHAR_T *
+char_buffer_start (const struct char_buffer *buffer)
+{
+  return (CHAR_T *) buffer->scratch.data;
+}
+
+/* Returns the number of CHAR_T objects in the buffer.  Only valid if
+   char_buffer_error (BUFFER) is false.  */
+static inline size_t
+char_buffer_size (const struct char_buffer *buffer)
+{
+  return buffer->current - char_buffer_start (buffer);
+}
+
+/* Reinitializes BUFFER->current and BUFFER->end to cover the entire
+   scratch buffer.  */
+static inline void
+char_buffer_rewind (struct char_buffer *buffer)
+{
+  buffer->current = char_buffer_start (buffer);
+  buffer->end = buffer->current + buffer->scratch.length / sizeof (CHAR_T);
+}
+
+/* Returns true if a previous call to char_buffer_add (BUFFER, CH)
+   failed.  */
+static inline bool
+char_buffer_error (const struct char_buffer *buffer)
+{
+  return __glibc_unlikely (buffer->current == NULL);
+}
+
+/* Slow path for char_buffer_add.  */
+static void
+char_buffer_add_slow (struct char_buffer *buffer, CHAR_T ch)
+{
+  if (char_buffer_error (buffer))
+    return;
+  size_t offset = buffer->end - (CHAR_T *) buffer->scratch.data;
+  if (!scratch_buffer_grow_preserve (&buffer->scratch))
+    {
+      buffer->current = NULL;
+      buffer->end = NULL;
+      return;
+    }
+  char_buffer_rewind (buffer);
+  buffer->current += offset;
+  *buffer->current++ = ch;
+}
+
+/* Adds CH to BUFFER.  This function does not report any errors, check
+   for them with char_buffer_error.  */
+static inline void
+char_buffer_add (struct char_buffer *buffer, CHAR_T ch)
+  __attribute__ ((always_inline));
+static inline void
+char_buffer_add (struct char_buffer *buffer, CHAR_T ch)
+{
+  if (__glibc_unlikely (buffer->current == buffer->end))
+    char_buffer_add_slow (buffer, ch);
+  else
+    *buffer->current++ = ch;
+}
+
 /* Read formatted input from S according to the format string
    FORMAT, using the argument list in ARG.
    Return the number of assignments made, or -1 for an input error.  */
@@ -262,46 +333,8 @@ _IO_vfscanf_internal (_IO_FILE *s, const char *format, _IO_va_list argptr,
   int skip_space = 0;
   /* Workspace.  */
   CHAR_T *tw;			/* Temporary pointer.  */
-  CHAR_T *wp = NULL;		/* Workspace.  */
-  size_t wpmax = 0;		/* Maximal size of workspace.  */
-  size_t wpsize;		/* Currently used bytes in workspace.  */
-  bool use_malloc = false;
-#define ADDW(Ch)							    \
-  do									    \
-    {									    \
-      if (__glibc_unlikely (wpsize == wpmax))				      \
-	{								    \
-	  CHAR_T *old = wp;						    \
-	  bool fits = __glibc_likely (wpmax <= SIZE_MAX / sizeof (CHAR_T) / 2); \
-	  size_t wpneed = MAX (UCHAR_MAX + 1, 2 * wpmax);		    \
-	  size_t newsize = fits ? wpneed * sizeof (CHAR_T) : SIZE_MAX;	    \
-	  if (!__libc_use_alloca (newsize))				    \
-	    {								    \
-	      wp = realloc (use_malloc ? wp : NULL, newsize);		    \
-	      if (wp == NULL)						    \
-		{							    \
-		  if (use_malloc)					    \
-		    free (old);						    \
-		  done = EOF;						    \
-		  goto errout;						    \
-		}							    \
-	      if (! use_malloc)						    \
-		MEMCPY (wp, old, wpsize);				    \
-	      wpmax = wpneed;						    \
-	      use_malloc = true;					    \
-	    }								    \
-	  else								    \
-	    {								    \
-	      size_t s = wpmax * sizeof (CHAR_T);			    \
-	      wp = (CHAR_T *) extend_alloca (wp, s, newsize);		    \
-	      wpmax = s / sizeof (CHAR_T);				    \
-	      if (old != NULL)						    \
-		MEMCPY (wp, old, wpsize);				    \
-	    }								    \
-	}								    \
-      wp[wpsize++] = (Ch);						    \
-    }									    \
-  while (0)
+  struct char_buffer charbuf;
+  scratch_buffer_init (&charbuf.scratch);
 
 #ifdef __va_copy
   __va_copy (arg, argptr);
@@ -449,7 +482,7 @@ _IO_vfscanf_internal (_IO_FILE *s, const char *format, _IO_va_list argptr,
       argpos = 0;
 
       /* Prepare temporary buffer.  */
-      wpsize = 0;
+      char_buffer_rewind (&charbuf);
 
       /* Check for a positional parameter specification.  */
       if (ISDIGIT ((UCHAR_T) *f))
@@ -1374,7 +1407,7 @@ _IO_vfscanf_internal (_IO_FILE *s, const char *format, _IO_va_list argptr,
 	  /* Check for a sign.  */
 	  if (c == L_('-') || c == L_('+'))
 	    {
-	      ADDW (c);
+	      char_buffer_add (&charbuf, c);
 	      if (width > 0)
 		--width;
 	      c = inchar ();
@@ -1386,7 +1419,7 @@ _IO_vfscanf_internal (_IO_FILE *s, const char *format, _IO_va_list argptr,
 	      if (width > 0)
 		--width;
 
-	      ADDW (c);
+	      char_buffer_add (&charbuf, c);
 	      c = inchar ();
 
 	      if (width != 0 && TOLOWER (c) == L_('x'))
@@ -1641,7 +1674,7 @@ _IO_vfscanf_internal (_IO_FILE *s, const char *format, _IO_va_list argptr,
 
 		      while ((unsigned char) *cmpp == c && avail >= 0)
 			{
-			  ADDW (c);
+			  char_buffer_add (&charbuf, c);
 			  if (*++cmpp == '\0')
 			    break;
 			  else
@@ -1652,12 +1685,19 @@ _IO_vfscanf_internal (_IO_FILE *s, const char *format, _IO_va_list argptr,
 			    }
 			}
 
+		      if (char_buffer_error (&charbuf))
+			{
+			  __set_errno (ENOMEM);
+			  done = EOF;
+			  goto errout;
+			}
+
 		      if (*cmpp != '\0')
 			{
 			  /* We are pushing all read characters back.  */
 			  if (cmpp > thousands)
 			    {
-			      wpsize -= cmpp - thousands;
+			      charbuf.current -= cmpp - thousands;
 			      ungetc (c, s);
 			      while (--cmpp > thousands)
 				ungetc_not_eof ((unsigned char) *cmpp, s);
@@ -1670,14 +1710,14 @@ _IO_vfscanf_internal (_IO_FILE *s, const char *format, _IO_va_list argptr,
 			width = avail;
 
 		      /* The last thousands character will be added back by
-			 the ADDW below.  */
-			--wpsize;
+			 the char_buffer_add below.  */
+			--charbuf.current;
 #endif
 		    }
 		  else
 		    break;
 
-		  ADDW (c);
+		  char_buffer_add (&charbuf, c);
 		  if (width > 0)
 		    --width;
 
@@ -1707,7 +1747,7 @@ _IO_vfscanf_internal (_IO_FILE *s, const char *format, _IO_va_list argptr,
 
 			while ((unsigned char) *cmpp == c && avail >= 0)
 			  {
-			    ADDW (c);
+			    char_buffer_add (&charbuf, c);
 			    if (*++cmpp == '\0')
 			      break;
 			    else
@@ -1718,12 +1758,19 @@ _IO_vfscanf_internal (_IO_FILE *s, const char *format, _IO_va_list argptr,
 			      }
 			  }
 
+			if (char_buffer_error (&charbuf))
+			  {
+			    __set_errno (ENOMEM);
+			    done = EOF;
+			    goto errout;
+			  }
+
 			if (*cmpp != '\0')
 			  {
 			    /* We are pushing all read characters back.  */
 			    if (cmpp > thousands)
 			      {
-				wpsize -= cmpp - thousands;
+				charbuf.current -= cmpp - thousands;
 				ungetc (c, s);
 				while (--cmpp > thousands)
 				  ungetc_not_eof ((unsigned char) *cmpp, s);
@@ -1736,26 +1783,35 @@ _IO_vfscanf_internal (_IO_FILE *s, const char *format, _IO_va_list argptr,
 			  width = avail;
 
 			/* The last thousands character will be added back by
-			   the ADDW below.  */
-			--wpsize;
+			   the char_buffer_add below.  */
+			--charbuf.current;
 #endif
 		      }
 		    else
 		      break;
 		  }
-		ADDW (c);
+		char_buffer_add (&charbuf, c);
 		if (width > 0)
 		  --width;
 
 		c = inchar ();
 	      }
 
-	  if (wpsize == 0
-	      || (wpsize == 1 && (wp[0] == L_('+') || wp[0] == L_('-'))))
+	  if (char_buffer_error (&charbuf))
+	    {
+	      __set_errno (ENOMEM);
+	      done = EOF;
+	      goto errout;
+	    }
+
+	  if (char_buffer_size (&charbuf) == 0
+	      || (char_buffer_size (&charbuf) == 1
+		  && (char_buffer_start (&charbuf)[0] == L_('+')
+		      || char_buffer_start (&charbuf)[0] == L_('-'))))
 	    {
 	      /* There was no number.  If we are supposed to read a pointer
 		 we must recognize "(nil)" as well.  */
-	      if (__builtin_expect (wpsize == 0
+	      if (__builtin_expect (char_buffer_size (&charbuf) == 0
 				    && (flags & READ_POINTER)
 				    && (width < 0 || width >= 5)
 				    && c == '('
@@ -1765,7 +1821,7 @@ _IO_vfscanf_internal (_IO_FILE *s, const char *format, _IO_va_list argptr,
 				    && inchar () == L_(')'), 1))
 		/* We must produce the value of a NULL pointer.  A single
 		   '0' digit is enough.  */
-		ADDW (L_('0'));
+		  char_buffer_add (&charbuf, L_('0'));
 	      else
 		{
 		  /* The last read character is not part of the number
@@ -1780,22 +1836,32 @@ _IO_vfscanf_internal (_IO_FILE *s, const char *format, _IO_va_list argptr,
 	    ungetc (c, s);
 
 	  /* Convert the number.  */
-	  ADDW (L_('\0'));
+	  char_buffer_add (&charbuf, L_('\0'));
+	  if (char_buffer_error (&charbuf))
+	    {
+	      __set_errno (ENOMEM);
+	      done = EOF;
+	      goto errout;
+	    }
 	  if (need_longlong && (flags & LONGDBL))
 	    {
 	      if (flags & NUMBER_SIGNED)
-		num.q = __strtoll_internal (wp, &tw, base, flags & GROUP);
+		num.q = __strtoll_internal
+		  (char_buffer_start (&charbuf), &tw, base, flags & GROUP);
 	      else
-		num.uq = __strtoull_internal (wp, &tw, base, flags & GROUP);
+		num.uq = __strtoull_internal
+		  (char_buffer_start (&charbuf), &tw, base, flags & GROUP);
 	    }
 	  else
 	    {
 	      if (flags & NUMBER_SIGNED)
-		num.l = __strtol_internal (wp, &tw, base, flags & GROUP);
+		num.l = __strtol_internal
+		  (char_buffer_start (&charbuf), &tw, base, flags & GROUP);
 	      else
-		num.ul = __strtoul_internal (wp, &tw, base, flags & GROUP);
+		num.ul = __strtoul_internal
+		  (char_buffer_start (&charbuf), &tw, base, flags & GROUP);
 	    }
-	  if (__glibc_unlikely (wp == tw))
+	  if (__glibc_unlikely (char_buffer_start (&charbuf) == tw))
 	    conv_error ();
 
 	  if (!(flags & SUPPRESS))
@@ -1864,42 +1930,42 @@ _IO_vfscanf_internal (_IO_FILE *s, const char *format, _IO_va_list argptr,
 	  if (TOLOWER (c) == L_('n'))
 	    {
 	      /* Maybe "nan".  */
-	      ADDW (c);
+	      char_buffer_add (&charbuf, c);
 	      if (__builtin_expect (width == 0
 				    || inchar () == EOF
 				    || TOLOWER (c) != L_('a'), 0))
 		conv_error ();
 	      if (width > 0)
 		--width;
-	      ADDW (c);
+	      char_buffer_add (&charbuf, c);
 	      if (__builtin_expect (width == 0
 				    || inchar () == EOF
 				    || TOLOWER (c) != L_('n'), 0))
 		conv_error ();
 	      if (width > 0)
 		--width;
-	      ADDW (c);
+	      char_buffer_add (&charbuf, c);
 	      /* It is "nan".  */
 	      goto scan_float;
 	    }
 	  else if (TOLOWER (c) == L_('i'))
 	    {
 	      /* Maybe "inf" or "infinity".  */
-	      ADDW (c);
+	      char_buffer_add (&charbuf, c);
 	      if (__builtin_expect (width == 0
 				    || inchar () == EOF
 				    || TOLOWER (c) != L_('n'), 0))
 		conv_error ();
 	      if (width > 0)
 		--width;
-	      ADDW (c);
+	      char_buffer_add (&charbuf, c);
 	      if (__builtin_expect (width == 0
 				    || inchar () == EOF
 				    || TOLOWER (c) != L_('f'), 0))
 		conv_error ();
 	      if (width > 0)
 		--width;
-	      ADDW (c);
+	      char_buffer_add (&charbuf, c);
 	      /* It is as least "inf".  */
 	      if (width != 0 && inchar () != EOF)
 		{
@@ -1908,35 +1974,35 @@ _IO_vfscanf_internal (_IO_FILE *s, const char *format, _IO_va_list argptr,
 		      if (width > 0)
 			--width;
 		      /* Now we have to read the rest as well.  */
-		      ADDW (c);
+		      char_buffer_add (&charbuf, c);
 		      if (__builtin_expect (width == 0
 					    || inchar () == EOF
 					    || TOLOWER (c) != L_('n'), 0))
 			conv_error ();
 		      if (width > 0)
 			--width;
-		      ADDW (c);
+		      char_buffer_add (&charbuf, c);
 		      if (__builtin_expect (width == 0
 					    || inchar () == EOF
 					    || TOLOWER (c) != L_('i'), 0))
 			conv_error ();
 		      if (width > 0)
 			--width;
-		      ADDW (c);
+		      char_buffer_add (&charbuf, c);
 		      if (__builtin_expect (width == 0
 					    || inchar () == EOF
 					    || TOLOWER (c) != L_('t'), 0))
 			conv_error ();
 		      if (width > 0)
 			--width;
-		      ADDW (c);
+		      char_buffer_add (&charbuf, c);
 		      if (__builtin_expect (width == 0
 					    || inchar () == EOF
 					    || TOLOWER (c) != L_('y'), 0))
 			conv_error ();
 		      if (width > 0)
 			--width;
-		      ADDW (c);
+		      char_buffer_add (&charbuf, c);
 		    }
 		  else
 		    /* Never mind.  */
@@ -1948,14 +2014,14 @@ _IO_vfscanf_internal (_IO_FILE *s, const char *format, _IO_va_list argptr,
 	  exp_char = L_('e');
 	  if (width != 0 && c == L_('0'))
 	    {
-	      ADDW (c);
+	      char_buffer_add (&charbuf, c);
 	      c = inchar ();
 	      if (width > 0)
 		--width;
 	      if (width != 0 && TOLOWER (c) == L_('x'))
 		{
 		  /* It is a number in hexadecimal format.  */
-		  ADDW (c);
+		  char_buffer_add (&charbuf, c);
 
 		  flags |= HEXA_FLOAT;
 		  exp_char = L_('p');
@@ -1972,23 +2038,29 @@ _IO_vfscanf_internal (_IO_FILE *s, const char *format, _IO_va_list argptr,
 
 	  while (1)
 	    {
+	      if (char_buffer_error (&charbuf))
+		{
+		  __set_errno (ENOMEM);
+		  done = EOF;
+		  goto errout;
+		}
 	      if (ISDIGIT (c))
 		{
-		  ADDW (c);
+		  char_buffer_add (&charbuf, c);
 		  got_digit = 1;
 		}
 	      else if (!got_e && (flags & HEXA_FLOAT) && ISXDIGIT (c))
 		{
-		  ADDW (c);
+		  char_buffer_add (&charbuf, c);
 		  got_digit = 1;
 		}
-	      else if (got_e && wp[wpsize - 1] == exp_char
+	      else if (got_e && charbuf.current[-1] == exp_char
 		       && (c == L_('-') || c == L_('+')))
-		ADDW (c);
+		char_buffer_add (&charbuf, c);
 	      else if (got_digit && !got_e
 		       && (CHAR_T) TOLOWER (c) == exp_char)
 		{
-		  ADDW (exp_char);
+		  char_buffer_add (&charbuf, exp_char);
 		  got_e = got_dot = 1;
 		}
 	      else
@@ -1996,11 +2068,11 @@ _IO_vfscanf_internal (_IO_FILE *s, const char *format, _IO_va_list argptr,
 #ifdef COMPILE_WSCANF
 		  if (! got_dot && c == decimal)
 		    {
-		      ADDW (c);
+		      char_buffer_add (&charbuf, c);
 		      got_dot = 1;
 		    }
 		  else if ((flags & GROUP) != 0 && ! got_dot && c == thousands)
-		    ADDW (c);
+		    char_buffer_add (&charbuf, c);
 		  else
 		    {
 		      /* The last read character is not part of the number
@@ -2029,7 +2101,7 @@ _IO_vfscanf_internal (_IO_FILE *s, const char *format, _IO_va_list argptr,
 		    {
 		      /* Add all the characters.  */
 		      for (cmpp = decimal; *cmpp != '\0'; ++cmpp)
-			ADDW ((unsigned char) *cmpp);
+			char_buffer_add (&charbuf, (unsigned char) *cmpp);
 		      if (width > 0)
 			width = avail;
 		      got_dot = 1;
@@ -2066,7 +2138,7 @@ _IO_vfscanf_internal (_IO_FILE *s, const char *format, _IO_va_list argptr,
 			{
 			  /* Add all the characters.  */
 			  for (cmpp = thousands; *cmpp != '\0'; ++cmpp)
-			    ADDW ((unsigned char) *cmpp);
+			    char_buffer_add (&charbuf, (unsigned char) *cmpp);
 			  if (width > 0)
 			    width = avail;
 			}
@@ -2088,13 +2160,20 @@ _IO_vfscanf_internal (_IO_FILE *s, const char *format, _IO_va_list argptr,
 		--width;
 	    }
 
+	  if (char_buffer_error (&charbuf))
+	    {
+	      __set_errno (ENOMEM);
+	      done = EOF;
+	      goto errout;
+	    }
+
 	  wctrans_t map;
 	  if (__builtin_expect ((flags & I18N) != 0, 0)
 	      /* Hexadecimal floats make no sense, fixing localized
 		 digits with ASCII letters.  */
 	      && !(flags & HEXA_FLOAT)
 	      /* Minimum requirement.  */
-	      && (wpsize == 0 || got_dot)
+	      && (char_buffer_size (&charbuf) == 0 || got_dot)
 	      && (map = __wctrans ("to_inpunct")) != NULL)
 	    {
 	      /* Reget the first character.  */
@@ -2113,20 +2192,23 @@ _IO_vfscanf_internal (_IO_FILE *s, const char *format, _IO_va_list argptr,
 		 for localized FP numbers, then we may have localized
 		 digits.  Note, we test GOT_DOT above.  */
 #ifdef COMPILE_WSCANF
-	      if (wpsize == 0 || (wpsize == 1 && wcdigits[11] == decimal))
+	      if (char_buffer_size (&charbuf) == 0
+		  || (char_buffer_size (&charbuf) == 1
+		      && wcdigits[11] == decimal))
 #else
 	      char mbdigits[12][MB_LEN_MAX + 1];
 
 	      mbstate_t state;
 	      memset (&state, '\0', sizeof (state));
 
-	      bool match_so_far = wpsize == 0;
+	      bool match_so_far = char_buffer_size (&charbuf) == 0;
 	      size_t mblen = __wcrtomb (mbdigits[11], wcdigits[11], &state);
 	      if (mblen != (size_t) -1)
 		{
 		  mbdigits[11][mblen] = '\0';
-		  match_so_far |= (wpsize == strlen (decimal)
-				   && strcmp (decimal, mbdigits[11]) == 0);
+		  match_so_far |=
+		    (char_buffer_size (&charbuf) == strlen (decimal)
+		     && strcmp (decimal, mbdigits[11]) == 0);
 		}
 	      else
 		{
@@ -2135,7 +2217,7 @@ _IO_vfscanf_internal (_IO_FILE *s, const char *format, _IO_va_list argptr,
 		     from a file.  */
 		  if (decimal_len <= MB_LEN_MAX)
 		    {
-		      match_so_far |= wpsize == decimal_len;
+		      match_so_far |= char_buffer_size (&charbuf) == decimal_len;
 		      memcpy (mbdigits[11], decimal, decimal_len + 1);
 		    }
 		  else
@@ -2190,13 +2272,19 @@ _IO_vfscanf_internal (_IO_FILE *s, const char *format, _IO_va_list argptr,
 		     conversion is done correctly. */
 		  while (1)
 		    {
-		      if (got_e && wp[wpsize - 1] == exp_char
+		      if (char_buffer_error (&charbuf))
+			{
+			  __set_errno (ENOMEM);
+			  done = EOF;
+			  goto errout;
+			}
+		      if (got_e && charbuf.current[-1] == exp_char
 			  && (c == L_('-') || c == L_('+')))
-			ADDW (c);
-		      else if (wpsize > 0 && !got_e
+			char_buffer_add (&charbuf, c);
+		      else if (char_buffer_size (&charbuf) > 0 && !got_e
 			       && (CHAR_T) TOLOWER (c) == exp_char)
 			{
-			  ADDW (exp_char);
+			  char_buffer_add (&charbuf, exp_char);
 			  got_e = got_dot = 1;
 			}
 		      else
@@ -2210,15 +2298,15 @@ _IO_vfscanf_internal (_IO_FILE *s, const char *format, _IO_va_list argptr,
 			      if (c == wcdigits[n])
 				{
 				  if (n < 10)
-				    ADDW (L_('0') + n);
+				    char_buffer_add (&charbuf, L_('0') + n);
 				  else if (n == 11 && !got_dot)
 				    {
-				      ADDW (decimal);
+				      char_buffer_add (&charbuf, decimal);
 				      got_dot = 1;
 				    }
 				  else if (n == 10 && have_locthousands
 					   && ! got_dot)
-				    ADDW (thousands);
+				    char_buffer_add (&charbuf, thousands);
 				  else
 				    /* The last read character is not part
 				       of the number anymore.  */
@@ -2245,13 +2333,14 @@ _IO_vfscanf_internal (_IO_FILE *s, const char *format, _IO_va_list argptr,
 				    width = avail;
 
 				  if (n < 10)
-				    ADDW (L_('0') + n);
+				    char_buffer_add (&charbuf, L_('0') + n);
 				  else if (n == 11 && !got_dot)
 				    {
 				      /* Add all the characters.  */
 				      for (cmpp = decimal; *cmpp != '\0';
 					   ++cmpp)
-					ADDW ((unsigned char) *cmpp);
+					char_buffer_add (&charbuf,
+							 (unsigned char) *cmpp);
 
 				      got_dot = 1;
 				    }
@@ -2261,7 +2350,8 @@ _IO_vfscanf_internal (_IO_FILE *s, const char *format, _IO_va_list argptr,
 				      /* Add all the characters.  */
 				      for (cmpp = thousands; *cmpp != '\0';
 					   ++cmpp)
-					ADDW ((unsigned char) *cmpp);
+					char_buffer_add (&charbuf,
+							 (unsigned char) *cmpp);
 				    }
 				  else
 				    /* The last read character is not part
@@ -2305,36 +2395,53 @@ _IO_vfscanf_internal (_IO_FILE *s, const char *format, _IO_va_list argptr,
 #endif
 	    }
 
+	  if (char_buffer_error (&charbuf))
+	    {
+	      __set_errno (ENOMEM);
+	      done = EOF;
+	      goto errout;
+	    }
+
 	  /* Have we read any character?  If we try to read a number
 	     in hexadecimal notation and we have read only the `0x'
 	     prefix this is an error.  */
-	  if (__builtin_expect (wpsize == 0
-				|| ((flags & HEXA_FLOAT) && wpsize == 2), 0))
+	  if (__glibc_unlikely (char_buffer_size (&charbuf) == 0
+				|| ((flags & HEXA_FLOAT)
+				    && char_buffer_size (&charbuf) == 2)))
 	    conv_error ();
 
 	scan_float:
 	  /* Convert the number.  */
-	  ADDW (L_('\0'));
+	  char_buffer_add (&charbuf, L_('\0'));
+	  if (char_buffer_error (&charbuf))
+	    {
+	      __set_errno (ENOMEM);
+	      done = EOF;
+	      goto errout;
+	    }
 	  if ((flags & LONGDBL) && !__ldbl_is_dbl)
 	    {
-	      long double d = __strtold_internal (wp, &tw, flags & GROUP);
-	      if (!(flags & SUPPRESS) && tw != wp)
+	      long double d = __strtold_internal
+		(char_buffer_start (&charbuf), &tw, flags & GROUP);
+	      if (!(flags & SUPPRESS) && tw != char_buffer_start (&charbuf))
 		*ARG (long double *) = negative ? -d : d;
 	    }
 	  else if (flags & (LONG | LONGDBL))
 	    {
-	      double d = __strtod_internal (wp, &tw, flags & GROUP);
-	      if (!(flags & SUPPRESS) && tw != wp)
+	      double d = __strtod_internal
+		(char_buffer_start (&charbuf), &tw, flags & GROUP);
+	      if (!(flags & SUPPRESS) && tw != char_buffer_start (&charbuf))
 		*ARG (double *) = negative ? -d : d;
 	    }
 	  else
 	    {
-	      float d = __strtof_internal (wp, &tw, flags & GROUP);
-	      if (!(flags & SUPPRESS) && tw != wp)
+	      float d = __strtof_internal
+		(char_buffer_start (&charbuf), &tw, flags & GROUP);
+	      if (!(flags & SUPPRESS) && tw != char_buffer_start (&charbuf))
 		*ARG (float *) = negative ? -d : d;
 	    }
 
-	  if (__glibc_unlikely (tw == wp))
+	  if (__glibc_unlikely (tw == char_buffer_start (&charbuf)))
 	    conv_error ();
 
 	  if (!(flags & SUPPRESS))
@@ -2380,12 +2487,13 @@ _IO_vfscanf_internal (_IO_FILE *s, const char *format, _IO_va_list argptr,
 #else
 	  /* Fill WP with byte flags indexed by character.
 	     We will use this flag map for matching input characters.  */
-	  if (wpmax < UCHAR_MAX + 1)
+	  if (!scratch_buffer_set_array_size
+	      (&charbuf.scratch, UCHAR_MAX + 1, 1))
 	    {
-	      wpmax = UCHAR_MAX + 1;
-	      wp = (char *) alloca (wpmax);
+	      done = EOF;
+	      goto errout;
 	    }
-	  memset (wp, '\0', UCHAR_MAX + 1);
+	  memset (charbuf.scratch.data, '\0', UCHAR_MAX + 1);
 
 	  fc = *f;
 	  if (fc == ']' || fc == '-')
@@ -2393,7 +2501,7 @@ _IO_vfscanf_internal (_IO_FILE *s, const char *format, _IO_va_list argptr,
 	      /* If ] or - appears before any char in the set, it is not
 		 the terminator or separator, but the first char in the
 		 set.  */
-	      wp[fc] = 1;
+	      ((char *)charbuf.scratch.data)[fc] = 1;
 	      ++f;
 	    }
 
@@ -2404,11 +2512,11 @@ _IO_vfscanf_internal (_IO_FILE *s, const char *format, _IO_va_list argptr,
 		/* Add all characters from the one before the '-'
 		   up to (but not including) the next format char.  */
 		for (fc = (unsigned char) f[-2]; fc < (unsigned char) *f; ++fc)
-		  wp[fc] = 1;
+		  ((char *)charbuf.scratch.data)[fc] = 1;
 	      }
 	    else
 	      /* Add the character to the flag map.  */
-	      wp[fc] = 1;
+	      ((char *)charbuf.scratch.data)[fc] = 1;
 
 	  if (__glibc_unlikely (fc == '\0'))
 	    conv_error();
@@ -2537,7 +2645,7 @@ _IO_vfscanf_internal (_IO_FILE *s, const char *format, _IO_va_list argptr,
 
 	      do
 		{
-		  if (wp[c] == not_in)
+		  if (((char *) charbuf.scratch.data)[c] == not_in)
 		    {
 		      ungetc_not_eof (c, s);
 		      break;
@@ -2765,7 +2873,7 @@ _IO_vfscanf_internal (_IO_FILE *s, const char *format, _IO_va_list argptr,
 #else
 	      do
 		{
-		  if (wp[c] == not_in)
+		  if (((char *) charbuf.scratch.data)[c] == not_in)
 		    {
 		      ungetc_not_eof (c, s);
 		      break;
@@ -2905,9 +3013,7 @@ _IO_vfscanf_internal (_IO_FILE *s, const char *format, _IO_va_list argptr,
   /* Unlock stream.  */
   UNLOCK_STREAM (s);
 
-  if (use_malloc)
-    free (wp);
-
+  scratch_buffer_free (&charbuf.scratch);
   if (errp != NULL)
     *errp |= errval;