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