From 654de02701ca5f4f8473cf78cb5770fb004d774a Mon Sep 17 00:00:00 2001 From: AlmaLinux RelEng Bot Date: Mon, 31 Aug 2026 15:52:47 -0400 Subject: [PATCH] import CS git glib2-2.56.4-177.el8_10 --- SOURCES/CVE-2026-15588.patch | 312 +++++++++++++++++++++++++++++++++++ SOURCES/CVE-2026-58010.patch | 99 +++++++++++ SOURCES/CVE-2026-58011.patch | 122 ++++++++++++++ SOURCES/CVE-2026-58012.patch | 221 +++++++++++++++++++++++++ SOURCES/CVE-2026-58013.patch | 127 ++++++++++++++ SOURCES/CVE-2026-58014.patch | 74 +++++++++ SOURCES/CVE-2026-58015.patch | 74 +++++++++ SPECS/glib2.spec | 52 +++++- 8 files changed, 1079 insertions(+), 2 deletions(-) create mode 100644 SOURCES/CVE-2026-15588.patch create mode 100644 SOURCES/CVE-2026-58010.patch create mode 100644 SOURCES/CVE-2026-58011.patch create mode 100644 SOURCES/CVE-2026-58012.patch create mode 100644 SOURCES/CVE-2026-58013.patch create mode 100644 SOURCES/CVE-2026-58014.patch create mode 100644 SOURCES/CVE-2026-58015.patch diff --git a/SOURCES/CVE-2026-15588.patch b/SOURCES/CVE-2026-15588.patch new file mode 100644 index 0000000..e7e20b8 --- /dev/null +++ b/SOURCES/CVE-2026-15588.patch @@ -0,0 +1,312 @@ +From fd2ce100affce9e822c81f51d66b2b4b93a76889 Mon Sep 17 00:00:00 2001 +From: Philip Withnall +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 isn’t trusted at this point, and there was previously nothing +limiting how long a line `GDBusAuth` would read. So an untrusted client +could exhaust the server’s 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 +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 doesn’t 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 doesn’t 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 +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); diff --git a/SOURCES/CVE-2026-58010.patch b/SOURCES/CVE-2026-58010.patch new file mode 100644 index 0000000..822a306 --- /dev/null +++ b/SOURCES/CVE-2026-58010.patch @@ -0,0 +1,99 @@ +From 1e659071a5138209dfefc647eebccbca43250434 Mon Sep 17 00:00:00 2001 +From: Philip Withnall +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 it’s +being checked for normal form. + +I can’t see how this could practically be exploited, but it’s 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 + +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); + diff --git a/SOURCES/CVE-2026-58011.patch b/SOURCES/CVE-2026-58011.patch new file mode 100644 index 0000000..43b0a37 --- /dev/null +++ b/SOURCES/CVE-2026-58011.patch @@ -0,0 +1,122 @@ +From 0a26e16a79b589f0fb534612a3febaf089cef981 Mon Sep 17 00:00:00 2001 +From: Philip Withnall +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 +--- + 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 +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 it’s 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 + +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 it’s 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 diff --git a/SOURCES/CVE-2026-58012.patch b/SOURCES/CVE-2026-58012.patch new file mode 100644 index 0000000..f4f16f7 --- /dev/null +++ b/SOURCES/CVE-2026-58012.patch @@ -0,0 +1,221 @@ +From 502cc4445106a6adc3bf21d47f5adf4c02165299 Mon Sep 17 00:00:00 2001 +From: Philip Withnall +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 + +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); diff --git a/SOURCES/CVE-2026-58013.patch b/SOURCES/CVE-2026-58013.patch new file mode 100644 index 0000000..d90f25a --- /dev/null +++ b/SOURCES/CVE-2026-58013.patch @@ -0,0 +1,127 @@ +From bb5c37897584ca09c583161f908046edc2ec9d85 Mon Sep 17 00:00:00 2001 +From: Philip Withnall +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 that’s how `GString`s +are allocated) it’s 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 +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 ++#include ++#include ++ ++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 (); ++} diff --git a/SOURCES/CVE-2026-58014.patch b/SOURCES/CVE-2026-58014.patch new file mode 100644 index 0000000..30a1f59 --- /dev/null +++ b/SOURCES/CVE-2026-58014.patch @@ -0,0 +1,74 @@ +From d3e1d6f9ea497bd1b6bb258c6d078f06b8cd4338 Mon Sep 17 00:00:00 2001 +From: Philip Withnall +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 + +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); diff --git a/SOURCES/CVE-2026-58015.patch b/SOURCES/CVE-2026-58015.patch new file mode 100644 index 0000000..187c141 --- /dev/null +++ b/SOURCES/CVE-2026-58015.patch @@ -0,0 +1,74 @@ +From 2f486f021aa46d172c7c7e8eef60dc7e22ad49f2 Mon Sep 17 00:00:00 2001 +From: RHEL Packaging Agent +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 + +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') + { diff --git a/SPECS/glib2.spec b/SPECS/glib2.spec index 3ccc740..52e83e0 100644 --- a/SPECS/glib2.spec +++ b/SPECS/glib2.spec @@ -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 - 2.56.4-177 +- Fix CVE-2026-15588: limit D-Bus auth line read length + +* Thu Jul 30 2026 RHEL Packaging Agent - 2.56.4-176 +- Fix CVE-2026-58010: off-by-one in GVariant tuple offset checking + +* Mon Jul 20 2026 RHEL Packaging Agent - 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 - 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 - 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 - 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 - 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 - 2.56.4-170 - Add patch for CVE-2026-58016 - Resolves: RHEL-190622