Skip to content

Commit cf0e885

Browse files
Fix aliasing bugs revealed by Tree Borrows (#889)
This PR fixes some aliasing bugs caused by improper usage of shared and mutable references intermixed with raw pointer accesses. These have been discovered by running the testsuite under [Tree Borrows](https://www.ralfj.de/blog/2023/06/02/tree-borrows.html), a new aliasing model for Rust and a potential replacement for Stacked Borrows. While TB is in general more lenient than SB, there are some cases where it is more strict due to SB relying on gross hacks, which have been removed from TB. Turns out that `deno_core` relied on such hacks. The problem is with the `DynFutureInfoErased`, which has arbitrary data that is being modified by a future while other references to it are held in the runtime. To prevent this, that is put into an `UnsafeCell` which makes such modifications allowed. But `UnsafeCell` only ensures that shared references can be modified without causing UB, it does _not_ permit mutable references to be aliased. Thus, one should take care not to create mutable references here. The easiest way of doing this is to remove the implementation of `DerefMut` and `AsMut` for `ArenaBox<T>`, and work around the few cases where that leads to breakage (by using raw pointers, which is also more robust in terms of the aliasing model). This fixes #884. See that issue for more information. I have not audited the entire crate, so it is possible that other TB bugs are lurking elsewhere; especially in the parts that Miri can currently not test properly. But this at least makes all the test that could work, do work.
1 parent 27bd85a commit cf0e885

File tree

3 files changed

+22
-29
lines changed

3 files changed

+22
-29
lines changed

core/arena/unique_arena.rs

+8-20
Original file line numberDiff line numberDiff line change
@@ -60,8 +60,8 @@ impl<T: 'static> ArenaBox<T> {
6060
/// This function returns a raw pointer without managing the memory, potentially leading to
6161
/// memory leaks if the pointer is not properly handled or deallocated.
6262
#[inline(always)]
63-
pub fn into_raw(mut alloc: ArenaBox<T>) -> NonNull<T> {
64-
let ptr = NonNull::from(alloc.data_mut());
63+
pub fn into_raw(alloc: ArenaBox<T>) -> NonNull<T> {
64+
let ptr: NonNull<ArenaBoxData<T>> = alloc.ptr;
6565
std::mem::forget(alloc);
6666
unsafe { Self::ptr_from_data(ptr) }
6767
}
@@ -94,8 +94,10 @@ impl<T> ArenaBox<T> {
9494
}
9595

9696
#[inline(always)]
97-
fn data_mut(&mut self) -> &mut ArenaBoxData<T> {
98-
unsafe { self.ptr.as_mut() }
97+
pub(crate) fn deref_data(&self) -> NonNull<T> {
98+
unsafe {
99+
NonNull::new_unchecked(std::ptr::addr_of_mut!((*self.ptr.as_ptr()).data))
100+
}
99101
}
100102
}
101103

@@ -123,20 +125,6 @@ impl<T> std::convert::AsRef<T> for ArenaBox<T> {
123125
}
124126
}
125127

126-
impl<T> std::ops::DerefMut for ArenaBox<T> {
127-
#[inline(always)]
128-
fn deref_mut(&mut self) -> &mut Self::Target {
129-
&mut self.data_mut().data
130-
}
131-
}
132-
133-
impl<T> std::convert::AsMut<T> for ArenaBox<T> {
134-
#[inline(always)]
135-
fn as_mut(&mut self) -> &mut T {
136-
&mut self.data_mut().data
137-
}
138-
}
139-
140128
impl<F, R> std::future::Future for ArenaBox<F>
141129
where
142130
F: Future<Output = R>,
@@ -145,10 +133,10 @@ where
145133

146134
#[inline(always)]
147135
fn poll(
148-
mut self: Pin<&mut Self>,
136+
self: Pin<&mut Self>,
149137
cx: &mut std::task::Context<'_>,
150138
) -> std::task::Poll<Self::Output> {
151-
unsafe { F::poll(Pin::new_unchecked(&mut self.data_mut().data), cx) }
139+
unsafe { F::poll(Pin::new_unchecked(self.deref_data().as_mut()), cx) }
152140
}
153141
}
154142

core/runtime/op_driver/erased_future.rs

+6-3
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,11 @@ impl<const MAX_SIZE: usize> TypeErased<MAX_SIZE> {
4141
}
4242

4343
#[inline(always)]
44-
pub fn raw_ptr<R>(&mut self) -> NonNull<R> {
45-
unsafe { NonNull::new_unchecked(self.memory.as_mut_ptr() as *mut _) }
44+
/// Safety: This uses a place projection to `this.memory`, which must be in-bounds.
45+
pub unsafe fn raw_ptr<R>(this: *mut Self) -> NonNull<R> {
46+
unsafe {
47+
NonNull::new_unchecked(std::ptr::addr_of_mut!((*this).memory) as *mut _)
48+
}
4649
}
4750

4851
#[inline(always)]
@@ -73,7 +76,7 @@ impl<const MAX_SIZE: usize> TypeErased<MAX_SIZE> {
7376

7477
impl<const MAX_SIZE: usize> Drop for TypeErased<MAX_SIZE> {
7578
fn drop(&mut self) {
76-
(self.drop)(self.raw_ptr::<()>())
79+
(self.drop)(unsafe { Self::raw_ptr(self) })
7780
}
7881
}
7982

core/runtime/op_driver/future_arena.rs

+8-6
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ use super::erased_future::TypeErased;
44
use crate::arena::ArenaBox;
55
use crate::arena::ArenaUnique;
66
use pin_project::pin_project;
7+
use std::cell::UnsafeCell;
78
use std::future::Future;
89
use std::marker::PhantomData;
910
use std::mem::MaybeUninit;
@@ -26,7 +27,7 @@ pub trait FutureContextMapper<T, C, R> {
2627

2728
struct DynFutureInfoErased<T, C> {
2829
ptr: MaybeUninit<NonNull<dyn ContextFuture<T, C>>>,
29-
data: TypeErased<MAX_ARENA_FUTURE_SIZE>,
30+
data: UnsafeCell<TypeErased<MAX_ARENA_FUTURE_SIZE>>,
3031
}
3132

3233
pub trait ContextFuture<T, C>: Future<Output = T> {
@@ -191,19 +192,20 @@ impl<T, C: Clone> FutureArena<T, C> {
191192
{
192193
unsafe {
193194
if let Some(reservation) = self.arena.reserve_space() {
194-
let mut alloc = self.arena.complete_reservation(
195+
let alloc = self.arena.complete_reservation(
195196
reservation,
196197
DynFutureInfoErased {
197198
ptr: MaybeUninit::uninit(),
198-
data: TypeErased::new(DynFutureInfo {
199+
data: UnsafeCell::new(TypeErased::new(DynFutureInfo {
199200
context,
200201
future,
201202
_phantom: PhantomData,
202-
}),
203+
})),
203204
},
204205
);
205-
let ptr = alloc.data.raw_ptr::<DynFutureInfo<T, C, M, F>>();
206-
alloc.ptr.write(ptr);
206+
let ptr =
207+
TypeErased::raw_ptr::<DynFutureInfo<T, C, M, F>>(alloc.data.get());
208+
(*alloc.deref_data().as_ptr()).ptr.write(ptr);
207209
return TypedFutureAllocation {
208210
inner: FutureAllocation::Arena(alloc),
209211
ptr,

0 commit comments

Comments
 (0)