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::prelude::*;
17use core::any::Any;
18use wasmtime_core::{
19    alloc::PanicOnOom,
20    slab::{Id, Slab},
21};
22
23/// Side table for each `externref`'s host data value.
24#[derive(Default)]
25pub 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)]
32pub struct ExternRefHostDataId(Id);
33
34fn deref_box<T: ?Sized>(b: &Box<T>) -> &T {
35    &**b
36}
37
38fn deref_box_mut<T: ?Sized>(b: &mut Box<T>) -> &mut T {
39    &mut **b
40}
41
42impl ExternRefHostDataTable {
43    /// Allocate a new `externref` host data value.
44    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.
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.
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.
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)]
72mod tests {
73    use super::*;
74
75    #[test]
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}