From 3719ac8db7614f480ab988a204b5e7e61ab8c0e5 Mon Sep 17 00:00:00 2001 From: Tobias Stoeckmann Date: Wed, 17 Sep 2025 20:09:44 +0200 Subject: [PATCH] strfuncs: Check for overflows when joining strings The functions g_strconcat, g_strjoinv and g_strjoin perform the concatination of strings in two phases. The first phase figures out the required amount of memory to hold the resulting string. The second phase actually copies the strings into the allocated memory. If the sum of the lengths of all strings to be joined exceeds G_SIZEMAX, then phase two triggers an out of boundary write due to insufficient amount of memory allocated. While this sounds impossible to do at first, actually it becomes a possibility on 32 bit systems with merely 20 MB of heap. The overflow can actually happen if the same string is joined multiple times. See attached unit test. While the same can be done with 64 bit systems, it takes much more memory and a lot of time. Fortunately the protection is rather cheap, although it adds two or three machine instructions and branches due to testing. --- glib/gstrfuncs.c | 16 ++++++++++++---- glib/tests/strfuncs.c | 41 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 53 insertions(+), 4 deletions(-) diff --git a/glib/gstrfuncs.c b/glib/gstrfuncs.c index 0141ece16..22ec7a1ee 100644 --- a/glib/gstrfuncs.c +++ b/glib/gstrfuncs.c @@ -581,7 +581,8 @@ g_strconcat (const gchar *string1, ...) s = va_arg (args, gchar*); while (s) { - l += strlen (s); + if (!g_size_checked_add (&l, l, strlen (s))) + g_error ("%s: overflow concatenating strings", G_STRLOC); s = va_arg (args, gchar*); } va_end (args); @@ -2645,13 +2646,18 @@ g_strjoinv (const gchar *separator, gsize i; gsize len; gsize separator_len; + gsize separators_len; separator_len = strlen (separator); /* First part, getting length */ len = 1 + strlen (str_array[0]); for (i = 1; str_array[i] != NULL; i++) - len += strlen (str_array[i]); - len += separator_len * (i - 1); + if (!g_size_checked_add (&len, len, strlen (str_array[i]))) + g_error ("%s: overflow joining strings", G_STRLOC); + + if (!g_size_checked_mul (&separators_len, separator_len, (i - 1)) || + !g_size_checked_add (&len, len, separators_len)) + g_error ("%s: overflow joining strings", G_STRLOC); /* Second part, building string */ string = g_new (gchar, len); @@ -2706,7 +2712,9 @@ g_strjoin (const gchar *separator, s = va_arg (args, gchar*); while (s) { - len += separator_len + strlen (s); + if (!g_size_checked_add (&len, len, separator_len) || + !g_size_checked_add (&len, len, strlen (s))) + g_error ("%s: overflow joining strings", G_STRLOC); s = va_arg (args, gchar*); } va_end (args); diff --git a/glib/tests/strfuncs.c b/glib/tests/strfuncs.c index 3a6f745f2..278fbaed7 100644 --- a/glib/tests/strfuncs.c +++ b/glib/tests/strfuncs.c @@ -660,6 +660,46 @@ test_strconcat (void) g_assert_null (g_strconcat (NULL, "bla", NULL)); } +/* Testing g_strjoinv() function with strings which cannot be joined in heap */ +static void +test_strjoinv_overflow (void) +{ +#if G_MAXSIZE > G_MAXUINT + g_test_skip ("Overflow joining strings requires G_MAXSIZE <= G_MAXUINT."); +#else + if (!g_test_undefined ()) + return; + + if (g_test_subprocess ()) + { + /* compromise between memory consumption and performance */ + const size_t count = 256; + gchar **array; + gchar *result; + gchar *string; + + string = g_strnfill (G_MAXSIZE / ((count - 1) * 2), 'A'); + array = g_malloc_n (count + 1, sizeof (*array)); + + for (size_t i = 0; i < count; i++) + array[i] = string; + array[count] = NULL; + + result = g_strjoinv (string, array); + + g_free (array); + g_free (result); + g_free (string); + } + else + { + g_test_trap_subprocess (NULL, 0, G_TEST_SUBPROCESS_DEFAULT); + g_test_trap_assert_failed (); + g_test_trap_assert_stderr ("*overflow joining strings*"); + } +#endif +} + /* Testing g_strjoinv() function with various positive and negative cases */ static void test_strjoinv (void) @@ -2797,6 +2837,7 @@ main (int argc, g_test_add_func ("/strfuncs/strip-context", test_strip_context); g_test_add_func ("/strfuncs/strjoin", test_strjoin); g_test_add_func ("/strfuncs/strjoinv", test_strjoinv); + g_test_add_func ("/strfuncs/strjoinv/overflow", test_strjoinv_overflow); g_test_add_func ("/strfuncs/strlcat", test_strlcat); g_test_add_func ("/strfuncs/strlcpy", test_strlcpy); g_test_add_func ("/strfuncs/strncasecmp", test_strncasecmp);