[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