1#[cfg(feature = "wasm")]
2use crate::engine_access::EngineAccess;
3#[cfg(feature = "wasm")]
4use crate::wire::ActionWire;
5
6#[derive(Debug, Clone)]
7pub struct RawProviderEvent {
8 pub payload_type: String,
9 pub payload: Vec<u8>,
10}
11
12pub const DEFAULT_FUEL: u64 = 10_000_000;
14pub const DEFAULT_MAX_MEMORY: usize = 16 * 1024 * 1024;
16pub const MAX_TABLES: usize = 4;
18pub const MAX_TABLE_ELEMENTS: usize = 65_536;
20pub const MAX_LOG_MESSAGES_PER_CALL: usize = 64;
22pub const MAX_LOG_MESSAGE_BYTES: usize = 4 * 1024;
24pub const MAX_INJECTED_ACTIONS_PER_CALL: usize = 64;
26pub const MAX_INJECTED_ACTION_BYTES_PER_CALL: usize = 256 * 1024;
28pub const MAX_PROVIDER_EVENTS_PER_CALL: usize = 256;
30pub const MAX_PROVIDER_EVENT_BYTES_PER_CALL: usize = 256 * 1024;
32#[cfg(feature = "wasm")]
34const WASM_PAGE_SIZE: u64 = 64 * 1024;
35
36#[cfg(feature = "wasm")]
43pub struct StoreData {
44 pub engine_access: *const dyn EngineAccess,
45 pub now: f64,
46 pub injected_actions: Vec<ActionWire>,
47 pub injected_action_bytes: usize,
49 pub program_name: String,
51 pub log_messages_forwarded: usize,
53 pub log_messages_dropped: usize,
55 pub provider_events: Vec<RawProviderEvent>,
56 pub provider_event_bytes: usize,
58 pub provider_events_enabled: bool,
59 pub limits: wasmtime::StoreLimits,
61}
62
63#[cfg(feature = "wasm")]
67unsafe impl Send for StoreData {}
68#[cfg(feature = "wasm")]
69unsafe impl Sync for StoreData {}
70
71#[cfg(feature = "wasm")]
72impl StoreData {
73 pub fn new(
76 program_name: String,
77 engine_access: *const dyn EngineAccess,
78 now: f64,
79 provider_events_enabled: bool,
80 max_memory: usize,
81 ) -> Self {
82 StoreData {
83 engine_access,
84 now,
85 injected_actions: Vec::new(),
86 injected_action_bytes: 0,
87 program_name,
88 log_messages_forwarded: 0,
89 log_messages_dropped: 0,
90 provider_events: Vec::new(),
91 provider_event_bytes: 0,
92 provider_events_enabled,
93 limits: wasmtime::StoreLimitsBuilder::new()
94 .memory_size(max_memory)
95 .instances(1)
96 .memories(1)
97 .tables(MAX_TABLES)
98 .table_elements(MAX_TABLE_ELEMENTS)
99 .trap_on_grow_failure(true)
100 .build(),
101 }
102 }
103
104 pub unsafe fn engine(&self) -> &dyn EngineAccess {
109 &*self.engine_access
110 }
111
112 pub fn reset_per_call(
114 &mut self,
115 engine_access: *const dyn EngineAccess,
116 now: f64,
117 provider_events_enabled: bool,
118 ) {
119 self.engine_access = engine_access;
120 self.now = now;
121 self.injected_actions.clear();
122 self.injected_action_bytes = 0;
123 self.log_messages_forwarded = 0;
124 self.log_messages_dropped = 0;
125 self.provider_events.clear();
126 self.provider_event_bytes = 0;
127 self.provider_events_enabled = provider_events_enabled;
128 }
129}
130
131#[cfg(feature = "wasm")]
133pub struct WasmRuntime {
134 engine: wasmtime::Engine,
135 fuel: u64,
136 max_memory: usize,
137}
138
139#[cfg(feature = "wasm")]
140impl WasmRuntime {
141 pub fn new() -> Result<Self, wasmtime::Error> {
142 let mut config = wasmtime::Config::new();
143 config.consume_fuel(true);
144 config.wasm_gc(false);
148 config.wasm_exceptions(false);
149 config.wasm_memory64(false);
151 let engine = wasmtime::Engine::new(&config)?;
152 Ok(WasmRuntime {
153 engine,
154 fuel: DEFAULT_FUEL,
155 max_memory: DEFAULT_MAX_MEMORY,
156 })
157 }
158
159 pub fn compile(&self, bytes: &[u8]) -> Result<wasmtime::Module, wasmtime::Error> {
162 let module = wasmtime::Module::new(&self.engine, bytes)?;
163 let memories = module
164 .exports()
165 .map(|e| e.ty())
166 .chain(module.imports().map(|i| i.ty()))
167 .filter_map(|ty| ty.memory().cloned());
168 for memory in memories {
169 let min_bytes = memory.minimum().saturating_mul(WASM_PAGE_SIZE);
170 if min_bytes > self.max_memory as u64 {
171 wasmtime::bail!(
172 "module declares a minimum memory of {} bytes, above the limit of {} bytes",
173 min_bytes,
174 self.max_memory
175 );
176 }
177 }
178 Ok(module)
179 }
180
181 pub fn engine(&self) -> &wasmtime::Engine {
182 &self.engine
183 }
184
185 pub fn fuel(&self) -> u64 {
186 self.fuel
187 }
188
189 pub fn max_memory(&self) -> usize {
191 self.max_memory
192 }
193
194 pub fn new_store(
196 &self,
197 data: StoreData,
198 ) -> Result<wasmtime::Store<StoreData>, wasmtime::Error> {
199 let mut store = wasmtime::Store::new(&self.engine, data);
200 store.limiter(|data| &mut data.limits);
201 store.set_fuel(self.fuel)?;
202 Ok(store)
203 }
204}
205
206#[cfg(all(test, feature = "wasm"))]
207mod tests {
208 use super::WasmRuntime;
209
210 #[test]
211 fn compiles_core_hook_instruction_set() {
212 let runtime = WasmRuntime::new().unwrap();
213 runtime
214 .compile(
215 br#"(module
216 (memory 2)
217 (func (export "bulk")
218 i32.const 0
219 i32.const 90
220 i32.const 4096
221 memory.fill
222 i32.const 8192
223 i32.const 0
224 i32.const 4096
225 memory.copy))"#,
226 )
227 .unwrap();
228 }
229
230 #[test]
231 fn rejects_webassembly_gc_modules() {
232 let runtime = WasmRuntime::new().unwrap();
233 let result = runtime.compile(
234 br#"(module
235 (type $record (struct (field i32)))
236 (func (export "make") (result (ref null $record))
237 ref.null $record))"#,
238 );
239
240 assert!(result.is_err(), "the hook sandbox must not enable Wasm GC");
241 }
242
243 #[test]
244 fn rejects_webassembly_exception_modules() {
245 let runtime = WasmRuntime::new().unwrap();
246 let result = runtime.compile(
247 br#"(module
248 (tag $failure)
249 (func (export "raise")
250 throw $failure))"#,
251 );
252
253 assert!(
254 result.is_err(),
255 "the hook sandbox must not enable Wasm exception handling"
256 );
257 }
258}