Fix CVE-2026-58012: buffer overflow in gregex substitutions

Backport upstream fix for CVE-2026-58012, a buffer overflow in
GRegex case changing substitutions when using G_REGEX_RAW mode.
The upstream patch was adapted for glib 2.70.1 by adjusting
the struct member name from orig_compile_opts to compile_opts.

CVE: CVE-2026-58012
Upstream patches:
 - https://gitlab.gnome.org/GNOME/glib/-/merge_requests/5132.patch
Resolves: RHEL-212200

This commit was backported by Ymir, a Red Hat Enterprise Linux software maintenance AI agent.

Assisted-by: Ymir
This commit is contained in:
RHEL Packaging Agent 2026-07-22 16:20:18 +00:00
parent d9b6761028
commit 8fd7323521
2 changed files with 230 additions and 1 deletions

View File

@ -0,0 +1,221 @@
From a9a4c378a05723eb164f193d585b6611e4646b65 Mon Sep 17 00:00:00 2001
From: Philip Withnall <pwithnall@gnome.org>
Date: Tue, 31 Mar 2026 16:13:57 +0100
Subject: [PATCH] gregex: Fix case changing substitutions with G_REGEX_RAW
In `G_REGEX_RAW` mode, the input string is treated as a byte array
(basically ASCII) rather than a unichar array. Accordingly, the case
changing code for substitutions needs to operate on bytes with
`G_REGEX_RAW`, rather than operating on unichars.
This fixes a potential buffer overflow when trying to do a case change
on a match of a set of bytes which are a truncated multi-byte UTF-8
encoding at the end of the input buffer.
Spotted by linhlhq as #YWH-PGM9867-193. I adapted their reproducer as
the unit test, but implemented the fix in `gregex.c` independently.
Signed-off-by: Philip Withnall <pwithnall@gnome.org>
Fixes: #3918
---
glib/gregex.c | 59 ++++++++++++++++++++++++++++++++++------------
glib/tests/regex.c | 53 +++++++++++++++++++++++++++++++++++++++++
2 files changed, 97 insertions(+), 15 deletions(-)
diff --git a/glib/gregex.c b/glib/gregex.c
index a8a35a424..948df3f95 100644
--- a/glib/gregex.c
+++ b/glib/gregex.c
@@ -2655,19 +2655,25 @@ split_replacement (const gchar *replacement,
return g_list_reverse (list);
}
-/* Change the case of c based on change_case. */
-#define CHANGE_CASE(c, change_case) \
+/* Change the case of c based on change_case.
+ * g_ascii_to*() will happily pass through non-ASCII bytes unchanged. */
+#define UTF8_CHANGE_CASE(c, change_case) \
(((change_case) & CHANGE_CASE_LOWER_MASK) ? \
g_unichar_tolower (c) : \
g_unichar_toupper (c))
+#define RAW_CHANGE_CASE(c, change_case) \
+ (((change_case) & CHANGE_CASE_LOWER_MASK) ? \
+ g_ascii_tolower (c) : \
+ g_ascii_toupper (c))
+/* If @text_is_raw is set, @text might not be valid UTF-8 (but will be
+ * nul-terminated). */
static void
string_append (GString *string,
const gchar *text,
+ gboolean text_is_raw,
ChangeCase *change_case)
{
- gunichar c;
-
if (text[0] == '\0')
return;
@@ -2677,22 +2683,44 @@ string_append (GString *string,
}
else if (*change_case & CHANGE_CASE_SINGLE_MASK)
{
- c = g_utf8_get_char (text);
- g_string_append_unichar (string, CHANGE_CASE (c, *change_case));
- g_string_append (string, g_utf8_next_char (text));
+ if (!text_is_raw)
+ {
+ gunichar c = g_utf8_get_char (text);
+ g_string_append_unichar (string, UTF8_CHANGE_CASE (c, *change_case));
+ g_string_append (string, g_utf8_next_char (text));
+ }
+ else
+ {
+ g_string_append_c (string, RAW_CHANGE_CASE (text[0], *change_case));
+ g_string_append (string, text + 1);
+ }
+
*change_case = CHANGE_CASE_NONE;
}
else
{
- while (*text != '\0')
+ if (!text_is_raw)
{
- c = g_utf8_get_char (text);
- g_string_append_unichar (string, CHANGE_CASE (c, *change_case));
- text = g_utf8_next_char (text);
+ while (*text != '\0')
+ {
+ gunichar c = g_utf8_get_char (text);
+ g_string_append_unichar (string, UTF8_CHANGE_CASE (c, *change_case));
+ text = g_utf8_next_char (text);
+ }
+ }
+ else
+ {
+ while (*text != '\0')
+ {
+ char c = *text;
+ g_string_append_c (string, RAW_CHANGE_CASE (c, *change_case));
+ text++;
+ }
}
}
}
+/* @match_info is (nullable) */
static gboolean
interpolate_replacement (const GMatchInfo *match_info,
GString *result,
@@ -2702,6 +2730,7 @@ interpolate_replacement (const GMatchInfo *match_info,
InterpolationData *idata;
gchar *match;
ChangeCase change_case = CHANGE_CASE_NONE;
+ gboolean is_raw = (match_info != NULL && (match_info->regex->compile_opts & G_REGEX_RAW));
for (list = data; list; list = list->next)
{
@@ -2709,10 +2738,10 @@ interpolate_replacement (const GMatchInfo *match_info,
switch (idata->type)
{
case REPL_TYPE_STRING:
- string_append (result, idata->text, &change_case);
+ string_append (result, idata->text, is_raw, &change_case);
break;
case REPL_TYPE_CHARACTER:
- g_string_append_c (result, CHANGE_CASE (idata->c, change_case));
+ g_string_append_c (result, UTF8_CHANGE_CASE (idata->c, change_case));
if (change_case & CHANGE_CASE_SINGLE_MASK)
change_case = CHANGE_CASE_NONE;
break;
@@ -2720,7 +2749,7 @@ interpolate_replacement (const GMatchInfo *match_info,
match = g_match_info_fetch (match_info, idata->num);
if (match)
{
- string_append (result, match, &change_case);
+ string_append (result, match, is_raw, &change_case);
g_free (match);
}
break;
@@ -2728,7 +2757,7 @@ interpolate_replacement (const GMatchInfo *match_info,
match = g_match_info_fetch_named (match_info, idata->text);
if (match)
{
- string_append (result, match, &change_case);
+ string_append (result, match, is_raw, &change_case);
g_free (match);
}
break;
diff --git a/glib/tests/regex.c b/glib/tests/regex.c
index 88d12edf6..818d6d0a0 100644
--- a/glib/tests/regex.c
+++ b/glib/tests/regex.c
@@ -2183,6 +2183,58 @@ pcre_ge (guint64 major, guint64 minor)
return (pcre_major > major) || (pcre_major == major && pcre_minor >= minor);
}
+static void
+test_replace_raw_change_case (void)
+{
+ GError *local_error = NULL;
+ GRegex *regex = NULL;
+
+ g_test_bug ("https://gitlab.gnome.org/GNOME/glib/-/issues/3918");
+ g_test_summary ("Test that case changes as part of a replacement are handled correctly in G_REGEX_RAW mode");
+
+ /*
+ * Match a multi-byte sequence in RAW mode. The pattern matches
+ * exactly 2 bytes. The subject contains a 4-byte UTF-8 lead (0xF4)
+ * followed by only one continuation byte, then NUL.
+ *
+ * The matched substring will be "\xf4\x80" (2 bytes, heap-allocated
+ * as 3-byte buffer with NUL). If the code regresses and tries to handle
+ * the replacement as UTF-8 then g_utf8_get_char() would see 0xF4 and try
+ * to read 4 bytes, going 1 byte past the NUL into OOB territory.
+ */
+ regex = g_regex_new ("..", G_REGEX_RAW, 0, &local_error);
+ g_assert_no_error (local_error);
+
+ /*
+ * Build a subject string with truncated UTF-8.
+ * \xF4 = 4-byte UTF-8 lead byte
+ * \x80 = continuation byte
+ * No 3rd/4th continuation bytes — the match is only 2 bytes.
+ *
+ * \U\0 = uppercase the entire match → triggers string_append()
+ * with case change on the 2-byte non-UTF-8 match.
+ */
+ char subject[] = "\xf4\x80";
+ char *result = g_regex_replace (regex, subject, -1, 0, "\\U\\0", 0, &local_error);
+ g_assert_no_error (local_error);
+
+ g_clear_pointer (&result, g_free);
+ g_clear_pointer (&regex, g_regex_unref);
+
+ /*
+ * Second variant: single-char case change \u with \0 backreference.
+ */
+ regex = g_regex_new (".", G_REGEX_RAW, 0, &local_error);
+ g_assert_no_error (local_error);
+
+ char subject2[] = "\xe6\xb0"; /* 3-byte UTF-8 lead, only 2 bytes */
+ result = g_regex_replace (regex, subject2, -1, 0, "\\u\\0", 0, &local_error);
+ g_assert_no_error (local_error);
+
+ g_clear_pointer (&result, g_free);
+ g_clear_pointer (&regex, g_regex_unref);
+}
+
int
main (int argc, char *argv[])
{
@@ -2200,6 +2252,7 @@ main (int argc, char *argv[])
g_test_add_func ("/regex/multiline", test_multiline);
g_test_add_func ("/regex/explicit-crlf", test_explicit_crlf);
g_test_add_func ("/regex/max-lookbehind", test_max_lookbehind);
+ g_test_add_func ("/regex/replace-raw-change-case", test_replace_raw_change_case);
/* TEST_NEW(pattern, compile_opts, match_opts) */
TEST_NEW("[A-Z]+", G_REGEX_CASELESS | G_REGEX_EXTENDED | G_REGEX_OPTIMIZE, G_REGEX_MATCH_NOTBOL | G_REGEX_MATCH_PARTIAL);

View File

@ -5,7 +5,7 @@
Name: mingw-glib2
Version: 2.70.1
Release: 5%{?dist}
Release: 6%{?dist}
Summary: MinGW Windows GLib2 library
License: LGPLv2+
@ -68,6 +68,9 @@ Patch5: mingw-glib2-2.70.1-CVE-2026-58016.patch
# https://github.com/GNOME/glib/commit/31f82e22e21bae520b7228f7f57d357fb20df8a4
Patch6: mingw-glib2-2.70.1-CVE-2025-14087.patch
# https://gitlab.gnome.org/GNOME/glib/-/issues/3918
Patch7: mingw-glib2-2.70.1-CVE-2026-58012.patch
%description
MinGW Windows Glib2 library.
@ -119,6 +122,7 @@ Static version of the MinGW Windows GLib2 library.
%patch4 -p1
%patch5 -p1
%patch6 -p1
%patch7 -p1
%build
%mingw_meson --default-library=both \
@ -296,6 +300,10 @@ find $RPM_BUILD_ROOT -name "*.la" -delete
%changelog
* Wed Jul 22 2026 RHEL Packaging Agent <redhat-ymir-agent@redhat.com> - 2.70.1-6
- Fix CVE-2026-58012: buffer overflow in gregex substitutions
Resolves: RHEL-212200
* Wed Jul 22 2026 RHEL Packaging Agent <redhat-ymir-agent@redhat.com> - 2.70.1-5
- Fix CVE-2025-14087: integer overflow in GVariant parser
Resolves: RHEL-154707