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