Fix a use-after-free bug when passing ExternRefs to Wasm

We _must not_ trigger a GC when moving refs from host code into
Wasm (e.g. returned from a host function or passed as arguments to a Wasm
function). After insertion into the table, this reference is no longer
rooted. If multiple references are being sent from the host into Wasm and we
allowed GCs during insertion, then the following events could happen:

* Reference A is inserted into the activations table. This does not trigger a
  GC, but does fill the table to capacity.

* The caller's reference to A is removed. Now the only reference to A is from
  the activations table.

* Reference B is inserted into the activations table. Because the table is at
  capacity, a GC is triggered.

* A is reclaimed because the only reference keeping it alive was the activation
  table's reference (it isn't inside any Wasm frames on the stack yet, so stack
  scanning and stack maps don't increment its reference count).

* We transfer control to Wasm, giving it A and B. Wasm uses A. That's a use
  after free.

To prevent uses after free, we cannot GC when moving refs into the
`VMExternRefActivationsTable` because we are passing them from the host to Wasm.

On the other hand, when we are *cloning* -- as opposed to moving -- refs from
the host to Wasm, then it is fine to GC while inserting into the activations
table, because the original referent that we are cloning from is still alive and
rooting the ref.
This commit is contained in:
Nick Fitzgerald
2021-08-31 14:06:02 -07:00
parent 4b256ab968
commit d2ce1ac753
14 changed files with 665 additions and 106 deletions

View File

@@ -71,29 +71,66 @@ impl From<WasmType> for wasmparser::Type {
/// WebAssembly function type -- equivalent of `wasmparser`'s FuncType.
#[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)]
pub struct WasmFuncType {
params: Box<[WasmType]>,
externref_params_count: usize,
returns: Box<[WasmType]>,
externref_returns_count: usize,
}
impl WasmFuncType {
#[inline]
pub fn new(params: Box<[WasmType]>, returns: Box<[WasmType]>) -> Self {
let externref_params_count = params.iter().filter(|p| **p == WasmType::ExternRef).count();
let externref_returns_count = params.iter().filter(|r| **r == WasmType::ExternRef).count();
WasmFuncType {
params,
externref_params_count,
returns,
externref_returns_count,
}
}
/// Function params types.
pub params: Box<[WasmType]>,
#[inline]
pub fn params(&self) -> &[WasmType] {
&self.params
}
/// How many `externref`s are in this function's params?
#[inline]
pub fn externref_params_count(&self) -> usize {
self.externref_params_count
}
/// Returns params types.
pub returns: Box<[WasmType]>,
#[inline]
pub fn returns(&self) -> &[WasmType] {
&self.returns
}
/// How many `externref`s are in this function's returns?
#[inline]
pub fn externref_returns_count(&self) -> usize {
self.externref_returns_count
}
}
impl TryFrom<wasmparser::FuncType> for WasmFuncType {
type Error = WasmError;
fn try_from(ty: wasmparser::FuncType) -> Result<Self, Self::Error> {
Ok(Self {
params: ty
.params
.into_vec()
.into_iter()
.map(WasmType::try_from)
.collect::<Result<_, Self::Error>>()?,
returns: ty
.returns
.into_vec()
.into_iter()
.map(WasmType::try_from)
.collect::<Result<_, Self::Error>>()?,
})
let params = ty
.params
.into_vec()
.into_iter()
.map(WasmType::try_from)
.collect::<Result<_, Self::Error>>()?;
let returns = ty
.returns
.into_vec()
.into_iter()
.map(WasmType::try_from)
.collect::<Result<_, Self::Error>>()?;
Ok(Self::new(params, returns))
}
}