Skip to content

Commit 6e19074

Browse files
committed
RootedGuard<T> construction related unsoundness
We're storing a `&mut Rooted<T>` in `RootedGuard` even though that pointer is aliased by the pointer held by the GC. This is... suspicious. Background information: There are 2 proposed memory models for rust right now, [tree borrows](https://perso.crans.org/vanille/treebor/index.html) and [stacked borrows](https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md). In the stacked borrows world, there's a stack-like structure containing "tags" describing which pointers have permission to access memory. Writing through a raw pointer will always invalidate all mutable references above it, and writing though a mutable reference will always invalidate all raw pointers above it. As such any ABAB interleaving off writes accesses through a mutable pointer A and a mutable reference B is always undefined behavior. In the tree borrows world, a mutable pointer is *equivalent* to the mutable reference it was created from. This could actually excuse the pattern here, *if* the pointer stored in the GC was created from the `&mut Rooted<T>` in `RootedGuard`. That's not how we construct this. *Maybe* we could modify things so it is, but that would would be playing with fire, and there's no reason to. --- That's not all. When we construct `RootedGuard`, we do so with the following code ```rust impl<'a, T: 'a + RootKind> RootedGuard<'a, T> { pub fn new(cx: *mut JSContext, root: &'a mut Rooted<T>, initial: T) -> Self { root.ptr.write(initial); unsafe { root.add_to_root_stack(cx); } RootedGuard { root } } } impl<T: RootKind> JS::Rooted<T> { pub unsafe fn add_to_root_stack(&mut self, cx: *mut JSContext) -> *mut Self { ... Eventually create a raw pointer from self } } ``` This pattern is inevitably undefined behavior, under both stacked borrows and tree borrows. As demonstrated by this toy example that can be run in miri. ```rust fn get_ptr(this: &mut usize) -> *mut usize { let ptr: *mut usize = this; ptr } fn main() { let rooted = &mut 0; let gc_ptr: *mut usize = get_ptr(rooted); let root_ptr: *mut usize = rooted; unsafe{ // Use the RootedGuard (writing to it) *root_ptr = 1; // Run a GC (even with just a read) *gc_ptr; }; } ``` Under stacked borrows: We start with a stack for rooted that looks like `[Unique(rooted), SharedReadWrite(root_ptr), Unique(get_ptr::this), SharedReadWrite(gc_ptr)]`. Note that stacked borrows inserts new raw pointers into the stack above the item that they are created from (it doesn't work like a stack). Then when we write to `root_ptr` we remove `Unique(get_ptr::this)` (because it's a unique pointer above us), and `SharedReadWrite(gc_ptr)` (because it's another `SharedReadWrite`, above us, with a non-`SharedReadWrite` between us and it - this is Stacked Borrows 2.1). Under tree borrows: `gc_ptr` shares permissions with `get_ptr::this`. `root_ptr` shares permissions with `rooted`. By writing to `root_ptr` we disable `get_ptr::this` and thus `gc_ptr`, resulting in undefined behavior. --- One more nitpick, `add_to_root_stack` in full is ```rust impl<T: RootKind> JS::Rooted<T> { pub unsafe fn add_to_root_stack(&mut self, cx: *mut JSContext) -> *mut Self { let ptr = self as *mut _; self.base.add_to_root_stack(cx, T::KIND) } } impl RootedBase { unsafe fn add_to_root_stack(&mut self, cx: *mut JSContext, kind: JS::RootKind) { let stack = Self::get_root_stack(cx, kind); self.stack = stack; self.prev = *stack; *stack = self as *mut _ as usize as _; } } ``` This is worrying because we create a `RootedBase` mutable reference and then cast that to a raw pointer. My understanding is that under the stacked borrows model (but not the tree borrows model) the set of memory allowed to be accessed to via a pointer created from a reference to `RootedBase` is limited to the size of the `RootedBase`. Thus the `GC` should not be using this pointer to look at the rest of `Rooted<T>` under the stacked borrows model. See rust-lang/unsafe-code-guidelines#256 --- All of these issues have the same fix. Cast the mutable reference to a mutable pointer *once*, derive everything else from that mutable pointer. In stacked borrows nothing below that pointer is ever used, so it is never invalidated. In tree borrows the same except the terminology would be no parent of that pointer. That's all this pull request does - it was originally going to be an issue but the change itself is small enough that it felt better to just make a PR. Note that the public `add_to_root_stack` function goes unused in `servo`, and that's the only public change in the API, so no coordination is necessary. Signed-off-by: Greg Morenz <[email protected]>
1 parent 2c59822 commit 6e19074

File tree

3 files changed

+26
-19
lines changed

3 files changed

+26
-19
lines changed

mozjs-sys/src/jsimpls.rs

+7-6
Original file line numberDiff line numberDiff line change
@@ -404,12 +404,12 @@ impl JSNativeWrapper {
404404
}
405405

406406
impl RootedBase {
407-
unsafe fn add_to_root_stack(&mut self, cx: *mut JSContext, kind: JS::RootKind) {
407+
unsafe fn add_to_root_stack(this: *mut Self, cx: *mut JSContext, kind: JS::RootKind) {
408408
let stack = Self::get_root_stack(cx, kind);
409-
self.stack = stack;
410-
self.prev = *stack;
409+
(*this).stack = stack;
410+
(*this).prev = *stack;
411411

412-
*stack = self as *mut _ as usize as _;
412+
*stack = this as usize as _;
413413
}
414414

415415
unsafe fn remove_from_root_stack(&mut self) {
@@ -440,8 +440,9 @@ impl<T: RootKind> JS::Rooted<T> {
440440
}
441441
}
442442

443-
pub unsafe fn add_to_root_stack(&mut self, cx: *mut JSContext) {
444-
self.base.add_to_root_stack(cx, T::KIND)
443+
pub unsafe fn add_to_root_stack(this: *mut Self, cx: *mut JSContext) {
444+
let base = unsafe { &raw mut (*this).base };
445+
RootedBase::add_to_root_stack(base, cx, T::KIND)
445446
}
446447

447448
pub unsafe fn remove_from_root_stack(&mut self) {

mozjs/src/conversions.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ use crate::rust::{HandleValue, MutableHandleValue};
4545
use crate::rust::{ToBoolean, ToInt32, ToInt64, ToNumber, ToUint16, ToUint32, ToUint64};
4646
use libc;
4747
use log::debug;
48+
use mozjs_sys::jsgc::Rooted;
4849
use std::borrow::Cow;
4950
use std::mem;
5051
use std::rc::Rc;
@@ -664,7 +665,7 @@ struct ForOfIteratorGuard<'a> {
664665
impl<'a> ForOfIteratorGuard<'a> {
665666
fn new(cx: *mut JSContext, root: &'a mut ForOfIterator) -> Self {
666667
unsafe {
667-
root.iterator.add_to_root_stack(cx);
668+
Rooted::add_to_root_stack(&raw mut root.iterator, cx);
668669
}
669670
ForOfIteratorGuard { root }
670671
}

mozjs/src/gc/root.rs

+17-12
Original file line numberDiff line numberDiff line change
@@ -21,16 +21,21 @@ use mozjs_sys::jsgc::ValueArray;
2121
crown::unrooted_must_root_lint::allow_unrooted_interior
2222
)]
2323
pub struct RootedGuard<'a, T: 'a + RootKind> {
24-
root: &'a mut Rooted<T>,
24+
root: *mut Rooted<T>,
25+
anchor: PhantomData<&'a mut Rooted<T>>,
2526
}
2627

2728
impl<'a, T: 'a + RootKind> RootedGuard<'a, T> {
2829
pub fn new(cx: *mut JSContext, root: &'a mut Rooted<T>, initial: T) -> Self {
2930
root.ptr.write(initial);
3031
unsafe {
31-
root.add_to_root_stack(cx);
32+
let root: *mut Rooted<T> = root;
33+
Rooted::add_to_root_stack(root, cx);
34+
RootedGuard {
35+
root,
36+
anchor: PhantomData,
37+
}
3238
}
33-
RootedGuard { root }
3439
}
3540

3641
pub fn handle(&'a self) -> Handle<'a, T> {
@@ -46,31 +51,31 @@ impl<'a, T: 'a + RootKind> RootedGuard<'a, T> {
4651
T: Copy,
4752
{
4853
// SAFETY: The rooted value is initialized as long as we exist
49-
unsafe { self.root.ptr.assume_init() }
54+
unsafe { (*self.root).ptr.assume_init() }
5055
}
5156

5257
pub fn set(&mut self, v: T) {
5358
// SAFETY: The rooted value is initialized as long as we exist
5459
unsafe {
5560
// Make sure the drop impl for T is called
56-
self.root.ptr.assume_init_drop()
61+
(*self.root).ptr.assume_init_drop();
62+
(*self.root).ptr.write(v);
5763
}
58-
self.root.ptr.write(v);
5964
}
6065
}
6166

6267
impl<'a, T: 'a + RootKind> Deref for RootedGuard<'a, T> {
6368
type Target = T;
6469
fn deref(&self) -> &T {
6570
// SAFETY: The rooted value is initialized as long as we exist
66-
unsafe { self.root.ptr.assume_init_ref() }
71+
unsafe { (*self.root).ptr.assume_init_ref() }
6772
}
6873
}
6974

7075
impl<'a, T: 'a + RootKind> DerefMut for RootedGuard<'a, T> {
7176
fn deref_mut(&mut self) -> &mut T {
7277
// SAFETY: The rooted value is initialized as long as we exist
73-
unsafe { self.root.ptr.assume_init_mut() }
78+
unsafe { (*self.root).ptr.assume_init_mut() }
7479
}
7580
}
7681

@@ -79,19 +84,19 @@ impl<'a, T: 'a + RootKind> Drop for RootedGuard<'a, T> {
7984
// SAFETY: The rooted value is initialized as long as we exist
8085
unsafe {
8186
// Make sure the drop impl for T is called
82-
self.root.ptr.assume_init_drop()
87+
(*self.root).ptr.assume_init_drop();
88+
(*self.root).ptr = MaybeUninit::zeroed();
8389
}
84-
self.root.ptr = MaybeUninit::zeroed();
8590

8691
unsafe {
87-
self.root.remove_from_root_stack();
92+
(*self.root).remove_from_root_stack();
8893
}
8994
}
9095
}
9196

9297
impl<'a, const N: usize> From<&RootedGuard<'a, ValueArray<N>>> for JS::HandleValueArray {
9398
fn from(array: &RootedGuard<'a, ValueArray<N>>) -> JS::HandleValueArray {
94-
JS::HandleValueArray::from(&*array.root)
99+
JS::HandleValueArray::from(unsafe { &*array.root })
95100
}
96101
}
97102

0 commit comments

Comments
 (0)