codec: clamp runtime frame length to field width (#8275)

This commit is contained in:
Minh Vu
2026-08-09 16:39:18 +02:00
committed by GitHub
parent 6b62ac48ed
commit 7f86b2ace5
2 changed files with 48 additions and 8 deletions
+21 -8
View File
@@ -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<Option<usize>> {
@@ -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);
}
}
}
+27
View File
@@ -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()