rust-lang/rust

String::push_str invalidates interior references even when it does not reallocate

matklad opened this issue · 5 comments

To my knowledge, the following code is intended to be legal:

fn main() {
    let mut buf = String::with_capacity(11);
    buf.push_str("hello");
    let hello: &str = unsafe { &*(buf.as_str() as *const _) }; // laundering the lifetime -- we take care that `buf` does not reallocate, so that's okay.
    buf.push_str(" world");
    println!("{}", hello);
}

However, Miri currently flags this as UB.

I believe this is #60847, but for String. Discovered while writing this post.

cc @RalfJung

I believe this is #60847, but for String

Yeah this looks pretty similar. String uses Vec under the hood though, so I wonder why the issue still exists here.

Looks like push_str is implemented via extend_from_slice -- and indeed, this errors, too:

fn main() {
    let mut v = Vec::with_capacity(10);
    v.push(0);
    let v0 = unsafe { &*(&v[0] as *const _) }; // laundering the lifetime -- we take care that `v` does not reallocate, so that's okay.
    v.extend_from_slice(&[1]);
    let _val = *v0;
}

The problem is in this line:

self.get_unchecked_mut(len..).copy_from_slice(slice);

The get_unchecked_mut here is a slice method, so this creates a slice covering the entire Vec, which overlaps with the mutable reference created before and thus invalidates it.

It shouldn't be too hard to locally fix this, but that duplicates some code and there are some other uses of get_unchecked_mut in vec.rs. Ideally, this would all use raw slices to avoid making any uniqueness promises. But without #60639, raw slices are not very useful.

Here's a possible fix:

diff --git a/src/liballoc/vec.rs b/src/liballoc/vec.rs
index 4769091183a..fd2b336eceb 100644
--- a/src/liballoc/vec.rs
+++ b/src/liballoc/vec.rs
@@ -2121,8 +2121,9 @@ where
         self.reserve(slice.len());
         unsafe {
             let len = self.len();
+            let dst_slice = slice::from_raw_parts_mut(self.as_mut_ptr().add(len), slice.len());
+            dst_slice.copy_from_slice(slice);
             self.set_len(len + slice.len());
-            self.get_unchecked_mut(len..).copy_from_slice(slice);
         }
     }
 }

Fix PR is up: #70558