Skip to main content

wasmtime/runtime/vm/gc/
func_ref.rs

1//! Implementation of the side table for `funcref`s in the GC heap.
2//!
3//! The actual `VMFuncRef`s are kept in a side table, rather than inside the GC
4//! heap, for the same reasons that an `externref`'s host data is kept in a side
5//! table. We cannot trust any data coming from the GC heap, but `VMFuncRef`s
6//! contain raw pointers, so if we stored `VMFuncRef`s inside the GC heap, we
7//! wouldn't be able to use the raw pointers from any `VMFuncRef` we got out of
8//! the heap. And that means we wouldn't be able to, for example, call a
9//! `funcref` we got from inside the GC heap.
10
11use crate::{
12    Result, bail_bug,
13    hash_map::HashMap,
14    type_registry::TypeRegistry,
15    vm::{SendSyncPtr, VMFuncRef},
16};
17use wasmtime_core::{
18    alloc::PanicOnOom,
19    slab::{Id, Slab},
20};
21use wasmtime_environ::VMSharedTypeIndex;
22
23/// An identifier into the `FuncRefTable`.
24#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
25#[repr(transparent)]
26pub struct FuncRefTableId(Id);
27
28impl FuncRefTableId {
29    /// Convert this `FuncRefTableId` into its raw `u32` ID.
30    pub fn into_raw(self) -> u32 {
31        self.0.into_raw()
32    }
33
34    /// Create a `FuncRefTableId` from a raw `u32` ID.
35    pub fn from_raw(raw: u32) -> Self {
36        Self(Id::from_raw(raw))
37    }
38}
39
40/// Side table mapping `FuncRefTableId`s that can be stored in the GC heap to
41/// raw `VMFuncRef`s.
42#[derive(Default)]
43pub struct FuncRefTable {
44    interned: HashMap<Option<SendSyncPtr<VMFuncRef>>, FuncRefTableId>,
45    slab: Slab<Option<SendSyncPtr<VMFuncRef>>>,
46}
47
48impl FuncRefTable {
49    /// Intern a `VMFuncRef` in the side table, returning an ID that can be
50    /// stored in the GC heap.
51    ///
52    /// # Safety
53    ///
54    /// The given `func_ref` must point to a valid `VMFuncRef` and must remain
55    /// valid for the duration of this table's lifetime.
56    pub unsafe fn intern(&mut self, func_ref: Option<SendSyncPtr<VMFuncRef>>) -> FuncRefTableId {
57        *self.interned.entry(func_ref).or_insert_with(|| {
58            // TODO(#12069): handle allocation failure here
59            FuncRefTableId(self.slab.alloc(func_ref).panic_on_oom())
60        })
61    }
62
63    /// Get the `VMFuncRef` associated with the given ID.
64    ///
65    /// Checks that the `VMFuncRef` is a subtype of the expected type.
66    pub fn get_typed(
67        &self,
68        types: &TypeRegistry,
69        id: FuncRefTableId,
70        expected_ty: VMSharedTypeIndex,
71    ) -> Result<Option<SendSyncPtr<VMFuncRef>>> {
72        let Some(f) = self.slab.get(id.0).copied() else {
73            bail_bug!("bad FuncRefTableId")
74        };
75
76        if let Some(f) = f {
77            // The safety contract for `intern` ensures that deref'ing `f` is safe.
78            let actual_ty = unsafe { f.as_ref().type_index };
79
80            // Ensure that the funcref actually is a subtype of the expected
81            // type. This protects against GC heap corruption being leveraged in
82            // attacks: if the attacker has a write gadget inside the GC heap,
83            // they can overwrite a funcref ID to point to a different funcref,
84            // but this check ensures that any calls to that wrong funcref at
85            // least remain well-typed, which reduces the attack surface and
86            // maintains memory safety.
87            if !types.is_subtype(actual_ty, expected_ty) {
88                bail_bug!("funcref table type mismatch")
89            }
90        }
91
92        Ok(f)
93    }
94
95    /// Get the `VMFuncRef` associated with the given ID, without checking the
96    /// type.
97    ///
98    /// Prefer `get_typed`. This method is only suitable for getting a
99    /// `VMFuncRef` as an untyped `funcref` function reference, and never as a
100    /// typed `(ref $some_func_type)` function reference.
101    pub fn get_untyped(&self, id: FuncRefTableId) -> Result<Option<SendSyncPtr<VMFuncRef>>> {
102        match self.slab.get(id.0).copied() {
103            Some(f) => Ok(f),
104            None => bail_bug!("bad FuncRefTableId"),
105        }
106    }
107}