Skip to content

Commit c3da11f

Browse files
eichhorlIDX GitHub Automation
andauthored
fix: Introduce a byte limit when fetching CUPs (#10675)
Before this PR there was no limit on the number of bytes downloaded by the orchestrator when fetching a CUP. It was only limited indirectly by the exponential timeout. Instead, we introduce a byte limit similar to what is already done for NNS delegations: https://github.com/dfinity/ic/blob/38556a3a0f8445310a9fd7323e28e6fb2eae2d30/rs/http_endpoints/nns_delegation_manager/src/nns_delegation_manager.rs#L338-L341 We chose the same byte limit (128 MB) that already exists for consensus artifacts as part of the P2P layer. --------- Co-authored-by: IDX GitHub Automation <infra+github-automation@dfinity.org>
1 parent e0d1486 commit c3da11f

10 files changed

Lines changed: 53 additions & 8 deletions

File tree

Cargo.lock

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

rs/limits/src/lib.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,15 @@ pub const DKG_DEALINGS_PER_BLOCK: usize = 1;
9797
/// The replica code should be designed in such a way that if we put a channel of size 1, the protocol should still work.
9898
pub const MAX_P2P_IO_CHANNEL_SIZE: usize = 100_000;
9999

100+
/// The maximum size of a single message exchanged between nodes of a subnet.
101+
///
102+
/// On purpose the value is big, otherwise there is risk of not processing important consensus messages.
103+
/// E.g. summary blocks generated by the consensus protocol for 40 node subnet can be bigger than 5MB.
104+
///
105+
/// This is used both by the P2P/QUIC transport layer and by the orchestrator when fetching a
106+
/// CatchUpPackage from a peer over HTTPS.
107+
pub const MAX_MESSAGE_SIZE_BYTES: usize = 128 * 1024 * 1024;
108+
100109
/// The maximum number of pre-signatures that may be paired with signature requests,
101110
/// per key ID.
102111
pub const MAX_PAIRED_PRE_SIGNATURES: usize = 100;

rs/orchestrator/BUILD.bazel

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ rust_library(
3636
"//rs/ic_os/sev/guest",
3737
"//rs/interfaces",
3838
"//rs/interfaces/registry",
39+
"//rs/limits",
3940
"//rs/monitoring/logger",
4041
"//rs/monitoring/metrics",
4142
"//rs/nns/constants",

rs/orchestrator/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ ic-http-utils = { path = "../http_utils" }
4343
ic-image-upgrader = { path = "./image_upgrader" }
4444
ic-interfaces = { path = "../interfaces" }
4545
ic-interfaces-registry = { path = "../interfaces/registry" }
46+
ic-limits = { path = "../limits" }
4647
ic-logger = { path = "../monitoring/logger" }
4748
ic-management-canister-types-private = { path = "../types/management_canister_types" }
4849
ic-metrics = { path = "../monitoring/metrics" }

rs/orchestrator/src/catch_up_package_provider.rs

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,12 +35,13 @@ use crate::{
3535
registry_helper::RegistryHelper,
3636
utils::https_endpoint_to_url,
3737
};
38-
use http_body_util::{BodyExt, Full};
38+
use http_body_util::{BodyExt, Full, Limited};
3939
use hyper::{Method, Request, StatusCode, body::Bytes};
4040
use hyper_rustls::HttpsConnectorBuilder;
4141
use hyper_util::{client::legacy::Client, rt::TokioExecutor};
4242
use ic_crypto_tls_interfaces::TlsConfig;
4343
use ic_interfaces::crypto::ThresholdSigVerifierByPublicKey;
44+
use ic_limits::MAX_MESSAGE_SIZE_BYTES;
4445
use ic_logger::{ReplicaLogger, info, warn};
4546
use ic_protobuf::{registry::node::v1::NodeRecord, types::v1 as pb};
4647
use ic_sys::fs::write_protobuf_using_tmp_file;
@@ -113,6 +114,7 @@ pub(crate) struct CatchUpPackageProvider {
113114
node_id: NodeId,
114115
backoff: Duration,
115116
initial_backoff: Duration,
117+
max_response_size_bytes: usize,
116118
local_cup_reader: LocalCUPReader,
117119
}
118120

@@ -154,6 +156,7 @@ impl CatchUpPackageProvider {
154156
logger,
155157
backoff: initial_backoff,
156158
initial_backoff,
159+
max_response_size_bytes: MAX_MESSAGE_SIZE_BYTES,
157160
local_cup_reader,
158161
}
159162
}
@@ -346,7 +349,10 @@ impl CatchUpPackageProvider {
346349
.map_err(|e| format!("Failed to query CUP endpoint at {url}: {e:?}"))?;
347350

348351
let status = res.status();
349-
let body_req = timeout(self.backoff, res.into_body().collect());
352+
let body_req = timeout(
353+
self.backoff,
354+
Limited::new(res.into_body(), self.max_response_size_bytes).collect(),
355+
);
350356

351357
let bytes = match body_req.await {
352358
Ok(result) => {
@@ -831,6 +837,32 @@ pub(crate) mod tests {
831837
assert_eq!(cup_provider.backoff, initial_backoff);
832838
}
833839

840+
#[tokio::test]
841+
async fn test_fetch_catch_up_package_body_exceeds_size_limit() {
842+
// The server responds with a full CUP body.
843+
let server_addr =
844+
start_server(TestService::SendBodyOrStall(Arc::new(Mutex::new(true)))).await;
845+
let url = format!("https://{server_addr}");
846+
let tmp_dir = tempfile::tempdir().unwrap();
847+
let node_id = node_test_id(1);
848+
849+
let mut cup_provider = make_cup_provider(
850+
tmp_dir.path().to_path_buf(),
851+
node_id,
852+
Duration::from_secs(5),
853+
);
854+
// Set a size limit far below the size of the CUP body the server sends, so that reading
855+
// the body is aborted rather than buffered in full.
856+
cup_provider.max_response_size_bytes = 4;
857+
858+
let err = cup_provider
859+
.fetch_catch_up_package(&node_id, url, None)
860+
.await
861+
.expect_err("Expected an error when the CUP body exceeds the size limit");
862+
863+
assert!(err.contains("LengthLimitError"), "Unexpected error: {err}");
864+
}
865+
834866
#[tokio::test]
835867
async fn test_fetch_catch_up_package_unresponsive_times_out() {
836868
let server_addr = start_server(TestService::Unresponsive).await;

rs/p2p/quic_transport/BUILD.bazel

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ rust_library(
2222
"//rs/crypto/tls_interfaces",
2323
"//rs/crypto/utils/tls",
2424
"//rs/interfaces/registry",
25+
"//rs/limits",
2526
"//rs/monitoring/logger",
2627
"//rs/monitoring/metrics",
2728
"//rs/phantom_newtype",

rs/p2p/quic_transport/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ ic-base-types = { path = "../../types/base_types" }
1919
ic-crypto-tls-interfaces = { path = "../../crypto/tls_interfaces" }
2020
ic-crypto-utils-tls = { path = "../../crypto/utils/tls" }
2121
ic-interfaces-registry = { path = "../../interfaces/registry" }
22+
ic-limits = { path = "../../limits" }
2223
ic-logger = { path = "../../monitoring/logger" }
2324
ic-metrics = { path = "../../monitoring/metrics" }
2425
ic-protobuf = { path = "../../protobuf" }

rs/p2p/quic_transport/src/connection_handle.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,13 @@ use std::sync::atomic::{AtomicU64, Ordering};
44

55
use bytes::Bytes;
66
use http::{Method, Request, Response, Version};
7+
use ic_limits::MAX_MESSAGE_SIZE_BYTES;
78
use ic_protobuf::transport::v1 as pb;
89
use prost::Message;
910
use quinn::Connection;
1011

1112
use crate::{
12-
ConnId, MAX_MESSAGE_SIZE_BYTES, MessagePriority, P2PError, ResetStreamOnDrop,
13+
ConnId, MessagePriority, P2PError, ResetStreamOnDrop,
1314
metrics::{
1415
INFALIBBLE, QuicTransportMetrics, observe_conn_error, observe_read_to_end_error,
1516
observe_stopped_error, observe_write_error,

rs/p2p/quic_transport/src/lib.rs

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -69,10 +69,6 @@ mod metrics;
6969
mod request_handler;
7070
pub use crate::connection_manager::create_udp_socket;
7171

72-
/// On purpose the value is big, otherwise there is risk of not processing important consensus messages.
73-
/// E.g. summary blocks generated by the consensus protocol for 40 node subnet can be bigger than 5MB.
74-
pub(crate) const MAX_MESSAGE_SIZE_BYTES: usize = 128 * 1024 * 1024;
75-
7672
/// The shutdown primitive is useful if futures should be cancelled at places different than '.await' points.
7773
/// Such functionality is needed to have explicit control on the state when exiting.
7874
pub struct Shutdown {

rs/p2p/quic_transport/src/request_handler.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ use axum::{Router, body::Body}; // TODO: try to remove the axum dep here
1616
use bytes::Bytes;
1717
use http::{Method, Request, Response, Version};
1818
use ic_base_types::NodeId;
19+
use ic_limits::MAX_MESSAGE_SIZE_BYTES;
1920
use ic_logger::{ReplicaLogger, info};
2021
use ic_protobuf::transport::v1 as pb;
2122
use prost::Message;
@@ -24,7 +25,7 @@ use tokio::task::JoinSet;
2425
use tower::ServiceExt;
2526

2627
use crate::{
27-
ConnId, MAX_MESSAGE_SIZE_BYTES, P2PError, ResetStreamOnDrop,
28+
ConnId, P2PError, ResetStreamOnDrop,
2829
connection_handle::ConnectionHandle,
2930
metrics::{
3031
ERROR_TYPE_APP, INFALIBBLE, QuicTransportMetrics, STREAM_TYPE_BIDI, observe_conn_error,

0 commit comments

Comments
 (0)