Add patch for CVE-2026-58012 to glib2
Backport upstream commit 49e067570dfa to fix CVE-2026-58012,
a buffer overflow in GRegex's string_append() when using
G_REGEX_RAW mode with case-changing substitutions on truncated
multi-byte UTF-8 sequences. The patch was adapted for glib
2.56.4 compatibility.
CVE: CVE-2026-58012
Upstream patches:
- 49e067570d.patch
Resolves: RHEL-212204
This commit was backported by Ymir, a Red Hat Enterprise Linux software maintenance AI agent.
Assisted-by: Ymir
This commit is contained in:
parent
b3d5305454
commit
5b8e9adef2
221
CVE-2026-58012.patch
Normal file
221
CVE-2026-58012.patch
Normal file
@ -0,0 +1,221 @@
|
||||
From 502cc4445106a6adc3bf21d47f5adf4c02165299 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 225b8967c..2597abe96 100644
|
||||
--- a/glib/gregex.c
|
||||
+++ b/glib/gregex.c
|
||||
@@ -2640,19 +2640,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;
|
||||
|
||||
@@ -2662,22 +2668,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,
|
||||
@@ -2687,6 +2715,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)
|
||||
{
|
||||
@@ -2694,10 +2723,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;
|
||||
@@ -2705,7 +2734,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;
|
||||
@@ -2713,7 +2742,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 56bd2d5eb..25c3ff783 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;
|
||||
+ char *result = NULL;
|
||||
+ char subject[] = "\xf4\x80";
|
||||
+ char subject2[] = "\xe6\xb0"; /* 3-byte UTF-8 lead, only 2 bytes */
|
||||
+
|
||||
+ g_test_bug ("https://gitlab.gnome.org/GNOME/glib/-/issues/3918");
|
||||
+
|
||||
+ /*
|
||||
+ * 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[])
|
||||
{
|
||||
@@ -2202,6 +2254,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);
|
||||
@ -5,7 +5,7 @@
|
||||
|
||||
Name: glib2
|
||||
Version: 2.56.4
|
||||
Release: 173%{?dist}
|
||||
Release: 174%{?dist}
|
||||
Summary: A library of handy utility functions
|
||||
|
||||
License: LGPLv2+
|
||||
@ -181,6 +181,9 @@ Patch38: CVE-2026-58015.patch
|
||||
# https://gitlab.gnome.org/GNOME/glib/-/commit/9f557746c52ae2a62fd5929f532b77024a18abe2
|
||||
Patch39: CVE-2026-58013.patch
|
||||
|
||||
# https://gitlab.gnome.org/GNOME/glib/-/commit/49e067570dfa208c45d76f0b602664fd11a629ef
|
||||
Patch40: CVE-2026-58012.patch
|
||||
|
||||
%description
|
||||
GLib is the low-level core library that forms the basis for projects
|
||||
such as GTK+ and GNOME. It provides data structure handling for C,
|
||||
@ -381,6 +384,10 @@ make %{?_smp_mflags} check
|
||||
%{_datadir}/installed-tests
|
||||
|
||||
%changelog
|
||||
* Mon Jul 20 2026 RHEL Packaging Agent <redhat-ymir-agent@redhat.com> - 2.56.4-174
|
||||
- Fix CVE-2026-58012: buffer overflow in gregex case changing substitutions
|
||||
- Resolves: RHEL-212204
|
||||
|
||||
* Mon Jul 20 2026 RHEL Packaging Agent <redhat-ymir-agent@redhat.com> - 2.56.4-173
|
||||
- Fix CVE-2026-58013: buffer over-read in GIOChannel with long terminators
|
||||
- Resolves: RHEL-212229
|
||||
|
||||
Loading…
Reference in New Issue
Block a user