From 5a83501544a7ff180a5f3490192a280252cd7d04 Mon Sep 17 00:00:00 2001 From: Ignacio Casal Quinteiro Date: Wed, 24 Jul 2024 15:20:35 +0200 Subject: [PATCH] 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 --- libsoup/websocket/soup-websocket-connection.c | 106 +++++++++++++++++- libsoup/websocket/soup-websocket-connection.h | 7 ++ 2 files changed, 110 insertions(+), 3 deletions(-) diff --git a/libsoup/websocket/soup-websocket-connection.c b/libsoup/websocket/soup-websocket-connection.c index a14481340..b1471acfa 100644 --- a/libsoup/websocket/soup-websocket-connection.c +++ b/libsoup/websocket/soup-websocket-connection.c @@ -78,6 +78,7 @@ enum { PROP_KEEPALIVE_INTERVAL, PROP_KEEPALIVE_PONG_TIMEOUT, PROP_EXTENSIONS, + PROP_MAX_TOTAL_MESSAGE_SIZE, LAST_PROPERTY }; @@ -120,6 +121,7 @@ typedef struct { char *origin; char *protocol; guint64 max_incoming_payload_size; + guint64 max_total_message_size; guint keepalive_interval; guint keepalive_pong_timeout; guint64 last_keepalive_seq_num; @@ -164,6 +166,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 @@ -696,8 +699,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; @@ -713,6 +716,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, @@ -971,6 +992,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: @@ -1109,7 +1136,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; } @@ -1420,6 +1447,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; @@ -1478,6 +1509,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; @@ -1708,6 +1743,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); /** @@ -2178,6 +2233,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 f047c0aae..ea0cb58ef 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_6 +guint64 soup_websocket_connection_get_max_total_message_size (SoupWebsocketConnection *self); + +SOUP_AVAILABLE_IN_3_6 +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); -- GitLab