import CS git glib2-2.56.4-177.el8_10

This commit is contained in:
AlmaLinux RelEng Bot 2026-08-31 15:52:47 -04:00
parent 5331870f3f
commit 654de02701
8 changed files with 1079 additions and 2 deletions

View File

@ -0,0 +1,312 @@
From fd2ce100affce9e822c81f51d66b2b4b93a76889 Mon Sep 17 00:00:00 2001
From: Philip Withnall <pwithnall@gnome.org>
Date: Sat, 4 Jul 2026 18:13:08 +0100
Subject: [PATCH 1/2] gdbusauth: Limit length of lines read from client
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
The client isnt trusted at this point, and there was previously nothing
limiting how long a line `GDBusAuth` would read. So an untrusted client
could exhaust the servers memory by sending anything except `\r\n`.
Fix that by applying a reasonably length limit when reading a line, and
add a unit test.
Spotted by Gitee Codepecker Lab.
Signed-off-by: Philip Withnall <pwithnall@gnome.org>
Fixes: #3985
---
gio/gdbusauth.c | 55 ++++++++++++++++++-
gio/tests/gdbus-auth.c | 122 +++++++++++++++++++++++++++++++++++++++++
2 files changed, 175 insertions(+), 2 deletions(-)
diff --git a/gio/gdbusauth.c b/gio/gdbusauth.c
index e9a953a2c..a94a3e359 100644
--- a/gio/gdbusauth.c
+++ b/gio/gdbusauth.c
@@ -262,6 +262,22 @@ find_mech_by_name (GDBusAuth *auth,
return ret;
}
+static size_t
+get_longest_mechanism_name_length (GDBusAuth *auth)
+{
+ size_t len = 0;
+ GList *l;
+
+ for (l = auth->priv->available_mechanisms; l != NULL; l = l->next)
+ {
+ Mechanism *m = l->data;
+
+ len = MAX (len, strlen (m->name));
+ }
+
+ return len;
+}
+
GDBusAuth *
_g_dbus_auth_new (GIOStream *stream)
{
@@ -270,6 +286,20 @@ _g_dbus_auth_new (GIOStream *stream)
NULL);
}
+/* Arbitrarily chosen limit on the length of a DATA command payload, to prevent
+ * unbounded reads from malicious clients.
+ *
+ * - The ANONYMOUS mechanism doesnt use DATA.
+ * - The EXTERNAL mechanism just uses it to transfer a decimal-encoded UID.
+ * - The DBUS_COOKIE_SHA1 mechanism transfers a challenge and a SHA1 hash. The
+ * hash is bounded in length, but the challenge is not, so could potentially
+ * hit this limit. It doesnt seem unreasonable to bound the challenge to
+ * ~4KB though. GDBus itself generates a 16 byte challenge.
+ *
+ * See https://dbus.freedesktop.org/doc/dbus-specification.html#auth-command-data
+ */
+#define MAX_DATA_PAYLOAD_LENGTH_BYTES 4096
+
/* ---------------------------------------------------------------------------------------------------- */
/* like g_data_input_stream_read_line() but sets error if there's no content to read */
static gchar *
@@ -307,6 +337,7 @@ _my_g_data_input_stream_read_line (GDataInputStream *dis,
*/
static gchar *
_my_g_input_stream_read_line_safe (GInputStream *i,
+ size_t max_line_length,
gsize *out_line_length,
GCancellable *cancellable,
GError **error)
@@ -316,11 +347,22 @@ _my_g_input_stream_read_line_safe (GInputStream *i,
gssize num_read;
gboolean last_was_cr;
+ g_assert (max_line_length <= SIZE_MAX - 2);
+
str = g_string_new (NULL);
last_was_cr = FALSE;
while (TRUE)
{
+ if (str->len >= max_line_length + 2 /* allow for \r\n */)
+ {
+ g_set_error_literal (error,
+ G_IO_ERROR,
+ G_IO_ERROR_FAILED,
+ _("Malformed D-Bus authentication line"));
+ goto fail;
+ }
+
num_read = g_input_stream_read (i,
&c,
1,
@@ -1073,7 +1115,11 @@ _g_dbus_auth_run_server (GDBusAuth *auth,
{
case SERVER_STATE_WAITING_FOR_AUTH:
debug_print ("SERVER: WaitingForAuth");
- line = _my_g_data_input_stream_read_line (dis, &line_length, cancellable, error);
+ line = _my_g_input_stream_read_line_safe (g_io_stream_get_input_stream (auth->priv->stream),
+ strlen ("AUTH ") + get_longest_mechanism_name_length (auth) + strlen (" ") + MAX_DATA_PAYLOAD_LENGTH_BYTES,
+ &line_length,
+ cancellable,
+ error);
debug_print ("SERVER: WaitingForAuth, read '%s'", line);
if (line == NULL)
goto out;
@@ -1272,7 +1318,11 @@ _g_dbus_auth_run_server (GDBusAuth *auth,
case SERVER_STATE_WAITING_FOR_DATA:
debug_print ("SERVER: WaitingForData");
- line = _my_g_data_input_stream_read_line (dis, &line_length, cancellable, error);
+ line = _my_g_input_stream_read_line_safe (g_io_stream_get_input_stream (auth->priv->stream),
+ strlen ("DATA ") + MAX_DATA_PAYLOAD_LENGTH_BYTES,
+ &line_length,
+ cancellable,
+ error);
debug_print ("SERVER: WaitingForData, read '%s'", line);
if (line == NULL)
goto out;
@@ -1319,6 +1369,7 @@ _g_dbus_auth_run_server (GDBusAuth *auth,
* appears after "BEGIN\r\n"....)
*/
line = _my_g_input_stream_read_line_safe (g_io_stream_get_input_stream (auth->priv->stream),
+ MAX (strlen ("BEGIN"), strlen ("NEGOTIATE_UNIX_FD")),
&line_length,
cancellable,
error);
diff --git a/gio/tests/gdbus-auth.c b/gio/tests/gdbus-auth.c
index f44e932e2..0fb780c1e 100644
--- a/gio/tests/gdbus-auth.c
+++ b/gio/tests/gdbus-auth.c
@@ -278,6 +278,127 @@ temp_dbus_keyrings_teardown (void)
g_unsetenv ("G_DBUS_COOKIE_SHA1_KEYRING_DIR_IGNORE_PERMISSION");
}
+static void
+async_result_cb (GObject *obj,
+ GAsyncResult *result,
+ void *user_data)
+{
+ GAsyncResult **result_out = user_data;
+
+ g_assert (result_out != NULL);
+ g_assert (*result_out == NULL);
+
+ *result_out = g_object_ref (result);
+ g_main_context_wakeup (g_main_context_get_thread_default ());
+}
+
+static gboolean
+server_new_connection_unexpected_cb (GDBusServer *server,
+ GDBusConnection *connection,
+ void *user_data)
+{
+ g_assert_not_reached ();
+ return FALSE;
+}
+
+static void
+test_auth_server_read_limit (void)
+{
+ GDBusServer *server = NULL;
+ unsigned long new_connection_id = 0;
+ const char *server_address;
+ GIOStream *client_stream = NULL;
+ GOutputStream *client_output_stream;
+ GInputStream *client_input_stream;
+ GAsyncResult *result = NULL;
+ char *write_buffer = NULL;
+ char read_buffer[100];
+ ssize_t read_len;
+ size_t bytes_written;
+ GError *local_error = NULL;
+
+ g_test_bug ("https://gitlab.gnome.org/GNOME/glib/-/issues/3985");
+
+ server = server_new_for_mechanism (NULL);
+
+ new_connection_id = g_signal_connect (server,
+ "new-connection",
+ G_CALLBACK (server_new_connection_unexpected_cb),
+ NULL);
+ server_address = g_dbus_server_get_client_address (server);
+ g_dbus_server_start (server);
+
+ /* Start connecting as a client */
+ g_dbus_address_get_stream (server_address, NULL, async_result_cb, &result);
+
+ while (result == NULL)
+ g_main_context_iteration (NULL, TRUE);
+
+ client_stream = g_dbus_address_get_stream_finish (result, NULL, &local_error);
+ g_assert_no_error (local_error);
+ g_clear_object (&result);
+
+ /* Send an over-long AUTH line, maliciously */
+ client_output_stream = g_io_stream_get_output_stream (client_stream);
+ client_input_stream = g_io_stream_get_input_stream (client_stream);
+
+ write_buffer = g_strdup_printf ("AUTH DBUS_COOKIE_SHA1 context%0*d 123 456\r\n", 5000, 0);
+
+ g_output_stream_write_all_async (client_output_stream,
+ write_buffer,
+ strlen (write_buffer),
+ G_PRIORITY_DEFAULT,
+ NULL,
+ async_result_cb,
+ &result);
+
+ while (result == NULL)
+ g_main_context_iteration (NULL, TRUE);
+
+ g_output_stream_write_all_finish (client_output_stream, result, &bytes_written, &local_error);
+ g_assert_no_error (local_error);
+ g_assert_cmpuint (bytes_written, ==, strlen (write_buffer));
+ g_clear_object (&result);
+
+ g_clear_pointer (&write_buffer, g_free);
+
+ /* Authentication should have been rejected, so reading or writing the stream
+ * should now fail. */
+ read_len = g_input_stream_read (client_input_stream,
+ read_buffer,
+ sizeof (read_buffer),
+ NULL,
+ &local_error);
+ g_assert_error (local_error, G_IO_ERROR, G_IO_ERROR_CONNECTION_CLOSED);
+ g_assert_cmpint (read_len, <, 0);
+ g_clear_error (&local_error);
+
+ write_buffer = g_strdup_printf ("AUTH\r\n");
+
+ g_output_stream_write_all (client_output_stream,
+ write_buffer,
+ strlen (write_buffer),
+ &bytes_written,
+ NULL,
+ &local_error);
+ g_assert_error (local_error, G_IO_ERROR, G_IO_ERROR_CONNECTION_CLOSED);
+ g_assert_cmpuint (bytes_written, ==, 0);
+ g_clear_error (&local_error);
+
+ g_clear_pointer (&write_buffer, g_free);
+
+ /* Cleanup */
+ g_clear_object (&client_stream);
+ g_dbus_server_stop (server);
+
+ if (new_connection_id != 0)
+ {
+ g_signal_handler_disconnect (server, new_connection_id);
+ new_connection_id = 0;
+ }
+ g_clear_object (&server);
+}
+
/* ---------------------------------------------------------------------------------------------------- */
int
@@ -297,6 +418,7 @@ main (int argc,
g_test_add_func ("/gdbus/auth/server/ANONYMOUS", auth_server_anonymous);
g_test_add_func ("/gdbus/auth/server/EXTERNAL", auth_server_external);
g_test_add_func ("/gdbus/auth/server/DBUS_COOKIE_SHA1", auth_server_dbus_cookie_sha1);
+ g_test_add_func ("/gdbus/auth/server/read-limit", test_auth_server_read_limit);
/* TODO: we currently don't have tests for
*
From c50053362f01e6927f9347532a93e32074495bde Mon Sep 17 00:00:00 2001
From: RHEL Packaging Agent <redhat-ymir-agent@redhat.com>
Date: Thu, 30 Jul 2026 12:09:40 +0000
Subject: [PATCH 2/2] Fix g_test_bug usage for GLib 2.56.x compatibility
In GLib 2.56.x, g_test_bug() requires g_test_bug_base() to be called first
to set the base URI. The upstream test used a full URL in g_test_bug() which
is only supported in newer GLib versions. Fix by adding g_test_bug_base()
in main() and using just the issue number in g_test_bug().
---
gio/tests/gdbus-auth.c | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/gio/tests/gdbus-auth.c b/gio/tests/gdbus-auth.c
index 0fb780c1e..53898dbeb 100644
--- a/gio/tests/gdbus-auth.c
+++ b/gio/tests/gdbus-auth.c
@@ -317,7 +317,7 @@ test_auth_server_read_limit (void)
size_t bytes_written;
GError *local_error = NULL;
- g_test_bug ("https://gitlab.gnome.org/GNOME/glib/-/issues/3985");
+ g_test_bug ("3985");
server = server_new_for_mechanism (NULL);
@@ -412,6 +412,7 @@ main (int argc,
temp_dbus_keyrings_setup ();
g_test_init (&argc, &argv, NULL);
+ g_test_bug_base ("https://gitlab.gnome.org/GNOME/glib/-/issues/");
g_test_add_func ("/gdbus/auth/client/EXTERNAL", auth_client_external);
g_test_add_func ("/gdbus/auth/client/DBUS_COOKIE_SHA1", auth_client_dbus_cookie_sha1);

View File

@ -0,0 +1,99 @@
From 1e659071a5138209dfefc647eebccbca43250434 Mon Sep 17 00:00:00 2001
From: Philip Withnall <pwithnall@gnome.org>
Date: Sun, 29 Mar 2026 19:10:41 +0100
Subject: [PATCH] gvariant: Fix an off-by-one error in an offset comparison
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
This allows a single byte out-of-bounds read off the end of the
(potentially untrusted) byte array backing a `GVariant` when its
being checked for normal form.
I cant see how this could practically be exploited, but its certainly
a security bug as the `GVariant` normal form checking code is supposed
to be robust to malicious inputs.
Spotted by linhlhq as #YWH-PGM9867-190, and fix and reproducer provided
by them too, thanks. Confirmed and turned into a unit test by me.
Signed-off-by: Philip Withnall <pwithnall@gnome.org>
Fixes: #3915
---
glib/gvariant-serialiser.c | 2 +-
glib/tests/gvariant.c | 41 ++++++++++++++++++++++++++++++++++++++
2 files changed, 42 insertions(+), 1 deletion(-)
diff --git a/glib/gvariant-serialiser.c b/glib/gvariant-serialiser.c
index c6e400b53..9e862e50b 100644
--- a/glib/gvariant-serialiser.c
+++ b/glib/gvariant-serialiser.c
@@ -1078,7 +1078,7 @@ gvs_tuple_is_normal (GVariantSerialised value)
while (offset & alignment)
{
- if (offset > value.size || value.data[offset] != '\0')
+ if (offset >= value.size || value.data[offset] != '\0')
return FALSE;
offset++;
}
diff --git a/glib/tests/gvariant.c b/glib/tests/gvariant.c
index c4a996c1f..4d3186e46 100644
--- a/glib/tests/gvariant.c
+++ b/glib/tests/gvariant.c
@@ -4842,6 +4842,45 @@ test_normal_checking_tuple_offsets (void)
g_variant_unref (variant);
}
+/* This is a regression test that looping over the padding bytes in a short
+ * (non-normal) tuple doesn't overflow the input data.
+ *
+ * See https://gitlab.gnome.org/GNOME/glib/-/issues/3915 */
+static void
+test_normal_checking_tuple_offsets6 (void)
+{
+ /*
+ * Type: (ynqiuxthdsog) - 12 members, first member 'y' (byte) has
+ * alignment 0, second 'n' (int16) has alignment 1.
+ * With 1 byte of data (0x28), after reading the first byte member,
+ * offset=1, alignment check for 'n' requires offset to be even,
+ * so the while loop checks value.data[1] - but size is only 1.
+ *
+ * Use heap allocation via GBytes so ASan reports heap-buffer-overflow.
+ */
+ guint8 *heap_data = NULL;
+ GBytes *bytes = NULL;
+ const GVariantType *data_type = G_VARIANT_TYPE ("(ynqiuxthdsog)");
+ GVariant *variant = NULL;
+ GVariant *normal_variant = NULL;
+
+ heap_data = g_malloc (1);
+ heap_data[0] = 0x28;
+ bytes = g_bytes_new_take (heap_data, 1);
+
+ variant = g_variant_new_from_bytes (data_type, bytes, FALSE);
+ g_assert_nonnull (variant);
+
+ g_assert_false (g_variant_is_normal_form (variant));
+
+ normal_variant = g_variant_get_normal_form (variant);
+ g_assert_nonnull (normal_variant);
+
+ g_bytes_unref (bytes);
+ g_variant_unref (normal_variant);
+ g_variant_unref (variant);
+}
+
/* Test that an empty object path is normalised successfully to the base object
* path, /. */
static void
@@ -4941,6 +4980,8 @@ main (int argc, char **argv)
test_normal_checking_array_offsets);
g_test_add_func ("/gvariant/normal-checking/tuple-offsets",
test_normal_checking_tuple_offsets);
+ g_test_add_func ("/gvariant/normal-checking/tuple-offsets6",
+ test_normal_checking_tuple_offsets6);
g_test_add_func ("/gvariant/normal-checking/empty-object-path",
test_normal_checking_empty_object_path);

View File

@ -0,0 +1,122 @@
From 0a26e16a79b589f0fb534612a3febaf089cef981 Mon Sep 17 00:00:00 2001
From: Philip Withnall <pwithnall@gnome.org>
Date: Sun, 29 Mar 2026 23:19:47 +0100
Subject: [PATCH 1/2] gdatetime: Factor out a couple of magic constants
This introduces no functional changes, it just makes the code a little
clearer.
Signed-off-by: Philip Withnall <pwithnall@gnome.org>
---
glib/gdatetime.c | 9 ++++++---
1 file changed, 6 insertions(+), 3 deletions(-)
diff --git a/glib/gdatetime.c b/glib/gdatetime.c
index ba8503813..6f9cff5c0 100644
--- a/glib/gdatetime.c
+++ b/glib/gdatetime.c
@@ -123,7 +123,7 @@ struct _GDateTime
gint interval;
/* 1 is 0001-01-01 in Proleptic Gregorian */
- gint32 days;
+ gint32 days; /* in range [MIN_DAYS, MAX_DAYS] */
volatile gint ref_count;
};
@@ -159,6 +159,9 @@ struct _GDateTime
#define JULIAN_YEAR(d) ((d)->julian / 365.25)
#define DAYS_PER_PERIOD (G_GINT64_CONSTANT (2914695))
+#define MIN_DAYS 1 /* the days count for 0001-01-01 in Proleptic Gregorian */
+#define MAX_DAYS 3652059 /* the days count for 9999-12-31 in Proleptic Gregorian */
+
static const guint16 days_in_months[2][13] =
{
{ 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 },
@@ -768,7 +771,7 @@ g_date_time_from_instant (GTimeZone *tz,
datetime->days = instant / USEC_PER_DAY;
datetime->usec = instant % USEC_PER_DAY;
- if (datetime->days < 1 || 3652059 < datetime->days)
+ if (datetime->days < MIN_DAYS || datetime->days > MAX_DAYS)
{
g_date_time_unref (datetime);
datetime = NULL;
@@ -804,7 +807,7 @@ g_date_time_deal_with_date_change (GDateTime *datetime)
gint64 full_time;
gint64 usec;
- if (datetime->days < 1 || datetime->days > 3652059)
+ if (datetime->days < MIN_DAYS || datetime->days > MAX_DAYS)
return FALSE;
was_dst = g_time_zone_is_dst (datetime->tz, datetime->interval);
From 727ab7692db7e15889c4ffd455cfac498c7c3ada Mon Sep 17 00:00:00 2001
From: Philip Withnall <pwithnall@gnome.org>
Date: Sun, 29 Mar 2026 23:46:17 +0100
Subject: [PATCH 2/2] gdatetime: Add missing range validation to
g_date_time_add_full()
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Otherwise its possible to create a non-`NULL` but invalid `GDateTime`,
which breaks all kinds of internal assumptions.
Spotted by linhlhq as #YWH-PGM9867-191. Thanks to them for providing a
suggested fix and a test case, which I have adapted and validated.
Signed-off-by: Philip Withnall <pwithnall@gnome.org>
Fixes: #3917
---
glib/gdatetime.c | 4 +++-
glib/tests/gdatetime.c | 18 ++++++++++++++++++
2 files changed, 21 insertions(+), 1 deletion(-)
diff --git a/glib/gdatetime.c b/glib/gdatetime.c
index 6f9cff5c0..057746b17 100644
--- a/glib/gdatetime.c
+++ b/glib/gdatetime.c
@@ -1960,7 +1960,9 @@ g_date_time_add_full (GDateTime *datetime,
new->days = full_time / USEC_PER_DAY;
new->usec = full_time % USEC_PER_DAY;
- /* XXX validate */
+ /* Validate its still in the range 0001-01-01 to 9999-12-31 */
+ if (new->days < MIN_DAYS || new->days > MAX_DAYS)
+ g_clear_pointer (&new, g_date_time_unref);
return new;
}
diff --git a/glib/tests/gdatetime.c b/glib/tests/gdatetime.c
index d0755e722..b0bda25a3 100644
--- a/glib/tests/gdatetime.c
+++ b/glib/tests/gdatetime.c
@@ -925,6 +925,24 @@ test_GDateTime_add_full (void)
TEST_ADD_FULL (2010, 8, 25, 22, 45, 0,
0, 1, 6, 1, 25, 0,
2010, 10, 2, 0, 10, 0);
+
+#define TEST_ADD_FULL_ERROR(y,m,d,h,mi,s,ay,am,ad,ah,ami,as) G_STMT_START { \
+ GDateTime *dt; \
+ dt = g_date_time_new_utc (y, m, d, h, mi, s); \
+ g_assert_null (g_date_time_add_full (dt, ay, am, ad, ah, ami, as)); \
+ g_date_time_unref (dt); \
+} G_STMT_END
+
+ TEST_ADD_FULL_ERROR ( 1, 12, 1, 0, 0, 0,
+ -1, 0, 0, 0, 0, 0);
+ TEST_ADD_FULL_ERROR ( 1, 12, 1, 0, 0, 0,
+ 10000, 0, 0, 0, 0, 0);
+ TEST_ADD_FULL_ERROR ( 9999, 12, 1, 0, 0, 0,
+ -10000, 0, 0, 0, 0, 0);
+ TEST_ADD_FULL_ERROR ( 1, 12, 1, 0, 0, 0,
+ 0, 0, 3660001, 0, 0, 0);
+ TEST_ADD_FULL_ERROR ( 9999, 12, 1, 0, 0, 0,
+ 0, 0, -3660001, 0, 0, 0);
}
static void

View 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 (&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);
+
+ 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[])
{
@@ -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);

View File

@ -0,0 +1,127 @@
From bb5c37897584ca09c583161f908046edc2ec9d85 Mon Sep 17 00:00:00 2001
From: Philip Withnall <pwithnall@gnome.org>
Date: Tue, 28 Apr 2026 16:45:14 +0100
Subject: [PATCH] giochannel: Fix memcmp() off the end of the buffer with long
terminators
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
If the line terminator is longer than a single byte, and the current
line extends to the end of the buffer, and the buffer (which is a
`GString`) is near a power of two in length (as thats how `GString`s
are allocated) its possible for the `memcmp()` which checks the
terminator to read off the end of the string buffer.
Fix that by checking the terminator length against the last character
before calling `memcmp()`. Add a unit test.
Spotted by linhlhq as #YWH-PGM9867-199. The fix is theirs (validated by
me), and the unit test is adapted from their proof of concept.
Signed-off-by: Philip Withnall <pwithnall@gnome.org>
Fixes: #3925
---
glib/giochannel.c | 3 +-
glib/tests/io-channel.c | 78 +++++++++++++++++++++++++++++++++++++++++
2 files changed, 80 insertions(+), 1 deletion(-)
create mode 100644 glib/tests/io-channel.c
diff --git a/glib/giochannel.c b/glib/giochannel.c
index f01817a83..32a1550b7 100644
--- a/glib/giochannel.c
+++ b/glib/giochannel.c
@@ -1807,7 +1807,8 @@ read_again:
{
if (channel->line_term)
{
- if (memcmp (channel->line_term, nextchar, line_term_len) == 0)
+ if ((size_t) (lastchar - nextchar) >= line_term_len &&
+ memcmp (channel->line_term, nextchar, line_term_len) == 0)
{
line_length = nextchar - use_buf->str;
got_term_len = line_term_len;
diff --git a/glib/tests/io-channel.c b/glib/tests/io-channel.c
new file mode 100644
index 000000000..447675ee8
--- /dev/null
+++ b/glib/tests/io-channel.c
@@ -0,0 +1,78 @@
+/* Test for CVE-2026-58013: memcmp() off the end of the buffer with long terminators
+ *
+ * Copyright © 2026 Philip Withnall
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#include <glib.h>
+#include <glib/gstdio.h>
+#include <string.h>
+
+static void
+test_read_line_long_terminator (void)
+{
+ guint8 *test_data = NULL;
+ gsize test_data_len = 0;
+ gint fd;
+ gchar *filename = NULL;
+ GIOChannel *channel = NULL;
+ GError *local_error = NULL;
+ gchar *line = NULL;
+ gsize line_length, terminator_pos;
+ const gchar *line_term;
+ gint line_term_length;
+ GIOStatus status;
+
+ /* Write out a temporary file containing 2047 bytes. This is enough to make it
+ * near the length of the GString buffer when read back in. */
+ fd = g_file_open_tmp ("glib-test-io-channel-XXXXXX", &filename, &local_error);
+ g_assert_no_error (local_error);
+ g_close (fd, NULL);
+ fd = -1;
+
+ test_data_len = 2047;
+ test_data = g_malloc (test_data_len);
+ memset (test_data, 'M', test_data_len);
+ g_file_set_contents (filename, (const gchar *) test_data, test_data_len, &local_error);
+ g_assert_no_error (local_error);
+
+ /* Create the channel. */
+ channel = g_io_channel_new_file (filename, "r", &local_error);
+ g_assert_no_error (local_error);
+
+ /* Use a long line terminator so it could potentially over-read the end of the buffer. */
+ g_io_channel_set_line_term (channel, "DEADBEEF", 8);
+
+ line_term = g_io_channel_get_line_term (channel, &line_term_length);
+ g_assert_cmpstr (line_term, ==, "DEADBEEF");
+ g_assert_cmpint (line_term_length, ==, 8);
+
+ g_io_channel_set_encoding (channel, "UTF-8", &local_error);
+ g_assert_no_error (local_error);
+
+ status = g_io_channel_read_line (channel, &line, &line_length,
+ &terminator_pos, &local_error);
+ g_assert_no_error (local_error);
+ g_assert_cmpint (status, ==, G_IO_STATUS_NORMAL);
+ g_assert_cmpuint (line_length, ==, 2047);
+ g_assert_cmpuint (terminator_pos, ==, 2047);
+ g_assert_cmpmem (line, line_length, test_data, test_data_len);
+
+ g_free (line);
+ g_io_channel_unref (channel);
+ g_free (test_data);
+ g_unlink (filename);
+ g_free (filename);
+}
+
+int
+main (int argc,
+ char *argv[])
+{
+ g_test_init (&argc, &argv, NULL);
+
+ g_test_add_func ("/io-channel/read-line/long-terminator", test_read_line_long_terminator);
+
+ return g_test_run ();
+}

View File

@ -0,0 +1,74 @@
From d3e1d6f9ea497bd1b6bb258c6d078f06b8cd4338 Mon Sep 17 00:00:00 2001
From: Philip Withnall <pwithnall@gnome.org>
Date: Sat, 11 Apr 2026 14:42:57 +0100
Subject: [PATCH] gkeyfile: Fix a one-byte heap under-read with
g_key_file_get_locale_string_list()
If this method was called on a key file key which has an empty value,
`len == 0` and this leads to a one-byte under-read off the start of the
key file buffer.
Spotted by linhlhq as #YWH-PGM9867-200. The suggested fix is theirs, and
the unit test is adapted from their report. I added the fuzzing test.
Signed-off-by: Philip Withnall <pwithnall@gnome.org>
Fixes: #3930
---
glib/gkeyfile.c | 2 +-
glib/tests/keyfile.c | 22 ++++++++++++++++++++++
2 files changed, 23 insertions(+), 1 deletion(-)
diff --git a/glib/gkeyfile.c b/glib/gkeyfile.c
index ae3bbbc1d..0ddde22db 100644
--- a/glib/gkeyfile.c
+++ b/glib/gkeyfile.c
@@ -2391,7 +2391,7 @@ g_key_file_get_locale_string_list (GKeyFile *key_file,
}
len = strlen (value);
- if (value[len - 1] == key_file->list_separator)
+ if (len > 0 && value[len - 1] == key_file->list_separator)
value[len - 1] = '\0';
list_separator[0] = key_file->list_separator;
diff --git a/glib/tests/keyfile.c b/glib/tests/keyfile.c
index 16f3b788b..d7c648260 100644
--- a/glib/tests/keyfile.c
+++ b/glib/tests/keyfile.c
@@ -751,6 +751,27 @@ test_locale_string (void)
g_free (old_locale);
}
+static void
+test_locale_string_empty (void)
+{
+ GKeyFile *keyfile = NULL;
+ GError *local_error = NULL;
+ const char *data =
+ "[valid]\n"
+ "key1=\n";
+
+ g_test_bug ("https://gitlab.gnome.org/GNOME/glib/-/issues/3930");
+
+ keyfile = g_key_file_new ();
+
+ g_key_file_load_from_data (keyfile, data, -1, G_KEY_FILE_NONE, &local_error);
+ g_assert_no_error (local_error);
+
+ check_locale_string_list_value (keyfile, "valid", "key1", NULL, NULL);
+
+ g_key_file_free (keyfile);
+}
+
static void
test_lists (void)
{
@@ -1745,6 +1766,7 @@ main (int argc, char *argv[])
g_test_add_func ("/keyfile/boolean", test_boolean);
g_test_add_func ("/keyfile/number", test_number);
g_test_add_func ("/keyfile/locale-string", test_locale_string);
+ g_test_add_func ("/keyfile/locale-string/empty", test_locale_string_empty);
g_test_add_func ("/keyfile/lists", test_lists);
g_test_add_func ("/keyfile/lists-set-get", test_lists_set_get);
g_test_add_func ("/keyfile/group-remove", test_group_remove);

View File

@ -0,0 +1,74 @@
From 2f486f021aa46d172c7c7e8eef60dc7e22ad49f2 Mon Sep 17 00:00:00 2001
From: RHEL Packaging Agent <redhat-ymir-agent@redhat.com>
Date: Mon, 20 Jul 2026 07:02:12 +0000
Subject: [PATCH] gdbusauthmechanismsha1: Validate cookie context
Without validation, the server could send a malicious context which
contains path traversal characters, allowing it to exfiltrate a SHA-1
hashed copy of arbitrary data from the client's file system.
To exploit this successfully would require the client to choose to
connect peer-to-peer to a malicious D-Bus server and to choose the SHA-1
authentication mechanism in preference to all the other mechanisms. This
is vanishingly unlikely.
Signed-off-by: Philip Withnall <pwithnall@gnome.org>
Fixes: #3931
---
gio/gdbusauthmechanismsha1.c | 35 +++++++++++++++++++++++++++++++++++
1 file changed, 35 insertions(+)
diff --git a/gio/gdbusauthmechanismsha1.c b/gio/gdbusauthmechanismsha1.c
index 0cbaf946d..85fb5edc8 100644
--- a/gio/gdbusauthmechanismsha1.c
+++ b/gio/gdbusauthmechanismsha1.c
@@ -1130,6 +1130,34 @@ initial_response = _g_dbus_win32_get_user_sid ();
return initial_response;
}
+/* Context names must be valid ASCII, nonzero length, and may not contain the
+ * characters slash ("/"), backslash ("\"), space (" "), newline ("\n"),
+ * carriage return ("\r"), tab ("\t"), or period (".").
+ *
+ * See https://dbus.freedesktop.org/doc/dbus-specification.html#auth-mechanisms-sha */
+static gboolean
+validate_cookie_context (const char *cookie_context)
+{
+ size_t i = 0;
+
+ g_return_val_if_fail (cookie_context != NULL, FALSE);
+
+ for (i = 0; cookie_context[i] != '\0'; i++)
+ {
+ if ((guint8) cookie_context[i] >= 128 ||
+ cookie_context[i] == '/' ||
+ cookie_context[i] == '\\' ||
+ cookie_context[i] == ' ' ||
+ cookie_context[i] == '\n' ||
+ cookie_context[i] == '\r' ||
+ cookie_context[i] == '\t' ||
+ cookie_context[i] == '.')
+ return FALSE;
+ }
+
+ return (i > 0);
+}
+
static void
mechanism_client_data_receive (GDBusAuthMechanism *mechanism,
const gchar *data,
@@ -1163,6 +1191,13 @@ mechanism_client_data_receive (GDBusAuthMechanism *mechanism,
}
cookie_context = tokens[0];
+ if (!validate_cookie_context (tokens[0]))
+ {
+ g_warning ("Malformed cookie_context '%s'", tokens[0]);
+ m->priv->state = G_DBUS_AUTH_MECHANISM_STATE_REJECTED;
+ goto out;
+ }
+
cookie_id = g_ascii_strtoll (tokens[1], &endp, 10);
if (*endp != '\0')
{

View File

@ -5,7 +5,7 @@
Name: glib2
Version: 2.56.4
Release: 170%{?dist}
Release: 177%{?dist}
Summary: A library of handy utility functions
License: LGPLv2+
@ -172,6 +172,27 @@ Patch35: CVE-2025-14512.patch
# https://github.com/GNOME/glib/commit/c9da977c178fbfc0e4caf99f9fdf5dc433d6fcc2
Patch36: CVE-2026-58016.patch
# https://gitlab.gnome.org/GNOME/glib/-/merge_requests/5171
Patch37: CVE-2026-58014.patch
# https://gitlab.gnome.org/GNOME/glib/-/commit/8b72ad09c874ddff122b3e67b3470c5e2eab7690
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
# https://gitlab.gnome.org/GNOME/glib/-/merge_requests/5131
Patch41: CVE-2026-58011.patch
# https://gitlab.gnome.org/GNOME/glib/-/commit/8338414f6560216efe67d3cbf549e32f8630252a
Patch42: CVE-2026-58010.patch
# https://gitlab.gnome.org/GNOME/glib/-/merge_requests/5240
Patch43: CVE-2026-15588.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,
@ -287,7 +308,8 @@ glib-compile-schemas %{_datadir}/glib-2.0/schemas &> /dev/null || :
glib-compile-schemas %{_datadir}/glib-2.0/schemas &> /dev/null || :
%check
make %{?_smp_mflags} check
# One job at a time. The tests are not safe to run in parallel until 2.60. See: glib!505
make check
%files -f glib20.lang
%license COPYING
@ -372,6 +394,32 @@ make %{?_smp_mflags} check
%{_datadir}/installed-tests
%changelog
* Thu Jul 30 2026 RHEL Packaging Agent <redhat-ymir-agent@redhat.com> - 2.56.4-177
- Fix CVE-2026-15588: limit D-Bus auth line read length
* Thu Jul 30 2026 RHEL Packaging Agent <redhat-ymir-agent@redhat.com> - 2.56.4-176
- Fix CVE-2026-58010: off-by-one in GVariant tuple offset checking
* Mon Jul 20 2026 RHEL Packaging Agent <redhat-ymir-agent@redhat.com> - 2.56.4-175
- Fix CVE-2026-58011: range validation in g_date_time_add_full()
- Resolves: RHEL-212187
* 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
* Mon Jul 20 2026 RHEL Packaging Agent <redhat-ymir-agent@redhat.com> - 2.56.4-172
- Fix CVE-2026-58015: validate D-Bus DBUS_COOKIE_SHA1 cookie context
- Resolves: RHEL-212254
* Sun Jul 12 2026 RHEL Packaging Agent <redhat-ymir-agent@redhat.com> - 2.56.4-171
- Fix one-byte heap under-read in g_key_file_get_locale_string_list()
- Resolves: RHEL-190587
* Thu Jul 09 2026 RHEL Packaging Agent <redhat-ymir-agent@redhat.com> - 2.56.4-170
- Add patch for CVE-2026-58016
- Resolves: RHEL-190622