Reel in unsafety around InstanceHandle (#856)

* Reel in unsafety around `InstanceHandle`

This commit is an attempt, or at least is targeted at being a start, at
reeling in the unsafety around the `InstanceHandle` type. Currently this
type represents a sort of moral `Rc<Instance>` but is a bit more
specialized since the underlying memory is allocated through mmap.

Additionally, though, `InstanceHandle` exposes a fundamental flaw in its
safety by safetly allowing mutable access so long as you have `&mut
InstanceHandle`. This type, however, is trivially created by simply
cloning a `InstanceHandle` to get an owned reference. This means that
`&mut InstanceHandle` does not actually provide any guarantees about
uniqueness, so there's no more safety than `&InstanceHandle` itself.

This commit removes all `&mut self` APIs from `InstanceHandle`,
additionally removing some where `&self` was `unsafe` and `&mut self`
was safe (since it was trivial to subvert this "safety"). In doing so
interior mutability patterns are now used much more extensively through
structures such as `Table` and `Memory`. Additionally a number of
methods were refactored to be a bit clearer and use helper functions
where possible.

This is a relatively large commit unfortunately, but it snowballed very
quickly into touching quite a few places. My hope though is that this
will prevent developers working on wasmtime internals as well as
developers still yet to migrate to the `wasmtime` crate from falling
into trivial unsafe traps by accidentally using `&mut` when they can't.
All existing users relying on `&mut` will need to migrate to some form
of interior mutability, such as using `RefCell` or `Cell`.

This commit also additionally marks `InstanceHandle::new` as an `unsafe`
function. The rationale for this is that the `&mut`-safety is only the
beginning for the safety of `InstanceHandle`. In general the wasmtime
internals are extremely unsafe and haven't been audited for appropriate
usage of `unsafe`. Until that's done it's hoped that we can warn users
with this `unsafe` constructor and otherwise push users to the
`wasmtime` crate which we know is safe.

* Fix windows build

* Wrap up mutable memory state in one structure

Rather than having separate fields

* Use `Cell::set`, not `Cell::replace`, where possible

* Add a helper function for offsets from VMContext

* Fix a typo from merging

* rustfmt

* Use try_from, not as

* Tweak style of some setters
This commit is contained in:
Alex Crichton
2020-01-24 14:20:35 -06:00
committed by GitHub
parent 3db1074c15
commit 47d6db0be8
18 changed files with 490 additions and 692 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -5,6 +5,7 @@
use crate::mmap::Mmap;
use crate::vmcontext::VMMemoryDefinition;
use more_asserts::{assert_ge, assert_le};
use std::cell::RefCell;
use std::convert::TryFrom;
use wasmtime_environ::{MemoryPlan, MemoryStyle, WASM_MAX_PAGES, WASM_PAGE_SIZE};
@@ -12,10 +13,7 @@ use wasmtime_environ::{MemoryPlan, MemoryStyle, WASM_MAX_PAGES, WASM_PAGE_SIZE};
#[derive(Debug)]
pub struct LinearMemory {
// The underlying allocation.
mmap: Mmap,
// The current logical size in wasm pages of this linear memory.
current: u32,
mmap: RefCell<WasmMmap>,
// The optional maximum size in wasm pages of this linear memory.
maximum: Option<u32>,
@@ -29,6 +27,14 @@ pub struct LinearMemory {
pub(crate) needs_signal_handlers: bool,
}
#[derive(Debug)]
struct WasmMmap {
// Our OS allocation of mmap'd memory.
alloc: Mmap,
// The current logical size in wasm pages of this linear memory.
size: u32,
}
impl LinearMemory {
/// Create a new linear memory instance with specified minimum and maximum number of wasm pages.
pub fn new(plan: &MemoryPlan) -> Result<Self, String> {
@@ -59,11 +65,13 @@ impl LinearMemory {
let mapped_pages = plan.memory.minimum as usize;
let mapped_bytes = mapped_pages * WASM_PAGE_SIZE as usize;
let mmap = Mmap::accessible_reserved(mapped_bytes, request_bytes)?;
let mmap = WasmMmap {
alloc: Mmap::accessible_reserved(mapped_bytes, request_bytes)?,
size: plan.memory.minimum,
};
Ok(Self {
mmap,
current: plan.memory.minimum,
mmap: mmap.into(),
maximum: plan.memory.maximum,
offset_guard_size: offset_guard_bytes,
needs_signal_handlers,
@@ -72,25 +80,26 @@ impl LinearMemory {
/// Returns the number of allocated wasm pages.
pub fn size(&self) -> u32 {
self.current
self.mmap.borrow().size
}
/// Grow memory by the specified amount of wasm pages.
///
/// Returns `None` if memory can't be grown by the specified amount
/// of wasm pages.
pub fn grow(&mut self, delta: u32) -> Option<u32> {
pub fn grow(&self, delta: u32) -> Option<u32> {
// Optimization of memory.grow 0 calls.
let mut mmap = self.mmap.borrow_mut();
if delta == 0 {
return Some(self.current);
return Some(mmap.size);
}
let new_pages = match self.current.checked_add(delta) {
let new_pages = match mmap.size.checked_add(delta) {
Some(new_pages) => new_pages,
// Linear memory size overflow.
None => return None,
};
let prev_pages = self.current;
let prev_pages = mmap.size;
if let Some(maximum) = self.maximum {
if new_pages > maximum {
@@ -111,7 +120,7 @@ impl LinearMemory {
let prev_bytes = usize::try_from(prev_pages).unwrap() * WASM_PAGE_SIZE as usize;
let new_bytes = usize::try_from(new_pages).unwrap() * WASM_PAGE_SIZE as usize;
if new_bytes > self.mmap.len() - self.offset_guard_size {
if new_bytes > mmap.alloc.len() - self.offset_guard_size {
// If the new size is within the declared maximum, but needs more memory than we
// have on hand, it's a dynamic heap and it can move.
let guard_bytes = self.offset_guard_size;
@@ -119,25 +128,26 @@ impl LinearMemory {
let mut new_mmap = Mmap::accessible_reserved(new_bytes, request_bytes).ok()?;
let copy_len = self.mmap.len() - self.offset_guard_size;
new_mmap.as_mut_slice()[..copy_len].copy_from_slice(&self.mmap.as_slice()[..copy_len]);
let copy_len = mmap.alloc.len() - self.offset_guard_size;
new_mmap.as_mut_slice()[..copy_len].copy_from_slice(&mmap.alloc.as_slice()[..copy_len]);
self.mmap = new_mmap;
mmap.alloc = new_mmap;
} else if delta_bytes > 0 {
// Make the newly allocated pages accessible.
self.mmap.make_accessible(prev_bytes, delta_bytes).ok()?;
mmap.alloc.make_accessible(prev_bytes, delta_bytes).ok()?;
}
self.current = new_pages;
mmap.size = new_pages;
Some(prev_pages)
}
/// Return a `VMMemoryDefinition` for exposing the memory to compiled wasm code.
pub fn vmmemory(&mut self) -> VMMemoryDefinition {
pub fn vmmemory(&self) -> VMMemoryDefinition {
let mut mmap = self.mmap.borrow_mut();
VMMemoryDefinition {
base: self.mmap.as_mut_ptr(),
current_length: self.current as usize * WASM_PAGE_SIZE as usize,
base: mmap.alloc.as_mut_ptr(),
current_length: mmap.size as usize * WASM_PAGE_SIZE as usize,
}
}
}

View File

@@ -3,6 +3,7 @@
//! `Table` is to WebAssembly tables what `LinearMemory` is to WebAssembly linear memories.
use crate::vmcontext::{VMCallerCheckedAnyfunc, VMTableDefinition};
use std::cell::RefCell;
use std::convert::{TryFrom, TryInto};
use wasmtime_environ::wasm::TableElementType;
use wasmtime_environ::{TablePlan, TableStyle};
@@ -10,7 +11,7 @@ use wasmtime_environ::{TablePlan, TableStyle};
/// A table instance.
#[derive(Debug)]
pub struct Table {
vec: Vec<VMCallerCheckedAnyfunc>,
vec: RefCell<Vec<VMCallerCheckedAnyfunc>>,
maximum: Option<u32>,
}
@@ -25,10 +26,10 @@ impl Table {
};
match plan.style {
TableStyle::CallerChecksSignature => Self {
vec: vec![
vec: RefCell::new(vec![
VMCallerCheckedAnyfunc::default();
usize::try_from(plan.table.minimum).unwrap()
],
]),
maximum: plan.table.maximum,
},
}
@@ -36,14 +37,14 @@ impl Table {
/// Returns the number of allocated elements.
pub fn size(&self) -> u32 {
self.vec.len().try_into().unwrap()
self.vec.borrow().len().try_into().unwrap()
}
/// Grow table by the specified amount of elements.
///
/// Returns `None` if table can't be grown by the specified amount
/// of elements.
pub fn grow(&mut self, delta: u32) -> Option<u32> {
pub fn grow(&self, delta: u32) -> Option<u32> {
let new_len = match self.size().checked_add(delta) {
Some(len) => {
if let Some(max) = self.maximum {
@@ -57,7 +58,7 @@ impl Table {
return None;
}
};
self.vec.resize(
self.vec.borrow_mut().resize(
usize::try_from(new_len).unwrap(),
VMCallerCheckedAnyfunc::default(),
);
@@ -67,34 +68,31 @@ impl Table {
/// Get reference to the specified element.
///
/// Returns `None` if the index is out of bounds.
pub fn get(&self, index: u32) -> Option<&VMCallerCheckedAnyfunc> {
self.vec.get(index as usize)
pub fn get(&self, index: u32) -> Option<VMCallerCheckedAnyfunc> {
self.vec.borrow().get(index as usize).cloned()
}
/// Get mutable reference to the specified element.
/// Set reference to the specified element.
///
/// Returns `None` if the index is out of bounds.
pub fn get_mut(&mut self, index: u32) -> Option<&mut VMCallerCheckedAnyfunc> {
self.vec.get_mut(index as usize)
/// # Panics
///
/// Panics if `index` is out of bounds.
pub fn set(&self, index: u32, func: VMCallerCheckedAnyfunc) -> Result<(), ()> {
match self.vec.borrow_mut().get_mut(index as usize) {
Some(slot) => {
*slot = func;
Ok(())
}
None => Err(()),
}
}
/// Return a `VMTableDefinition` for exposing the table to compiled wasm code.
pub fn vmtable(&mut self) -> VMTableDefinition {
pub fn vmtable(&self) -> VMTableDefinition {
let mut vec = self.vec.borrow_mut();
VMTableDefinition {
base: self.vec.as_mut_ptr() as *mut u8,
current_elements: self.vec.len().try_into().unwrap(),
base: vec.as_mut_ptr() as *mut u8,
current_elements: vec.len().try_into().unwrap(),
}
}
}
impl AsRef<[VMCallerCheckedAnyfunc]> for Table {
fn as_ref(&self) -> &[VMCallerCheckedAnyfunc] {
self.vec.as_slice()
}
}
impl AsMut<[VMCallerCheckedAnyfunc]> for Table {
fn as_mut(&mut self) -> &mut [VMCallerCheckedAnyfunc] {
self.vec.as_mut_slice()
}
}

View File

@@ -605,16 +605,16 @@ impl VMContext {
/// This is unsafe because it doesn't work on just any `VMContext`, it must
/// be a `VMContext` allocated as part of an `Instance`.
#[allow(clippy::cast_ptr_alignment)]
pub(crate) unsafe fn instance(&mut self) -> &mut Instance {
&mut *((self as *mut Self as *mut u8).offset(-Instance::vmctx_offset()) as *mut Instance)
pub(crate) unsafe fn instance(&self) -> &Instance {
&*((self as *const Self as *mut u8).offset(-Instance::vmctx_offset()) as *const Instance)
}
/// Return a mutable reference to the host state associated with this `Instance`.
/// Return a reference to the host state associated with this `Instance`.
///
/// # Safety
/// This is unsafe because it doesn't work on just any `VMContext`, it must
/// be a `VMContext` allocated as part of an `Instance`.
pub unsafe fn host_state(&mut self) -> &mut dyn Any {
pub unsafe fn host_state(&self) -> &dyn Any {
self.instance().host_state()
}
}