Revamp memory management of InstanceHandle (#1624)

* Revamp memory management of `InstanceHandle`

This commit fixes a known but in Wasmtime where an instance could still
be used after it was freed. Unfortunately the fix here is a bit of a
hammer, but it's the best that we can do for now. The changes made in
this commit are:

* A `Store` now stores all `InstanceHandle` objects it ever creates.
  This keeps all instances alive unconditionally (along with all host
  functions and such) until the `Store` is itself dropped. Note that a
  `Store` is reference counted so basically everything has to be dropped
  to drop anything, there's no longer any partial deallocation of instances.

* The `InstanceHandle` type's own reference counting has been removed.
  This is largely redundant with what's already happening in `Store`, so
  there's no need to manage two reference counts.

* Each `InstanceHandle` no longer tracks its dependencies in terms of
  instance handles. This set was actually inaccurate due to dynamic
  updates to tables and such, so we needed to revamp it anyway.

* Initialization of an `InstanceHandle` is now deferred until after
  `InstanceHandle::new`. This allows storing the `InstanceHandle` before
  side-effectful initialization, such as copying element segments or
  running the start function, to ensure that regardless of the result of
  instantiation the underlying `InstanceHandle` is still available to
  persist in storage.

Overall this should fix a known possible way to safely segfault Wasmtime
today (yay!) and it should also fix some flaikness I've seen on CI.
Turns out one of the spec tests
(bulk-memory-operations/partial-init-table-segment.wast) exercises this
functionality and we were hitting sporating use-after-free, but only on
Windows.

* Shuffle some APIs around

* Comment weak cycle
This commit is contained in:
Alex Crichton
2020-04-29 12:47:49 -05:00
committed by GitHub
parent 738e2742da
commit 654e953fbf
21 changed files with 315 additions and 256 deletions

View File

@@ -1,7 +1,8 @@
use crate::trampoline::{generate_global_export, generate_memory_export, generate_table_export};
use crate::trampoline::{
generate_global_export, generate_memory_export, generate_table_export, StoreInstanceHandle,
};
use crate::values::{from_checked_anyfunc, into_checked_anyfunc, Val};
use crate::Mutability;
use crate::{ExternType, GlobalType, MemoryType, TableType, ValType};
use crate::{ExternType, GlobalType, MemoryType, Mutability, TableType, ValType};
use crate::{Func, Store, Trap};
use anyhow::{anyhow, bail, Result};
use std::slice;
@@ -92,21 +93,20 @@ impl Extern {
pub(crate) fn from_wasmtime_export(
wasmtime_export: wasmtime_runtime::Export,
store: &Store,
instance_handle: InstanceHandle,
instance: StoreInstanceHandle,
) -> Extern {
match wasmtime_export {
wasmtime_runtime::Export::Function(f) => {
Extern::Func(Func::from_wasmtime_function(f, store, instance_handle))
Extern::Func(Func::from_wasmtime_function(f, instance))
}
wasmtime_runtime::Export::Memory(m) => {
Extern::Memory(Memory::from_wasmtime_memory(m, store, instance_handle))
Extern::Memory(Memory::from_wasmtime_memory(m, instance))
}
wasmtime_runtime::Export::Global(g) => {
Extern::Global(Global::from_wasmtime_global(g, store, instance_handle))
Extern::Global(Global::from_wasmtime_global(g, instance))
}
wasmtime_runtime::Export::Table(t) => {
Extern::Table(Table::from_wasmtime_table(t, store, instance_handle))
Extern::Table(Table::from_wasmtime_table(t, instance))
}
}
}
@@ -114,9 +114,9 @@ impl Extern {
pub(crate) fn comes_from_same_store(&self, store: &Store) -> bool {
let my_store = match self {
Extern::Func(f) => f.store(),
Extern::Global(g) => &g.store,
Extern::Memory(m) => &m.store,
Extern::Table(t) => &t.store,
Extern::Global(g) => &g.instance.store,
Extern::Memory(m) => &m.instance.store,
Extern::Table(t) => &t.instance.store,
};
Store::same(my_store, store)
}
@@ -163,9 +163,8 @@ impl From<Table> for Extern {
/// instances are equivalent in their functionality.
#[derive(Clone)]
pub struct Global {
store: Store,
instance: StoreInstanceHandle,
wasmtime_export: wasmtime_runtime::ExportGlobal,
wasmtime_handle: InstanceHandle,
}
impl Global {
@@ -187,11 +186,10 @@ impl Global {
if val.ty() != *ty.content() {
bail!("value provided does not match the type of this global");
}
let (wasmtime_handle, wasmtime_export) = generate_global_export(store, &ty, val)?;
let (instance, wasmtime_export) = generate_global_export(store, &ty, val)?;
Ok(Global {
store: store.clone(),
instance,
wasmtime_export,
wasmtime_handle,
})
}
@@ -246,7 +244,7 @@ impl Global {
if val.ty() != ty {
bail!("global of type {:?} cannot be set to {:?}", ty, val.ty());
}
if !val.comes_from_same_store(&self.store) {
if !val.comes_from_same_store(&self.instance.store) {
bail!("cross-`Store` values are not supported");
}
unsafe {
@@ -264,13 +262,11 @@ impl Global {
pub(crate) fn from_wasmtime_global(
wasmtime_export: wasmtime_runtime::ExportGlobal,
store: &Store,
wasmtime_handle: InstanceHandle,
instance: StoreInstanceHandle,
) -> Global {
Global {
store: store.clone(),
instance,
wasmtime_export,
wasmtime_handle,
}
}
}
@@ -292,18 +288,17 @@ impl Global {
/// instances are equivalent in their functionality.
#[derive(Clone)]
pub struct Table {
store: Store,
wasmtime_handle: InstanceHandle,
instance: StoreInstanceHandle,
wasmtime_export: wasmtime_runtime::ExportTable,
}
fn set_table_item(
handle: &InstanceHandle,
instance: &InstanceHandle,
table_index: wasm::DefinedTableIndex,
item_index: u32,
item: wasmtime_runtime::VMCallerCheckedAnyfunc,
) -> Result<()> {
handle
instance
.table_set(table_index, item_index, item)
.map_err(|()| anyhow!("table element index out of bounds"))
}
@@ -322,18 +317,17 @@ impl Table {
/// Returns an error if `init` does not match the element type of the table.
pub fn new(store: &Store, ty: TableType, init: Val) -> Result<Table> {
let item = into_checked_anyfunc(init, store)?;
let (wasmtime_handle, wasmtime_export) = generate_table_export(store, &ty)?;
let (instance, wasmtime_export) = generate_table_export(store, &ty)?;
// Initialize entries with the init value.
let definition = unsafe { &*wasmtime_export.definition };
let index = wasmtime_handle.table_index(definition);
let index = instance.table_index(definition);
for i in 0..definition.current_elements {
set_table_item(&wasmtime_handle, index, i, item.clone())?;
set_table_item(&instance, index, i, item.clone())?;
}
Ok(Table {
store: store.clone(),
wasmtime_handle,
instance,
wasmtime_export,
})
}
@@ -345,10 +339,7 @@ impl Table {
}
fn wasmtime_table_index(&self) -> wasm::DefinedTableIndex {
unsafe {
self.wasmtime_handle
.table_index(&*self.wasmtime_export.definition)
}
unsafe { self.instance.table_index(&*self.wasmtime_export.definition) }
}
/// Returns the table element value at `index`.
@@ -356,8 +347,8 @@ impl Table {
/// Returns `None` if `index` is out of bounds.
pub fn get(&self, index: u32) -> Option<Val> {
let table_index = self.wasmtime_table_index();
let item = self.wasmtime_handle.table_get(table_index, index)?;
Some(from_checked_anyfunc(item, &self.store))
let item = self.instance.table_get(table_index, index)?;
Some(from_checked_anyfunc(item, &self.instance.store))
}
/// Writes the `val` provided into `index` within this table.
@@ -368,8 +359,8 @@ impl Table {
/// the right type to be stored in this table.
pub fn set(&self, index: u32, val: Val) -> Result<()> {
let table_index = self.wasmtime_table_index();
let item = into_checked_anyfunc(val, &self.store)?;
set_table_item(&self.wasmtime_handle, table_index, index, item)
let item = into_checked_anyfunc(val, &self.instance.store)?;
set_table_item(&self.instance, table_index, index, item)
}
/// Returns the current size of this table.
@@ -387,12 +378,11 @@ impl Table {
/// error if `init` is not of the right type.
pub fn grow(&self, delta: u32, init: Val) -> Result<u32> {
let index = self.wasmtime_table_index();
let item = into_checked_anyfunc(init, &self.store)?;
if let Some(len) = self.wasmtime_handle.clone().table_grow(index, delta) {
let mut wasmtime_handle = self.wasmtime_handle.clone();
let item = into_checked_anyfunc(init, &self.instance.store)?;
if let Some(len) = self.instance.table_grow(index, delta) {
for i in 0..delta {
let i = len - (delta - i);
set_table_item(&mut wasmtime_handle, index, i, item.clone())?;
set_table_item(&self.instance, index, i, item.clone())?;
}
Ok(len)
} else {
@@ -414,7 +404,7 @@ impl Table {
src_index: u32,
len: u32,
) -> Result<()> {
if !Store::same(&dst_table.store, &src_table.store) {
if !Store::same(&dst_table.instance.store, &src_table.instance.store) {
bail!("cross-`Store` table copies are not supported");
}
@@ -423,10 +413,10 @@ impl Table {
// come from different modules.
let dst_table_index = dst_table.wasmtime_table_index();
let dst_table = dst_table.wasmtime_handle.get_defined_table(dst_table_index);
let dst_table = dst_table.instance.get_defined_table(dst_table_index);
let src_table_index = src_table.wasmtime_table_index();
let src_table = src_table.wasmtime_handle.get_defined_table(src_table_index);
let src_table = src_table.instance.get_defined_table(src_table_index);
runtime::Table::copy(dst_table, src_table, dst_index, src_index, len)
.map_err(Trap::from_jit)?;
@@ -435,12 +425,10 @@ impl Table {
pub(crate) fn from_wasmtime_table(
wasmtime_export: wasmtime_runtime::ExportTable,
store: &Store,
wasmtime_handle: wasmtime_runtime::InstanceHandle,
instance: StoreInstanceHandle,
) -> Table {
Table {
store: store.clone(),
wasmtime_handle,
instance,
wasmtime_export,
}
}
@@ -654,8 +642,7 @@ impl Table {
/// [open an issue]: https://github.com/bytecodealliance/wasmtime/issues/new
#[derive(Clone)]
pub struct Memory {
store: Store,
wasmtime_handle: InstanceHandle,
instance: StoreInstanceHandle,
wasmtime_export: wasmtime_runtime::ExportMemory,
}
@@ -683,11 +670,10 @@ impl Memory {
/// # }
/// ```
pub fn new(store: &Store, ty: MemoryType) -> Memory {
let (wasmtime_handle, wasmtime_export) =
let (instance, wasmtime_export) =
generate_memory_export(store, &ty).expect("generated memory");
Memory {
store: store.clone(),
wasmtime_handle,
instance,
wasmtime_export,
}
}
@@ -827,22 +813,19 @@ impl Memory {
/// ```
pub fn grow(&self, delta: u32) -> Result<u32> {
let index = self
.wasmtime_handle
.instance
.memory_index(unsafe { &*self.wasmtime_export.definition });
self.wasmtime_handle
.clone()
self.instance
.memory_grow(index, delta)
.ok_or_else(|| anyhow!("failed to grow memory"))
}
pub(crate) fn from_wasmtime_memory(
wasmtime_export: wasmtime_runtime::ExportMemory,
store: &Store,
wasmtime_handle: wasmtime_runtime::InstanceHandle,
instance: StoreInstanceHandle,
) -> Memory {
Memory {
store: store.clone(),
wasmtime_handle,
instance,
wasmtime_export,
}
}