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