1 use crate::error::Error;
2 use crate::module::Module;
3 use crate::region::RegionInternal;
4 use crate::sysdeps::host_page_size;
5 use libc::c_void;
6 use lucet_module::GlobalValue;
7 use rand::{thread_rng, Rng, RngCore};
8 use std::fmt;
9 use std::sync::{Arc, Mutex, Weak};
10
instance_heap_offset() -> usize11 pub fn instance_heap_offset() -> usize {
12 1 * host_page_size()
13 }
14
15 /// A set of pointers into virtual memory that can be allocated into an `Alloc`.
16 ///
17 /// The `'r` lifetime parameter represents the lifetime of the region that backs this virtual
18 /// address space.
19 ///
20 /// The memory layout in a `Slot` is meant to be reused in order to reduce overhead on region
21 /// implementations. To back the layout with real memory, use `Region::allocate_runtime`.
22 ///
23 /// To ensure a `Slot` can only be backed by one allocation at a time, it contains a mutex, but
24 /// otherwise can be freely copied.
25 #[repr(C)]
26 pub struct Slot {
27 /// The beginning of the contiguous virtual memory chunk managed by this `Alloc`.
28 ///
29 /// The first part of this memory, pointed to by `start`, is always backed by real memory, and
30 /// is used to store the lucet_instance structure.
31 pub start: *mut c_void,
32
33 /// The next part of memory contains the heap and its guard pages.
34 ///
35 /// The heap is backed by real memory according to the `HeapSpec`. Guard pages trigger a sigsegv
36 /// when accessed.
37 pub heap: *mut c_void,
38
39 /// The stack comes after the heap.
40 ///
41 /// Because the stack grows downwards, we get the added safety of ensuring that stack overflows
42 /// go into the guard pages, if the `Limits` specify guard pages. The stack is always the size
43 /// given by `Limits.stack_pages`.
44 pub stack: *mut c_void,
45
46 /// The WebAssembly Globals follow the stack and a single guard page.
47 pub globals: *mut c_void,
48
49 /// The signal handler stack follows the globals.
50 ///
51 /// Having a separate signal handler stack allows the signal handler to run in situations where
52 /// the normal stack has grown into the guard page.
53 pub sigstack: *mut c_void,
54
55 /// Limits of the memory.
56 ///
57 /// Should not change through the lifetime of the `Alloc`.
58 pub limits: Limits,
59
60 pub region: Weak<dyn RegionInternal>,
61 }
62
63 // raw pointers require unsafe impl
64 unsafe impl Send for Slot {}
65 unsafe impl Sync for Slot {}
66
67 impl Slot {
stack_top(&self) -> *mut c_void68 pub fn stack_top(&self) -> *mut c_void {
69 (self.stack as usize + self.limits.stack_size) as *mut c_void
70 }
71 }
72
73 /// The strategy by which a `Region` selects an allocation to back an `Instance`.
74 #[derive(Clone)]
75 pub enum AllocStrategy {
76 /// Allocate from the next slot available.
77 Linear,
78 /// Allocate randomly from the set of available slots.
79 Random,
80 /// Allocate randomly from the set of available slots using the
81 /// supplied random number generator.
82 ///
83 /// This strategy is used to create reproducible behavior for testing.
84 CustomRandom(Arc<Mutex<dyn RngCore + Send>>),
85 }
86
87 impl fmt::Debug for AllocStrategy {
fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result88 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
89 match *self {
90 AllocStrategy::Linear => write!(f, "AllocStrategy::Linear"),
91 AllocStrategy::Random => write!(f, "AllocStrategy::Random"),
92 AllocStrategy::CustomRandom(_) => write!(f, "AllocStrategy::CustomRandom(...)"),
93 }
94 }
95 }
96
97 impl AllocStrategy {
98 /// For a given `AllocStrategy`, use the number of free_slots and
99 /// capacity to determine the next slot to allocate for an
100 /// `Instance`.
next(&mut self, free_slots: usize, capacity: usize) -> Result<usize, Error>101 pub fn next(&mut self, free_slots: usize, capacity: usize) -> Result<usize, Error> {
102 if free_slots == 0 {
103 return Err(Error::RegionFull(capacity));
104 }
105 match self {
106 AllocStrategy::Linear => Ok(free_slots - 1),
107 AllocStrategy::Random => {
108 // Instantiate a random number generator and get a
109 // random slot index.
110 let mut rng = thread_rng();
111 Ok(rng.gen_range(0, free_slots))
112 }
113 AllocStrategy::CustomRandom(custom_rng) => {
114 // Get a random slot index using the supplied random
115 // number generator.
116 let mut rng = custom_rng.lock().unwrap();
117 Ok(rng.gen_range(0, free_slots))
118 }
119 }
120 }
121 }
122
123 /// The structure that manages the allocations backing an `Instance`.
124 ///
125 /// `Alloc`s are not to be created directly, but rather are created by `Region`s during instance
126 /// creation.
127 pub struct Alloc {
128 pub heap_accessible_size: usize,
129 pub heap_inaccessible_size: usize,
130 pub heap_memory_size_limit: usize,
131 pub slot: Option<Slot>,
132 pub region: Arc<dyn RegionInternal>,
133 }
134
135 impl Drop for Alloc {
drop(&mut self)136 fn drop(&mut self) {
137 // eprintln!("Alloc::drop()");
138 self.region.clone().drop_alloc(self);
139 }
140 }
141
142 #[derive(Clone, Copy, Debug, Eq, PartialEq)]
143 pub enum AddrLocation {
144 Heap,
145 InaccessibleHeap,
146 StackGuard,
147 Stack,
148 Globals,
149 SigStackGuard,
150 SigStack,
151 Unknown,
152 }
153
154 impl AddrLocation {
155 /// If a fault occurs in this location, is it fatal to the entire process?
156 ///
157 /// This is currently a permissive baseline that only returns true for unknown locations and the
158 /// signal stack guard, in case a `Region` implementation uses faults to populate the accessible
159 /// locations like the heap and the globals.
is_fault_fatal(self) -> bool160 pub fn is_fault_fatal(self) -> bool {
161 use AddrLocation::*;
162 match self {
163 SigStackGuard | Unknown => true,
164 _ => false,
165 }
166 }
167 }
168
169 impl Alloc {
170 /// Where in an `Alloc` does a particular address fall?
addr_location(&self, addr: *const c_void) -> AddrLocation171 pub fn addr_location(&self, addr: *const c_void) -> AddrLocation {
172 let addr = addr as usize;
173
174 let heap_start = self.slot().heap as usize;
175 let heap_inaccessible_start = heap_start + self.heap_accessible_size;
176 let heap_inaccessible_end = heap_start + self.slot().limits.heap_address_space_size;
177
178 if (addr >= heap_start) && (addr < heap_inaccessible_start) {
179 return AddrLocation::Heap;
180 }
181 if (addr >= heap_inaccessible_start) && (addr < heap_inaccessible_end) {
182 return AddrLocation::InaccessibleHeap;
183 }
184
185 let stack_start = self.slot().stack as usize;
186 let stack_end = stack_start + self.slot().limits.stack_size;
187 let stack_guard_start = stack_start - host_page_size();
188
189 if (addr >= stack_guard_start) && (addr < stack_start) {
190 return AddrLocation::StackGuard;
191 }
192 if (addr >= stack_start) && (addr < stack_end) {
193 return AddrLocation::Stack;
194 }
195
196 let globals_start = self.slot().globals as usize;
197 let globals_end = globals_start + self.slot().limits.globals_size;
198
199 if (addr >= globals_start) && (addr < globals_end) {
200 return AddrLocation::Globals;
201 }
202
203 let sigstack_start = self.slot().sigstack as usize;
204 let sigstack_end = sigstack_start + self.slot().limits.signal_stack_size;
205 let sigstack_guard_start = sigstack_start - host_page_size();
206
207 if (addr >= sigstack_guard_start) && (addr < sigstack_start) {
208 return AddrLocation::SigStackGuard;
209 }
210 if (addr >= sigstack_start) && (addr < sigstack_end) {
211 return AddrLocation::SigStack;
212 }
213
214 AddrLocation::Unknown
215 }
216
expand_heap(&mut self, expand_bytes: u32, module: &dyn Module) -> Result<u32, Error>217 pub fn expand_heap(&mut self, expand_bytes: u32, module: &dyn Module) -> Result<u32, Error> {
218 let slot = self.slot();
219
220 if expand_bytes == 0 {
221 // no expansion takes place, which is not an error
222 return Ok(self.heap_accessible_size as u32);
223 }
224
225 let host_page_size = host_page_size() as u32;
226
227 if self.heap_accessible_size as u32 % host_page_size != 0 {
228 lucet_bail!("heap is not page-aligned; this is a bug");
229 }
230
231 if expand_bytes > std::u32::MAX - host_page_size - 1 {
232 bail_limits_exceeded!("expanded heap would overflow address space");
233 }
234
235 // round the expansion up to a page boundary
236 let expand_pagealigned =
237 ((expand_bytes + host_page_size - 1) / host_page_size) * host_page_size;
238
239 // `heap_inaccessible_size` tracks the size of the allocation that is addressible but not
240 // accessible. We cannot perform an expansion larger than this size.
241 if expand_pagealigned as usize > self.heap_inaccessible_size {
242 bail_limits_exceeded!("expanded heap would overflow addressable memory");
243 }
244
245 // the above makes sure this expression does not underflow
246 let guard_remaining = self.heap_inaccessible_size - expand_pagealigned as usize;
247
248 if let Some(heap_spec) = module.heap_spec() {
249 // The compiler specifies how much guard (memory which traps on access) must be beyond the
250 // end of the accessible memory. We cannot perform an expansion that would make this region
251 // smaller than the compiler expected it to be.
252 if guard_remaining < heap_spec.guard_size as usize {
253 bail_limits_exceeded!("expansion would leave guard memory too small");
254 }
255
256 // The compiler indicates that the module has specified a maximum memory size. Don't let
257 // the heap expand beyond that:
258 if let Some(max_size) = heap_spec.max_size {
259 if self.heap_accessible_size + expand_pagealigned as usize > max_size as usize {
260 bail_limits_exceeded!(
261 "expansion would exceed module-specified heap limit: {:?}",
262 max_size
263 );
264 }
265 }
266 } else {
267 return Err(Error::NoLinearMemory("cannot expand heap".to_owned()));
268 }
269 // The runtime sets a limit on how much of the heap can be backed by real memory. Don't let
270 // the heap expand beyond that:
271 if self.heap_accessible_size + expand_pagealigned as usize > self.heap_memory_size_limit {
272 bail_limits_exceeded!(
273 "expansion would exceed runtime-specified heap limit: {:?}",
274 slot.limits
275 );
276 }
277
278 let newly_accessible = self.heap_accessible_size;
279
280 self.region
281 .clone()
282 .expand_heap(slot, newly_accessible as u32, expand_pagealigned)?;
283
284 self.heap_accessible_size += expand_pagealigned as usize;
285 self.heap_inaccessible_size -= expand_pagealigned as usize;
286
287 Ok(newly_accessible as u32)
288 }
289
reset_heap(&mut self, module: &dyn Module) -> Result<(), Error>290 pub fn reset_heap(&mut self, module: &dyn Module) -> Result<(), Error> {
291 self.region.clone().reset_heap(self, module)
292 }
293
heap_len(&self) -> usize294 pub fn heap_len(&self) -> usize {
295 self.heap_accessible_size
296 }
297
slot(&self) -> &Slot298 pub fn slot(&self) -> &Slot {
299 self.slot
300 .as_ref()
301 .expect("alloc missing its slot before drop")
302 }
303
304 /// Return the heap as a byte slice.
heap(&self) -> &[u8]305 pub unsafe fn heap(&self) -> &[u8] {
306 std::slice::from_raw_parts(self.slot().heap as *mut u8, self.heap_accessible_size)
307 }
308
309 /// Return the heap as a mutable byte slice.
heap_mut(&mut self) -> &mut [u8]310 pub unsafe fn heap_mut(&mut self) -> &mut [u8] {
311 std::slice::from_raw_parts_mut(self.slot().heap as *mut u8, self.heap_accessible_size)
312 }
313
314 /// Return the heap as a slice of 32-bit words.
heap_u32(&self) -> &[u32]315 pub unsafe fn heap_u32(&self) -> &[u32] {
316 assert!(self.slot().heap as usize % 4 == 0, "heap is 4-byte aligned");
317 assert!(
318 self.heap_accessible_size % 4 == 0,
319 "heap size is multiple of 4-bytes"
320 );
321 std::slice::from_raw_parts(self.slot().heap as *mut u32, self.heap_accessible_size / 4)
322 }
323
324 /// Return the heap as a mutable slice of 32-bit words.
heap_u32_mut(&mut self) -> &mut [u32]325 pub unsafe fn heap_u32_mut(&mut self) -> &mut [u32] {
326 assert!(self.slot().heap as usize % 4 == 0, "heap is 4-byte aligned");
327 assert!(
328 self.heap_accessible_size % 4 == 0,
329 "heap size is multiple of 4-bytes"
330 );
331 std::slice::from_raw_parts_mut(self.slot().heap as *mut u32, self.heap_accessible_size / 4)
332 }
333
334 /// Return the heap as a slice of 64-bit words.
heap_u64(&self) -> &[u64]335 pub unsafe fn heap_u64(&self) -> &[u64] {
336 assert!(self.slot().heap as usize % 8 == 0, "heap is 8-byte aligned");
337 assert!(
338 self.heap_accessible_size % 8 == 0,
339 "heap size is multiple of 8-bytes"
340 );
341 std::slice::from_raw_parts(self.slot().heap as *mut u64, self.heap_accessible_size / 8)
342 }
343
344 /// Return the heap as a mutable slice of 64-bit words.
heap_u64_mut(&mut self) -> &mut [u64]345 pub unsafe fn heap_u64_mut(&mut self) -> &mut [u64] {
346 assert!(self.slot().heap as usize % 8 == 0, "heap is 8-byte aligned");
347 assert!(
348 self.heap_accessible_size % 8 == 0,
349 "heap size is multiple of 8-bytes"
350 );
351 std::slice::from_raw_parts_mut(self.slot().heap as *mut u64, self.heap_accessible_size / 8)
352 }
353
354 /// Return the stack as a mutable byte slice.
355 ///
356 /// Since the stack grows down, `alloc.stack_mut()[0]` is the top of the stack, and
357 /// `alloc.stack_mut()[alloc.limits.stack_size - 1]` is the last byte at the bottom of the
358 /// stack.
stack_mut(&mut self) -> &mut [u8]359 pub unsafe fn stack_mut(&mut self) -> &mut [u8] {
360 std::slice::from_raw_parts_mut(self.slot().stack as *mut u8, self.slot().limits.stack_size)
361 }
362
363 /// Return the stack as a mutable slice of 64-bit words.
364 ///
365 /// Since the stack grows down, `alloc.stack_mut()[0]` is the top of the stack, and
366 /// `alloc.stack_mut()[alloc.limits.stack_size - 1]` is the last word at the bottom of the
367 /// stack.
stack_u64_mut(&mut self) -> &mut [u64]368 pub unsafe fn stack_u64_mut(&mut self) -> &mut [u64] {
369 assert!(
370 self.slot().stack as usize % 8 == 0,
371 "stack is 8-byte aligned"
372 );
373 assert!(
374 self.slot().limits.stack_size % 8 == 0,
375 "stack size is multiple of 8-bytes"
376 );
377 std::slice::from_raw_parts_mut(
378 self.slot().stack as *mut u64,
379 self.slot().limits.stack_size / 8,
380 )
381 }
382
383 /// Return the globals as a slice.
globals(&self) -> &[GlobalValue]384 pub unsafe fn globals(&self) -> &[GlobalValue] {
385 std::slice::from_raw_parts(
386 self.slot().globals as *const GlobalValue,
387 self.slot().limits.globals_size / std::mem::size_of::<GlobalValue>(),
388 )
389 }
390
391 /// Return the globals as a mutable slice.
globals_mut(&mut self) -> &mut [GlobalValue]392 pub unsafe fn globals_mut(&mut self) -> &mut [GlobalValue] {
393 std::slice::from_raw_parts_mut(
394 self.slot().globals as *mut GlobalValue,
395 self.slot().limits.globals_size / std::mem::size_of::<GlobalValue>(),
396 )
397 }
398
399 /// Return the sigstack as a mutable byte slice.
sigstack_mut(&mut self) -> &mut [u8]400 pub unsafe fn sigstack_mut(&mut self) -> &mut [u8] {
401 std::slice::from_raw_parts_mut(
402 self.slot().sigstack as *mut u8,
403 self.slot().limits.signal_stack_size,
404 )
405 }
406
mem_in_heap<T>(&self, ptr: *const T, len: usize) -> bool407 pub fn mem_in_heap<T>(&self, ptr: *const T, len: usize) -> bool {
408 let start = ptr as usize;
409 let end = start + len;
410
411 let heap_start = self.slot().heap as usize;
412 let heap_end = heap_start + self.heap_accessible_size;
413
414 // TODO: check for off-by-ones
415 start <= end
416 && start >= heap_start
417 && start < heap_end
418 && end >= heap_start
419 && end <= heap_end
420 }
421 }
422
423 /// Runtime limits for the various memories that back a Lucet instance.
424 ///
425 /// Each value is specified in bytes, and must be evenly divisible by the host page size (4K).
426 #[derive(Clone, Copy, Debug)]
427 #[repr(C)]
428 pub struct Limits {
429 /// Max size of the heap, which can be backed by real memory. (default 1M)
430 pub heap_memory_size: usize,
431 /// Size of total virtual memory. (default 8G)
432 pub heap_address_space_size: usize,
433 /// Size of the guest stack. (default 128K)
434 pub stack_size: usize,
435 /// Size of the globals region in bytes; each global uses 8 bytes. (default 4K)
436 pub globals_size: usize,
437 /// Size of the signal stack in bytes. (default SIGSTKSZ for release builds, at least 12K for
438 /// debug builds; minimum MINSIGSTKSZ)
439 ///
440 /// This difference is to account for the greatly increased stack size usage in the signal
441 /// handler when running without optimizations.
442 ///
443 /// Note that debug vs. release mode is determined by `cfg(debug_assertions)`, so if you are
444 /// specifically enabling debug assertions in your release builds, the default signal stack may
445 /// be larger.
446 pub signal_stack_size: usize,
447 }
448
449 // this constant isn't exported by `libc` on Mac
450 #[cfg(target_os = "macos")]
451 pub const MINSIGSTKSZ: usize = 32 * 1024;
452
453 #[cfg(not(target_os = "macos"))]
454 pub const MINSIGSTKSZ: usize = libc::MINSIGSTKSZ;
455
456 /// The recommended size of a signal handler stack for a Lucet instance.
457 ///
458 /// This value is used as the `signal_stack_size` in `Limits::default()`.
459 ///
460 /// The value of this constant depends on the platform, and on whether Rust optimizations are
461 /// enabled. In release mode, it is equal to [`SIGSTKSIZE`][sigstksz]. In debug mode, it is equal to
462 /// `SIGSTKSZ` or 12KiB, whichever is greater.
463 ///
464 /// [sigstksz]: https://pubs.opengroup.org/onlinepubs/009695399/basedefs/signal.h.html
465 pub const DEFAULT_SIGNAL_STACK_SIZE: usize = {
466 // on Linux, `SIGSTKSZ` is too small for the signal handler when compiled in debug mode
467 #[cfg(all(debug_assertions, not(target_os = "macos")))]
468 const SIZE: usize = 12 * 1024;
469
470 // on Mac, `SIGSTKSZ` is way larger than we need; it would be nice to combine these debug cases once
471 // `std::cmp::max` is a const fn
472 #[cfg(all(debug_assertions, target_os = "macos"))]
473 const SIZE: usize = libc::SIGSTKSZ;
474
475 #[cfg(not(debug_assertions))]
476 const SIZE: usize = libc::SIGSTKSZ;
477
478 SIZE
479 };
480
481 impl Limits {
default() -> Limits482 pub const fn default() -> Limits {
483 Limits {
484 heap_memory_size: 16 * 64 * 1024,
485 heap_address_space_size: 0x0002_0000_0000,
486 stack_size: 128 * 1024,
487 globals_size: 4096,
488 signal_stack_size: DEFAULT_SIGNAL_STACK_SIZE,
489 }
490 }
491 }
492
493 impl Limits {
total_memory_size(&self) -> usize494 pub fn total_memory_size(&self) -> usize {
495 // Memory is laid out as follows:
496 // * the instance (up to instance_heap_offset)
497 // * the heap, followed by guard pages
498 // * the stack (grows towards heap guard pages)
499 // * globals
500 // * one guard page (to catch signal stack overflow)
501 // * the signal stack
502
503 [
504 instance_heap_offset(),
505 self.heap_address_space_size,
506 host_page_size(),
507 self.stack_size,
508 self.globals_size,
509 host_page_size(),
510 self.signal_stack_size,
511 ]
512 .iter()
513 .try_fold(0usize, |acc, &x| acc.checked_add(x))
514 .expect("total_memory_size doesn't overflow")
515 }
516
517 /// Validate that the limits are aligned to page sizes, and that the stack is not empty.
validate(&self) -> Result<(), Error>518 pub fn validate(&self) -> Result<(), Error> {
519 if self.heap_memory_size % host_page_size() != 0 {
520 return Err(Error::InvalidArgument(
521 "memory size must be a multiple of host page size",
522 ));
523 }
524 if self.heap_address_space_size % host_page_size() != 0 {
525 return Err(Error::InvalidArgument(
526 "address space size must be a multiple of host page size",
527 ));
528 }
529 if self.heap_memory_size > self.heap_address_space_size {
530 return Err(Error::InvalidArgument(
531 "address space size must be at least as large as memory size",
532 ));
533 }
534 if self.stack_size % host_page_size() != 0 {
535 return Err(Error::InvalidArgument(
536 "stack size must be a multiple of host page size",
537 ));
538 }
539 if self.globals_size % host_page_size() != 0 {
540 return Err(Error::InvalidArgument(
541 "globals size must be a multiple of host page size",
542 ));
543 }
544 if self.stack_size <= 0 {
545 return Err(Error::InvalidArgument("stack size must be greater than 0"));
546 }
547 if self.signal_stack_size < MINSIGSTKSZ {
548 tracing::info!(
549 "signal stack size of {} requires manual configuration of signal stacks",
550 self.signal_stack_size
551 );
552 tracing::debug!(
553 "signal stack size must be at least MINSIGSTKSZ \
554 (defined in <signal.h>; {} on this system)",
555 MINSIGSTKSZ,
556 );
557 }
558 if cfg!(debug_assertions) && self.signal_stack_size < 12 * 1024 {
559 tracing::info!(
560 "signal stack size of {} requires manual configuration of signal stacks",
561 self.signal_stack_size
562 );
563 tracing::debug!(
564 "in debug mode, signal stack size must be at least MINSIGSTKSZ \
565 (defined in <signal.h>; {} on this system) or 12KiB, whichever is larger",
566 MINSIGSTKSZ,
567 );
568 }
569 if self.signal_stack_size % host_page_size() != 0 {
570 return Err(Error::InvalidArgument(
571 "signal stack size must be a multiple of host page size",
572 ));
573 }
574 Ok(())
575 }
576 }
577
validate_sigstack_size(signal_stack_size: usize) -> Result<(), Error>578 pub fn validate_sigstack_size(signal_stack_size: usize) -> Result<(), Error> {
579 if signal_stack_size < MINSIGSTKSZ {
580 return Err(Error::InvalidArgument(
581 "signal stack size must be at least MINSIGSTKSZ (defined in <signal.h>)",
582 ));
583 }
584 if cfg!(debug_assertions) && signal_stack_size < 12 * 1024 {
585 return Err(Error::InvalidArgument(
586 "signal stack size must be at least 12KiB for debug builds",
587 ));
588 }
589 Ok(())
590 }
591
592 pub mod tests;
593