Skip to main content

wasmtime/runtime/vm/gc/
host_data.rs

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
16use crate::bail_bug;
17use crate::prelude::*;
18use core::any::Any;
19use wasmtime_core::{
20    alloc::PanicOnOom,
21    slab::{Id, Slab},
22};
23
24/// Side table for each `externref`'s host data value.
25#[derive(Default)]
26pub struct ExternRefHostDataTable {
27    slab: Slab<Box<dyn Any + Send + Sync>>,
28}
29
30/// ID into the `externref` host data table.
31#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
32#[repr(transparent)]
33pub struct ExternRefHostDataId(Id);
34
35fn deref_box<T: ?Sized>(b: &Box<T>) -> &T {
36    &**b
37}
38
39fn deref_box_mut<T: ?Sized>(b: &mut Box<T>) -> &mut T {
40    &mut **b
41}
42
43impl ExternRefHostDataTable {
44    /// Allocate a new `externref` host data value.
45    pub fn alloc(&mut self, value: Box<dyn Any + Send + Sync>) -> ExternRefHostDataId {
46        // TODO(#12069): handle allocation failure here
47        let id = self.slab.alloc(value).panic_on_oom();
48        let id = ExternRefHostDataId(id);
49        log::trace!("allocated new externref host data: {id:?}");
50        id
51    }
52
53    /// Deallocate an `externref` host data value.
54    pub fn dealloc(&mut self, id: ExternRefHostDataId) -> Result<Box<dyn Any + Send + Sync>> {
55        // Verify this exists before deleting it
56        self.get(id)?;
57        log::trace!("deallocated externref host data: {id:?}");
58        Ok(self.slab.dealloc(id.0))
59    }
60
61    /// Get a shared borrow of the host data associated with the given ID.
62    pub fn get(&self, id: ExternRefHostDataId) -> Result<&(dyn Any + Send + Sync)> {
63        let data: &Box<dyn Any + Send + Sync> = match self.slab.get(id.0) {
64            Some(data) => data,
65            None => bail_bug!("invalid `ExternRefHostDataId`"),
66        };
67        Ok(deref_box(data))
68    }
69
70    /// Get a mutable borrow of the host data associated with the given ID.
71    pub fn get_mut(&mut self, id: ExternRefHostDataId) -> Result<&mut (dyn Any + Send + Sync)> {
72        let data: &mut Box<dyn Any + Send + Sync> = match self.slab.get_mut(id.0) {
73            Some(data) => data,
74            None => bail_bug!("invalid `ExternRefHostDataId`"),
75        };
76        Ok(deref_box_mut(data))
77    }
78}
79
80#[cfg(test)]
81mod tests {
82    use super::*;
83
84    #[test]
85    fn correct_dyn_object() {
86        let mut table = ExternRefHostDataTable::default();
87
88        let x = 42_u32;
89        let id = table.alloc(Box::new(x));
90        assert!(table.get(id).unwrap().is::<u32>());
91        assert_eq!(*table.get(id).unwrap().downcast_ref::<u32>().unwrap(), 42);
92        assert!(table.get_mut(id).unwrap().is::<u32>());
93        assert_eq!(
94            *table.get_mut(id).unwrap().downcast_ref::<u32>().unwrap(),
95            42
96        );
97    }
98}