From 7f86b2ace52b737db68513edbe94e3086ff06224 Mon Sep 17 00:00:00 2001 From: Minh Vu Date: Sun, 9 Aug 2026 16:39:18 +0200 Subject: [PATCH] codec: clamp runtime frame length to field width (#8275) --- tokio-util/src/codec/length_delimited.rs | 29 +++++++++++++++++------- tokio-util/tests/length_delimited.rs | 27 ++++++++++++++++++++++ 2 files changed, 48 insertions(+), 8 deletions(-) diff --git a/tokio-util/src/codec/length_delimited.rs b/tokio-util/src/codec/length_delimited.rs index 31799fdeb..8a7efbbdc 100644 --- a/tokio-util/src/codec/length_delimited.rs +++ b/tokio-util/src/codec/length_delimited.rs @@ -493,8 +493,12 @@ impl LengthDelimitedCodec { /// words, if a frame is currently in process of being decoded with a frame /// size greater than `val` but less than the max frame length in effect /// before calling this function, then the frame will be allowed. + /// + /// If `val` is larger than what the length field can represent, it is + /// clipped to the maximum representable value. pub fn set_max_frame_length(&mut self, val: usize) { self.builder.max_frame_length(val); + self.builder.adjust_max_frame_len(); } fn decode_head(&mut self, src: &mut BytesMut) -> io::Result> { @@ -1049,16 +1053,25 @@ impl Builder { } fn adjust_max_frame_len(&mut self) { - // Calculate the maximum number that can be represented using `length_field_len` bytes. - let max_number = match 1u64.checked_shl((8 * self.length_field_len) as u32) { + let max_allowed_len = self.max_allowed_frame_len(); + + if self.max_frame_len > max_allowed_len { + self.max_frame_len = max_allowed_len; + } + } + + fn max_allowed_frame_len(&self) -> usize { + let max_allowed_len = self + .max_length_field_value() + .saturating_add_signed(self.length_adjustment as i64); + + usize::try_from(max_allowed_len).unwrap_or(usize::MAX) + } + + fn max_length_field_value(&self) -> u64 { + match 1u64.checked_shl((8 * self.length_field_len) as u32) { Some(shl) => shl - 1, None => u64::MAX, - }; - - let max_allowed_len = max_number.saturating_add_signed(self.length_adjustment as i64); - - if self.max_frame_len as u64 > max_allowed_len { - self.max_frame_len = usize::try_from(max_allowed_len).unwrap_or(usize::MAX); } } } diff --git a/tokio-util/tests/length_delimited.rs b/tokio-util/tests/length_delimited.rs index 14d4ab521..2ff60d13e 100644 --- a/tokio-util/tests/length_delimited.rs +++ b/tokio-util/tests/length_delimited.rs @@ -699,6 +699,33 @@ fn frame_does_not_fit() { assert_eq!(codec.max_frame_length(), 255); } +#[test] +fn runtime_max_frame_len_respects_length_field() { + for (adjustment, max_frame_len) in [(-1, 254), (0, 255), (1, 256)] { + let mut codec = LengthDelimitedCodec::builder() + .length_field_length(1) + .length_adjustment(adjustment) + .new_codec(); + + codec.set_max_frame_length(1_000); + assert_eq!(codec.max_frame_length(), max_frame_len); + + let mut dst = BytesMut::new(); + codec + .encode(Bytes::from(vec![0; max_frame_len]), &mut dst) + .unwrap(); + assert_eq!(dst[0], u8::MAX); + + let mut dst = BytesMut::from(&b"prefix"[..]); + let original = dst.clone(); + let result = codec.encode(Bytes::from(vec![0; max_frame_len + 1]), &mut dst); + + assert!(result.is_err()); + assert_eq!(result.unwrap_err().kind(), io::ErrorKind::InvalidInput); + assert_eq!(dst, original); + } +} + #[test] fn neg_adjusted_frame_does_not_fit() { let codec = LengthDelimitedCodec::builder()