lucet_runtime_internals/alloc/
tests.rs

1#[macro_export]
2macro_rules! alloc_tests {
3    ( $TestRegion:path ) => {
4        use libc::c_void;
5        use std::sync::Arc;
6        use $TestRegion as TestRegion;
7        use $crate::alloc::{host_page_size, Limits, MINSIGSTKSZ};
8        use $crate::context::{Context, ContextHandle};
9        use $crate::error::Error;
10        use $crate::instance::InstanceInternal;
11        use $crate::module::{GlobalValue, HeapSpec, MockModuleBuilder};
12        use $crate::region::Region;
13        use $crate::val::Val;
14
15        const LIMITS_HEAP_MEM_SIZE: usize = 16 * 64 * 1024;
16        const LIMITS_HEAP_ADDRSPACE_SIZE: usize = 8 * 1024 * 1024;
17        const LIMITS_STACK_SIZE: usize = 64 * 1024;
18        const LIMITS_GLOBALS_SIZE: usize = 4 * 1024;
19
20        const LIMITS: Limits = Limits {
21            heap_memory_size: LIMITS_HEAP_MEM_SIZE,
22            heap_address_space_size: LIMITS_HEAP_ADDRSPACE_SIZE,
23            stack_size: LIMITS_STACK_SIZE,
24            globals_size: LIMITS_GLOBALS_SIZE,
25            ..Limits::default()
26        };
27
28        const SPEC_HEAP_RESERVED_SIZE: u64 = LIMITS_HEAP_ADDRSPACE_SIZE as u64 / 2;
29        const SPEC_HEAP_GUARD_SIZE: u64 = LIMITS_HEAP_ADDRSPACE_SIZE as u64 / 2;
30
31        // one wasm page, not host page
32        const ONEPAGE_INITIAL_SIZE: u64 = 64 * 1024;
33        const ONEPAGE_MAX_SIZE: u64 = 64 * 1024;
34
35        const ONE_PAGE_HEAP: HeapSpec = HeapSpec {
36            reserved_size: SPEC_HEAP_RESERVED_SIZE,
37            guard_size: SPEC_HEAP_GUARD_SIZE,
38            initial_size: ONEPAGE_INITIAL_SIZE,
39            max_size: Some(ONEPAGE_MAX_SIZE),
40        };
41
42        const THREEPAGE_INITIAL_SIZE: u64 = 64 * 1024;
43        const THREEPAGE_MAX_SIZE: u64 = 3 * 64 * 1024;
44
45        const THREE_PAGE_MAX_HEAP: HeapSpec = HeapSpec {
46            reserved_size: SPEC_HEAP_RESERVED_SIZE,
47            guard_size: 0,
48            initial_size: THREEPAGE_INITIAL_SIZE,
49            max_size: Some(THREEPAGE_MAX_SIZE),
50        };
51
52        /// This test shows an `AllocHandle` passed to `Region::allocate_runtime` will have its heap
53        /// and stack of the correct size and read/writability.
54        #[test]
55        fn allocate_runtime_works() {
56            let region = TestRegion::create(1, &LIMITS).expect("region created");
57            let mut inst = region
58                .new_instance(
59                    MockModuleBuilder::new()
60                        .with_heap_spec(ONE_PAGE_HEAP)
61                        .build(),
62                )
63                .expect("new_instance succeeds");
64
65            let heap_len = inst.alloc().heap_len();
66            assert_eq!(heap_len, ONEPAGE_INITIAL_SIZE as usize);
67
68            let heap = unsafe { inst.alloc_mut().heap_mut() };
69
70            assert_eq!(heap[0], 0);
71            heap[0] = 0xFF;
72            assert_eq!(heap[0], 0xFF);
73
74            assert_eq!(heap[heap_len - 1], 0);
75            heap[heap_len - 1] = 0xFF;
76            assert_eq!(heap[heap_len - 1], 0xFF);
77
78            let stack = unsafe { inst.alloc_mut().stack_mut() };
79            assert_eq!(stack.len(), LIMITS_STACK_SIZE);
80
81            assert_eq!(stack[0], 0);
82            stack[0] = 0xFF;
83            assert_eq!(stack[0], 0xFF);
84
85            assert_eq!(stack[LIMITS_STACK_SIZE - 1], 0);
86            stack[LIMITS_STACK_SIZE - 1] = 0xFF;
87            assert_eq!(stack[LIMITS_STACK_SIZE - 1], 0xFF);
88        }
89
90        /// This test shows the heap works properly after a single expand.
91        #[test]
92        fn expand_heap_once() {
93            expand_heap_once_template(THREE_PAGE_MAX_HEAP)
94        }
95
96        fn expand_heap_once_template(heap_spec: HeapSpec) {
97            let region = TestRegion::create(1, &LIMITS).expect("region created");
98            let module = MockModuleBuilder::new()
99                .with_heap_spec(heap_spec.clone())
100                .build();
101            let mut inst = region
102                .new_instance(module.clone())
103                .expect("new_instance succeeds");
104
105            let heap_len = inst.alloc().heap_len();
106            assert_eq!(heap_len, heap_spec.initial_size as usize);
107
108            let new_heap_area = inst
109                .alloc_mut()
110                .expand_heap(64 * 1024, module.as_ref())
111                .expect("expand_heap succeeds");
112            assert_eq!(heap_len, new_heap_area as usize);
113
114            let new_heap_len = inst.alloc().heap_len();
115            assert_eq!(new_heap_len, heap_len + (64 * 1024));
116
117            let heap = unsafe { inst.alloc_mut().heap_mut() };
118            assert_eq!(heap[new_heap_len - 1], 0);
119            heap[new_heap_len - 1] = 0xFF;
120            assert_eq!(heap[new_heap_len - 1], 0xFF);
121        }
122
123        /// This test shows the heap works properly after two expands.
124        #[test]
125        fn expand_heap_twice() {
126            let region = TestRegion::create(1, &LIMITS).expect("region created");
127            let module = MockModuleBuilder::new()
128                .with_heap_spec(THREE_PAGE_MAX_HEAP)
129                .build();
130            let mut inst = region
131                .new_instance(module.clone())
132                .expect("new_instance succeeds");
133
134            let heap_len = inst.alloc().heap_len();
135            assert_eq!(heap_len, THREEPAGE_INITIAL_SIZE as usize);
136
137            let new_heap_area = inst
138                .alloc_mut()
139                .expand_heap(64 * 1024, module.as_ref())
140                .expect("expand_heap succeeds");
141            assert_eq!(heap_len, new_heap_area as usize);
142
143            let new_heap_len = inst.alloc().heap_len();
144            assert_eq!(new_heap_len, heap_len + (64 * 1024));
145
146            let second_new_heap_area = inst
147                .alloc_mut()
148                .expand_heap(64 * 1024, module.as_ref())
149                .expect("expand_heap succeeds");
150            assert_eq!(new_heap_len, second_new_heap_area as usize);
151
152            let second_new_heap_len = inst.alloc().heap_len();
153            assert_eq!(second_new_heap_len as u64, THREEPAGE_MAX_SIZE);
154
155            let heap = unsafe { inst.alloc_mut().heap_mut() };
156            assert_eq!(heap[new_heap_len - 1], 0);
157            heap[new_heap_len - 1] = 0xFF;
158            assert_eq!(heap[new_heap_len - 1], 0xFF);
159        }
160
161        /// This test shows that if you try to expand past the max given by the heap spec, the
162        /// expansion fails, but the existing heap can still be used. This test uses a region with
163        /// multiple slots in order to exercise more edge cases with adjacent managed memory.
164        #[test]
165        fn expand_past_spec_max() {
166            let region = TestRegion::create(10, &LIMITS).expect("region created");
167            let module = MockModuleBuilder::new()
168                .with_heap_spec(THREE_PAGE_MAX_HEAP)
169                .build();
170            let mut inst = region
171                .new_instance(module.clone())
172                .expect("new_instance succeeds");
173
174            let heap_len = inst.alloc().heap_len();
175            assert_eq!(heap_len, THREEPAGE_INITIAL_SIZE as usize);
176
177            let new_heap_area = inst
178                .alloc_mut()
179                .expand_heap(THREEPAGE_MAX_SIZE as u32, module.as_ref());
180            assert!(new_heap_area.is_err(), "heap expansion past spec fails");
181
182            let new_heap_len = inst.alloc().heap_len();
183            assert_eq!(new_heap_len, heap_len);
184
185            let heap = unsafe { inst.alloc_mut().heap_mut() };
186            assert_eq!(heap[new_heap_len - 1], 0);
187            heap[new_heap_len - 1] = 0xFF;
188            assert_eq!(heap[new_heap_len - 1], 0xFF);
189        }
190
191        const EXPANDPASTLIMIT_INITIAL_SIZE: u64 = LIMITS_HEAP_MEM_SIZE as u64 - (64 * 1024);
192        const EXPANDPASTLIMIT_MAX_SIZE: u64 = LIMITS_HEAP_MEM_SIZE as u64 + (64 * 1024);
193        const EXPAND_PAST_LIMIT_SPEC: HeapSpec = HeapSpec {
194            reserved_size: SPEC_HEAP_RESERVED_SIZE,
195            guard_size: SPEC_HEAP_GUARD_SIZE,
196            initial_size: EXPANDPASTLIMIT_INITIAL_SIZE,
197            max_size: Some(EXPANDPASTLIMIT_MAX_SIZE),
198        };
199
200        /// This test shows that a heap refuses to grow past the alloc limits, even if the runtime
201        /// spec says it can grow bigger. This test uses a region with multiple slots in order to
202        /// exercise more edge cases with adjacent managed memory.
203        #[test]
204        fn expand_past_heap_limit() {
205            let region = TestRegion::create(10, &LIMITS).expect("region created");
206            let module = MockModuleBuilder::new()
207                .with_heap_spec(EXPAND_PAST_LIMIT_SPEC)
208                .build();
209            let mut inst = region
210                .new_instance(module.clone())
211                .expect("new_instance succeeds");
212
213            let heap_len = inst.alloc().heap_len();
214            assert_eq!(heap_len, EXPANDPASTLIMIT_INITIAL_SIZE as usize);
215
216            let new_heap_area = inst
217                .alloc_mut()
218                .expand_heap(64 * 1024, module.as_ref())
219                .expect("expand_heap succeeds");
220            assert_eq!(heap_len, new_heap_area as usize);
221
222            let new_heap_len = inst.alloc().heap_len();
223            assert_eq!(new_heap_len, LIMITS_HEAP_MEM_SIZE);
224
225            let past_limit_heap_area = inst.alloc_mut().expand_heap(64 * 1024, module.as_ref());
226            assert!(
227                past_limit_heap_area.is_err(),
228                "heap expansion past limit fails"
229            );
230
231            let still_heap_len = inst.alloc().heap_len();
232            assert_eq!(still_heap_len, LIMITS_HEAP_MEM_SIZE);
233
234            let heap = unsafe { inst.alloc_mut().heap_mut() };
235            assert_eq!(heap[new_heap_len - 1], 0);
236            heap[new_heap_len - 1] = 0xFF;
237            assert_eq!(heap[new_heap_len - 1], 0xFF);
238        }
239
240        const INITIAL_OVERSIZE_HEAP: HeapSpec = HeapSpec {
241            reserved_size: SPEC_HEAP_RESERVED_SIZE,
242            guard_size: SPEC_HEAP_GUARD_SIZE,
243            initial_size: SPEC_HEAP_RESERVED_SIZE + (64 * 1024),
244            max_size: None,
245        };
246
247        /// This test shows that a heap refuses to grow past the alloc limits, even if the runtime
248        /// spec says it can grow bigger. This test uses a region with multiple slots in order to
249        /// exercise more edge cases with adjacent managed memory.
250        #[test]
251        fn reject_initial_oversize_heap() {
252            let region = TestRegion::create(10, &LIMITS).expect("region created");
253            let res = region.new_instance(
254                MockModuleBuilder::new()
255                    .with_heap_spec(INITIAL_OVERSIZE_HEAP)
256                    .build(),
257            );
258            assert!(res.is_err(), "new_instance fails");
259        }
260
261        /// This test shows that we reject limits with a larger memory size than address space size
262        #[test]
263        fn reject_undersized_address_space() {
264            const LIMITS: Limits = Limits {
265                heap_memory_size: LIMITS_HEAP_ADDRSPACE_SIZE + 4096,
266                heap_address_space_size: LIMITS_HEAP_ADDRSPACE_SIZE,
267                stack_size: LIMITS_STACK_SIZE,
268                globals_size: LIMITS_GLOBALS_SIZE,
269                ..Limits::default()
270            };
271            let res = TestRegion::create(10, &LIMITS);
272            assert!(res.is_err(), "region creation fails");
273        }
274
275        const SMALL_GUARD_HEAP: HeapSpec = HeapSpec {
276            reserved_size: SPEC_HEAP_RESERVED_SIZE,
277            guard_size: SPEC_HEAP_GUARD_SIZE - 1,
278            initial_size: LIMITS_HEAP_MEM_SIZE as u64,
279            max_size: None,
280        };
281
282        /// This test shows that a heap spec with a guard size smaller than the limits is
283        /// allowed.
284        #[test]
285        fn accept_small_guard_heap() {
286            let region = TestRegion::create(1, &LIMITS).expect("region created");
287            let _inst = region
288                .new_instance(
289                    MockModuleBuilder::new()
290                        .with_heap_spec(SMALL_GUARD_HEAP)
291                        .build(),
292                )
293                .expect("new_instance succeeds");
294        }
295
296        const LARGE_GUARD_HEAP: HeapSpec = HeapSpec {
297            reserved_size: SPEC_HEAP_RESERVED_SIZE,
298            guard_size: SPEC_HEAP_GUARD_SIZE + 1,
299            initial_size: ONEPAGE_INITIAL_SIZE,
300            max_size: None,
301        };
302
303        /// This test shows that a `HeapSpec` with a guard size larger than the limits is not
304        /// allowed.
305        #[test]
306        fn reject_large_guard_heap() {
307            let region = TestRegion::create(1, &LIMITS).expect("region created");
308            let res = region.new_instance(
309                MockModuleBuilder::new()
310                    .with_heap_spec(LARGE_GUARD_HEAP)
311                    .build(),
312            );
313            assert!(res.is_err(), "new_instance fails");
314        }
315
316        /// This test shows that a `Slot` can be reused after an `AllocHandle` is dropped, and that
317        /// its memory is reset.
318        #[test]
319        fn reuse_slot_works() {
320            fn peek_n_poke(region: &Arc<TestRegion>) {
321                let mut inst = region
322                    .new_instance(
323                        MockModuleBuilder::new()
324                            .with_heap_spec(ONE_PAGE_HEAP)
325                            .build(),
326                    )
327                    .expect("new_instance succeeds");
328
329                let heap_len = inst.alloc().heap_len();
330                assert_eq!(heap_len, ONEPAGE_INITIAL_SIZE as usize);
331
332                let heap = unsafe { inst.alloc_mut().heap_mut() };
333
334                assert_eq!(heap[0], 0);
335                heap[0] = 0xFF;
336                assert_eq!(heap[0], 0xFF);
337
338                assert_eq!(heap[heap_len - 1], 0);
339                heap[heap_len - 1] = 0xFF;
340                assert_eq!(heap[heap_len - 1], 0xFF);
341
342                let stack = unsafe { inst.alloc_mut().stack_mut() };
343                assert_eq!(stack.len(), LIMITS_STACK_SIZE);
344
345                assert_eq!(stack[0], 0);
346                stack[0] = 0xFF;
347                assert_eq!(stack[0], 0xFF);
348
349                assert_eq!(stack[LIMITS_STACK_SIZE - 1], 0);
350                stack[LIMITS_STACK_SIZE - 1] = 0xFF;
351                assert_eq!(stack[LIMITS_STACK_SIZE - 1], 0xFF);
352
353                let globals = unsafe { inst.alloc_mut().globals_mut() };
354                assert_eq!(
355                    globals.len(),
356                    LIMITS_GLOBALS_SIZE / std::mem::size_of::<GlobalValue>()
357                );
358
359                unsafe {
360                    assert_eq!(globals[0].i_64, 0);
361                    globals[0].i_64 = 0xFF;
362                    assert_eq!(globals[0].i_64, 0xFF);
363                }
364
365                unsafe {
366                    assert_eq!(globals[globals.len() - 1].i_64, 0);
367                    globals[globals.len() - 1].i_64 = 0xFF;
368                    assert_eq!(globals[globals.len() - 1].i_64, 0xFF);
369                }
370
371                let sigstack = unsafe { inst.alloc_mut().sigstack_mut() };
372                assert_eq!(sigstack.len(), LIMITS.signal_stack_size);
373
374                assert_eq!(sigstack[0], 0);
375                sigstack[0] = 0xFF;
376                assert_eq!(sigstack[0], 0xFF);
377
378                assert_eq!(sigstack[sigstack.len() - 1], 0);
379                sigstack[sigstack.len() - 1] = 0xFF;
380                assert_eq!(sigstack[sigstack.len() - 1], 0xFF);
381            }
382
383            // with a region size of 1, the slot must be reused
384            let region = TestRegion::create(1, &LIMITS).expect("region created");
385
386            peek_n_poke(&region);
387            peek_n_poke(&region);
388        }
389
390        /// This test shows that the reset method clears the heap and resets its protections.
391        #[test]
392        fn alloc_reset() {
393            let region = TestRegion::create(1, &LIMITS).expect("region created");
394            let module = MockModuleBuilder::new()
395                .with_heap_spec(THREE_PAGE_MAX_HEAP)
396                .build();
397            let mut inst = region
398                .new_instance(module.clone())
399                .expect("new_instance succeeds");
400
401            let heap_len = inst.alloc().heap_len();
402            assert_eq!(heap_len, THREEPAGE_INITIAL_SIZE as usize);
403
404            let heap = unsafe { inst.alloc_mut().heap_mut() };
405
406            assert_eq!(heap[0], 0);
407            heap[0] = 0xFF;
408            assert_eq!(heap[0], 0xFF);
409
410            assert_eq!(heap[heap_len - 1], 0);
411            heap[heap_len - 1] = 0xFF;
412            assert_eq!(heap[heap_len - 1], 0xFF);
413
414            // Making a new mock module here because the borrow checker doesn't like referencing
415            // `inst.module` while `inst.alloc()` is borrowed mutably. The `Instance` tests don't have
416            // this weirdness
417            inst.alloc_mut()
418                .reset_heap(module.as_ref())
419                .expect("reset succeeds");
420
421            let reset_heap_len = inst.alloc().heap_len();
422            assert_eq!(reset_heap_len, THREEPAGE_INITIAL_SIZE as usize);
423
424            let heap = unsafe { inst.alloc_mut().heap_mut() };
425
426            assert_eq!(heap[0], 0);
427            heap[0] = 0xFF;
428            assert_eq!(heap[0], 0xFF);
429
430            assert_eq!(heap[reset_heap_len - 1], 0);
431            heap[reset_heap_len - 1] = 0xFF;
432            assert_eq!(heap[reset_heap_len - 1], 0xFF);
433        }
434
435        /// This test shows that the reset method clears the heap and restores it to the spec
436        /// initial size after growing the heap.
437        #[test]
438        fn alloc_grow_reset() {
439            let region = TestRegion::create(1, &LIMITS).expect("region created");
440            let module = MockModuleBuilder::new()
441                .with_heap_spec(THREE_PAGE_MAX_HEAP)
442                .build();
443            let mut inst = region
444                .new_instance(module.clone())
445                .expect("new_instance succeeds");
446
447            let heap_len = inst.alloc().heap_len();
448            assert_eq!(heap_len, THREEPAGE_INITIAL_SIZE as usize);
449
450            let heap = unsafe { inst.alloc_mut().heap_mut() };
451
452            assert_eq!(heap[0], 0);
453            heap[0] = 0xFF;
454            assert_eq!(heap[0], 0xFF);
455
456            assert_eq!(heap[heap_len - 1], 0);
457            heap[heap_len - 1] = 0xFF;
458            assert_eq!(heap[heap_len - 1], 0xFF);
459
460            let new_heap_area = inst
461                .alloc_mut()
462                .expand_heap(
463                    (THREEPAGE_MAX_SIZE - THREEPAGE_INITIAL_SIZE) as u32,
464                    module.as_ref(),
465                )
466                .expect("expand_heap succeeds");
467            assert_eq!(heap_len, new_heap_area as usize);
468
469            let new_heap_len = inst.alloc().heap_len();
470            assert_eq!(new_heap_len, THREEPAGE_MAX_SIZE as usize);
471
472            // Making a new mock module here because the borrow checker doesn't like referencing
473            // `inst.module` while `inst.alloc()` is borrowed mutably. The `Instance` tests don't have
474            // this weirdness
475            inst.alloc_mut()
476                .reset_heap(module.as_ref())
477                .expect("reset succeeds");
478
479            let reset_heap_len = inst.alloc().heap_len();
480            assert_eq!(reset_heap_len, THREEPAGE_INITIAL_SIZE as usize);
481
482            let heap = unsafe { inst.alloc_mut().heap_mut() };
483
484            assert_eq!(heap[0], 0);
485            heap[0] = 0xFF;
486            assert_eq!(heap[0], 0xFF);
487
488            assert_eq!(heap[reset_heap_len - 1], 0);
489            heap[reset_heap_len - 1] = 0xFF;
490            assert_eq!(heap[reset_heap_len - 1], 0xFF);
491        }
492
493        const GUARDLESS_HEAP: HeapSpec = HeapSpec {
494            reserved_size: SPEC_HEAP_RESERVED_SIZE,
495            guard_size: 0,
496            initial_size: ONEPAGE_INITIAL_SIZE,
497            max_size: None,
498        };
499
500        /// This test shows the alloc works even with a zero guard size.
501        #[test]
502        fn guardless_heap_create() {
503            let region = TestRegion::create(1, &LIMITS).expect("region created");
504            let mut inst = region
505                .new_instance(
506                    MockModuleBuilder::new()
507                        .with_heap_spec(GUARDLESS_HEAP)
508                        .build(),
509                )
510                .expect("new_instance succeeds");
511
512            let heap_len = inst.alloc().heap_len();
513            assert_eq!(heap_len, ONEPAGE_INITIAL_SIZE as usize);
514
515            let heap = unsafe { inst.alloc_mut().heap_mut() };
516
517            assert_eq!(heap[0], 0);
518            heap[0] = 0xFF;
519            assert_eq!(heap[0], 0xFF);
520
521            assert_eq!(heap[heap_len - 1], 0);
522            heap[heap_len - 1] = 0xFF;
523            assert_eq!(heap[heap_len - 1], 0xFF);
524
525            let stack = unsafe { inst.alloc_mut().stack_mut() };
526            assert_eq!(stack.len(), LIMITS_STACK_SIZE);
527
528            assert_eq!(stack[0], 0);
529            stack[0] = 0xFF;
530            assert_eq!(stack[0], 0xFF);
531
532            assert_eq!(stack[LIMITS_STACK_SIZE - 1], 0);
533            stack[LIMITS_STACK_SIZE - 1] = 0xFF;
534            assert_eq!(stack[LIMITS_STACK_SIZE - 1], 0xFF);
535        }
536
537        /// This test shows a guardless heap works properly after a single expand.
538        #[test]
539        fn guardless_expand_heap_once() {
540            expand_heap_once_template(GUARDLESS_HEAP)
541        }
542
543        const INITIAL_EMPTY_HEAP: HeapSpec = HeapSpec {
544            reserved_size: SPEC_HEAP_RESERVED_SIZE,
545            guard_size: SPEC_HEAP_GUARD_SIZE,
546            initial_size: 0,
547            max_size: None,
548        };
549
550        /// This test shows an initially-empty heap works properly after a single expand.
551        #[test]
552        fn initial_empty_expand_heap_once() {
553            expand_heap_once_template(INITIAL_EMPTY_HEAP)
554        }
555
556        const INITIAL_EMPTY_GUARDLESS_HEAP: HeapSpec = HeapSpec {
557            reserved_size: SPEC_HEAP_RESERVED_SIZE,
558            guard_size: 0,
559            initial_size: 0,
560            max_size: None,
561        };
562
563        /// This test shows an initially-empty, guardless heap works properly after a single
564        /// expand.
565        #[test]
566        fn initial_empty_guardless_expand_heap_once() {
567            expand_heap_once_template(INITIAL_EMPTY_GUARDLESS_HEAP)
568        }
569
570        const CONTEXT_TEST_LIMITS: Limits = Limits {
571            heap_memory_size: 4096,
572            heap_address_space_size: 2 * 4096,
573            stack_size: 4096,
574            globals_size: 4096,
575            ..Limits::default()
576        };
577        const CONTEXT_TEST_INITIAL_SIZE: u64 = 4096;
578        const CONTEXT_TEST_HEAP: HeapSpec = HeapSpec {
579            reserved_size: 4096,
580            guard_size: 4096,
581            initial_size: CONTEXT_TEST_INITIAL_SIZE,
582            max_size: Some(4096),
583        };
584
585        /// This test shows that alloced memory will create a heap and a stack that child context
586        /// code can use.
587        #[test]
588        fn context_alloc_child() {
589            extern "C" fn heap_touching_child(heap: *mut u8) {
590                let heap = unsafe {
591                    std::slice::from_raw_parts_mut(heap, CONTEXT_TEST_INITIAL_SIZE as usize)
592                };
593                heap[0] = 123;
594                heap[4095] = 45;
595            }
596
597            let region = TestRegion::create(1, &CONTEXT_TEST_LIMITS).expect("region created");
598            let mut inst = region
599                .new_instance(
600                    MockModuleBuilder::new()
601                        .with_heap_spec(CONTEXT_TEST_HEAP)
602                        .build(),
603                )
604                .expect("new_instance succeeds");
605
606            let mut parent = ContextHandle::new();
607            unsafe {
608                let heap_ptr = inst.alloc_mut().heap_mut().as_ptr() as *mut c_void;
609                let mut child = ContextHandle::create_and_init(
610                    inst.alloc_mut().stack_u64_mut(),
611                    heap_touching_child as usize,
612                    &[Val::CPtr(heap_ptr)],
613                )
614                .expect("context init succeeds");
615                Context::swap(&mut parent, &mut child);
616                assert_eq!(inst.alloc().heap()[0], 123);
617                assert_eq!(inst.alloc().heap()[4095], 45);
618            }
619        }
620
621        /// This test shows that an alloced memory will create a heap and stack, the child code can
622        /// write a pattern to that stack, and we can read back that same pattern after it is done
623        /// running.
624        #[test]
625        fn context_stack_pattern() {
626            const STACK_PATTERN_LENGTH: usize = 1024;
627            extern "C" fn stack_pattern_child(heap: *mut u64) {
628                let heap = unsafe {
629                    std::slice::from_raw_parts_mut(heap, CONTEXT_TEST_INITIAL_SIZE as usize / 8)
630                };
631                let mut onthestack = [0u8; STACK_PATTERN_LENGTH];
632                // While not used, this array is load-bearing! A function that executes after the
633                // guest completes, `instance_kill_state_exit_guest_region`, may end up using
634                // sufficient stack space to trample over values in this function's call frame.
635                //
636                // Padding it out with a duplicate pattern makes enough space for `onthestack` to
637                // not be clobbered.
638                let mut ignored = [0u8; STACK_PATTERN_LENGTH];
639                for i in 0..STACK_PATTERN_LENGTH {
640                    ignored[i] = (i % 256) as u8;
641                    onthestack[i] = (i % 256) as u8;
642                }
643                heap[0] = onthestack.as_ptr() as u64;
644            }
645
646            let region = TestRegion::create(1, &CONTEXT_TEST_LIMITS).expect("region created");
647            let mut inst = region
648                .new_instance(
649                    MockModuleBuilder::new()
650                        .with_heap_spec(CONTEXT_TEST_HEAP)
651                        .build(),
652                )
653                .expect("new_instance succeeds");
654
655            let mut parent = ContextHandle::new();
656            unsafe {
657                let heap_ptr = inst.alloc_mut().heap_mut().as_ptr() as *mut c_void;
658                let mut child = ContextHandle::create_and_init(
659                    inst.alloc_mut().stack_u64_mut(),
660                    stack_pattern_child as usize,
661                    &[Val::CPtr(heap_ptr)],
662                )
663                .expect("context init succeeds");
664                Context::swap(&mut parent, &mut child);
665
666                let stack_pattern = inst.alloc().heap_u64()[0] as usize;
667                assert!(stack_pattern > inst.alloc().slot().stack as usize);
668                assert!(
669                    stack_pattern + STACK_PATTERN_LENGTH < inst.alloc().slot().stack_top() as usize
670                );
671                let stack_pattern =
672                    std::slice::from_raw_parts(stack_pattern as *const u8, STACK_PATTERN_LENGTH);
673                for i in 0..STACK_PATTERN_LENGTH {
674                    assert_eq!(stack_pattern[i], (i % 256) as u8);
675                }
676            }
677        }
678
679        #[test]
680        fn drop_region_first() {
681            let region = TestRegion::create(1, &Limits::default()).expect("region can be created");
682            let inst = region
683                .new_instance(MockModuleBuilder::new().build())
684                .expect("new_instance succeeds");
685            drop(region);
686            drop(inst);
687        }
688
689        #[test]
690        fn slot_counts_work() {
691            let module = MockModuleBuilder::new()
692                .with_heap_spec(ONE_PAGE_HEAP)
693                .build();
694            let region = TestRegion::create(2, &LIMITS).expect("region created");
695            assert_eq!(region.capacity(), 2);
696            assert_eq!(region.free_slots(), 2);
697            assert_eq!(region.used_slots(), 0);
698            let inst1 = region
699                .new_instance(module.clone())
700                .expect("new_instance succeeds");
701            assert_eq!(region.capacity(), 2);
702            assert_eq!(region.free_slots(), 1);
703            assert_eq!(region.used_slots(), 1);
704            let inst2 = region.new_instance(module).expect("new_instance succeeds");
705            assert_eq!(region.capacity(), 2);
706            assert_eq!(region.free_slots(), 0);
707            assert_eq!(region.used_slots(), 2);
708            drop(inst1);
709            assert_eq!(region.capacity(), 2);
710            assert_eq!(region.free_slots(), 1);
711            assert_eq!(region.used_slots(), 1);
712            drop(inst2);
713            assert_eq!(region.capacity(), 2);
714            assert_eq!(region.free_slots(), 2);
715            assert_eq!(region.used_slots(), 0);
716        }
717
718        #[test]
719        fn reject_sigstack_smaller_than_min() {
720            if MINSIGSTKSZ == 0 {
721                // can't trigger the error on this platform
722                return;
723            }
724            let limits = Limits {
725                // keep it page-aligned but make it too small
726                signal_stack_size: (MINSIGSTKSZ.checked_sub(1).unwrap() / host_page_size())
727                    * host_page_size(),
728                ..Limits::default()
729            };
730            let res = TestRegion::create(1, &limits);
731            match res {
732                Err(Error::InvalidArgument(
733                    "signal stack size must be at least MINSIGSTKSZ (defined in <signal.h>)",
734                )) => (),
735                Err(e) => panic!("unexpected error: {}", e),
736                Ok(_) => panic!("unexpected success"),
737            }
738        }
739
740        /// This test ensures that a signal stack smaller than 12KiB is rejected when Lucet is
741        /// compiled in debug mode.
742        #[test]
743        #[cfg(debug_assertions)]
744        fn reject_debug_sigstack_smaller_than_12kib() {
745            if 8192 < MINSIGSTKSZ {
746                // can't trigger the error on this platform, as the MINSIGSTKSZ check runs first
747                return;
748            }
749            let limits = Limits {
750                signal_stack_size: 8192,
751                ..Limits::default()
752            };
753            let res = TestRegion::create(1, &limits);
754            match res {
755                Err(Error::InvalidArgument(
756                    "signal stack size must be at least 12KiB for debug builds",
757                )) => (),
758                Err(e) => panic!("unexpected error: {}", e),
759                Ok(_) => panic!("unexpected success"),
760            }
761        }
762
763        #[test]
764        fn reject_unaligned_sigstack() {
765            let limits = Limits {
766                signal_stack_size: std::cmp::max(libc::SIGSTKSZ, 12 * 1024)
767                    .checked_add(1)
768                    .unwrap(),
769                ..Limits::default()
770            };
771            let res = TestRegion::create(1, &limits);
772            match res {
773                Err(Error::InvalidArgument(
774                    "signal stack size must be a multiple of host page size",
775                )) => (),
776                Err(e) => panic!("unexpected error: {}", e),
777                Ok(_) => panic!("unexpected success"),
778            }
779        }
780    };
781}
782
783#[cfg(test)]
784alloc_tests!(crate::region::mmap::MmapRegion);