[PATCH v4 2/2] stdio-common: Fix buffer overflow in scanf %mc [BZ #34008]

Rocket Ma marocketbd@gmail.com
Sun Apr 5 18:18:21 GMT 2026


* stdio-common/vfscanf-internal.c: When enlarging allocated buffer with
format %mc or %mC, glibc allocates one byte less, leading to
user-controlled one byte overflow. This commit fixes BZ #34008, or
CVE-2026-5450. Unify newsize calculation of allocated buffer.

Signed-off-by: Rocket Ma <marocketbd@gmail.com>
---
 stdio-common/vfscanf-internal.c | 74 ++++++++++++++++++++-------------
 1 file changed, 46 insertions(+), 28 deletions(-)

diff --git a/stdio-common/vfscanf-internal.c b/stdio-common/vfscanf-internal.c
index 59fc8208aa..6bf2a55876 100644
--- a/stdio-common/vfscanf-internal.c
+++ b/stdio-common/vfscanf-internal.c
@@ -265,6 +265,19 @@ char_buffer_add (struct char_buffer *buffer, CHAR_T ch)
     *buffer->current++ = ch;
 }
 
+/* Calculate the result size of expanded char array in %ms, %mS,
+   %m[, %lm[, %mc or %mC. */
+static __always_inline size_t
+grow_to_fit (size_t oldsize, int need, int extra)
+{
+  /* extra = 0 if %m[cC], %m[cC] always have positive width */
+  if ((extra && need < 0) || oldsize < need)
+    return oldsize * 2;
+  /* oldsize >= need:
+     grow requested capacity and `extra' byte for `\0' */
+  return oldsize + need + extra;
+}
+
 /* 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.  */
@@ -804,7 +817,8 @@ __vfscanf_internal (FILE *s, const char *format, va_list argptr,
 		      && *strptr + strsize - str <= MB_LEN_MAX)
 		    {
 		      /* We have to enlarge the buffer if the `m' flag
-			 was given.  */
+			 was given. And we may not expand str by width
+			 as the wcrtomb may return various bytes */
 		      size_t strleng = str - *strptr;
 		      char *newstr;
 
@@ -854,9 +868,7 @@ __vfscanf_internal (FILE *s, const char *format, va_list argptr,
 			  && (char *) str == *strptr + strsize)
 			{
 			  /* Enlarge the buffer.  */
-			  size_t newsize
-			    = strsize
-			      + (strsize >= width ? width - 1 : strsize);
+			  size_t newsize = grow_to_fit (strsize, width, 0);
 
 			  str = (char *) realloc (*strptr, newsize);
 			  if (str == NULL)
@@ -928,8 +940,7 @@ __vfscanf_internal (FILE *s, const char *format, va_list argptr,
 		  if ((flags & MALLOC)
 		      && wstr == (wchar_t *) *strptr + strsize)
 		    {
-		      size_t newsize
-			= strsize + (strsize > width ? width - 1 : strsize);
+		      size_t newsize = grow_to_fit (strsize, width, 0);
 		      /* Enlarge the buffer.  */
 		      wstr = (wchar_t *) realloc (*strptr,
 						  newsize * sizeof (wchar_t));
@@ -983,8 +994,7 @@ __vfscanf_internal (FILE *s, const char *format, va_list argptr,
 		if (!(flags & SUPPRESS) && (flags & MALLOC)
 		    && wstr == (wchar_t *) *strptr + strsize)
 		  {
-		    size_t newsize
-		      = strsize + (strsize > width ? width - 1 : strsize);
+		    size_t newsize = grow_to_fit (strsize, width, 0);
 		    /* Enlarge the buffer.  */
 		    wstr = (wchar_t *) realloc (*strptr,
 						newsize * sizeof (wchar_t));
@@ -1099,7 +1109,8 @@ __vfscanf_internal (FILE *s, const char *format, va_list argptr,
 			&& *strptr + strsize - str <= MB_LEN_MAX)
 		      {
 			/* We have to enlarge the buffer if the `a' or `m'
-			   flag was given.  */
+			   flag was given. And we may not expand str by
+			   width as the wcrtomb may return various bytes */
 			size_t strleng = str - *strptr;
 			char *newstr;
 
@@ -1157,7 +1168,8 @@ __vfscanf_internal (FILE *s, const char *format, va_list argptr,
 			  && (char *) str == *strptr + strsize)
 			{
 			  /* Enlarge the buffer.  */
-			  str = (char *) realloc (*strptr, 2 * strsize);
+			  size_t newsize = grow_to_fit (strsize, width, 1);
+			  str = (char *) realloc (*strptr, newsize);
 			  if (str == NULL)
 			    {
 			      /* Can't allocate that much.  Last-ditch
@@ -1189,7 +1201,7 @@ __vfscanf_internal (FILE *s, const char *format, va_list argptr,
 			    {
 			      *strptr = (char *) str;
 			      str += strsize;
-			      strsize *= 2;
+			      strsize = newsize;
 			    }
 			}
 		    }
@@ -1287,9 +1299,10 @@ __vfscanf_internal (FILE *s, const char *format, va_list argptr,
 			&& wstr == (wchar_t *) *strptr + strsize)
 		      {
 			/* Enlarge the buffer.  */
-			wstr = (wchar_t *) realloc (*strptr,
-						    (2 * strsize)
-						    * sizeof (wchar_t));
+			size_t newsize = grow_to_fit (strsize, width, 1);
+
+			wstr = (wchar_t *) realloc (
+			    *strptr, newsize * sizeof (wchar_t));
 			if (wstr == NULL)
 			  {
 			    /* Can't allocate that much.  Last-ditch
@@ -1323,7 +1336,7 @@ __vfscanf_internal (FILE *s, const char *format, va_list argptr,
 			  {
 			    *strptr = (char *) wstr;
 			    wstr += strsize;
-			    strsize *= 2;
+			    strsize = newsize;
 			  }
 		      }
 		  }
@@ -1363,9 +1376,10 @@ __vfscanf_internal (FILE *s, const char *format, va_list argptr,
 		      && wstr == (wchar_t *) *strptr + strsize)
 		    {
 		      /* Enlarge the buffer.  */
+		      size_t newsize = grow_to_fit (strsize, width, 1);
+
 		      wstr = (wchar_t *) realloc (*strptr,
-						  (2 * strsize
-						   * sizeof (wchar_t)));
+						  newsize * sizeof (wchar_t));
 		      if (wstr == NULL)
 			{
 			  /* Can't allocate that much.  Last-ditch effort.  */
@@ -1398,7 +1412,7 @@ __vfscanf_internal (FILE *s, const char *format, va_list argptr,
 			{
 			  *strptr = (char *) wstr;
 			  wstr += strsize;
-			  strsize *= 2;
+			  strsize = newsize;
 			}
 		    }
 		}
@@ -2755,9 +2769,10 @@ digits_extended_fail:
 			  && wstr == (wchar_t *) *strptr + strsize)
 			{
 			  /* Enlarge the buffer.  */
-			  wstr = (wchar_t *) realloc (*strptr,
-						      (2 * strsize)
-						      * sizeof (wchar_t));
+			  size_t newsize = grow_to_fit (strsize, width, 1);
+
+			  wstr = (wchar_t *) realloc (
+			      *strptr, newsize * sizeof (wchar_t));
 			  if (wstr == NULL)
 			    {
 			      /* Can't allocate that much.  Last-ditch
@@ -2791,7 +2806,7 @@ digits_extended_fail:
 			    {
 			      *strptr = (char *) wstr;
 			      wstr += strsize;
-			      strsize *= 2;
+			      strsize = newsize;
 			    }
 			}
 		    }
@@ -2840,9 +2855,10 @@ digits_extended_fail:
 			  && wstr == (wchar_t *) *strptr + strsize)
 			{
 			  /* Enlarge the buffer.  */
-			  wstr = (wchar_t *) realloc (*strptr,
-						      (2 * strsize
-						       * sizeof (wchar_t)));
+			  size_t newsize = grow_to_fit (strsize, width, 1);
+
+			  wstr = (wchar_t *) realloc (
+			      *strptr, newsize * sizeof (wchar_t));
 			  if (wstr == NULL)
 			    {
 			      /* Can't allocate that much.  Last-ditch
@@ -2876,7 +2892,7 @@ digits_extended_fail:
 			    {
 			      *strptr = (char *) wstr;
 			      wstr += strsize;
-			      strsize *= 2;
+			      strsize = newsize;
 			    }
 			}
 		    }
@@ -2984,7 +3000,9 @@ digits_extended_fail:
 		      if ((flags & MALLOC)
 			  && *strptr + strsize - str <= MB_LEN_MAX)
 			{
-			  /* Enlarge the buffer.  */
+			  /* Enlarge the buffer. And we may not
+			   expand str by width as the wcrtomb may
+			   return various bytes */
 			  size_t strleng = str - *strptr;
 			  char *newstr;
 
@@ -3052,7 +3070,7 @@ digits_extended_fail:
 			  && (char *) str == *strptr + strsize)
 			{
 			  /* Enlarge the buffer.  */
-			  size_t newsize = 2 * strsize;
+			  size_t newsize = grow_to_fit (strsize, width, 1);
 
 			allocagain:
 			  str = (char *) realloc (*strptr, newsize);
-- 
2.53.0



More information about the Libc-alpha mailing list