diff --git a/CHANGELOG.md b/CHANGELOG.md index 91169d2f4..48e2df99b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +# 1.11.1 (February 3rd, 2026) + +- Fix integer overflow in `BytesMut::reserve` + # 1.11.0 (November 14th, 2025) - Bump MSRV to 1.57 (#788) diff --git a/Cargo.toml b/Cargo.toml index 62211136f..57df5910d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,7 +4,7 @@ name = "bytes" # When releasing to crates.io: # - Update CHANGELOG.md. # - Create "v1.x.y" git tag. -version = "1.11.0" +version = "1.11.1" edition = "2021" rust-version = "1.57" license = "MIT" diff --git a/ci/miri.sh b/ci/miri.sh index 7df29f360..ca7f41df5 100755 --- a/ci/miri.sh +++ b/ci/miri.sh @@ -8,3 +8,6 @@ export MIRIFLAGS="-Zmiri-strict-provenance" cargo miri test cargo miri test --target mips64-unknown-linux-gnuabi64 + +# run with wrapping integer overflow instead of panic +cargo miri test --release diff --git a/src/bytes_mut.rs b/src/bytes_mut.rs index 565e91d9b..15e67d94c 100644 --- a/src/bytes_mut.rs +++ b/src/bytes_mut.rs @@ -695,9 +695,15 @@ impl BytesMut { let offset = self.ptr.as_ptr().offset_from(ptr) as usize; + let new_cap_plus_offset = match new_cap.checked_add(offset) { + Some(new_cap_plus_offset) => new_cap_plus_offset, + None if !allocate => return false, + None => panic!("overflow"), + }; + // Compare the condition in the `kind == KIND_VEC` case above // for more details. - if v_capacity >= new_cap + offset { + if v_capacity >= new_cap_plus_offset { self.cap = new_cap; // no copy is necessary } else if v_capacity >= new_cap && offset >= len { @@ -713,14 +719,12 @@ impl BytesMut { if !allocate { return false; } - // calculate offset - let off = (self.ptr.as_ptr() as usize) - (v.as_ptr() as usize); // new_cap is calculated in terms of `BytesMut`, not the underlying // `Vec`, so it does not take the offset into account. // // Thus we have to manually add it here. - new_cap = new_cap.checked_add(off).expect("overflow"); + new_cap = new_cap_plus_offset; // The vector capacity is not sufficient. The reserve request is // asking for more than the initial buffer capacity. Allocate more @@ -742,13 +746,13 @@ impl BytesMut { // the unused capacity of the vector is copied over to the new // allocation, so we need to ensure that we don't have any data we // care about in the unused capacity before calling `reserve`. - debug_assert!(off + len <= v.capacity()); - v.set_len(off + len); + debug_assert!(offset + len <= v.capacity()); + v.set_len(offset + len); v.reserve(new_cap - v.len()); // Update the info - self.ptr = vptr(v.as_mut_ptr().add(off)); - self.cap = v.capacity() - off; + self.ptr = vptr(v.as_mut_ptr().add(offset)); + self.cap = v.capacity() - offset; } return true; diff --git a/tests/test_bytes.rs b/tests/test_bytes.rs index ec9a60e6c..b9bd5e12b 100644 --- a/tests/test_bytes.rs +++ b/tests/test_bytes.rs @@ -1707,3 +1707,16 @@ fn bytes_mut_put_bytes_specialization() { // If allocation is reused, capacity should be equal to original vec capacity. assert_eq!(bytes_mut.capacity(), capacity); } + +#[test] +#[should_panic] +fn bytes_mut_reserve_overflow() { + let mut a = BytesMut::from(&b"hello world"[..]); + let mut b = a.split_off(5); + // Ensure b becomes the unique owner of the backing storage + drop(a); + // Trigger overflow in new_cap + offset inside reserve + b.reserve(usize::MAX - 6); + // This call relies on the corrupted cap and may cause UB & HBO + b.put_u8(b'h'); +}