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    hash_map::HashMap,
13    type_registry::TypeRegistry,
14    vm::{SendSyncPtr, VMFuncRef},
15};
16use wasmtime_environ::VMSharedTypeIndex;
17use wasmtime_slab::{Id, Slab};
18
19/// An identifier into the `FuncRefTable`.
20#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
21#[repr(transparent)]
22pub struct FuncRefTableId(Id);
23
24impl FuncRefTableId {
25    /// Convert this `FuncRefTableId` into its raw `u32` ID.
26    pub fn into_raw(self) -> u32 {
27        self.0.into_raw()
28    }
29
30    /// Create a `FuncRefTableId` from a raw `u32` ID.
31    pub fn from_raw(raw: u32) -> Self {
32        Self(Id::from_raw(raw))
33    }
34}
35
36/// Side table mapping `FuncRefTableId`s that can be stored in the GC heap to
37/// raw `VMFuncRef`s.
38#[derive(Default)]
39pub struct FuncRefTable {
40    interned: HashMap<Option<SendSyncPtr<VMFuncRef>>, FuncRefTableId>,
41    slab: Slab<Option<SendSyncPtr<VMFuncRef>>>,
42}
43
44impl FuncRefTable {
45    /// Intern a `VMFuncRef` in the side table, returning an ID that can be
46    /// stored in the GC heap.
47    ///
48    /// # Safety
49    ///
50    /// The given `func_ref` must point to a valid `VMFuncRef` and must remain
51    /// valid for the duration of this table's lifetime.
52    pub unsafe fn intern(&mut self, func_ref: Option<SendSyncPtr<VMFuncRef>>) -> FuncRefTableId {
53        *self
54            .interned
55            .entry(func_ref)
56            .or_insert_with(|| FuncRefTableId(self.slab.alloc(func_ref)))
57    }
58
59    /// Get the `VMFuncRef` associated with the given ID.
60    ///
61    /// Checks that the `VMFuncRef` is a subtype of the expected type.
62    pub fn get_typed(
63        &self,
64        types: &TypeRegistry,
65        id: FuncRefTableId,
66        expected_ty: VMSharedTypeIndex,
67    ) -> Option<SendSyncPtr<VMFuncRef>> {
68        let f = self.slab.get(id.0).copied().expect("bad FuncRefTableId");
69
70        if let Some(f) = f {
71            // The safety contract for `intern` ensures that deref'ing `f` is safe.
72            let actual_ty = unsafe { f.as_ref().type_index };
73
74            // Ensure that the funcref actually is a subtype of the expected
75            // type. This protects against GC heap corruption being leveraged in
76            // attacks: if the attacker has a write gadget inside the GC heap, they
77            // can overwrite a funcref ID to point to a different funcref, but this
78            // assertion ensures that any calls to that wrong funcref at least
79            // remain well-typed, which reduces the attack surface and maintains
80            // memory safety.
81            assert!(types.is_subtype(actual_ty, expected_ty));
82        }
83
84        f
85    }
86
87    /// Get the `VMFuncRef` associated with the given ID, without checking the
88    /// type.
89    ///
90    /// Prefer `get_typed`. This method is only suitable for getting a
91    /// `VMFuncRef` as an untyped `funcref` function reference, and never as a
92    /// typed `(ref $some_func_type)` function reference.
93    pub fn get_untyped(&self, id: FuncRefTableId) -> Option<SendSyncPtr<VMFuncRef>> {
94        self.slab.get(id.0).copied().expect("bad FuncRefTableId")
95    }
96}