You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@iggy.apache.org by hg...@apache.org on 2026/07/06 17:24:53 UTC

(iggy) 04/14: fix(server-ng): reject snapshot replies exceeding binary frame cap

This is an automated email from the ASF dual-hosted git repository.

hubcio pushed a commit to branch http-listener-rebased
in repository https://gitbox.apache.org/repos/asf/iggy.git

commit 52daec4fb197321c1e1d1aba676f76d011138f87
Author: Hubert Gruszecki <h....@gmail.com>
AuthorDate: Mon Jul 6 18:03:15 2026 +0200

    fix(server-ng): reject snapshot replies exceeding binary frame cap
    
    The snapshot archive is the first unbounded non-replicated reply body. The
    binary reply frames as [256-byte header][archive], and the client's
    message_bus::read_message tears the connection down untyped on any frame past
    MAX_MESSAGE_SIZE (64 MiB), while a frame past u32::MAX would panic
    build_reply_with_body on shard 0. Guard the archive length at the binary
    dispatch arm and return a typed SnapshotFileCompletionFailed the SDK decodes.
    The HTTP path streams via Body and stays uncapped.
---
 core/server-ng/src/dispatch.rs | 31 ++++++++++++++++++++++++++++---
 1 file changed, 28 insertions(+), 3 deletions(-)

diff --git a/core/server-ng/src/dispatch.rs b/core/server-ng/src/dispatch.rs
index 81ecdcd6d..70de9b972 100644
--- a/core/server-ng/src/dispatch.rs
+++ b/core/server-ng/src/dispatch.rs
@@ -78,12 +78,13 @@ use iggy_binary_protocol::responses::clients::get_clients::GetClientsResponse;
 use iggy_binary_protocol::responses::consumer_groups::SyncConsumerGroupResponse;
 use iggy_binary_protocol::responses::system::get_snapshot::GetSnapshotResponse;
 use iggy_binary_protocol::{
-    AckLevel, ClientVersionInfo, Command2, EvictionReason, GenericHeader, KIND_CONSUMER_GROUP,
-    Operation, ProtocolVersion, RequestHeader, WireDecode, WireEncode, WireIdentifier,
-    is_protocol_compatible,
+    AckLevel, ClientVersionInfo, Command2, EvictionReason, GenericHeader, HEADER_SIZE,
+    KIND_CONSUMER_GROUP, Operation, ProtocolVersion, RequestHeader, WireDecode, WireEncode,
+    WireIdentifier, is_protocol_compatible,
 };
 use iggy_common::{IggyError, PollingStrategy, SnapshotCompression, SystemSnapshotType};
 use message_bus::client_listener::RequestHandler;
+use message_bus::framing::MAX_MESSAGE_SIZE;
 use message_bus::replica::listener::MessageHandler;
 use message_bus::{AUTO_COMMIT_CLIENT_ID, IggyMessageBus, MessageBus};
 use metadata::impls::metadata::{
@@ -1223,6 +1224,30 @@ async fn handle_get_snapshot(
     };
     match result {
         Ok(archive) => {
+            // The reply frames as `[256-byte header][archive]`. The client's
+            // `message_bus::read_message` rejects any frame past `MAX_MESSAGE_SIZE`
+            // (64 MiB) by tearing the connection down untyped, and a frame past
+            // `u32::MAX` would panic `build_reply_with_body`. The archive is the
+            // only unbounded non-replicated body, so refuse an oversized one with a
+            // typed error the SDK decodes. The HTTP path streams via `Body` (not
+            // this framing), so it stays uncapped.
+            let frame_size = HEADER_SIZE + archive.len();
+            if frame_size > MAX_MESSAGE_SIZE {
+                warn!(
+                    transport_client_id,
+                    frame_size,
+                    max = MAX_MESSAGE_SIZE,
+                    "snapshot archive exceeds the client frame limit; refusing to send"
+                );
+                send_non_replicated_deny(
+                    shard,
+                    request,
+                    transport_client_id,
+                    IggyError::SnapshotFileCompletionFailed.as_code(),
+                )
+                .await;
+                return;
+            }
             send_non_replicated_bytes(
                 shard,
                 request,