Script 'mail_helper' called by obssrc
Hello community,
here is the log from the commit of package gnome-remote-desktop for
openSUSE:Factory checked in at 2026-02-12 17:26:48
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/gnome-remote-desktop (Old)
and /work/SRC/openSUSE:Factory/.gnome-remote-desktop.new.1977 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "gnome-remote-desktop"
Thu Feb 12 17:26:48 2026 rev:40 rq:1332558 version:49.2
Changes:
--------
---
/work/SRC/openSUSE:Factory/gnome-remote-desktop/gnome-remote-desktop.changes
2025-12-10 15:31:32.848825577 +0100
+++
/work/SRC/openSUSE:Factory/.gnome-remote-desktop.new.1977/gnome-remote-desktop.changes
2026-02-12 17:27:49.664463244 +0100
@@ -1,0 +2,17 @@
+Tue Feb 10 23:09:37 UTC 2026 - Michael Gorse <[email protected]>
+
+- Backport connection throttling (bsc#1244053 CVE-2025-5024
+ glgo#GNOME/gnome-desktop!321):
+ + 0001-control-Allow-controlling-all-daemon-types.patch
+ + 0002-daemon-Use-GError-auto-pointer.patch
+ + 0004-rdp-sam-Dup-fd-kept-in-struct.patch
+ + 0005-rdp-server-Set-socket-backlog-count-to-5.patch
+ + 0006-utils-Add-some-time-conversion-helpers.patch
+ + 0007-utils-Add-helper-to-close-connection-and-notify.patch
+ + 0008-Introduce-throttler-class.patch
+ + 0009-rdp-server-Throttle-connections-using-GrdThrottler.patch
+ + 0010-throttler-Introduce-limits-struct.patch
+ + 0011-vnc-server-Hook-up-VNC-server-to-the-throttler.patch
+ + 0013-throttler-Allow-overriding-hard-coded-parallel-conne.patch
+
+-------------------------------------------------------------------
New:
----
0001-control-Allow-controlling-all-daemon-types.patch
0002-daemon-Use-GError-auto-pointer.patch
0004-rdp-sam-Dup-fd-kept-in-struct.patch
0005-rdp-server-Set-socket-backlog-count-to-5.patch
0006-utils-Add-some-time-conversion-helpers.patch
0007-utils-Add-helper-to-close-connection-and-notify.patch
0008-Introduce-throttler-class.patch
0009-rdp-server-Throttle-connections-using-GrdThrottler.patch
0010-throttler-Introduce-limits-struct.patch
0011-vnc-server-Hook-up-VNC-server-to-the-throttler.patch
0013-throttler-Allow-overriding-hard-coded-parallel-conne.patch
----------(New B)----------
New: glgo#GNOME/gnome-desktop!321):
+ 0001-control-Allow-controlling-all-daemon-types.patch
+ 0002-daemon-Use-GError-auto-pointer.patch
New: + 0001-control-Allow-controlling-all-daemon-types.patch
+ 0002-daemon-Use-GError-auto-pointer.patch
+ 0004-rdp-sam-Dup-fd-kept-in-struct.patch
New: + 0002-daemon-Use-GError-auto-pointer.patch
+ 0004-rdp-sam-Dup-fd-kept-in-struct.patch
+ 0005-rdp-server-Set-socket-backlog-count-to-5.patch
New: + 0004-rdp-sam-Dup-fd-kept-in-struct.patch
+ 0005-rdp-server-Set-socket-backlog-count-to-5.patch
+ 0006-utils-Add-some-time-conversion-helpers.patch
New: + 0005-rdp-server-Set-socket-backlog-count-to-5.patch
+ 0006-utils-Add-some-time-conversion-helpers.patch
+ 0007-utils-Add-helper-to-close-connection-and-notify.patch
New: + 0006-utils-Add-some-time-conversion-helpers.patch
+ 0007-utils-Add-helper-to-close-connection-and-notify.patch
+ 0008-Introduce-throttler-class.patch
New: + 0007-utils-Add-helper-to-close-connection-and-notify.patch
+ 0008-Introduce-throttler-class.patch
+ 0009-rdp-server-Throttle-connections-using-GrdThrottler.patch
New: + 0008-Introduce-throttler-class.patch
+ 0009-rdp-server-Throttle-connections-using-GrdThrottler.patch
+ 0010-throttler-Introduce-limits-struct.patch
New: + 0009-rdp-server-Throttle-connections-using-GrdThrottler.patch
+ 0010-throttler-Introduce-limits-struct.patch
+ 0011-vnc-server-Hook-up-VNC-server-to-the-throttler.patch
New: + 0010-throttler-Introduce-limits-struct.patch
+ 0011-vnc-server-Hook-up-VNC-server-to-the-throttler.patch
+ 0013-throttler-Allow-overriding-hard-coded-parallel-conne.patch
New: + 0011-vnc-server-Hook-up-VNC-server-to-the-throttler.patch
+ 0013-throttler-Allow-overriding-hard-coded-parallel-conne.patch
----------(New E)----------
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Other differences:
------------------
++++++ gnome-remote-desktop.spec ++++++
--- /var/tmp/diff_new_pack.YiPtAK/_old 2026-02-12 17:27:50.380493621 +0100
+++ /var/tmp/diff_new_pack.YiPtAK/_new 2026-02-12 17:27:50.380493621 +0100
@@ -1,7 +1,7 @@
#
# spec file for package gnome-remote-desktop
#
-# Copyright (c) 2025 SUSE LLC and contributors
+# Copyright (c) 2026 SUSE LLC and contributors
#
# All modifications and additions to the file contributed by third parties
# remain the property of their copyright owners, unless otherwise agreed
@@ -30,6 +30,28 @@
Group: System/Management
URL: https://gitlab.gnome.org/GNOME/gnome-remote-desktop
Source0: %{name}-%{version}.tar.zst
+# PATCH-FIX-UPSTREAM 0001-control-Allow-controlling-all-daemon-types.patch
bsc#1244053 [email protected] -- allow controlling all daemon types.
+Patch0: 0001-control-Allow-controlling-all-daemon-types.patch
+# PATCH-FIX-UPSTREAM 0002-daemon-Use-GError-auto-pointer.patch bsc#1244053
[email protected] -- daemon: Use GError auto-pointer.
+Patch1: 0002-daemon-Use-GError-auto-pointer.patch
+# PATCH-FIX-UPSTREAM 0004-rdp-sam-Dup-fd-kept-in-struct.patch bsc#1244053
[email protected] -- rdp-sam: Dup fd kept in struct.
+Patch2: 0004-rdp-sam-Dup-fd-kept-in-struct.patch
+# PATCH-FIX-UPSTREAM 0005-rdp-server-Set-socket-backlog-count-to-5.patch
bsc#1244053 [email protected] -- rdp-server: Set socket backlog count to 5.
+Patch3: 0005-rdp-server-Set-socket-backlog-count-to-5.patch
+# PATCH-FIX-UPSTREAM 0006-utils-Add-some-time-conversion-helpers.patch
bsc#1244053 [email protected] -- add some time conversion helpers.
+Patch4: 0006-utils-Add-some-time-conversion-helpers.patch
+# PATCH-FIX-UPSTREAM
0007-utils-Add-helper-to-close-connection-and-notify.patch bsc#1244053
[email protected] -- add helper to close connection and notify.
+Patch5: 0007-utils-Add-helper-to-close-connection-and-notify.patch
+# PATCH-FIX-UPSTREAM 0008-Introduce-throttler-class.patch bsc#1244053
[email protected] -- introduce throttler class.
+Patch6: 0008-Introduce-throttler-class.patch
+# PATCH-FIX-UPSTREAM
0009-rdp-server-Throttle-connections-using-GrdThrottler.patch bsc#1244053
[email protected] -- throttle connections using GrdThrottler.
+Patch7: 0009-rdp-server-Throttle-connections-using-GrdThrottler.patch
+# PATCH-FIX-UPSTREAM 0010-throttler-Introduce-limits-struct.patch bsc#1244053
[email protected] -- introduce limits struct.
+Patch8: 0010-throttler-Introduce-limits-struct.patch
+# PATCH-FIX-UPSTREAM 0011-vnc-server-Hook-up-VNC-server-to-the-throttler.patch
bsc#1244053 [email protected] -- hook up VNC server to the throttler.
+Patch9: 0011-vnc-server-Hook-up-VNC-server-to-the-throttler.patch
+# PATCH-FIX-UPSTREAM
0013-throttler-Allow-overriding-hard-coded-parallel-conne.patch bsc#1244053
[email protected] -- allow overriding hard coded parallel connections limit.
+Patch10: 0013-throttler-Allow-overriding-hard-coded-parallel-conne.patch
%if 0%{?sle_version} && 0%{?sle_version} < 160000
BuildRequires: gcc13
++++++ 0001-control-Allow-controlling-all-daemon-types.patch ++++++
>From 0b194010245ebd2e32a88e2f63a736c9cf445400 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jonas=20=C3=85dahl?= <[email protected]>
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:
<https://gitlab.gnome.org/GNOME/gnome-remote-desktop/-/merge_requests/321>
---
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 858ab10..284cb11 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");
--
2.53.0
++++++ 0002-daemon-Use-GError-auto-pointer.patch ++++++
>From e724ea4439b3e58aeec4b80406691b787af38519 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jonas=20=C3=85dahl?= <[email protected]>
Date: Mon, 26 May 2025 16:09:45 +0200
Subject: [PATCH 02/13] daemon: Use GError auto pointer
Part-of:
<https://gitlab.gnome.org/GNOME/gnome-remote-desktop/-/merge_requests/321>
---
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 c082c6b..9895420 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;
}
--
2.53.0
++++++ 0004-rdp-sam-Dup-fd-kept-in-struct.patch ++++++
>From 99d246dfae3562640c65cb08a369e172947c7210 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jonas=20=C3=85dahl?= <[email protected]>
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:
<https://gitlab.gnome.org/GNOME/gnome-remote-desktop/-/merge_requests/321>
---
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 5898825..daa3ebb 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);
--
2.53.0
++++++ 0005-rdp-server-Set-socket-backlog-count-to-5.patch ++++++
>From f1e6d6dd2e995b15d65f86f39d4aec17d2b56eaa Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jonas=20=C3=85dahl?= <[email protected]>
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:
<https://gitlab.gnome.org/GNOME/gnome-remote-desktop/-/merge_requests/321>
---
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 0ac486d..2691a65 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
{
@@ -312,6 +313,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,
--
2.53.0
++++++ 0006-utils-Add-some-time-conversion-helpers.patch ++++++
>From b09acfa1a8d88113bc500135a712c0d067e26fa3 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jonas=20=C3=85dahl?= <[email protected]>
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:
<https://gitlab.gnome.org/GNOME/gnome-remote-desktop/-/merge_requests/321>
---
src/grd-utils.h | 18 ++++++++++++++++++
1 file changed, 18 insertions(+)
diff --git a/src/grd-utils.h b/src/grd-utils.h
index 30a3b9e..099da56 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);
+}
--
2.53.0
++++++ 0007-utils-Add-helper-to-close-connection-and-notify.patch ++++++
>From 800ea5c29fac579fc6660175075d3333a2d9786c Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jonas=20=C3=85dahl?= <[email protected]>
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:
<https://gitlab.gnome.org/GNOME/gnome-remote-desktop/-/merge_requests/321>
---
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 32739a9..bf20882 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 099da56..4703db8 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)
{
--
2.53.0
++++++ 0008-Introduce-throttler-class.patch ++++++
>From 4c729e468705e1af48b7aea57584743a8c63dc1d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jonas=20=C3=85dahl?= <[email protected]>
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:
<https://gitlab.gnome.org/GNOME/gnome-remote-desktop/-/merge_requests/321>
---
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 0000000..a21563a
--- /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 0000000..d57d653
--- /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 <gio/gio.h>
+#include <glib-object.h>
+
+#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 1b2cb93..a39c991 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',
--
2.53.0
++++++ 0009-rdp-server-Throttle-connections-using-GrdThrottler.patch ++++++
>From 557d6197a2ff368dbf5b5da91e4f65c4c427e819 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jonas=20=C3=85dahl?= <[email protected]>
Date: Tue, 10 Feb 2026 16:44:43 -0600
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:
<https://gitlab.gnome.org/GNOME/gnome-remote-desktop/-/merge_requests/321>
Backported by Mike Gorse <[email protected]>
---
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 -urp gnome-remote-desktop-49.2.orig/src/grd-daemon-system.c
gnome-remote-desktop-49.2/src/grd-daemon-system.c
--- gnome-remote-desktop-49.2.orig/src/grd-daemon-system.c 2026-02-10
17:57:24.786514313 -0600
+++ gnome-remote-desktop-49.2/src/grd-daemon-system.c 2026-02-10
17:58:00.383080115 -0600
@@ -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 (GrdDBusRemoteDesk
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 -urp gnome-remote-desktop-49.2.orig/src/grd-rdp-server.c
gnome-remote-desktop-49.2/src/grd-rdp-server.c
--- gnome-remote-desktop-49.2.orig/src/grd-rdp-server.c 2026-02-10
17:57:37.936440841 -0600
+++ gnome-remote-desktop-49.2/src/grd-rdp-server.c 2026-02-10
18:08:41.674218612 -0600
@@ -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;
@@ -206,18 +209,17 @@ on_routing_token_peeked (GObject *s
}
}
-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
@@ -225,14 +227,24 @@ 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,
rdp_server->hwaccel_vulkan,
rdp_server->hwaccel_nvidia)))
- return TRUE;
+ return;
rdp_server->sessions = g_list_append (rdp_server->sessions, session_rdp);
@@ -243,15 +255,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
@@ -378,16 +410,13 @@ grd_rdp_server_start (GrdRdpServer *rdp
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;
@@ -420,6 +449,8 @@ grd_rdp_server_stop (GrdRdpServer *rdp_s
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);
@@ -479,6 +510,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);
@@ -489,12 +521,30 @@ grd_rdp_server_dispose (GObject *object)
static void
grd_rdp_server_constructed (GObject *object)
{
- G_OBJECT_CLASS (grd_rdp_server_parent_class)->constructed (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);
+ }
-static void
-grd_rdp_server_init (GrdRdpServer *rdp_server)
-{
rdp_server->pending_binding_attempts = RDP_SERVER_N_BINDING_ATTEMPTS;
winpr_InitializeSSL (WINPR_SSL_INIT_DEFAULT);
@@ -504,6 +554,13 @@ grd_rdp_server_init (GrdRdpServer *rdp_s
* 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
diff -urp gnome-remote-desktop-49.2.orig/src/grd-session-rdp.c
gnome-remote-desktop-49.2/src/grd-session-rdp.c
--- gnome-remote-desktop-49.2.orig/src/grd-session-rdp.c 2026-02-10
17:57:24.812514709 -0600
+++ gnome-remote-desktop-49.2/src/grd-session-rdp.c 2026-02-10
17:58:00.385139366 -0600
@@ -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
@@ -1643,6 +1644,7 @@ grd_session_rdp_stop (GrdSession *sessio
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);
@@ -1833,6 +1835,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);
++++++ 0010-throttler-Introduce-limits-struct.patch ++++++
>From 44a9c90d99e13d495d0a3480a655490d8aede854 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jonas=20=C3=85dahl?= <[email protected]>
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:
<https://gitlab.gnome.org/GNOME/gnome-remote-desktop/-/merge_requests/321>
---
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 2132e42..d823063 100644
--- a/src/grd-rdp-server.c
+++ b/src/grd-rdp-server.c
@@ -541,7 +541,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 a21563a..5c404ed 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 d57d653..9e72b8f 100644
--- a/src/grd-throttler.h
+++ b/src/grd-throttler.h
@@ -23,6 +23,8 @@
#include <gio/gio.h>
#include <glib-object.h>
+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 */
--
2.53.0
++++++ 0011-vnc-server-Hook-up-VNC-server-to-the-throttler.patch ++++++
>From aea108e613c18567ea1bd3bce753aaf013d5fb1e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jonas=20=C3=85dahl?= <[email protected]>
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:
<https://gitlab.gnome.org/GNOME/gnome-remote-desktop/-/merge_requests/321>
---
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 f7e1408..7cd41e9 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 8322065..4cfbcad 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
--
2.53.0
++++++ 0013-throttler-Allow-overriding-hard-coded-parallel-conne.patch ++++++
>From 0af092962ecadcff3e06d595fa9607395b412761 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jonas=20=C3=85dahl?= <[email protected]>
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:
<https://gitlab.gnome.org/GNOME/gnome-remote-desktop/-/merge_requests/321>
---
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 ea17df1..7e1a0a3 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 d823063..cb3ce4d 100644
--- a/src/grd-rdp-server.c
+++ b/src/grd-rdp-server.c
@@ -541,9 +541,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 8393ace..afe09ee 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 6660b96..94dfd3b 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 5c404ed..8f19783 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 9e72b8f..a955420 100644
--- a/src/grd-throttler.h
+++ b/src/grd-throttler.h
@@ -23,6 +23,8 @@
#include <gio/gio.h>
#include <glib-object.h>
+#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 4cfbcad..49f1605 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
--
2.53.0