1 //! Interfaces for accessing instance data from hostcalls.
2 //!
3 //! This module contains both a Rust-friendly API ([`Vmctx`](struct.Vmctx.html)) as well as C-style
4 //! exports for compatibility with hostcalls written against `lucet-runtime-c`.
5 
6 pub use crate::c_api::lucet_vmctx;
7 
8 use crate::alloc::instance_heap_offset;
9 use crate::context::Context;
10 use crate::error::Error;
11 use crate::instance::{
12     EmptyYieldVal, Instance, InstanceInternal, State, TerminationDetails, YieldedVal,
13     CURRENT_INSTANCE, HOST_CTX,
14 };
15 use lucet_module::{FunctionHandle, GlobalValue};
16 use std::any::Any;
17 use std::borrow::{Borrow, BorrowMut};
18 use std::cell::{Ref, RefCell, RefMut};
19 use std::marker::PhantomData;
20 
21 /// An opaque handle to a running instance's context.
22 #[derive(Debug)]
23 pub struct Vmctx {
24     vmctx: *const lucet_vmctx,
25     /// A view of the underlying instance's heap.
26     ///
27     /// This must never be dropped automatically, as the view does not own the heap. Rather, this is
28     /// a value used to implement dynamic borrowing of the heap contents that are owned and managed
29     /// by the instance and its `Alloc`.
30     heap_view: RefCell<Box<[u8]>>,
31     /// A view of the underlying instance's globals.
32     ///
33     /// This must never be dropped automatically, as the view does not own the globals. Rather, this
34     /// is a value used to implement dynamic borrowing of the globals that are owned and managed by
35     /// the instance and its `Alloc`.
36     globals_view: RefCell<Box<[GlobalValue]>>,
37 }
38 
39 impl Drop for Vmctx {
drop(&mut self)40     fn drop(&mut self) {
41         let heap_view = self.heap_view.replace(Box::new([]));
42         let globals_view = self.globals_view.replace(Box::new([]));
43         // as described in the definition of `Vmctx`, we cannot allow the boxed views of the heap
44         // and globals to be dropped
45         Box::leak(heap_view);
46         Box::leak(globals_view);
47     }
48 }
49 
50 pub trait VmctxInternal {
51     /// Get a reference to the `Instance` for this guest.
instance(&self) -> &Instance52     fn instance(&self) -> &Instance;
53 
54     /// Get a mutable reference to the `Instance` for this guest.
55     ///
56     /// ### Safety
57     ///
58     /// Using this method, you could hold on to multiple mutable references to the same
59     /// `Instance`. Only use one at a time! This method does not take `&mut self` because otherwise
60     /// you could not use orthogonal `&mut` refs that come from `Vmctx`, like the heap or
61     /// terminating the instance.
instance_mut(&self) -> &mut Instance62     unsafe fn instance_mut(&self) -> &mut Instance;
63 
64     /// Try to take and return the value passed to `Instance::resume_with_val()`.
65     ///
66     /// If there is no resumed value, or if the dynamic type check of the value fails, this returns
67     /// `None`.
try_take_resumed_val<R: Any + 'static>(&self) -> Option<R>68     fn try_take_resumed_val<R: Any + 'static>(&self) -> Option<R>;
69 
70     /// Suspend the instance, returning a value in
71     /// [`RunResult::Yielded`](../enum.RunResult.html#variant.Yielded) to where the instance was run
72     /// or resumed.
73     ///
74     /// If there are any live borrows of the heap view, globals view, or an embed_ctx, the
75     /// function will terminate the instance with `TerminationDetails::BorrowError`.
76     ///
77     /// After suspending, the instance may be resumed by calling
78     /// [`Instance::resume_with_val()`](../struct.Instance.html#method.resume_with_val) from the
79     /// host with a value of type `R`. If resumed with a value of some other type, this returns
80     /// `None`.
81     ///
82     /// The dynamic type checks used by the other yield methods should make this explicit option
83     /// type redundant, however this interface is used to avoid exposing a panic to the C API.
yield_val_try_val<A: Any + 'static, R: Any + 'static>(&self, val: A) -> Option<R>84     fn yield_val_try_val<A: Any + 'static, R: Any + 'static>(&self, val: A) -> Option<R>;
85 }
86 
87 impl VmctxInternal for Vmctx {
instance(&self) -> &Instance88     fn instance(&self) -> &Instance {
89         unsafe { instance_from_vmctx(self.vmctx) }
90     }
91 
instance_mut(&self) -> &mut Instance92     unsafe fn instance_mut(&self) -> &mut Instance {
93         instance_from_vmctx(self.vmctx)
94     }
95 
try_take_resumed_val<R: Any + 'static>(&self) -> Option<R>96     fn try_take_resumed_val<R: Any + 'static>(&self) -> Option<R> {
97         let inst = unsafe { self.instance_mut() };
98         if let Some(val) = inst.resumed_val.take() {
99             match val.downcast() {
100                 Ok(val) => Some(*val),
101                 Err(val) => {
102                     inst.resumed_val = Some(val);
103                     None
104                 }
105             }
106         } else {
107             None
108         }
109     }
110 
yield_val_try_val<A: Any + 'static, R: Any + 'static>(&self, val: A) -> Option<R>111     fn yield_val_try_val<A: Any + 'static, R: Any + 'static>(&self, val: A) -> Option<R> {
112         self.yield_impl::<A, R>(val);
113         self.try_take_resumed_val()
114     }
115 }
116 
117 impl Vmctx {
118     /// Create a `Vmctx` from the compiler-inserted `vmctx` argument in a guest function.
119     ///
120     /// This is almost certainly not what you want to use to get a `Vmctx`; instead use the first
121     /// argument of a function with the `#[lucet_hostcall]` attribute, which must have the type
122     /// `&Vmctx`.
from_raw(vmctx: *const lucet_vmctx) -> Vmctx123     pub unsafe fn from_raw(vmctx: *const lucet_vmctx) -> Vmctx {
124         let inst = instance_from_vmctx(vmctx);
125         assert!(inst.valid_magic());
126 
127         let res = Vmctx {
128             vmctx,
129             heap_view: RefCell::new(Box::<[u8]>::from_raw(inst.heap_mut())),
130             globals_view: RefCell::new(Box::<[GlobalValue]>::from_raw(inst.globals_mut())),
131         };
132         res
133     }
134 
135     /// Return the underlying `vmctx` pointer.
as_raw(&self) -> *const lucet_vmctx136     pub fn as_raw(&self) -> *const lucet_vmctx {
137         self.vmctx
138     }
139 
140     /// Return the WebAssembly heap as a slice of bytes.
141     ///
142     /// If the heap is already mutably borrowed by `heap_mut()`, the instance will
143     /// terminate with `TerminationDetails::BorrowError`.
heap(&self) -> Ref<'_, [u8]>144     pub fn heap(&self) -> Ref<'_, [u8]> {
145         unsafe {
146             self.reconstitute_heap_view_if_needed();
147         }
148         let r = self
149             .heap_view
150             .try_borrow()
151             .unwrap_or_else(|_| panic!(TerminationDetails::BorrowError("heap")));
152         Ref::map(r, |b| b.borrow())
153     }
154 
155     /// Return the WebAssembly heap as a mutable slice of bytes.
156     ///
157     /// If the heap is already borrowed by `heap()` or `heap_mut()`, the instance will terminate
158     /// with `TerminationDetails::BorrowError`.
heap_mut(&self) -> RefMut<'_, [u8]>159     pub fn heap_mut(&self) -> RefMut<'_, [u8]> {
160         unsafe {
161             self.reconstitute_heap_view_if_needed();
162         }
163         let r = self
164             .heap_view
165             .try_borrow_mut()
166             .unwrap_or_else(|_| panic!(TerminationDetails::BorrowError("heap_mut")));
167         RefMut::map(r, |b| b.borrow_mut())
168     }
169 
170     /// Check whether the heap has grown, and replace the heap view if it has.
171     ///
172     /// This handles the case where the length of the heap is modified by a call to
173     /// `Vmctx::grow_memory()`, or by yielding, which gives the host an opportunity to modify the
174     /// heap. We ensure dynamically that references to the heap returned by `Vmctx::{heap,
175     /// heap_mut}` can't live across these calls, but we still need to update the boxed slice view
176     /// to account for the length change.
177     ///
178     /// TODO: There is still an unsound case, though, when a heap reference is held across a call
179     /// back into the guest via `Vmctx::get_func_from_idx()`. That guest code may grow the heap as
180     /// well, causing any outstanding heap references to become invalid. We will address this when
181     /// we rework the interface for calling back into the guest.
reconstitute_heap_view_if_needed(&self)182     unsafe fn reconstitute_heap_view_if_needed(&self) {
183         let inst = self.instance_mut();
184         if inst.heap_mut().len() != self.heap_view.borrow().len() {
185             let old_heap_view = self
186                 .heap_view
187                 .replace(Box::<[u8]>::from_raw(inst.heap_mut()));
188             // as described in the definition of `Vmctx`, we cannot allow the boxed view of the heap
189             // to be dropped
190             Box::leak(old_heap_view);
191         }
192     }
193 
194     /// Check whether a given range in the host address space overlaps with the memory that backs
195     /// the instance heap.
check_heap<T>(&self, ptr: *const T, len: usize) -> bool196     pub fn check_heap<T>(&self, ptr: *const T, len: usize) -> bool {
197         self.instance().check_heap(ptr, len)
198     }
199 
200     /// Check whether a context value of a particular type exists.
contains_embed_ctx<T: Any>(&self) -> bool201     pub fn contains_embed_ctx<T: Any>(&self) -> bool {
202         self.instance().contains_embed_ctx::<T>()
203     }
204 
205     /// Get a reference to a context value of a particular type.
206     ///
207     /// If a context of that type does not exist, the instance will terminate with
208     /// `TerminationDetails::CtxNotFound`.
209     ///
210     /// If the context is already mutably borrowed by `get_embed_ctx_mut`, the instance will
211     /// terminate with `TerminationDetails::BorrowError`.
get_embed_ctx<T: Any>(&self) -> Ref<'_, T>212     pub fn get_embed_ctx<T: Any>(&self) -> Ref<'_, T> {
213         match self.instance().embed_ctx.try_get::<T>() {
214             Some(Ok(t)) => t,
215             Some(Err(_)) => panic!(TerminationDetails::BorrowError("get_embed_ctx")),
216             None => panic!(TerminationDetails::CtxNotFound),
217         }
218     }
219 
220     /// Get a mutable reference to a context value of a particular type.
221     ///
222     /// If a context of that type does not exist, the instance will terminate with
223     /// `TerminationDetails::CtxNotFound`.
224     ///
225     /// If the context is already borrowed by some other use of `get_embed_ctx` or
226     /// `get_embed_ctx_mut`, the instance will terminate with `TerminationDetails::BorrowError`.
get_embed_ctx_mut<T: Any>(&self) -> RefMut<'_, T>227     pub fn get_embed_ctx_mut<T: Any>(&self) -> RefMut<'_, T> {
228         match unsafe { self.instance_mut().embed_ctx.try_get_mut::<T>() } {
229             Some(Ok(t)) => t,
230             Some(Err(_)) => panic!(TerminationDetails::BorrowError("get_embed_ctx_mut")),
231             None => panic!(TerminationDetails::CtxNotFound),
232         }
233     }
234 
235     /// Terminate this guest and return to the host context without unwinding.
236     ///
237     /// This is almost certainly not what you want to use to terminate an instance from a hostcall,
238     /// as any resources currently in scope will not be dropped. Instead, use
239     /// `lucet_hostcall_terminate!` which unwinds to the enclosing hostcall body.
terminate_no_unwind(&self, details: TerminationDetails) -> !240     pub unsafe fn terminate_no_unwind(&self, details: TerminationDetails) -> ! {
241         self.instance_mut().terminate(details)
242     }
243 
244     /// Grow the guest memory by the given number of WebAssembly pages.
245     ///
246     /// On success, returns the number of pages that existed before the call.
247     ///
248     /// If there are any live borrows from `heap()` or `heap_mut()`, this function will terminate
249     /// the instance with `TerminationDetails::BorrowError`.
grow_memory(&self, additional_pages: u32) -> Result<u32, Error>250     pub fn grow_memory(&self, additional_pages: u32) -> Result<u32, Error> {
251         self.ensure_no_heap_borrows();
252         unsafe { self.instance_mut().grow_memory(additional_pages) }
253     }
254 
255     /// Return the WebAssembly globals as a slice of `i64`s.
256     ///
257     /// If the globals are already mutably borrowed by `globals_mut()`, the instance will terminate
258     /// with `TerminationDetails::BorrowError`.
globals(&self) -> Ref<'_, [GlobalValue]>259     pub fn globals(&self) -> Ref<'_, [GlobalValue]> {
260         let r = self
261             .globals_view
262             .try_borrow()
263             .unwrap_or_else(|_| panic!(TerminationDetails::BorrowError("globals")));
264         Ref::map(r, |b| b.borrow())
265     }
266 
267     /// Return the WebAssembly globals as a mutable slice of `i64`s.
268     ///
269     /// If the globals are already borrowed by `globals()` or `globals_mut()`, the instance will
270     /// terminate with `TerminationDetails::BorrowError`.
globals_mut(&self) -> RefMut<'_, [GlobalValue]>271     pub fn globals_mut(&self) -> RefMut<'_, [GlobalValue]> {
272         let r = self
273             .globals_view
274             .try_borrow_mut()
275             .unwrap_or_else(|_| panic!(TerminationDetails::BorrowError("globals_mut")));
276         RefMut::map(r, |b| b.borrow_mut())
277     }
278 
279     /// Get a function pointer by WebAssembly table and function index.
280     ///
281     /// This is useful when a hostcall takes a function pointer as its argument, as WebAssembly uses
282     /// table indices as its runtime representation of function pointers.
283     ///
284     /// # Safety
285     ///
286     /// We do not currently reflect function type information into the Rust type system, so callers
287     /// of the returned function must take care to cast it to the correct type before calling. The
288     /// correct type will include the `vmctx` argument, which the caller is responsible for passing
289     /// from its own context.
290     ///
291     /// There is currently no guarantee that guest functions will return before faulting, or
292     /// terminating the instance in a subsequent hostcall. This means that any Rust resources that
293     /// are held open when the guest function is called might be leaked if the guest function, for
294     /// example, divides by zero. Work to make this safer is
295     /// [ongoing](https://github.com/bytecodealliance/lucet/pull/254).
296     ///
297     /// ```no_run
298     /// use lucet_runtime_macros::lucet_hostcall;
299     /// use lucet_runtime_internals::lucet_hostcall_terminate;
300     /// use lucet_runtime_internals::vmctx::{lucet_vmctx, Vmctx};
301     ///
302     /// #[lucet_hostcall]
303     /// #[no_mangle]
304     /// pub unsafe extern "C" fn hostcall_call_binop(
305     ///     vmctx: &Vmctx,
306     ///     binop_table_idx: u32,
307     ///     binop_func_idx: u32,
308     ///     operand1: u32,
309     ///     operand2: u32,
310     /// ) -> u32 {
311     ///     if let Ok(binop) = vmctx.get_func_from_idx(binop_table_idx, binop_func_idx) {
312     ///         let typed_binop = std::mem::transmute::<
313     ///             usize,
314     ///             extern "C" fn(*const lucet_vmctx, u32, u32) -> u32,
315     ///         >(binop.ptr.as_usize());
316     ///         unsafe { (typed_binop)(vmctx.as_raw(), operand1, operand2) }
317     ///     } else {
318     ///         lucet_hostcall_terminate!("invalid function index")
319     ///     }
320     /// }
321     /// ```
get_func_from_idx( &self, table_idx: u32, func_idx: u32, ) -> Result<FunctionHandle, Error>322     pub fn get_func_from_idx(
323         &self,
324         table_idx: u32,
325         func_idx: u32,
326     ) -> Result<FunctionHandle, Error> {
327         self.instance()
328             .module()
329             .get_func_from_idx(table_idx, func_idx)
330     }
331 
332     /// Suspend the instance, returning an empty
333     /// [`RunResult::Yielded`](../enum.RunResult.html#variant.Yielded) to where the instance was run
334     /// or resumed.
335     ///
336     /// If there are any live borrows of the heap view, globals view, or an embed_ctx, the function
337     /// will terminate the instance with `TerminationDetails::BorrowError`.
338     ///
339     /// After suspending, the instance may be resumed by the host using
340     /// [`Instance::resume()`](../struct.Instance.html#method.resume).
341     ///
342     /// (The reason for the trailing underscore in the name is that Rust reserves `yield` as a
343     /// keyword for future use.)
yield_(&self)344     pub fn yield_(&self) {
345         self.yield_val_expecting_val::<EmptyYieldVal, EmptyYieldVal>(EmptyYieldVal);
346     }
347 
348     /// Suspend the instance, returning an empty
349     /// [`RunResult::Yielded`](../enum.RunResult.html#variant.Yielded) to where the instance was run
350     /// or resumed.
351     ///
352     /// If there are any live borrows of the heap view, globals view, or an embed_ctx, the function
353     /// will terminate the instance with `TerminationDetails::BorrowError`.
354     ///
355     /// After suspending, the instance may be resumed by calling
356     /// [`Instance::resume_with_val()`](../struct.Instance.html#method.resume_with_val) from the
357     /// host with a value of type `R`.
yield_expecting_val<R: Any + 'static>(&self) -> R358     pub fn yield_expecting_val<R: Any + 'static>(&self) -> R {
359         self.yield_val_expecting_val::<EmptyYieldVal, R>(EmptyYieldVal)
360     }
361 
362     /// Suspend the instance, returning a value in
363     /// [`RunResult::Yielded`](../enum.RunResult.html#variant.Yielded) to where the instance was run
364     /// or resumed.
365     ///
366     /// If there are any live borrows of the heap view, globals view, or an embed_ctx, the function
367     /// will terminate the instance with `TerminationDetails::BorrowError`.
368     ///
369     /// After suspending, the instance may be resumed by the host using
370     /// [`Instance::resume()`](../struct.Instance.html#method.resume).
yield_val<A: Any + 'static>(&self, val: A)371     pub fn yield_val<A: Any + 'static>(&self, val: A) {
372         self.yield_val_expecting_val::<A, EmptyYieldVal>(val);
373     }
374 
375     /// Suspend the instance, returning a value in
376     /// [`RunResult::Yielded`](../enum.RunResult.html#variant.Yielded) to where the instance was run
377     /// or resumed.
378     ///
379     /// If there are any live borrows of the heap view, globals view, or an embed_ctx, the function
380     /// will terminate the instance with `TerminationDetails::BorrowError`.
381     ///
382     /// After suspending, the instance may be resumed by calling
383     /// [`Instance::resume_with_val()`](../struct.Instance.html#method.resume_with_val) from the
384     /// host with a value of type `R`.
yield_val_expecting_val<A: Any + 'static, R: Any + 'static>(&self, val: A) -> R385     pub fn yield_val_expecting_val<A: Any + 'static, R: Any + 'static>(&self, val: A) -> R {
386         self.yield_impl::<A, R>(val);
387         self.take_resumed_val()
388     }
389 
yield_impl<A: Any + 'static, R: Any + 'static>(&self, val: A)390     fn yield_impl<A: Any + 'static, R: Any + 'static>(&self, val: A) {
391         self.ensure_no_borrows();
392         let inst = unsafe { self.instance_mut() };
393         let expecting: Box<PhantomData<R>> = Box::new(PhantomData);
394         inst.state = State::Yielding {
395             val: YieldedVal::new(val),
396             expecting: expecting as Box<dyn Any>,
397         };
398 
399         HOST_CTX.with(|host_ctx| unsafe { Context::swap(&mut inst.ctx, &mut *host_ctx.get()) });
400     }
401 
402     /// Take and return the value passed to
403     /// [`Instance::resume_with_val()`](../struct.Instance.html#method.resume_with_val), terminating
404     /// the instance if there is no value present, or the dynamic type check of the value fails.
take_resumed_val<R: Any + 'static>(&self) -> R405     fn take_resumed_val<R: Any + 'static>(&self) -> R {
406         self.try_take_resumed_val()
407             .unwrap_or_else(|| panic!(TerminationDetails::YieldTypeMismatch))
408     }
409 
410     /// Ensure there are no outstanding borrows to the contents of the `Vmctx`.
411     ///
412     /// For example, it is critical for safety that the heap or embedder contexts are not borrowed
413     /// when yielding or calling back into Wasm, because their contents can be changed from the host
414     /// side or by subsequent Wasm execution.
415     ///
416     /// Terminates the instance with a `TerminationDetails::BorrowError` if a borrow exists.
ensure_no_borrows(&self)417     fn ensure_no_borrows(&self) {
418         self.ensure_no_heap_borrows();
419         if self.globals_view.try_borrow_mut().is_err() {
420             panic!(TerminationDetails::BorrowError("globals"));
421         }
422         if self.instance().embed_ctx.is_any_value_borrowed() {
423             panic!(TerminationDetails::BorrowError("embed_ctx"));
424         }
425     }
426 
427     /// Ensure there are no outstanding borrows to the heap.
428     ///
429     /// Terminates the instance with a `TerminationDetails::BorrowError` if a borrow exists.
ensure_no_heap_borrows(&self)430     fn ensure_no_heap_borrows(&self) {
431         if self.heap_view.try_borrow_mut().is_err() {
432             panic!(TerminationDetails::BorrowError("heap"));
433         }
434     }
435 }
436 
437 /// Get an `Instance` from the `vmctx` pointer.
438 ///
439 /// Only safe to call from within the guest context.
instance_from_vmctx<'a>(vmctx: *const lucet_vmctx) -> &'a mut Instance440 pub unsafe fn instance_from_vmctx<'a>(vmctx: *const lucet_vmctx) -> &'a mut Instance {
441     assert!(!vmctx.is_null(), "vmctx is not null");
442 
443     let inst_ptr = (vmctx as usize - instance_heap_offset()) as *mut Instance;
444 
445     // We shouldn't actually need to access the thread local, only the exception handler should
446     // need to. But, as long as the thread local exists, we should make sure that the guest
447     // hasn't pulled any shenanigans and passed a bad vmctx. (Codegen should ensure the guest
448     // cant pull any shenanigans but there have been bugs before.)
449     CURRENT_INSTANCE.with(|current_instance| {
450         if let Some(current_inst_ptr) = current_instance.borrow().map(|nn| nn.as_ptr()) {
451             assert_eq!(
452                 inst_ptr, current_inst_ptr,
453                 "vmctx corresponds to current instance"
454             );
455         } else {
456             panic!(
457                 "current instance is not set; thread local storage failure can indicate \
458                  dynamic linking issues"
459             );
460         }
461     });
462 
463     let inst = inst_ptr.as_mut().unwrap();
464     assert!(inst.valid_magic());
465     inst
466 }
467 
468 impl Instance {
469     /// Terminate the guest and swap back to the host context without unwinding.
470     ///
471     /// This is almost certainly not what you want to use to terminate from a hostcall; use panics
472     /// with `TerminationDetails` instead.
terminate(&mut self, details: TerminationDetails) -> !473     pub(crate) unsafe fn terminate(&mut self, details: TerminationDetails) -> ! {
474         self.state = State::Terminating { details };
475         #[allow(unused_unsafe)] // The following unsafe will be incorrectly warned as unused
476         HOST_CTX.with(|host_ctx| unsafe { Context::set(&*host_ctx.get()) })
477     }
478 }
479