Repository: tokio-rs/bytes (Rust)

Issue:
Unsoundness in impl BufMut for Vec<u8>?
`BufMut` documents that `BufMut::remaining_mut` returns the number of bytes that can be written until the end of the allocation is reached.

https://github.com/tokio-rs/bytes/blob/ed1d24e57079a86c6201b5fd6be6f789265f0e6a/src/buf/buf_mut.rs#L30-L55

`BufMut` is an unsafe trait and is implemented for `Vec<u8>`. `Vec` is a growable buffer which means writing to it is unbounded until we reach the max allocation size.

`bytes` implements this method for `Vec<u8>` using `usize::MAX` as the maximum allocation size:

https://github.com/tokio-rs/bytes/blob/ed1d24e57079a86c6201b5fd6be6f789265f0e6a/src/buf/buf_mut.rs#L1025-L1029

`Vec` in the Rust `alloc` and `std` crates documents that its max allocation size is `isize::MAX`.

https://doc.rust-lang.org/stable/alloc/vec/index.html

> Vectors ensure they never allocate more than `isize::MAX` bytes.

Because `BufMut` is an unsafe trait, consumers of this trait are relying on it being implemented correctly to prevent memory unsafety. Does this incorrect implementation of `remaining_mut` have any unsafety implications?

Aside: I looked on https://tokio.rs and in GitHub and could not find a security reporting guide, hence this issue.


Discussion:
Thanks for the report. I think we can probably make the change, but I don't see a possible way to exploit this. At some point, when growing the vec, if it is no longer able to allocate it should panic.

> Aside: I looked on https://tokio.rs and in GitHub and could not find a security reporting guide, hence this issue.

In theory, when opening an issue, it should ask you what kind it is. One option is "Report a security vulnerability" which should redirect you here: https://github.com/tokio-rs/tokio/security/policy.

I don't think we need to remove this as I don't see an exploit.


> [...] I don't see a possible way to exploit this. At some point, when growing the vec, if it is no longer able to allocate it should panic.

That's good to hear. I didn't look too deeply into how `BufMut` is used and what parts of it must be correct to ensure safety. I noticed the bug and that the trait was `unsafe`. Thanks for confirming that this bug is not exploitable.

> In theory, when opening an issue, it should ask you what kind it is. One option is "Report a security vulnerability" which should redirect you here: https://github.com/tokio-rs/tokio/security/policy.

Ah, I did not look at the security tab in the tokio repo; I only looked at the security tab in the bytes repo. I think GitHub has a mechanism to set an organization-wide security policy such that it will appear on every repo in the org. The rust-lang org has this setup:  https://github.com/rust-lang/.github/blob/master/SECURITY.md

Thanks again for confirming this report. Happy weekend.
> At some point, when growing the vec, if it is no longer able to allocate it should panic.

No, rust aborts the program on allocation failures. In fact, if it panicked, we'd have another problem because the `chunk_mut` method says that it should never panic.



Patch:
diff --git a/src/buf/buf_mut.rs b/src/buf/buf_mut.rs
index 844474fb9..bf33fe635 100644
--- a/src/buf/buf_mut.rs
+++ b/src/buf/buf_mut.rs
@@ -33,6 +33,10 @@ pub unsafe trait BufMut {
     /// This value is greater than or equal to the length of the slice returned
     /// by `chunk_mut()`.
     ///
+    /// Writing to a `BufMut` may involve allocating more memory on the fly.
+    /// Implementations may fail before reaching the number of bytes indicated
+    /// by this method if they encounter an allocation failure.
+    ///
     /// # Examples
     ///
     /// ```
@@ -158,6 +162,9 @@ pub unsafe trait BufMut {
     /// `chunk_mut()` returning an empty slice implies that `remaining_mut()` will
     /// return 0 and `remaining_mut()` returning 0 implies that `chunk_mut()` will
     /// return an empty slice.
+    ///
+    /// This function may trigger an out-of-memory abort if it tries to allocate
+    /// memory and fails to do so.
     // The `chunk_mut` method was previously called `bytes_mut`. This alias makes the
     // rename more easily discoverable.
     #[cfg_attr(docsrs, doc(alias = "bytes_mut"))]
@@ -1025,7 +1032,8 @@ unsafe impl BufMut for &mut [u8] {
 unsafe impl BufMut for Vec<u8> {
     #[inline]
     fn remaining_mut(&self) -> usize {
-        usize::MAX - self.len()
+        // A vector can never have more than isize::MAX bytes
+        core::isize::MAX as usize - self.len()
     }
 
     #[inline]


Test patch:
diff --git a/tests/test_buf_mut.rs b/tests/test_buf_mut.rs
index b85ab9c67..f63198226 100644
--- a/tests/test_buf_mut.rs
+++ b/tests/test_buf_mut.rs
@@ -9,7 +9,7 @@ use core::usize;
 fn test_vec_as_mut_buf() {
     let mut buf = Vec::with_capacity(64);
 
-    assert_eq!(buf.remaining_mut(), usize::MAX);
+    assert_eq!(buf.remaining_mut(), isize::MAX as usize);
 
     assert!(buf.chunk_mut().len() >= 64);
 
@@ -17,7 +17,7 @@ fn test_vec_as_mut_buf() {
 
     assert_eq!(&buf, b"zomg");
 
-    assert_eq!(buf.remaining_mut(), usize::MAX - 4);
+    assert_eq!(buf.remaining_mut(), isize::MAX as usize - 4);
     assert_eq!(buf.capacity(), 64);
 
     for _ in 0..16 {