1e6ff8411STyler Rockwood #![cfg(all(not(target_os = "windows"), not(miri)))]
2e6ff8411STyler Rockwood use std::{
3e6ff8411STyler Rockwood     alloc::{GlobalAlloc, Layout, System},
4e6ff8411STyler Rockwood     ops::Range,
5e6ff8411STyler Rockwood     ptr::NonNull,
6e6ff8411STyler Rockwood     sync::Arc,
7e6ff8411STyler Rockwood };
8*94740588SNick Fitzgerald use wasmtime::error::Context as _;
9e6ff8411STyler Rockwood use wasmtime::*;
10e6ff8411STyler Rockwood 
align_up(v: usize, align: usize) -> usize11e6ff8411STyler Rockwood fn align_up(v: usize, align: usize) -> usize {
12e6ff8411STyler Rockwood     return (v + (align - 1)) & (!(align - 1));
13e6ff8411STyler Rockwood }
14e6ff8411STyler Rockwood 
15e6ff8411STyler Rockwood struct CustomStack {
16e6ff8411STyler Rockwood     base: NonNull<u8>,
17e6ff8411STyler Rockwood     len: usize,
18e6ff8411STyler Rockwood }
19e6ff8411STyler Rockwood unsafe impl Send for CustomStack {}
20e6ff8411STyler Rockwood unsafe impl Sync for CustomStack {}
21e6ff8411STyler Rockwood impl CustomStack {
new(base: NonNull<u8>, len: usize) -> Self22e6ff8411STyler Rockwood     fn new(base: NonNull<u8>, len: usize) -> Self {
23e6ff8411STyler Rockwood         CustomStack { base, len }
24e6ff8411STyler Rockwood     }
25e6ff8411STyler Rockwood }
26e6ff8411STyler Rockwood unsafe impl StackMemory for CustomStack {
top(&self) -> *mut u827e6ff8411STyler Rockwood     fn top(&self) -> *mut u8 {
28e6ff8411STyler Rockwood         unsafe { self.base.as_ptr().add(self.len) }
29e6ff8411STyler Rockwood     }
range(&self) -> Range<usize>30e6ff8411STyler Rockwood     fn range(&self) -> Range<usize> {
31e6ff8411STyler Rockwood         let base = self.base.as_ptr() as usize;
32e6ff8411STyler Rockwood         base..base + self.len
33e6ff8411STyler Rockwood     }
guard_range(&self) -> Range<*mut u8>34110e70f3SAlex Crichton     fn guard_range(&self) -> Range<*mut u8> {
35110e70f3SAlex Crichton         std::ptr::null_mut()..std::ptr::null_mut()
36110e70f3SAlex Crichton     }
37e6ff8411STyler Rockwood }
38e6ff8411STyler Rockwood 
39e6ff8411STyler Rockwood // A creator that allocates stacks on the heap instead of mmap'ing.
40e6ff8411STyler Rockwood struct CustomStackCreator {
41e6ff8411STyler Rockwood     memory: NonNull<u8>,
42e6ff8411STyler Rockwood     size: usize,
43e6ff8411STyler Rockwood     layout: Layout,
44e6ff8411STyler Rockwood }
45e6ff8411STyler Rockwood 
46e6ff8411STyler Rockwood unsafe impl Send for CustomStackCreator {}
47e6ff8411STyler Rockwood unsafe impl Sync for CustomStackCreator {}
48e6ff8411STyler Rockwood impl CustomStackCreator {
new() -> Result<Self>49e6ff8411STyler Rockwood     fn new() -> Result<Self> {
50e6ff8411STyler Rockwood         // 1MB
51e6ff8411STyler Rockwood         const MINIMUM_STACK_SIZE: usize = 1 * 1_024 * 1_024;
52e6ff8411STyler Rockwood         let page_size = rustix::param::page_size();
53e6ff8411STyler Rockwood         let size = align_up(MINIMUM_STACK_SIZE, page_size);
54e6ff8411STyler Rockwood         // Add an extra page for the guard page
55e6ff8411STyler Rockwood         let layout = Layout::from_size_align(size + page_size, page_size)
56e6ff8411STyler Rockwood             .context("unable to compute stack layout")?;
57e6ff8411STyler Rockwood         let memory = unsafe {
58e6ff8411STyler Rockwood             let mem = System.alloc(layout);
59e6ff8411STyler Rockwood             let notnull = NonNull::new(mem);
60e6ff8411STyler Rockwood             if let Some(mem) = notnull {
61f7d16d81STyler Rockwood                 // It's required that stack memory is zeroed for wasmtime
62f7d16d81STyler Rockwood                 libc::memset(mem.as_ptr().cast(), 0, layout.size());
63e6ff8411STyler Rockwood                 // Mark guard page as protected
64e6ff8411STyler Rockwood                 rustix::mm::mprotect(
65e6ff8411STyler Rockwood                     mem.as_ptr().cast(),
66e6ff8411STyler Rockwood                     page_size,
67e6ff8411STyler Rockwood                     rustix::mm::MprotectFlags::empty(),
68e6ff8411STyler Rockwood                 )?;
69e6ff8411STyler Rockwood             }
70e6ff8411STyler Rockwood             notnull
71e6ff8411STyler Rockwood         }
72e6ff8411STyler Rockwood         .context("unable to allocate stack memory")?;
73e6ff8411STyler Rockwood         Ok(CustomStackCreator {
74e6ff8411STyler Rockwood             memory,
75e6ff8411STyler Rockwood             size,
76e6ff8411STyler Rockwood             layout,
77e6ff8411STyler Rockwood         })
78e6ff8411STyler Rockwood     }
range(&self) -> Range<usize>79e6ff8411STyler Rockwood     fn range(&self) -> Range<usize> {
80e6ff8411STyler Rockwood         let page_size = rustix::param::page_size();
81e6ff8411STyler Rockwood         let base = unsafe { self.memory.as_ptr().add(page_size) as usize };
82e6ff8411STyler Rockwood         base..base + self.size
83e6ff8411STyler Rockwood     }
84e6ff8411STyler Rockwood }
85e6ff8411STyler Rockwood impl Drop for CustomStackCreator {
drop(&mut self)86e6ff8411STyler Rockwood     fn drop(&mut self) {
87e6ff8411STyler Rockwood         let page_size = rustix::param::page_size();
88e6ff8411STyler Rockwood         unsafe {
89e6ff8411STyler Rockwood             // Unprotect the guard page as the allocator could reuse it.
90e6ff8411STyler Rockwood             rustix::mm::mprotect(
91e6ff8411STyler Rockwood                 self.memory.as_ptr().cast(),
92e6ff8411STyler Rockwood                 page_size,
93e6ff8411STyler Rockwood                 rustix::mm::MprotectFlags::READ | rustix::mm::MprotectFlags::WRITE,
94e6ff8411STyler Rockwood             )
95e6ff8411STyler Rockwood             .unwrap();
96e6ff8411STyler Rockwood             System.dealloc(self.memory.as_ptr(), self.layout);
97e6ff8411STyler Rockwood         }
98e6ff8411STyler Rockwood     }
99e6ff8411STyler Rockwood }
100e6ff8411STyler Rockwood unsafe impl StackCreator for CustomStackCreator {
new_stack(&self, size: usize, zeroed: bool) -> Result<Box<dyn StackMemory>>101980a136eSNick Fitzgerald     fn new_stack(&self, size: usize, zeroed: bool) -> Result<Box<dyn StackMemory>> {
102980a136eSNick Fitzgerald         if zeroed {
103980a136eSNick Fitzgerald             bail!("CustomStackCreator does not support stack zeroing");
104980a136eSNick Fitzgerald         }
105e6ff8411STyler Rockwood         if size != self.size {
106e6ff8411STyler Rockwood             bail!("must use the size we allocated for this stack memory creator");
107e6ff8411STyler Rockwood         }
108e6ff8411STyler Rockwood         let page_size = rustix::param::page_size();
109e6ff8411STyler Rockwood         // skip over the page size
110e6ff8411STyler Rockwood         let base_ptr = unsafe { self.memory.as_ptr().add(page_size) };
111e6ff8411STyler Rockwood         let base = NonNull::new(base_ptr).context("unable to compute stack base")?;
112e6ff8411STyler Rockwood         Ok(Box::new(CustomStack::new(base, self.size)))
113e6ff8411STyler Rockwood     }
114e6ff8411STyler Rockwood }
115e6ff8411STyler Rockwood 
config() -> (Store<()>, Arc<CustomStackCreator>)116e6ff8411STyler Rockwood fn config() -> (Store<()>, Arc<CustomStackCreator>) {
117e6ff8411STyler Rockwood     let stack_creator = Arc::new(CustomStackCreator::new().unwrap());
118e6ff8411STyler Rockwood     let mut config = Config::new();
119e6ff8411STyler Rockwood     config
120e6ff8411STyler Rockwood         .max_wasm_stack(stack_creator.size / 2)
121e6ff8411STyler Rockwood         .async_stack_size(stack_creator.size)
122e6ff8411STyler Rockwood         .with_host_stack(stack_creator.clone());
123e6ff8411STyler Rockwood     (
124e6ff8411STyler Rockwood         Store::new(&Engine::new(&config).unwrap(), ()),
125e6ff8411STyler Rockwood         stack_creator,
126e6ff8411STyler Rockwood     )
127e6ff8411STyler Rockwood }
128e6ff8411STyler Rockwood 
129e6ff8411STyler Rockwood #[tokio::test]
130e657756dSAlex Crichton #[cfg_attr(asan, ignore)]
called_on_custom_heap_stack() -> Result<()>131e6ff8411STyler Rockwood async fn called_on_custom_heap_stack() -> Result<()> {
132e6ff8411STyler Rockwood     let (mut store, stack_creator) = config();
133e6ff8411STyler Rockwood     let module = Module::new(
134e6ff8411STyler Rockwood         store.engine(),
135e6ff8411STyler Rockwood         r#"
136e6ff8411STyler Rockwood             (module
137e6ff8411STyler Rockwood                 (import "host" "callback" (func $callback (result i64)))
138e6ff8411STyler Rockwood                 (func $f (result i64) (call $callback))
139e6ff8411STyler Rockwood                 (export "f" (func $f))
140e6ff8411STyler Rockwood             )
141e6ff8411STyler Rockwood         "#,
142e6ff8411STyler Rockwood     )?;
143e6ff8411STyler Rockwood 
1448652011fSNick Fitzgerald     let ty = FuncType::new(store.engine(), [], [ValType::I64]);
145e6ff8411STyler Rockwood     let host_func = Func::new(&mut store, ty, move |_caller, _params, results| {
146e6ff8411STyler Rockwood         let foo = 42;
147e6ff8411STyler Rockwood         // output an address on the stack
148e6ff8411STyler Rockwood         results[0] = Val::I64((&foo as *const i32) as usize as i64);
149e6ff8411STyler Rockwood         Ok(())
150e6ff8411STyler Rockwood     });
151e6ff8411STyler Rockwood     let export = wasmtime::Extern::Func(host_func);
152e6ff8411STyler Rockwood     let instance = Instance::new_async(&mut store, &module, &[export]).await?;
153e6ff8411STyler Rockwood     let mut results = [Val::I64(0)];
154e6ff8411STyler Rockwood     instance
155e6ff8411STyler Rockwood         .get_func(&mut store, "f")
156e6ff8411STyler Rockwood         .context("missing function export")?
157e6ff8411STyler Rockwood         .call_async(&mut store, &[], &mut results)
158e6ff8411STyler Rockwood         .await?;
159e6ff8411STyler Rockwood     // Make sure the stack address we wrote was within our custom stack range
160e6ff8411STyler Rockwood     let stack_address = results[0].i64().unwrap() as usize;
161e6ff8411STyler Rockwood     assert!(stack_creator.range().contains(&stack_address));
162e6ff8411STyler Rockwood     Ok(())
163e6ff8411STyler Rockwood }
164