From d3d44b114b1f8ce55e523e5f55c2f36e84a4141e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20=C3=85dahl?= Date: Mon, 26 May 2025 16:08:54 +0200 Subject: [PATCH 01/13] control: Allow controlling all daemon types Useful for gracefully terminating daemon without sending signals. Part-of: --- src/grd-control.c | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/src/grd-control.c b/src/grd-control.c index 858ab10a..284cb113 100644 --- a/src/grd-control.c +++ b/src/grd-control.c @@ -33,13 +33,25 @@ main (int argc, char **argv) { g_autoptr(GApplication) app = NULL; gboolean terminate = FALSE; + gboolean headless = FALSE; + gboolean system = FALSE; + gboolean handover = FALSE; GOptionEntry entries[] = { { "terminate", 0, 0, G_OPTION_ARG_NONE, &terminate, "Terminate the daemon", NULL }, + { "headless", 0, 0, G_OPTION_ARG_NONE, &headless, + "Control headless daemon", NULL }, +#if defined(HAVE_RDP) && defined(HAVE_LIBSYSTEMD) + { "system", 0, 0, G_OPTION_ARG_NONE, &system, + "Control system daemon", NULL }, + { "handover", 0, 0, G_OPTION_ARG_NONE, &handover, + "Control handover daemon", NULL }, +#endif /* HAVE_RDP && HAVE_LIBSYSTEMD */ { NULL } }; GError *error = NULL; GOptionContext *context; + const char *app_id; context = g_option_context_new ("- control gnome-remote-desktop"); g_option_context_add_main_entries (context, entries, NULL); @@ -56,7 +68,16 @@ main (int argc, char **argv) return 1; } - app = g_application_new (GRD_DAEMON_USER_APPLICATION_ID, 0); + if (headless) + app_id = GRD_DAEMON_HEADLESS_APPLICATION_ID; + else if (system) + app_id = GRD_DAEMON_SYSTEM_APPLICATION_ID; + else if (handover) + app_id = GRD_DAEMON_HANDOVER_APPLICATION_ID; + else + app_id = GRD_DAEMON_USER_APPLICATION_ID; + + app = g_application_new (app_id, G_APPLICATION_DEFAULT_FLAGS); if (!g_application_register (app, NULL, NULL)) { g_warning ("Failed to register with application\n"); -- GitLab From c87e8e3d4ca4212b43cb9689f6b6cb0dd4e73225 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20=C3=85dahl?= Date: Mon, 26 May 2025 16:09:45 +0200 Subject: [PATCH 02/13] daemon: Use GError auto pointer Part-of: --- src/grd-daemon.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/grd-daemon.c b/src/grd-daemon.c index c082c6b5..98954200 100644 --- a/src/grd-daemon.c +++ b/src/grd-daemon.c @@ -989,7 +989,7 @@ main (int argc, char **argv) }; g_autoptr (GOptionContext) option_context = NULL; g_autoptr (GrdDaemon) daemon = NULL; - GError *error = NULL; + g_autoptr (GError) error = NULL; GrdRuntimeMode runtime_mode; g_set_application_name (_("GNOME Remote Desktop")); @@ -999,7 +999,6 @@ main (int argc, char **argv) if (!g_option_context_parse (option_context, &argc, &argv, &error)) { g_printerr ("Invalid option: %s\n", error->message); - g_error_free (error); return EXIT_FAILURE; } @@ -1048,7 +1047,6 @@ main (int argc, char **argv) if (!daemon) { g_printerr ("Failed to initialize: %s\n", error->message); - g_error_free (error); return EXIT_FAILURE; } -- GitLab From 45a0466a710eb4b93c34a7dbb73add73abf51cce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20=C3=85dahl?= Date: Mon, 26 May 2025 17:04:45 +0200 Subject: [PATCH 03/13] rdp-server: Remove empty constructor vfunc No need to implement it if it doesn't do anything. Part-of: --- src/grd-rdp-server.c | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/grd-rdp-server.c b/src/grd-rdp-server.c index a2ce8ff5..e9cce4b4 100644 --- a/src/grd-rdp-server.c +++ b/src/grd-rdp-server.c @@ -490,12 +490,6 @@ grd_rdp_server_dispose (GObject *object) G_OBJECT_CLASS (grd_rdp_server_parent_class)->dispose (object); } -static void -grd_rdp_server_constructed (GObject *object) -{ - G_OBJECT_CLASS (grd_rdp_server_parent_class)->constructed (object); -} - static void grd_rdp_server_init (GrdRdpServer *rdp_server) { @@ -518,7 +512,6 @@ grd_rdp_server_class_init (GrdRdpServerClass *klass) object_class->set_property = grd_rdp_server_set_property; object_class->get_property = grd_rdp_server_get_property; object_class->dispose = grd_rdp_server_dispose; - object_class->constructed = grd_rdp_server_constructed; g_object_class_install_property (object_class, PROP_CONTEXT, -- GitLab From 2125730374d70e0894f758440c56b5f31af80174 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20=C3=85dahl?= Date: Wed, 28 May 2025 14:08:12 +0200 Subject: [PATCH 04/13] rdp-sam: Dup fd kept in struct The SAM file instance intends to keeps the file descriptor alive so that it doesn't go away; however it failed to do this due to fdopen() taking ownership of it, thus closing it during fclose(). This meant we'd close an arbitrary potentially reused file descriptor when the SAM file instance eventually got cleaned up. Address this by reordering things a bit, while putting a duplicated file descriptor in the SAM file instance struct. Part-of: --- src/grd-rdp-sam.c | 28 +++++++++++++++++++--------- 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/src/grd-rdp-sam.c b/src/grd-rdp-sam.c index 5898825a..daa3ebb0 100644 --- a/src/grd-rdp-sam.c +++ b/src/grd-rdp-sam.c @@ -72,11 +72,12 @@ grd_rdp_sam_create_sam_file (const char *username, { const char *grd_path = "/gnome-remote-desktop"; const char *template = "/rdp-sam-XXXXXX"; + int duped_fd; GrdRdpSAMFile *rdp_sam_file; g_autofree char *file_dir = NULL; g_autofree char *filename = NULL; g_autofree char *sam_string = NULL; - int fd; + g_autofd int fd = -1; FILE *sam_file; file_dir = g_strdup_printf ("%s%s", g_get_user_runtime_dir (), grd_path); @@ -98,20 +99,29 @@ grd_rdp_sam_create_sam_file (const char *username, return NULL; } - rdp_sam_file = g_new0 (GrdRdpSAMFile, 1); - rdp_sam_file->fd = fd; - rdp_sam_file->filename = g_steal_pointer (&filename); - - sam_string = create_sam_string (username, password); - - sam_file = fdopen (rdp_sam_file->fd, "w+"); + sam_file = fdopen (fd, "w+"); if (!sam_file) { g_warning ("[RDP] Failed to open SAM database: %s", g_strerror (errno)); - grd_rdp_sam_free_sam_file (rdp_sam_file); return NULL; } + duped_fd = dup (fd); + if (duped_fd < 0) + { + fclose (sam_file); + g_warning ("[RDP] Failed to dup fd: %s", g_strerror (errno)); + return NULL; + } + + rdp_sam_file = g_new0 (GrdRdpSAMFile, 1); + rdp_sam_file->fd = duped_fd; + rdp_sam_file->filename = g_steal_pointer (&filename); + + g_steal_fd (&fd); + + sam_string = create_sam_string (username, password); + fputs (sam_string, sam_file); fclose (sam_file); -- GitLab From b16b81b7c37a42910f194dfe7808ab31aa00339b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20=C3=85dahl?= Date: Wed, 28 May 2025 22:33:54 +0200 Subject: [PATCH 05/13] rdp-server: Set socket backlog count to 5 This limits the number of pending connections. Part-of: --- src/grd-rdp-server.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/grd-rdp-server.c b/src/grd-rdp-server.c index e9cce4b4..36b55c98 100644 --- a/src/grd-rdp-server.c +++ b/src/grd-rdp-server.c @@ -36,6 +36,7 @@ #define RDP_SERVER_N_BINDING_ATTEMPTS 10 #define RDP_SERVER_BINDING_ATTEMPT_INTERVAL_MS 500 +#define RDP_SERVER_SOCKET_BACKLOG_COUNT 5 enum { @@ -320,6 +321,9 @@ bind_socket (GrdRdpServer *rdp_server, uint16_t selected_rdp_port = 0; gboolean negotiate_port; + g_socket_listener_set_backlog (G_SOCKET_LISTENER (rdp_server), + RDP_SERVER_SOCKET_BACKLOG_COUNT); + g_object_get (G_OBJECT (settings), "rdp-port", &rdp_port, "rdp-negotiate-port", &negotiate_port, -- GitLab From d26d49a7821968bd8277ae9ce558367cdfe136a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20=C3=85dahl?= Date: Wed, 28 May 2025 22:55:20 +0200 Subject: [PATCH 06/13] utils: Add some time conversion helpers Useful when converting between second, microseconds, etc. Part-of: --- src/grd-utils.h | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/grd-utils.h b/src/grd-utils.h index 30a3b9ec..099da563 100644 --- a/src/grd-utils.h +++ b/src/grd-utils.h @@ -99,3 +99,21 @@ gboolean grd_systemd_get_unit (GBusType bus_type, gboolean grd_systemd_unit_get_active_state (GDBusProxy *unit_proxy, GrdSystemdUnitActiveState *active_state, GError **error); + +static inline int64_t +us (int64_t us) +{ + return us; +} + +static inline int64_t +ms2us (int64_t ms) +{ + return us (ms * 1000); +} + +static inline int64_t +s2us (uint64_t s) +{ + return ms2us (s * 1000); +} -- GitLab From 97c7d6f3cdd816704afef5dc6e838fdb449a66f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20=C3=85dahl?= Date: Mon, 30 Jun 2025 16:16:31 +0200 Subject: [PATCH 07/13] utils: Add helper to close connection and notify Closing doesn't notify that it was closed, so lets add a helper that does so, that we then can uses to rely on the closed property being up to date. Part-of: --- src/grd-utils.c | 7 +++++++ src/grd-utils.h | 2 ++ 2 files changed, 9 insertions(+) diff --git a/src/grd-utils.c b/src/grd-utils.c index 32739a93..bf208828 100644 --- a/src/grd-utils.c +++ b/src/grd-utils.c @@ -478,3 +478,10 @@ grd_systemd_unit_get_active_state (GDBusProxy *unit_proxy, return TRUE; } + +void +grd_close_connection_and_notify (GSocketConnection *connection) +{ + g_io_stream_close (G_IO_STREAM (connection), NULL, NULL); + g_object_notify (G_OBJECT (connection), "closed"); +} diff --git a/src/grd-utils.h b/src/grd-utils.h index 099da563..4703db8f 100644 --- a/src/grd-utils.h +++ b/src/grd-utils.h @@ -100,6 +100,8 @@ gboolean grd_systemd_unit_get_active_state (GDBusProxy *unit_pro GrdSystemdUnitActiveState *active_state, GError **error); +void grd_close_connection_and_notify (GSocketConnection *connection); + static inline int64_t us (int64_t us) { -- GitLab From 6fdaa79c0215378867d3fd148a2d14b38be2843b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20=C3=85dahl?= Date: Wed, 28 May 2025 22:40:38 +0200 Subject: [PATCH 08/13] Introduce throttler class This handles the throttling of incoming connections, and is intended to act as a first line of defence against denial of service attacks. It implements the following: * It limits the global number of concurrent active connections * It limits the number of concurrent active connections per peer (roughly IP address) * It limits the number of new connections per second coming from a peer * It maintains a limited number of pending connections that is waiting to be handled This avoids running into situations where an uncontrolled number of connection attempts causes resources to be exhausted, e.g. number of file descriptor exceeding the process limit. The limits are currently hard coded. Related: CVE-2025-5024 Part-of: --- src/grd-throttler.c | 470 ++++++++++++++++++++++++++++++++++++++++++++ src/grd-throttler.h | 41 ++++ src/meson.build | 2 + 3 files changed, 513 insertions(+) create mode 100644 src/grd-throttler.c create mode 100644 src/grd-throttler.h diff --git a/src/grd-throttler.c b/src/grd-throttler.c new file mode 100644 index 00000000..a21563ae --- /dev/null +++ b/src/grd-throttler.c @@ -0,0 +1,470 @@ +/* + * Copyright (C) 2025 Red Hat + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + */ + +#include "config.h" + +#include "grd-throttler.h" + +#include "grd-utils.h" + +#define MAX_GLOBAL_CONNECTIONS 10 +#define MAX_CONNECTIONS_PER_PEER 5 +#define MAX_PENDING_CONNECTIONS 5 +#define MAX_ATTEMPTS_PER_SECOND 5 + +#define PRUNE_TIME_CUTOFF_US (s2us (1)) + +typedef struct _GrdPeer +{ + GrdThrottler *throttler; + char *name; + int active_connections; + GArray *connect_timestamps; + GQueue *delayed_connections; + int64_t last_accept_us; +} GrdPeer; + +struct _GrdThrottler +{ + GObject parent; + + int active_connections; + + GrdThrottlerAllowCallback allow_callback; + gpointer user_data; + + GHashTable *peers; + + GSource *delayed_connections_source; +}; + +G_DEFINE_TYPE (GrdThrottler, grd_throttler, G_TYPE_OBJECT) + +static GQuark quark_remote_address; + +static void +maybe_queue_timeout (GrdThrottler *throttler); + +static void +prune_old_timestamps (GArray *timestamps, + int64_t now_us) +{ + size_t i; + + if (!timestamps) + return; + + /* Prune all timestamps older than 1 second, so we can determine how many + * connections that happened the last second by looking at how many timestamps + * we have. + */ + for (i = 0; i < timestamps->len; i++) + { + int64_t ts_us = g_array_index (timestamps, int64_t, i); + + if (now_us - ts_us < PRUNE_TIME_CUTOFF_US) + break; + } + g_array_remove_range (timestamps, 0, i); +} + +static void +maybe_dispose_peer (GrdThrottler *throttler, + GrdPeer *peer, + GHashTableIter *iter) +{ + if (peer->active_connections > 0) + return; + + if (peer->connect_timestamps && peer->connect_timestamps->len > 0) + return; + + if (!g_queue_is_empty (peer->delayed_connections)) + return; + + if (iter) + g_hash_table_iter_remove (iter); + else + g_hash_table_remove (throttler->peers, peer->name); +} + +static void +grd_throttler_register_connection (GrdThrottler *throttler, + GrdPeer *peer) +{ + int64_t now_us; + + peer->active_connections++; + throttler->active_connections++; + + if (!peer->connect_timestamps) + peer->connect_timestamps = g_array_new (FALSE, FALSE, sizeof (int64_t)); + + now_us = g_get_monotonic_time (); + + prune_old_timestamps (peer->connect_timestamps, now_us); + g_array_append_val (peer->connect_timestamps, now_us); +} + +static void +grd_throttler_unregister_connection (GrdThrottler *throttler, + GSocketConnection *connection, + GrdPeer *peer) +{ + g_assert (peer->active_connections > 0); + g_assert (throttler->active_connections > 0); + + peer->active_connections--; + throttler->active_connections--; + + maybe_dispose_peer (throttler, peer, NULL); + maybe_queue_timeout (throttler); +} + +static void +grd_throttler_deny_connection (GrdThrottler *throttler, + const char *peer_name, + GSocketConnection *connection) +{ + g_debug ("Denying connection from %s", peer_name); + g_io_stream_close (G_IO_STREAM (connection), NULL, NULL); +} + +static void +on_connection_closed_changed (GSocketConnection *connection, + GParamSpec *pspec, + GrdThrottler *throttler) +{ + const char *peer_name; + GrdPeer *peer; + + g_assert (g_io_stream_is_closed (G_IO_STREAM (connection))); + + peer_name = g_object_get_qdata (G_OBJECT (connection), quark_remote_address); + peer = g_hash_table_lookup (throttler->peers, peer_name); + grd_throttler_unregister_connection (throttler, connection, peer); +} + +static void +grd_throttler_allow_connection (GrdThrottler *throttler, + GSocketConnection *connection, + GrdPeer *peer) +{ + g_debug ("Accepting connection from %s", peer->name); + + throttler->allow_callback (throttler, connection, throttler->user_data); + + peer->last_accept_us = g_get_monotonic_time (); + + g_object_set_qdata_full (G_OBJECT (connection), quark_remote_address, + g_strdup (peer->name), g_free); + grd_throttler_register_connection (throttler, peer); + g_signal_connect (connection, "notify::closed", + G_CALLBACK (on_connection_closed_changed), throttler); +} + +static gboolean +source_dispatch (GSource *source, + GSourceFunc callback, + gpointer user_data) +{ + g_source_set_ready_time (source, -1); + + return callback (user_data); +} + +static GSourceFuncs source_funcs = +{ + .dispatch = source_dispatch, +}; + +static void +prune_closed_connections (GQueue *queue) +{ + GList *l; + + l = queue->head; + while (l) + { + GSocketConnection *connection = G_SOCKET_CONNECTION (l->data); + GList *l_next = l->next; + + if (g_io_stream_is_closed (G_IO_STREAM (connection))) + { + g_queue_delete_link (queue, l); + g_object_unref (connection); + } + + l = l_next; + } +} + +static gboolean +is_connection_limit_reached (GrdThrottler *throttler, + GrdPeer *peer) +{ + if (peer->active_connections >= MAX_CONNECTIONS_PER_PEER) + return TRUE; + + if (throttler->active_connections >= MAX_GLOBAL_CONNECTIONS) + return TRUE; + + return FALSE; +} + +static gboolean +is_new_connection_allowed (GrdThrottler *throttler, + GrdPeer *peer) +{ + if (is_connection_limit_reached (throttler, peer)) + return FALSE; + + if (peer->connect_timestamps && + peer->connect_timestamps->len >= MAX_ATTEMPTS_PER_SECOND) + return FALSE; + + return TRUE; +} + +static gboolean +dispatch_delayed_connections (gpointer user_data) +{ + GrdThrottler *throttler = GRD_THROTTLER (user_data); + GHashTableIter iter; + gpointer key, value; + + g_hash_table_iter_init (&iter, throttler->peers); + while (g_hash_table_iter_next (&iter, &key, &value)) + { + GrdPeer *peer = value; + GQueue *queue = peer->delayed_connections; + GSocketConnection *connection; + + prune_closed_connections (queue); + connection = g_queue_peek_head (queue); + + if (!connection) + { + maybe_dispose_peer (throttler, peer, &iter); + continue; + } + + if (is_new_connection_allowed (throttler, peer)) + { + g_queue_pop_head (queue); + grd_throttler_allow_connection (throttler, connection, peer); + g_object_unref (connection); + } + } + + maybe_queue_timeout (throttler); + + return G_SOURCE_CONTINUE; +} + +static void +ensure_delayed_connections_source (GrdThrottler *throttler) +{ + if (throttler->delayed_connections_source) + return; + + throttler->delayed_connections_source = g_source_new (&source_funcs, + sizeof (GSource)); + g_source_set_callback (throttler->delayed_connections_source, + dispatch_delayed_connections, throttler, NULL); + g_source_attach (throttler->delayed_connections_source, NULL); + g_source_unref (throttler->delayed_connections_source); +} + +static void +maybe_queue_timeout (GrdThrottler *throttler) +{ + GHashTableIter iter; + gpointer key, value; + int64_t next_timeout_us = INT64_MAX; + + g_hash_table_iter_init (&iter, throttler->peers); + while (g_hash_table_iter_next (&iter, &key, &value)) + { + GrdPeer *peer = value; + + if (is_connection_limit_reached (throttler, peer)) + continue; + + if (g_queue_is_empty (peer->delayed_connections)) + continue; + + next_timeout_us = MIN (next_timeout_us, + peer->last_accept_us + + G_USEC_PER_SEC / MAX_ATTEMPTS_PER_SECOND); + } + + + if (next_timeout_us == INT64_MAX) + next_timeout_us = -1; + + if (next_timeout_us >= 0) + { + ensure_delayed_connections_source (throttler); + g_source_set_ready_time (throttler->delayed_connections_source, + next_timeout_us); + } +} + +static void +maybe_delay_connection (GrdThrottler *throttler, + GSocketConnection *connection, + GrdPeer *peer, + int64_t now_us) +{ + GQueue *delayed_connections; + + delayed_connections = peer->delayed_connections; + if (!delayed_connections) + { + delayed_connections = g_queue_new (); + peer->delayed_connections = delayed_connections; + } + + if (g_queue_get_length (delayed_connections) > MAX_PENDING_CONNECTIONS) + { + grd_throttler_deny_connection (throttler, peer->name, connection); + return; + } + + g_debug ("Delaying connection from %s", peer->name); + + g_queue_push_tail (delayed_connections, g_object_ref (connection)); + maybe_queue_timeout (throttler); +} + +static GrdPeer * +ensure_peer (GrdThrottler *throttler, + const char *peer_name) +{ + GrdPeer *peer; + + peer = g_hash_table_lookup (throttler->peers, peer_name); + if (peer) + return peer; + + peer = g_new0 (GrdPeer, 1); + peer->throttler = throttler; + peer->name = g_strdup (peer_name); + peer->delayed_connections = g_queue_new (); + + g_hash_table_insert (throttler->peers, + g_strdup (peer_name), peer); + + return peer; +} + +void +grd_throttler_handle_connection (GrdThrottler *throttler, + GSocketConnection *connection) +{ + g_autoptr (GError) error = NULL; + g_autoptr (GSocketAddress) remote_address = NULL; + GInetAddress *inet_address; + g_autofree char *peer_name = NULL; + GrdPeer *peer; + int64_t now_us; + + remote_address = g_socket_connection_get_remote_address (connection, &error); + if (!remote_address) + { + g_warning ("Failed to get remote address: %s", error->message); + grd_throttler_deny_connection (throttler, "unknown peer", connection); + return; + } + + inet_address = + g_inet_socket_address_get_address (G_INET_SOCKET_ADDRESS (remote_address)); + peer_name = g_inet_address_to_string (inet_address); + + g_debug ("New incoming connection from %s", peer_name); + + peer = ensure_peer (throttler, peer_name); + + prune_closed_connections (peer->delayed_connections); + + now_us = g_get_monotonic_time (); + prune_old_timestamps (peer->connect_timestamps, now_us); + if (is_new_connection_allowed (throttler, peer) && + g_queue_get_length (peer->delayed_connections) == 0) + { + grd_throttler_allow_connection (throttler, connection, peer); + return; + } + + maybe_delay_connection (throttler, connection, peer, now_us); +} + +GrdThrottler * +grd_throttler_new (GrdThrottlerAllowCallback allow_callback, + gpointer user_data) +{ + GrdThrottler *throttler; + + throttler = g_object_new (GRD_TYPE_THROTTLER, NULL); + throttler->allow_callback = allow_callback; + throttler->user_data = user_data; + + return throttler; +} + +static void +grd_peer_free (GrdPeer *peer) +{ + if (peer->delayed_connections) + g_queue_free_full (peer->delayed_connections, g_object_unref); + g_clear_pointer (&peer->connect_timestamps, g_array_unref); + g_clear_pointer (&peer->name, g_free); + g_free (peer); +} + +static void +grd_throttler_finalize (GObject *object) +{ + GrdThrottler *throttler = GRD_THROTTLER(object); + + g_clear_pointer (&throttler->delayed_connections_source, g_source_destroy); + g_clear_pointer (&throttler->peers, g_hash_table_unref); + + G_OBJECT_CLASS (grd_throttler_parent_class)->finalize (object); +} + +static void +grd_throttler_init (GrdThrottler *throttler) +{ + throttler->peers = + g_hash_table_new_full (g_str_hash, g_str_equal, + g_free, (GDestroyNotify) grd_peer_free); +} + +static void +grd_throttler_class_init (GrdThrottlerClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->finalize = grd_throttler_finalize; + + quark_remote_address = + g_quark_from_static_string ("grd-remote-address-string"); +} diff --git a/src/grd-throttler.h b/src/grd-throttler.h new file mode 100644 index 00000000..d57d653a --- /dev/null +++ b/src/grd-throttler.h @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2025 Red Hat + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + */ + +#ifndef GRD_THROTTLER_H +#define GRD_THROTTLER_H + +#include +#include + +#define GRD_TYPE_THROTTLER (grd_throttler_get_type()) +G_DECLARE_FINAL_TYPE (GrdThrottler, grd_throttler, GRD, THROTTLER, GObject) + +typedef void (* GrdThrottlerAllowCallback) (GrdThrottler *throttler, + GSocketConnection *connection, + gpointer user_data); + +void +grd_throttler_handle_connection (GrdThrottler *throttler, + GSocketConnection *connection); + +GrdThrottler * +grd_throttler_new (GrdThrottlerAllowCallback allow_callback, + gpointer user_data); + +#endif /* GRD_THROTTLER_H */ diff --git a/src/meson.build b/src/meson.build index a501977a..49e0509b 100644 --- a/src/meson.build +++ b/src/meson.build @@ -74,6 +74,8 @@ daemon_sources = files([ 'grd-settings-user.h', 'grd-stream.c', 'grd-stream.h', + 'grd-throttler.c', + 'grd-throttler.h', 'grd-types.h', 'grd-utils.c', 'grd-utils.h', -- GitLab From 959cd39ae528a6751d94b0dcff173a470a5bc7ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20=C3=85dahl?= Date: Wed, 28 May 2025 22:46:27 +0200 Subject: [PATCH 09/13] rdp-server: Throttle connections using GrdThrottler This avoids system resource exhaustion by a single attacker. Related: CVE-2025-5024 Part-of: --- src/grd-daemon-system.c | 4 ++ src/grd-rdp-server.c | 100 ++++++++++++++++++++++++++++++++-------- src/grd-session-rdp.c | 5 ++ 3 files changed, 91 insertions(+), 18 deletions(-) diff --git a/src/grd-daemon-system.c b/src/grd-daemon-system.c index 1e25a08b..e230ee85 100644 --- a/src/grd-daemon-system.c +++ b/src/grd-daemon-system.c @@ -35,6 +35,7 @@ #include "grd-rdp-server.h" #include "grd-session-rdp.h" #include "grd-settings.h" +#include "grd-utils.h" #define MAX_HANDOVER_WAIT_TIME_MS (30 * 1000) @@ -148,6 +149,7 @@ on_handle_take_client (GrdDBusRemoteDesktopRdpHandover *interface, fd_list, fd_variant); + grd_close_connection_and_notify (remote_client->socket_connection); g_clear_object (&remote_client->socket_connection); g_clear_handle_id (&remote_client->abort_handover_source_id, g_source_remove); @@ -524,6 +526,8 @@ static void grd_remote_client_free (GrdRemoteClient *remote_client) { g_clear_pointer (&remote_client->id, g_free); + if (remote_client->socket_connection) + grd_close_connection_and_notify (remote_client->socket_connection); g_clear_object (&remote_client->socket_connection); unregister_handover_iface (remote_client, remote_client->handover_src); unregister_handover_iface (remote_client, remote_client->handover_dst); diff --git a/src/grd-rdp-server.c b/src/grd-rdp-server.c index 36b55c98..5ae5002a 100644 --- a/src/grd-rdp-server.c +++ b/src/grd-rdp-server.c @@ -32,6 +32,7 @@ #include "grd-hwaccel-vulkan.h" #include "grd-rdp-routing-token.h" #include "grd-session-rdp.h" +#include "grd-throttler.h" #include "grd-utils.h" #define RDP_SERVER_N_BINDING_ATTEMPTS 10 @@ -60,6 +61,8 @@ struct _GrdRdpServer { GSocketService parent; + GrdThrottler *throttler; + GList *sessions; GList *stopped_sessions; @@ -216,18 +219,17 @@ on_routing_token_peeked (GObject *source_object, } } -static gboolean -on_incoming_as_system_headless (GSocketService *service, - GSocketConnection *connection) +static void +allow_connection_peek_cb (GrdThrottler *throttler, + GSocketConnection *connection, + gpointer user_data) { - GrdRdpServer *rdp_server = GRD_RDP_SERVER (service); + GrdRdpServer *rdp_server = GRD_RDP_SERVER (user_data); grd_routing_token_peek_async (rdp_server, connection, rdp_server->cancellable, on_routing_token_peeked); - - return TRUE; } static gboolean @@ -235,12 +237,22 @@ on_incoming (GSocketService *service, GSocketConnection *connection) { GrdRdpServer *rdp_server = GRD_RDP_SERVER (service); + + grd_throttler_handle_connection (rdp_server->throttler, + connection); + return TRUE; +} + +static void +accept_connection (GrdRdpServer *rdp_server, + GSocketConnection *connection) +{ GrdSessionRdp *session_rdp; - g_debug ("New incoming RDP connection"); + g_debug ("Creating new RDP session"); if (!(session_rdp = grd_session_rdp_new (rdp_server, connection))) - return TRUE; + return; rdp_server->sessions = g_list_append (rdp_server->sessions, session_rdp); @@ -251,15 +263,35 @@ on_incoming (GSocketService *service, g_signal_connect (session_rdp, "post-connected", G_CALLBACK (on_session_post_connect), rdp_server); +} - return TRUE; +static void +allow_connection_accept_cb (GrdThrottler *throttler, + GSocketConnection *connection, + gpointer user_data) +{ + GrdRdpServer *rdp_server = GRD_RDP_SERVER (user_data); + + accept_connection (rdp_server, connection); } void grd_rdp_server_notify_incoming (GSocketService *service, GSocketConnection *connection) { - on_incoming (service, connection); + GrdRdpServer *rdp_server = GRD_RDP_SERVER (service); + GrdRuntimeMode runtime_mode = grd_context_get_runtime_mode (rdp_server->context); + + switch (runtime_mode) + { + case GRD_RUNTIME_MODE_HANDOVER: + accept_connection (rdp_server, connection); + break; + case GRD_RUNTIME_MODE_SYSTEM: + case GRD_RUNTIME_MODE_SCREEN_SHARE: + case GRD_RUNTIME_MODE_HEADLESS: + g_assert_not_reached (); + } } static gboolean @@ -386,16 +418,13 @@ grd_rdp_server_start (GrdRdpServer *rdp_server, switch (runtime_mode) { - case GRD_RUNTIME_MODE_SCREEN_SHARE: - case GRD_RUNTIME_MODE_HEADLESS: - g_signal_connect (rdp_server, "incoming", G_CALLBACK (on_incoming), NULL); - break; case GRD_RUNTIME_MODE_SYSTEM: - g_signal_connect (rdp_server, "incoming", - G_CALLBACK (on_incoming_as_system_headless), NULL); - g_assert (!rdp_server->cancellable); rdp_server->cancellable = g_cancellable_new (); + G_GNUC_FALLTHROUGH; + case GRD_RUNTIME_MODE_SCREEN_SHARE: + case GRD_RUNTIME_MODE_HEADLESS: + g_signal_connect (rdp_server, "incoming", G_CALLBACK (on_incoming), NULL); break; case GRD_RUNTIME_MODE_HANDOVER: break; @@ -428,6 +457,8 @@ grd_rdp_server_stop (GrdRdpServer *rdp_server) g_clear_handle_id (&rdp_server->cleanup_sessions_idle_id, g_source_remove); grd_rdp_server_cleanup_stopped_sessions (rdp_server); + g_clear_object (&rdp_server->throttler); + if (rdp_server->cancellable) { g_cancellable_cancel (rdp_server->cancellable); @@ -487,6 +518,7 @@ grd_rdp_server_dispose (GObject *object) g_assert (!rdp_server->binding_timeout_source_id); g_assert (!rdp_server->cleanup_sessions_idle_id); g_assert (!rdp_server->stopped_sessions); + g_assert (!rdp_server->throttler); g_assert (!rdp_server->hwaccel_nvidia); g_assert (!rdp_server->hwaccel_vulkan); @@ -495,8 +527,32 @@ grd_rdp_server_dispose (GObject *object) } static void -grd_rdp_server_init (GrdRdpServer *rdp_server) +grd_rdp_server_constructed (GObject *object) { + GrdRdpServer *rdp_server = GRD_RDP_SERVER (object); + GrdRuntimeMode runtime_mode = + grd_context_get_runtime_mode (rdp_server->context); + GrdThrottlerAllowCallback allow_callback = NULL; + + switch (runtime_mode) + { + case GRD_RUNTIME_MODE_SCREEN_SHARE: + case GRD_RUNTIME_MODE_HEADLESS: + allow_callback = allow_connection_accept_cb; + break; + case GRD_RUNTIME_MODE_SYSTEM: + allow_callback = allow_connection_peek_cb; + break; + case GRD_RUNTIME_MODE_HANDOVER: + break; + } + + if (allow_callback) + { + rdp_server->throttler = grd_throttler_new (allow_callback, + rdp_server); + } + rdp_server->pending_binding_attempts = RDP_SERVER_N_BINDING_ATTEMPTS; winpr_InitializeSSL (WINPR_SSL_INIT_DEFAULT); @@ -506,6 +562,13 @@ grd_rdp_server_init (GrdRdpServer *rdp_server) * Run the primitives benchmark here to save time, when initializing a session */ primitives_get (); + + G_OBJECT_CLASS (grd_rdp_server_parent_class)->constructed (object); +} + +static void +grd_rdp_server_init (GrdRdpServer *rdp_server) +{ } static void @@ -516,6 +579,7 @@ grd_rdp_server_class_init (GrdRdpServerClass *klass) object_class->set_property = grd_rdp_server_set_property; object_class->get_property = grd_rdp_server_get_property; object_class->dispose = grd_rdp_server_dispose; + object_class->constructed = grd_rdp_server_constructed; g_object_class_install_property (object_class, PROP_CONTEXT, diff --git a/src/grd-session-rdp.c b/src/grd-session-rdp.c index 1d8784ec..7305827a 100644 --- a/src/grd-session-rdp.c +++ b/src/grd-session-rdp.c @@ -48,6 +48,7 @@ #include "grd-rdp-server.h" #include "grd-rdp-session-metrics.h" #include "grd-settings.h" +#include "grd-utils.h" #define MAX_MONITOR_COUNT_HEADLESS 16 #define MAX_MONITOR_COUNT_SCREEN_SHARE 1 @@ -1675,6 +1676,7 @@ grd_session_rdp_stop (GrdSession *session) g_clear_object (&session_rdp->renderer); peer->Close (peer); + grd_close_connection_and_notify (session_rdp->connection); g_clear_object (&session_rdp->connection); g_clear_object (&rdp_peer_context->network_autodetection); @@ -1858,6 +1860,9 @@ grd_session_rdp_dispose (GObject *object) g_clear_object (&session_rdp->layout_manager); clear_rdp_peer (session_rdp); + + if (session_rdp->connection) + grd_close_connection_and_notify (session_rdp->connection); g_clear_object (&session_rdp->connection); g_clear_object (&session_rdp->renderer); -- GitLab From 503dc304c90b99a4040f71dbf75a2d353ca77237 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20=C3=85dahl?= Date: Fri, 30 May 2025 16:54:00 +0200 Subject: [PATCH 10/13] throttler: Introduce limits struct This will be used by the server implementation to customize throttling limits. This will be used by the VNC server to limit to one global session at a time, replacing it's own existing condition. Part-of: --- src/grd-rdp-server.c | 3 ++- src/grd-throttler.c | 64 ++++++++++++++++++++++++++++++++++++-------- src/grd-throttler.h | 14 ++++++++-- 3 files changed, 67 insertions(+), 14 deletions(-) diff --git a/src/grd-rdp-server.c b/src/grd-rdp-server.c index 5ae5002a..8a4dfe59 100644 --- a/src/grd-rdp-server.c +++ b/src/grd-rdp-server.c @@ -549,7 +549,8 @@ grd_rdp_server_constructed (GObject *object) if (allow_callback) { - rdp_server->throttler = grd_throttler_new (allow_callback, + rdp_server->throttler = grd_throttler_new (grd_throttler_limits_new (), + allow_callback, rdp_server); } diff --git a/src/grd-throttler.c b/src/grd-throttler.c index a21563ae..5c404ed7 100644 --- a/src/grd-throttler.c +++ b/src/grd-throttler.c @@ -23,10 +23,18 @@ #include "grd-utils.h" -#define MAX_GLOBAL_CONNECTIONS 10 -#define MAX_CONNECTIONS_PER_PEER 5 -#define MAX_PENDING_CONNECTIONS 5 -#define MAX_ATTEMPTS_PER_SECOND 5 +#define DEFAULT_MAX_GLOBAL_CONNECTIONS 10 +#define DEFAULT_MAX_CONNECTIONS_PER_PEER 5 +#define DEFAULT_MAX_PENDING_CONNECTIONS 5 +#define DEFAULT_MAX_ATTEMPTS_PER_SECOND 10 + +struct _GrdThrottlerLimits +{ + int max_global_connections; + int max_connections_per_peer; + int max_pending_connections; + int max_attempts_per_second; +}; #define PRUNE_TIME_CUTOFF_US (s2us (1)) @@ -44,6 +52,8 @@ struct _GrdThrottler { GObject parent; + GrdThrottlerLimits *limits; + int active_connections; GrdThrottlerAllowCallback allow_callback; @@ -219,10 +229,12 @@ static gboolean is_connection_limit_reached (GrdThrottler *throttler, GrdPeer *peer) { - if (peer->active_connections >= MAX_CONNECTIONS_PER_PEER) + GrdThrottlerLimits *limits = throttler->limits; + + if (peer->active_connections >= limits->max_connections_per_peer) return TRUE; - if (throttler->active_connections >= MAX_GLOBAL_CONNECTIONS) + if (throttler->active_connections >= limits->max_global_connections) return TRUE; return FALSE; @@ -232,11 +244,13 @@ static gboolean is_new_connection_allowed (GrdThrottler *throttler, GrdPeer *peer) { + GrdThrottlerLimits *limits = throttler->limits; + if (is_connection_limit_reached (throttler, peer)) return FALSE; if (peer->connect_timestamps && - peer->connect_timestamps->len >= MAX_ATTEMPTS_PER_SECOND) + peer->connect_timestamps->len >= limits->max_attempts_per_second) return FALSE; return TRUE; @@ -295,6 +309,7 @@ ensure_delayed_connections_source (GrdThrottler *throttler) static void maybe_queue_timeout (GrdThrottler *throttler) { + GrdThrottlerLimits *limits = throttler->limits; GHashTableIter iter; gpointer key, value; int64_t next_timeout_us = INT64_MAX; @@ -312,7 +327,7 @@ maybe_queue_timeout (GrdThrottler *throttler) next_timeout_us = MIN (next_timeout_us, peer->last_accept_us + - G_USEC_PER_SEC / MAX_ATTEMPTS_PER_SECOND); + G_USEC_PER_SEC / limits->max_attempts_per_second); } @@ -333,6 +348,7 @@ maybe_delay_connection (GrdThrottler *throttler, GrdPeer *peer, int64_t now_us) { + GrdThrottlerLimits *limits = throttler->limits; GQueue *delayed_connections; delayed_connections = peer->delayed_connections; @@ -342,7 +358,7 @@ maybe_delay_connection (GrdThrottler *throttler, peer->delayed_connections = delayed_connections; } - if (g_queue_get_length (delayed_connections) > MAX_PENDING_CONNECTIONS) + if (g_queue_get_length (delayed_connections) > limits->max_pending_connections) { grd_throttler_deny_connection (throttler, peer->name, connection); return; @@ -416,15 +432,40 @@ grd_throttler_handle_connection (GrdThrottler *throttler, maybe_delay_connection (throttler, connection, peer, now_us); } +void +grd_throttler_limits_set_max_global_connections (GrdThrottlerLimits *limits, + int limit) +{ + limits->max_global_connections = limit; +} + +GrdThrottlerLimits * +grd_throttler_limits_new (void) +{ + GrdThrottlerLimits *limits; + + limits = g_new0 (GrdThrottlerLimits, 1); + limits->max_global_connections = DEFAULT_MAX_GLOBAL_CONNECTIONS; + limits->max_connections_per_peer = DEFAULT_MAX_CONNECTIONS_PER_PEER; + limits->max_pending_connections = DEFAULT_MAX_PENDING_CONNECTIONS; + limits->max_attempts_per_second = DEFAULT_MAX_ATTEMPTS_PER_SECOND; + + return limits; +} + GrdThrottler * -grd_throttler_new (GrdThrottlerAllowCallback allow_callback, - gpointer user_data) +grd_throttler_new (GrdThrottlerLimits *limits, + GrdThrottlerAllowCallback allow_callback, + gpointer user_data) { GrdThrottler *throttler; + g_assert (limits); + throttler = g_object_new (GRD_TYPE_THROTTLER, NULL); throttler->allow_callback = allow_callback; throttler->user_data = user_data; + throttler->limits = limits; return throttler; } @@ -446,6 +487,7 @@ grd_throttler_finalize (GObject *object) g_clear_pointer (&throttler->delayed_connections_source, g_source_destroy); g_clear_pointer (&throttler->peers, g_hash_table_unref); + g_clear_pointer (&throttler->limits, g_free); G_OBJECT_CLASS (grd_throttler_parent_class)->finalize (object); } diff --git a/src/grd-throttler.h b/src/grd-throttler.h index d57d653a..9e72b8f1 100644 --- a/src/grd-throttler.h +++ b/src/grd-throttler.h @@ -23,6 +23,8 @@ #include #include +typedef struct _GrdThrottlerLimits GrdThrottlerLimits; + #define GRD_TYPE_THROTTLER (grd_throttler_get_type()) G_DECLARE_FINAL_TYPE (GrdThrottler, grd_throttler, GRD, THROTTLER, GObject) @@ -34,8 +36,16 @@ void grd_throttler_handle_connection (GrdThrottler *throttler, GSocketConnection *connection); +void +grd_throttler_limits_set_max_global_connections (GrdThrottlerLimits *limits, + int limit); + +GrdThrottlerLimits * +grd_throttler_limits_new (void); + GrdThrottler * -grd_throttler_new (GrdThrottlerAllowCallback allow_callback, - gpointer user_data); +grd_throttler_new (GrdThrottlerLimits *limits, + GrdThrottlerAllowCallback allow_callback, + gpointer user_data); #endif /* GRD_THROTTLER_H */ -- GitLab From 555f2be6669d7a182a950866dd2a4d2d49299033 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20=C3=85dahl?= Date: Fri, 30 May 2025 16:55:46 +0200 Subject: [PATCH 11/13] vnc-server: Hook up VNC server to the throttler This allows us to replace existing single session limitation with limiting the throttler to one accepted connection at any given time. Part-of: --- src/grd-session-vnc.c | 2 ++ src/grd-vnc-server.c | 48 +++++++++++++++++++++++++++++++------------ 2 files changed, 37 insertions(+), 13 deletions(-) diff --git a/src/grd-session-vnc.c b/src/grd-session-vnc.c index f7e14080..7cd41e9f 100644 --- a/src/grd-session-vnc.c +++ b/src/grd-session-vnc.c @@ -34,6 +34,7 @@ #include "grd-prompt.h" #include "grd-settings.h" #include "grd-stream.h" +#include "grd-utils.h" #include "grd-vnc-server.h" #include "grd-vnc-pipewire-stream.h" @@ -827,6 +828,7 @@ grd_session_vnc_stop (GrdSession *session) grd_session_vnc_detach_source (session_vnc); + grd_close_connection_and_notify (session_vnc->connection); g_clear_object (&session_vnc->connection); g_clear_object (&session_vnc->clipboard_vnc); g_clear_pointer (&session_vnc->rfb_screen->frameBuffer, g_free); diff --git a/src/grd-vnc-server.c b/src/grd-vnc-server.c index 83220655..4cfbcad2 100644 --- a/src/grd-vnc-server.c +++ b/src/grd-vnc-server.c @@ -30,6 +30,7 @@ #include "grd-context.h" #include "grd-debug.h" #include "grd-session-vnc.h" +#include "grd-throttler.h" #include "grd-utils.h" enum @@ -43,6 +44,8 @@ struct _GrdVncServer { GSocketService parent; + GrdThrottler *throttler; + GList *sessions; GList *stopped_sessions; @@ -53,6 +56,11 @@ struct _GrdVncServer G_DEFINE_TYPE (GrdVncServer, grd_vnc_server, G_TYPE_SOCKET_SERVICE) +static void +allow_connection_cb (GrdThrottler *throttler, + GSocketConnection *connection, + gpointer user_data); + GrdContext * grd_vnc_server_get_context (GrdVncServer *vnc_server) { @@ -103,22 +111,15 @@ on_session_stopped (GrdSession *session, GrdVncServer *vnc_server) } } -static gboolean -on_incoming (GSocketService *service, - GSocketConnection *connection) +static void +allow_connection_cb (GrdThrottler *throttler, + GSocketConnection *connection, + gpointer user_data) { - GrdVncServer *vnc_server = GRD_VNC_SERVER (service); + GrdVncServer *vnc_server = GRD_VNC_SERVER (user_data); GrdSessionVnc *session_vnc; - g_debug ("New incoming VNC connection"); - - if (vnc_server->sessions) - { - /* TODO: Add the rfbScreen instance to GrdVncServer to support multiple - * sessions. */ - g_message ("Refusing new VNC connection: already an active session"); - return TRUE; - } + g_debug ("Creating new VNC session"); session_vnc = grd_session_vnc_new (vnc_server, connection); vnc_server->sessions = g_list_append (vnc_server->sessions, session_vnc); @@ -126,7 +127,16 @@ on_incoming (GSocketService *service, g_signal_connect (session_vnc, "stopped", G_CALLBACK (on_session_stopped), vnc_server); +} +static gboolean +on_incoming (GSocketService *service, + GSocketConnection *connection) +{ + GrdVncServer *vnc_server = GRD_VNC_SERVER (service); + + grd_throttler_handle_connection (vnc_server->throttler, + connection); return TRUE; } @@ -187,6 +197,8 @@ grd_vnc_server_stop (GrdVncServer *vnc_server) grd_vnc_server_cleanup_stopped_sessions (vnc_server); g_clear_handle_id (&vnc_server->cleanup_sessions_idle_id, g_source_remove); + + g_clear_object (&vnc_server->throttler); } static void @@ -235,6 +247,7 @@ grd_vnc_server_dispose (GObject *object) g_assert (!vnc_server->sessions); g_assert (!vnc_server->stopped_sessions); g_assert (!vnc_server->cleanup_sessions_idle_id); + g_assert (!vnc_server->throttler); G_OBJECT_CLASS (grd_vnc_server_parent_class)->dispose (object); } @@ -253,6 +266,15 @@ grd_vnc_server_constructed (GObject *object) static void grd_vnc_server_init (GrdVncServer *vnc_server) { + GrdThrottlerLimits *limits; + + limits = grd_throttler_limits_new (); + /* TODO: Add the rfbScreen instance to GrdVncServer to support multiple + * sessions. */ + grd_throttler_limits_set_max_global_connections (limits, 1); + vnc_server->throttler = grd_throttler_new (limits, + allow_connection_cb, + vnc_server); } static void -- GitLab From b14851df71b52b6cf685d8e39a6eb4425694f9b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20=C3=85dahl?= Date: Mon, 30 Jun 2025 16:01:12 +0200 Subject: [PATCH 12/13] daemon: Add missing newline to error message Part-of: --- src/grd-daemon.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/grd-daemon.c b/src/grd-daemon.c index 98954200..ea17df1d 100644 --- a/src/grd-daemon.c +++ b/src/grd-daemon.c @@ -1010,7 +1010,7 @@ main (int argc, char **argv) if (count_trues (3, headless, system, handover) > 1) { - g_printerr ("Invalid option: More than one runtime mode specified"); + g_printerr ("Invalid option: More than one runtime mode specified\n"); return EXIT_FAILURE; } -- GitLab From 23b0761f09f5d722cde6922c2a095f165820e63b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20=C3=85dahl?= Date: Mon, 30 Jun 2025 16:01:37 +0200 Subject: [PATCH 13/13] throttler: Allow overriding hard coded parallel connections limit Overriding is done via an argument passed to the daemon. This allows using the same daemon with a larger amount of users, which one would achieve by overriding the relevant systemd service file by both adding the --max-parallel-connections argument, as well as bumping the limit of number of open file descriptors to something adequate. Part-of: --- src/grd-daemon.c | 24 ++++++++++++++++++++++++ src/grd-rdp-server.c | 7 ++++--- src/grd-settings.c | 19 +++++++++++++++++++ src/grd-settings.h | 5 +++++ src/grd-throttler.c | 9 ++++++--- src/grd-throttler.h | 4 +++- src/grd-vnc-server.c | 20 +++++++++++--------- 7 files changed, 72 insertions(+), 16 deletions(-) diff --git a/src/grd-daemon.c b/src/grd-daemon.c index ea17df1d..7e1a0a34 100644 --- a/src/grd-daemon.c +++ b/src/grd-daemon.c @@ -49,6 +49,8 @@ #define RDP_SERVER_RESTART_DELAY_MS 3000 +#define DEFAULT_MAX_PARALLEL_CONNECTIONS 10 + enum { PROP_0, @@ -92,6 +94,9 @@ typedef struct _GrdDaemonPrivate G_DEFINE_TYPE_WITH_PRIVATE (GrdDaemon, grd_daemon, G_TYPE_APPLICATION) +#define QUOTE1(a) #a +#define QUOTE(a) QUOTE1(a) + #ifdef HAVE_RDP static void maybe_start_rdp_server (GrdDaemon *daemon); #endif @@ -969,6 +974,7 @@ main (int argc, char **argv) gboolean handover = FALSE; int rdp_port = -1; int vnc_port = -1; + int max_parallel_connections = DEFAULT_MAX_PARALLEL_CONNECTIONS; GOptionEntry entries[] = { { "version", 0, 0, G_OPTION_ARG_NONE, &print_version, @@ -985,6 +991,10 @@ main (int argc, char **argv) "RDP port", NULL }, { "vnc-port", 0, 0, G_OPTION_ARG_INT, &vnc_port, "VNC port", NULL }, + { "max-parallel-connections", 0, 0, + G_OPTION_ARG_INT, &max_parallel_connections, + "Max number of parallel connections (0 for unlimited, " + "default: " QUOTE(DEFAULT_MAX_PARALLEL_CONNECTIONS) ")", NULL }, { NULL } }; g_autoptr (GOptionContext) option_context = NULL; @@ -1014,6 +1024,17 @@ main (int argc, char **argv) return EXIT_FAILURE; } + if (max_parallel_connections == 0) + { + max_parallel_connections = INT_MAX; + } + else if (max_parallel_connections < 0) + { + g_printerr ("Invalid number of max parallel connections: %d\n", + max_parallel_connections); + return EXIT_FAILURE; + } + if (headless) runtime_mode = GRD_RUNTIME_MODE_HEADLESS; else if (system) @@ -1060,5 +1081,8 @@ main (int argc, char **argv) if (vnc_port != -1) grd_settings_override_vnc_port (settings, vnc_port); + grd_settings_override_max_parallel_connections (settings, + max_parallel_connections); + return g_application_run (G_APPLICATION (daemon), argc, argv); } diff --git a/src/grd-rdp-server.c b/src/grd-rdp-server.c index 8a4dfe59..b6d52341 100644 --- a/src/grd-rdp-server.c +++ b/src/grd-rdp-server.c @@ -549,9 +549,10 @@ grd_rdp_server_constructed (GObject *object) if (allow_callback) { - rdp_server->throttler = grd_throttler_new (grd_throttler_limits_new (), - allow_callback, - rdp_server); + rdp_server->throttler = + grd_throttler_new (grd_throttler_limits_new (rdp_server->context), + allow_callback, + rdp_server); } rdp_server->pending_binding_attempts = RDP_SERVER_N_BINDING_ATTEMPTS; diff --git a/src/grd-settings.c b/src/grd-settings.c index 8393ace5..afe09eeb 100644 --- a/src/grd-settings.c +++ b/src/grd-settings.c @@ -85,6 +85,8 @@ typedef struct _GrdSettingsPrivate GrdVncScreenShareMode screen_share_mode; GrdVncAuthMethod auth_method; } vnc; + + int max_parallel_connections; } GrdSettingsPrivate; G_DEFINE_TYPE_WITH_PRIVATE (GrdSettings, grd_settings, G_TYPE_OBJECT) @@ -101,6 +103,23 @@ grd_settings_get_runtime_mode (GrdSettings *settings) return priv->runtime_mode; } +void +grd_settings_override_max_parallel_connections (GrdSettings *settings, + int max_parallel_connections) +{ + GrdSettingsPrivate *priv = grd_settings_get_instance_private (settings); + + priv->max_parallel_connections = max_parallel_connections; +} + +int +grd_settings_get_max_parallel_connections (GrdSettings *settings) +{ + GrdSettingsPrivate *priv = grd_settings_get_instance_private (settings); + + return priv->max_parallel_connections; +} + void grd_settings_override_rdp_port (GrdSettings *settings, int port) diff --git a/src/grd-settings.h b/src/grd-settings.h index 6660b962..94dfd3b2 100644 --- a/src/grd-settings.h +++ b/src/grd-settings.h @@ -37,6 +37,11 @@ struct _GrdSettingsClass GrdRuntimeMode grd_settings_get_runtime_mode (GrdSettings *settings); +void grd_settings_override_max_parallel_connections (GrdSettings *settings, + int max_parallel_connections); + +int grd_settings_get_max_parallel_connections (GrdSettings *settings); + void grd_settings_override_rdp_port (GrdSettings *settings, int port); diff --git a/src/grd-throttler.c b/src/grd-throttler.c index 5c404ed7..8f19783f 100644 --- a/src/grd-throttler.c +++ b/src/grd-throttler.c @@ -21,9 +21,10 @@ #include "grd-throttler.h" +#include "grd-context.h" +#include "grd-settings.h" #include "grd-utils.h" -#define DEFAULT_MAX_GLOBAL_CONNECTIONS 10 #define DEFAULT_MAX_CONNECTIONS_PER_PEER 5 #define DEFAULT_MAX_PENDING_CONNECTIONS 5 #define DEFAULT_MAX_ATTEMPTS_PER_SECOND 10 @@ -440,12 +441,14 @@ grd_throttler_limits_set_max_global_connections (GrdThrottlerLimits *limits, } GrdThrottlerLimits * -grd_throttler_limits_new (void) +grd_throttler_limits_new (GrdContext *context) { + GrdSettings *settings = grd_context_get_settings (context); GrdThrottlerLimits *limits; limits = g_new0 (GrdThrottlerLimits, 1); - limits->max_global_connections = DEFAULT_MAX_GLOBAL_CONNECTIONS; + limits->max_global_connections = + grd_settings_get_max_parallel_connections (settings); limits->max_connections_per_peer = DEFAULT_MAX_CONNECTIONS_PER_PEER; limits->max_pending_connections = DEFAULT_MAX_PENDING_CONNECTIONS; limits->max_attempts_per_second = DEFAULT_MAX_ATTEMPTS_PER_SECOND; diff --git a/src/grd-throttler.h b/src/grd-throttler.h index 9e72b8f1..a9554202 100644 --- a/src/grd-throttler.h +++ b/src/grd-throttler.h @@ -23,6 +23,8 @@ #include #include +#include "grd-types.h" + typedef struct _GrdThrottlerLimits GrdThrottlerLimits; #define GRD_TYPE_THROTTLER (grd_throttler_get_type()) @@ -41,7 +43,7 @@ grd_throttler_limits_set_max_global_connections (GrdThrottlerLimits *limits, int limit); GrdThrottlerLimits * -grd_throttler_limits_new (void); +grd_throttler_limits_new (GrdContext *context); GrdThrottler * grd_throttler_new (GrdThrottlerLimits *limits, diff --git a/src/grd-vnc-server.c b/src/grd-vnc-server.c index 4cfbcad2..49f16056 100644 --- a/src/grd-vnc-server.c +++ b/src/grd-vnc-server.c @@ -255,26 +255,28 @@ grd_vnc_server_dispose (GObject *object) static void grd_vnc_server_constructed (GObject *object) { + GrdVncServer *vnc_server = GRD_VNC_SERVER (object); + GrdThrottlerLimits *limits; + if (grd_get_debug_flags () & GRD_DEBUG_VNC) rfbLogEnable (1); else rfbLogEnable (0); - G_OBJECT_CLASS (grd_vnc_server_parent_class)->constructed (object); -} - -static void -grd_vnc_server_init (GrdVncServer *vnc_server) -{ - GrdThrottlerLimits *limits; - - limits = grd_throttler_limits_new (); + limits = grd_throttler_limits_new (vnc_server->context); /* TODO: Add the rfbScreen instance to GrdVncServer to support multiple * sessions. */ grd_throttler_limits_set_max_global_connections (limits, 1); vnc_server->throttler = grd_throttler_new (limits, allow_connection_cb, vnc_server); + + G_OBJECT_CLASS (grd_vnc_server_parent_class)->constructed (object); +} + +static void +grd_vnc_server_init (GrdVncServer *vnc_server) +{ } static void -- GitLab