From 176cb31003252a69d3fc7908e8f505c0ee006b7a Mon Sep 17 00:00:00 2001 From: Ignacio Casal Quinteiro Date: Wed, 24 Jul 2024 15:20:35 +0200 Subject: [PATCH 1/4] websocket: add a way to restrict the total message size Otherwise a client could send small packages smaller than total-incoming-payload-size but still to break the server with a big allocation Fixes: #390 Upstream-Status: Backport [https://gitlab.gnome.org/GNOME/libsoup/-/commit/db87805ab565d67533dfed2cb409dbfd63c7fdce] CVE: CVE-2025-32049 Signed-off-by: Changqing Li --- libsoup/websocket/soup-websocket-connection.c | 107 +++++++++++++++++- libsoup/websocket/soup-websocket-connection.h | 7 ++ 2 files changed, 110 insertions(+), 4 deletions(-) diff --git a/libsoup/websocket/soup-websocket-connection.c b/libsoup/websocket/soup-websocket-connection.c index 5eb8150..19bdd39 100644 --- a/libsoup/websocket/soup-websocket-connection.c +++ b/libsoup/websocket/soup-websocket-connection.c @@ -84,7 +84,7 @@ enum { PROP_MAX_INCOMING_PAYLOAD_SIZE, PROP_KEEPALIVE_INTERVAL, PROP_EXTENSIONS, - + PROP_MAX_TOTAL_MESSAGE_SIZE, LAST_PROPERTY }; @@ -126,6 +126,7 @@ typedef struct { char *origin; char *protocol; guint64 max_incoming_payload_size; + guint64 max_total_message_size; guint keepalive_interval; gushort peer_close_code; @@ -156,6 +157,7 @@ typedef struct { } SoupWebsocketConnectionPrivate; #define MAX_INCOMING_PAYLOAD_SIZE_DEFAULT 128 * 1024 +#define MAX_TOTAL_MESSAGE_SIZE_DEFAULT 128 * 1024 #define READ_BUFFER_SIZE 1024 #define MASK_LENGTH 4 @@ -670,8 +672,8 @@ bad_data_error_and_close (SoupWebsocketConnection *self) } static void -too_big_error_and_close (SoupWebsocketConnection *self, - guint64 payload_len) +too_big_incoming_payload_error_and_close (SoupWebsocketConnection *self, + guint64 payload_len) { SoupWebsocketConnectionPrivate *priv = soup_websocket_connection_get_instance_private (self); GError *error; @@ -687,6 +689,24 @@ too_big_error_and_close (SoupWebsocketConnection *self, emit_error_and_close (self, error, TRUE); } +static void +too_big_message_error_and_close (SoupWebsocketConnection *self, + guint64 len) +{ + SoupWebsocketConnectionPrivate *priv = soup_websocket_connection_get_instance_private (self); + GError *error; + + error = g_error_new_literal (SOUP_WEBSOCKET_ERROR, + SOUP_WEBSOCKET_CLOSE_TOO_BIG, + priv->connection_type == SOUP_WEBSOCKET_CONNECTION_SERVER ? + "Received WebSocket payload from the client larger than configured max-total-message-size" : + "Received WebSocket payload from the server larger than configured max-total-message-size"); + g_debug ("%s received message of size %" G_GUINT64_FORMAT " or greater, but max supported size is %" G_GUINT64_FORMAT, + priv->connection_type == SOUP_WEBSOCKET_CONNECTION_SERVER ? "server" : "client", + len, priv->max_total_message_size); + emit_error_and_close (self, error, TRUE); +} + static void close_connection (SoupWebsocketConnection *self, gushort code, @@ -918,6 +938,12 @@ process_contents (SoupWebsocketConnection *self, switch (priv->message_opcode) { case 0x01: case 0x02: + /* Safety valve */ + if (priv->max_total_message_size > 0 && + (priv->message_data->len + payload_len) > priv->max_total_message_size) { + too_big_message_error_and_close (self, (priv->message_data->len + payload_len)); + return; + } g_byte_array_append (priv->message_data, payload, payload_len); break; default: @@ -1056,7 +1082,7 @@ process_frame (SoupWebsocketConnection *self) /* Safety valve */ if (priv->max_incoming_payload_size > 0 && payload_len > priv->max_incoming_payload_size) { - too_big_error_and_close (self, payload_len); + too_big_incoming_payload_error_and_close (self, payload_len); return FALSE; } @@ -1363,6 +1389,10 @@ soup_websocket_connection_get_property (GObject *object, g_value_set_pointer (value, priv->extensions); break; + case PROP_MAX_TOTAL_MESSAGE_SIZE: + g_value_set_uint64 (value, priv->max_total_message_size); + break; + default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; @@ -1416,6 +1446,10 @@ soup_websocket_connection_set_property (GObject *object, priv->extensions = g_value_get_pointer (value); break; + case PROP_MAX_TOTAL_MESSAGE_SIZE: + priv->max_total_message_size = g_value_get_uint64 (value); + break; + default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; @@ -1628,6 +1662,26 @@ soup_websocket_connection_class_init (SoupWebsocketConnectionClass *klass) G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); + /** + * SoupWebsocketConnection:max-total-message-size: + * + * The total message size for incoming packets. + * + * The protocol expects or 0 to not limit it. + * + * Since: 3.8 + */ + properties[PROP_MAX_TOTAL_MESSAGE_SIZE] = + g_param_spec_uint64 ("max-total-message-size", + "Max total message size", + "Max total message size ", + 0, + G_MAXUINT64, + MAX_TOTAL_MESSAGE_SIZE_DEFAULT, + G_PARAM_READWRITE | + G_PARAM_CONSTRUCT | + G_PARAM_STATIC_STRINGS); + g_object_class_install_properties (gobject_class, LAST_PROPERTY, properties); /** @@ -2111,6 +2165,51 @@ soup_websocket_connection_set_max_incoming_payload_size (SoupWebsocketConnection } } +/** + * soup_websocket_connection_get_max_total_message_size: + * @self: the WebSocket + * + * Gets the maximum total message size allowed for packets. + * + * Returns: the maximum total message size. + * + * Since: 3.8 + */ +guint64 +soup_websocket_connection_get_max_total_message_size (SoupWebsocketConnection *self) +{ + SoupWebsocketConnectionPrivate *priv = soup_websocket_connection_get_instance_private (self); + + g_return_val_if_fail (SOUP_IS_WEBSOCKET_CONNECTION (self), MAX_TOTAL_MESSAGE_SIZE_DEFAULT); + + return priv->max_total_message_size; +} + +/** + * soup_websocket_connection_set_max_total_message_size: + * @self: the WebSocket + * @max_total_message_size: the maximum total message size + * + * Sets the maximum total message size allowed for packets. + * + * It does not limit the outgoing packet size. + * + * Since: 3.8 + */ +void +soup_websocket_connection_set_max_total_message_size (SoupWebsocketConnection *self, + guint64 max_total_message_size) +{ + SoupWebsocketConnectionPrivate *priv = soup_websocket_connection_get_instance_private (self); + + g_return_if_fail (SOUP_IS_WEBSOCKET_CONNECTION (self)); + + if (priv->max_total_message_size != max_total_message_size) { + priv->max_total_message_size = max_total_message_size; + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_MAX_TOTAL_MESSAGE_SIZE]); + } +} + /** * soup_websocket_connection_get_keepalive_interval: * @self: the WebSocket diff --git a/libsoup/websocket/soup-websocket-connection.h b/libsoup/websocket/soup-websocket-connection.h index eeb093d..922de56 100644 --- a/libsoup/websocket/soup-websocket-connection.h +++ b/libsoup/websocket/soup-websocket-connection.h @@ -88,6 +88,13 @@ SOUP_AVAILABLE_IN_ALL void soup_websocket_connection_set_max_incoming_payload_size (SoupWebsocketConnection *self, guint64 max_incoming_payload_size); +SOUP_AVAILABLE_IN_3_0 +guint64 soup_websocket_connection_get_max_total_message_size (SoupWebsocketConnection *self); + +SOUP_AVAILABLE_IN_3_0 +void soup_websocket_connection_set_max_total_message_size (SoupWebsocketConnection *self, + guint64 max_total_message_size); + SOUP_AVAILABLE_IN_ALL guint soup_websocket_connection_get_keepalive_interval (SoupWebsocketConnection *self); -- 2.34.1