From 58c46310eab5d280668e66c1e837e6ce85c8b982 Mon Sep 17 00:00:00 2001 From: RHEL Packaging Agent Date: Wed, 19 Aug 2026 15:26:49 +0000 Subject: [PATCH] Fix CVE-2026-58012: buffer overflow in gregex.c with G_REGEX_RAW Add backport patch for CVE-2026-58012 which fixes case changing substitutions when using G_REGEX_RAW mode. Without this fix, g_regex_replace() could perform out-of-bounds reads when processing non-UTF-8 matched data with case change escape sequences (e.g. \U\0) because it incorrectly assumed UTF-8 encoding. The fix adds raw-mode-aware case conversion that uses ASCII operations instead of UTF-8 character functions. CVE: CVE-2026-58012 Upstream patches: - https://gitlab.gnome.org/GNOME/glib/-/merge_requests/5132.patch Resolves: RHEL-212218 This commit was backported by Ymir, a Red Hat Enterprise Linux software maintenance AI agent. Assisted-by: Ymir --- CVE-2026-58012.patch | 207 +++++++++++++++++++++++++++++++++++++++++++ glib2.spec | 9 +- 2 files changed, 215 insertions(+), 1 deletion(-) create mode 100644 CVE-2026-58012.patch diff --git a/CVE-2026-58012.patch b/CVE-2026-58012.patch new file mode 100644 index 0000000..6898a6b --- /dev/null +++ b/CVE-2026-58012.patch @@ -0,0 +1,207 @@ +From ee0aa3a0ef60a368082122df14494ec91ec97e63 Mon Sep 17 00:00:00 2001 +From: Philip Withnall +Date: Wed, 19 Aug 2026 15:17:20 +0000 +Subject: [PATCH] gregex: Fix case changing substitutions with G_REGEX_RAW + +--- + glib/gregex.c | 59 ++++++++++++++++++++++++++++++++++------------ + glib/tests/regex.c | 54 ++++++++++++++++++++++++++++++++++++++++++ + 2 files changed, 98 insertions(+), 15 deletions(-) + +diff --git a/glib/gregex.c b/glib/gregex.c +index 5e6ddfb46..7cd8106ae 100644 +--- a/glib/gregex.c ++++ b/glib/gregex.c +@@ -2647,19 +2647,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; + +@@ -2669,22 +2675,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, +@@ -2694,6 +2722,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) + { +@@ -2701,10 +2730,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; +@@ -2712,7 +2741,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; +@@ -2720,7 +2749,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 c57bd8cdc..1a8d30ab3 100644 +--- a/glib/tests/regex.c ++++ b/glib/tests/regex.c +@@ -2187,6 +2187,59 @@ 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; ++ char subject[] = "\xf4\x80"; ++ char subject2[] = "\xe6\xb0"; /* 3-byte UTF-8 lead, only 2 bytes */ ++ char *result = 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. ++ */ ++ 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 (®ex, 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); ++ ++ 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 (®ex, g_regex_unref); ++} ++ + int + main (int argc, char *argv[]) + { +@@ -2206,6 +2259,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); diff --git a/glib2.spec b/glib2.spec index 850711e..0b4d014 100644 --- a/glib2.spec +++ b/glib2.spec @@ -1,6 +1,6 @@ Name: glib2 Version: 2.68.4 -Release: 22%{?dist} +Release: 23%{?dist} Summary: A library of handy utility functions License: LGPLv2+ @@ -98,6 +98,9 @@ Patch: 5247.patch # https://gitlab.gnome.org/GNOME/glib/-/commit/c9da977c178fbfc0e4caf99f9fdf5dc433d6fcc2 Patch: CVE-2026-58016.patch +# https://gitlab.gnome.org/GNOME/glib/-/merge_requests/5132 +Patch: CVE-2026-58012.patch + BuildRequires: chrpath BuildRequires: gcc BuildRequires: gcc-c++ @@ -319,6 +322,10 @@ glib-compile-schemas %{_datadir}/glib-2.0/schemas &> /dev/null || : %{_datadir}/installed-tests %changelog +* Wed Aug 19 2026 RHEL Packaging Agent - 2.68.4-23 +- Fix CVE-2026-58012: case changing substitutions with G_REGEX_RAW + Resolves: RHEL-212218 + * Fri Jul 24 2026 RHEL Packaging Agent - 2.68.4-22 - Fix CVE-2026-58016: XML parser state handling for element nesting Resolves: RHEL-190627