1 //! Implementation of the side table for `externref` host data.
2 //!
3 //! The actual host data is kept in a side table, rather than inside the GC
4 //! heap, because we do not trust any data coming from the GC heap. If we
5 //! constructed `&dyn Any`s from GC heap data and called any function loaded
6 //! from the `dyn Any`'s vtable, then any bug in our collector could lead to
7 //! corrupted vtables, which could lead to security vulnerabilities and sandbox
8 //! escapes.
9 //!
10 //! Much better to store host data IDs inside the GC heap, and then do checked
11 //! accesses into the host data table from those untrusted IDs. At worst, we can
12 //! return the wrong (but still valid) host data object or panic. This is way
13 //! less catastrophic than doing an indirect call to an attacker-controlled
14 //! function pointer.
15 
16 use crate::prelude::*;
17 use core::any::Any;
18 use wasmtime_core::{
19     alloc::PanicOnOom,
20     slab::{Id, Slab},
21 };
22 
23 /// Side table for each `externref`'s host data value.
24 #[derive(Default)]
25 pub struct ExternRefHostDataTable {
26     slab: Slab<Box<dyn Any + Send + Sync>>,
27 }
28 
29 /// ID into the `externref` host data table.
30 #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
31 #[repr(transparent)]
32 pub struct ExternRefHostDataId(Id);
33 
deref_box<T: ?Sized>(b: &Box<T>) -> &T34 fn deref_box<T: ?Sized>(b: &Box<T>) -> &T {
35     &**b
36 }
37 
deref_box_mut<T: ?Sized>(b: &mut Box<T>) -> &mut T38 fn deref_box_mut<T: ?Sized>(b: &mut Box<T>) -> &mut T {
39     &mut **b
40 }
41 
42 impl ExternRefHostDataTable {
43     /// Allocate a new `externref` host data value.
alloc(&mut self, value: Box<dyn Any + Send + Sync>) -> ExternRefHostDataId44     pub fn alloc(&mut self, value: Box<dyn Any + Send + Sync>) -> ExternRefHostDataId {
45         // TODO(#12069): handle allocation failure here
46         let id = self.slab.alloc(value).panic_on_oom();
47         let id = ExternRefHostDataId(id);
48         log::trace!("allocated new externref host data: {id:?}");
49         id
50     }
51 
52     /// Deallocate an `externref` host data value.
dealloc(&mut self, id: ExternRefHostDataId) -> Box<dyn Any + Send + Sync>53     pub fn dealloc(&mut self, id: ExternRefHostDataId) -> Box<dyn Any + Send + Sync> {
54         log::trace!("deallocated externref host data: {id:?}");
55         self.slab.dealloc(id.0)
56     }
57 
58     /// Get a shared borrow of the host data associated with the given ID.
get(&self, id: ExternRefHostDataId) -> &(dyn Any + Send + Sync)59     pub fn get(&self, id: ExternRefHostDataId) -> &(dyn Any + Send + Sync) {
60         let data: &Box<dyn Any + Send + Sync> = self.slab.get(id.0).unwrap();
61         deref_box(data)
62     }
63 
64     /// Get a mutable borrow of the host data associated with the given ID.
get_mut(&mut self, id: ExternRefHostDataId) -> &mut (dyn Any + Send + Sync)65     pub fn get_mut(&mut self, id: ExternRefHostDataId) -> &mut (dyn Any + Send + Sync) {
66         let data: &mut Box<dyn Any + Send + Sync> = self.slab.get_mut(id.0).unwrap();
67         deref_box_mut(data)
68     }
69 }
70 
71 #[cfg(test)]
72 mod tests {
73     use super::*;
74 
75     #[test]
correct_dyn_object()76     fn correct_dyn_object() {
77         let mut table = ExternRefHostDataTable::default();
78 
79         let x = 42_u32;
80         let id = table.alloc(Box::new(x));
81         assert!(table.get(id).is::<u32>());
82         assert_eq!(*table.get(id).downcast_ref::<u32>().unwrap(), 42);
83         assert!(table.get_mut(id).is::<u32>());
84         assert_eq!(*table.get_mut(id).downcast_ref::<u32>().unwrap(), 42);
85     }
86 }
87