glib2/CVE-2026-15588.patch
RHEL Packaging Agent a06b1ba6d5 Fix CVE-2026-15588: limit D-Bus auth line read length
Backport upstream fix from GNOME/glib MR !5240 (commit 407349aa)
for CVE-2026-15588. The patch adds a line length limit in
GDBusAuth to prevent an untrusted D-Bus client from exhausting
server memory by sending arbitrarily long authentication lines.
A corresponding unit test is included.

CVE: CVE-2026-15588
Upstream patches:
 - https://gitlab.gnome.org/GNOME/glib/-/merge_requests/5240.patch
Resolves: RHEL-219128

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

Assisted-by: Ymir
2026-08-19 16:29:30 +00:00

264 lines
10 KiB
Diff
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

From 9bbe8d82a27f24ef371718bae8815cd7fca92def Mon Sep 17 00:00:00 2001
From: Philip Withnall <pwithnall@gnome.org>
Date: Sat, 4 Jul 2026 18:13:08 +0100
Subject: [PATCH] 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 | 44 +++++++++++++++
gio/tests/gdbus-auth.c | 119 +++++++++++++++++++++++++++++++++++++++++
2 files changed, 163 insertions(+)
diff --git a/gio/gdbusauth.c b/gio/gdbusauth.c
index cb84d0d64..72ffd0b4d 100644
--- a/gio/gdbusauth.c
+++ b/gio/gdbusauth.c
@@ -262,6 +262,21 @@ find_mech_by_name (GDBusAuth *auth,
return ret;
}
+static size_t
+get_longest_mechanism_name_length (GDBusAuth *auth)
+{
+ size_t len = 0;
+
+ for (GList *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 +285,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 +336,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 +346,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,
@@ -1075,6 +1116,7 @@ _g_dbus_auth_run_server (GDBusAuth *auth,
case SERVER_STATE_WAITING_FOR_AUTH:
debug_print ("SERVER: WaitingForAuth");
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);
@@ -1296,6 +1338,7 @@ _g_dbus_auth_run_server (GDBusAuth *auth,
case SERVER_STATE_WAITING_FOR_DATA:
debug_print ("SERVER: WaitingForData");
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);
@@ -1338,6 +1381,7 @@ _g_dbus_auth_run_server (GDBusAuth *auth,
case SERVER_STATE_WAITING_FOR_BEGIN:
debug_print ("SERVER: WaitingForBegin");
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 18288f36d..ed1894969 100644
--- a/gio/tests/gdbus-auth.c
+++ b/gio/tests/gdbus-auth.c
@@ -278,6 +278,124 @@ 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_summary ("Test that GDBusServer limits the lengths of reads it does during auth from a client");
+ 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);
+
+ g_clear_signal_handler (&new_connection_id, server);
+ g_clear_object (&server);
+}
+
/* ---------------------------------------------------------------------------------------------------- */
int
@@ -297,6 +415,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
*