diff --git a/http/src/http_config.rs b/http/src/http_config.rs index 4912227775..b72d0951d4 100644 --- a/http/src/http_config.rs +++ b/http/src/http_config.rs @@ -7,8 +7,9 @@ pub const DEFAULT_CONFIG: HttpConfig = HttpConfig { max_headers: 128, response_header_initial_capacity: 16, copy_loops_per_yield: 16, - received_body_max_len: 524_288_000u64, + received_body_max_len: 500 * 1024 * 1024, received_body_initial_len: 128, + received_body_max_preallocate: 1024 * 1024, }; /** @@ -34,6 +35,7 @@ memory will be allocated for each conn. Although a tcp packet can be up to 64kb, better to use a value less than 1.5kb. **Default**: `512` + **Unit**: byte count ### `request_buffer_initial_len` @@ -43,6 +45,7 @@ headers. It will grow nonlinearly until `max_head_len` or the end of the headers whichever happens first. **Default**: `128` + **Unit**: byte count ### `received_body_initial_len` @@ -78,6 +81,22 @@ insertion when they reach this size. **Unit**: Header count +### `received_body_max_preallocate` + +When we receive a fixed-length (not chunked-encoding) body that is smaller than this size, we can +allocate a buffer with exactly the right size before we receive the body. However, if this is +unbounded, malicious clients can issue headers with large content-length and then keep the +connection open without sending any bytes, allowing them to allocate memory faster than their +bandwidth usage. This does not limit the ability to receive fixed-length bodies larger than this, +but the memory allocation will grow as with chunked bodies. Note that this has no impact on chunked +bodies. If this is set higher than the `received_body_max_len`, this parameter has no effect. This +parameter only impacts [`ReceivedBody::read_string`] and [`ReceivedBody::read_bytes`]. + +**Default**: `1mb` in bytes + +**Unit**: Byte count + + ## Security parameters These parameters represent worst cases, to delineate between malicious (or malformed) requests and @@ -112,6 +131,7 @@ pub struct HttpConfig { pub(crate) response_header_initial_capacity: usize, pub(crate) copy_loops_per_yield: usize, pub(crate) received_body_initial_len: usize, + pub(crate) received_body_max_preallocate: usize, } #[allow(missing_docs)] @@ -160,6 +180,16 @@ impl HttpConfig { self.received_body_max_len = received_body_max_len; self } + + /// See [`received_body_max_len`][HttpConfig#received_body_max_len] + #[must_use] + pub fn with_received_body_max_preallocate( + mut self, + received_body_max_preallocate: usize, + ) -> Self { + self.received_body_max_preallocate = received_body_max_preallocate; + self + } } impl Default for HttpConfig { diff --git a/http/src/received_body.rs b/http/src/received_body.rs index a7567137a3..3e05061976 100644 --- a/http/src/received_body.rs +++ b/http/src/received_body.rs @@ -62,6 +62,7 @@ pub struct ReceivedBody<'conn, Transport> { max_len: u64, initial_len: usize, copy_loops_per_yield: usize, + max_preallocate: usize, } fn slice_from(min: u64, buf: &[u8]) -> Option<&[u8]> { @@ -115,6 +116,7 @@ where max_len: config.received_body_max_len, initial_len: config.received_body_initial_len, copy_loops_per_yield: config.copy_loops_per_yield, + max_preallocate: config.received_body_max_preallocate, } } @@ -171,11 +173,15 @@ where } /// Set the maximum length that can be read from this body before error + /// + /// See also [`HttpConfig::received_body_max_len`][HttpConfig#received_body_max_len] pub fn set_max_len(&mut self, max_len: u64) { self.max_len = max_len; } /// chainable setter for the maximum length that can be read from this body before error + /// + /// See also [`HttpConfig::received_body_max_len`][HttpConfig#received_body_max_len] #[must_use] pub fn with_max_len(mut self, max_len: u64) -> Self { self.set_max_len(max_len); @@ -205,7 +211,7 @@ where let len = usize::try_from(len) .map_err(|_| crate::Error::ReceivedBodyTooLong(self.max_len))?; - Vec::with_capacity(len) + Vec::with_capacity(len.min(self.max_preallocate)) } else { Vec::with_capacity(self.initial_len) };