sebastiencs / shared-arena

A thread-safe & efficient memory pool

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

`Arena::alloc_with` is unsound when combined with types only sound behind `&`

CAD97 opened this issue · comments

Example:

pub struct Evil {
    _priv: (),
}

impl Evil {
    pub fn laughter(&mut self) {
        // SAFETY: it is impossible to construct `&mut Evil`.
        unsafe { unreachable_unchecked() };
        // A realistic case would have some safety invariant
        // that only matters for access by &mut, not by &ref.
    }
}

pub fn initialize_evil(place: &mut MaybeUninit<Evil>) -> &Evil {
    // SAFETY: `Evil` is ZST, thus does not need initialization.
    assert!(size_of::<Evil>() == 0);
    // SAFETY: `&Evil` cannot be used to call `Evil::laughter`.
    unsafe { place.assume_init_ref() }
}

// ... elsewhere ...
let mut arena_box = arena.alloc_with(initialize_evil);
arena_box.laughter(); // 💥

This exploits the fact that initializer is only required to return &T to create a reference to a type in some state where the &T-accessible API is sound, but &mut T access is unsound. This should be resolvable by changing initializer to require returning &mut T instead, which will serve as a witness not just that some T exists at the address, but that all of the API accessible from &mut T is sound to use, not just the &T API.

As a somewhat practical example, consider Vec. Since this is a type I do not control, constructing such an instance of the type is still "library UB" and a new function making construction of a bad Vec unsound could be added. However, if I create some Vec where the capacity does not match the size the pointer was allocated with, I believe the current &Vec API remains sound, even though the &mut Vec API is terribly unsound when Vec's invariants are violated in this manner.

This was prompted by someone asking about the shape of the function on urlo. https://users.rust-lang.org/t/asserting-initialization-of-maybeuninit-with-address/88491?u=cad97