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