Skip to main content

nub_host_kvm/mem/
layout.rs

1/*
2Copyright 2025  The Hyperlight Authors.
3
4Licensed under the Apache License, Version 2.0 (the "License");
5you may not use this file except in compliance with the License.
6You may obtain a copy of the License at
7
8    http://www.apache.org/licenses/LICENSE-2.0
9
10Unless required by applicable law or agreed to in writing, software
11distributed under the License is distributed on an "AS IS" BASIS,
12WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13See the License for the specific language governing permissions and
14limitations under the License.
15 */
16//! This module describes the virtual and physical addresses of a
17//! number of special regions in the guest VM, although we hope
18//! to reduce the number of these over time.
19//!
20//! A snapshot freshly created from an empty VM will result in roughly
21//! the following physical layout:
22//!
23//! +-------------------------------------------+
24//! |             Guest Page Tables             |
25//! +-------------------------------------------+
26//! |              Init Data                    | (GuestBlob size)
27//! +-------------------------------------------+
28//! |             Guest Heap                    |
29//! +-------------------------------------------+
30//! |                PEB Struct                 | (HyperlightPEB size)
31//! +-------------------------------------------+
32//! |               Guest Code                  |
33//! +-------------------------------------------+ 0x1_000
34//! |              NULL guard page              |
35//! +-------------------------------------------+ 0x0_000
36//!
37//! Everything except for the guest page tables is currently
38//! identity-mapped; the guest page tables themselves are mapped at
39//! `nub_host_common::layout::SNAPSHOT_PT_GVA_MIN` =
40//! 0xffff_8000_0000_0000.
41//!
42//! - `InitData` - some extra data that can be loaded onto the sandbox during
43//!   initialization.
44//!
45//! - `GuestHeap` - this is a buffer that is used for heap data in the guest. the length
46//!   of this field is returned by the `heap_size()` method of this struct
47//!
48//! There is also a scratch region at the top of physical memory,
49//! which is mostly laid out as a large undifferentiated blob of
50//! memory, although at present the snapshot process specially
51//! privileges the statically allocated input and output data regions:
52//!
53//! +-------------------------------------------+ (top of physical memory)
54//! |         Exception Stack, Metadata         |
55//! +-------------------------------------------+ (1 page below)
56//! |              Scratch Memory               |
57//! +-------------------------------------------+
58//! |                Output Data                |
59//! +-------------------------------------------+
60//! |                Input Data                 |
61//! +-------------------------------------------+ (scratch size)
62
63use std::fmt::Debug;
64use std::mem::{offset_of, size_of};
65
66use nub_arch_x86_abi::PARALLEL_INVOKE_SLOT_BYTES;
67use nub_host_common::mem::{HyperlightPEB, PAGE_SIZE_USIZE};
68use tracing::{Span, instrument};
69
70use super::memory_region::MemoryRegionType::{Code, Heap, InitData, Peb};
71use super::memory_region::{
72    DEFAULT_GUEST_BLOB_MEM_FLAGS, MemoryRegion_, MemoryRegionFlags, MemoryRegionKind,
73    MemoryRegionVecBuilder,
74};
75use crate::error::HyperlightError::{MemoryRequestTooBig, MemoryRequestTooSmall};
76use crate::sandbox::SandboxConfiguration;
77use crate::{Result, new_error};
78
79#[derive(Copy, Clone)]
80pub(crate) struct SandboxMemoryLayout {
81    pub(super) sandbox_memory_config: SandboxConfiguration,
82    /// The heap size of this sandbox.
83    pub(super) heap_size: usize,
84    init_data_size: usize,
85
86    /// The following fields are offsets to the actual PEB struct fields.
87    /// They are used when writing the PEB struct itself
88    peb_offset: usize,
89    peb_input_data_offset: usize,
90    peb_output_data_offset: usize,
91    peb_init_data_offset: usize,
92    peb_heap_data_offset: usize,
93
94    guest_heap_buffer_offset: usize,
95    init_data_offset: usize,
96    pt_size: Option<usize>,
97
98    // other
99    pub(crate) peb_address: usize,
100    code_size: usize,
101    // The offset in the sandbox memory where the code starts
102    guest_code_offset: usize,
103    pub(crate) init_data_permissions: Option<MemoryRegionFlags>,
104
105    // The size of the scratch region in physical memory; note that
106    // this will appear under the top of physical memory.
107    scratch_size: usize,
108    // The guest-visible size of the snapshot region in physical
109    // memory. After compaction this may be smaller than the full
110    // snapshot blob (which also contains a PT tail that is only
111    // host-accessible).
112    snapshot_size: usize,
113}
114
115impl Debug for SandboxMemoryLayout {
116    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
117        let mut ff = f.debug_struct("SandboxMemoryLayout");
118        ff.field(
119            "Total Memory Size",
120            &format_args!("{:#x}", self.get_memory_size().unwrap_or(0)),
121        )
122        .field("Heap Size", &format_args!("{:#x}", self.heap_size))
123        .field(
124            "Init Data Size",
125            &format_args!("{:#x}", self.init_data_size),
126        )
127        .field("PEB Address", &format_args!("{:#x}", self.peb_address))
128        .field("PEB Offset", &format_args!("{:#x}", self.peb_offset))
129        .field("Code Size", &format_args!("{:#x}", self.code_size))
130        .field(
131            "Input Data Offset",
132            &format_args!("{:#x}", self.peb_input_data_offset),
133        )
134        .field(
135            "Output Data Offset",
136            &format_args!("{:#x}", self.peb_output_data_offset),
137        )
138        .field(
139            "Init Data Offset",
140            &format_args!("{:#x}", self.peb_init_data_offset),
141        )
142        .field(
143            "Guest Heap Offset",
144            &format_args!("{:#x}", self.peb_heap_data_offset),
145        );
146        ff.field(
147            "Guest Heap Buffer Offset",
148            &format_args!("{:#x}", self.guest_heap_buffer_offset),
149        )
150        .field(
151            "Init Data Offset",
152            &format_args!("{:#x}", self.init_data_offset),
153        )
154        .field("PT Size", &format_args!("{:#x}", self.pt_size.unwrap_or(0)))
155        .field(
156            "Guest Code Offset",
157            &format_args!("{:#x}", self.guest_code_offset),
158        )
159        .field(
160            "Scratch region size",
161            &format_args!("{:#x}", self.scratch_size),
162        )
163        .finish()
164    }
165}
166
167impl SandboxMemoryLayout {
168    /// The maximum amount of memory a single sandbox will be allowed.
169    ///
170    /// Both the scratch region and the snapshot region are bounded by
171    /// this size. The value is arbitrary but chosen to be large enough
172    /// for most workloads while preventing accidental resource exhaustion.
173    const MAX_MEMORY_SIZE: usize = (16 * 1024 * 1024 * 1024) - Self::BASE_ADDRESS; // 16 GiB - BASE_ADDRESS
174
175    /// The base address of the sandbox's memory.
176    pub(crate) const BASE_ADDRESS: usize = 0x1000;
177
178    /// Virtual-address base where the kernel is mapped. Resolved at
179    /// runtime so the per-process `guest_va_base()` (env-overridable
180    /// on Linux) is the single source of truth.
181    ///
182    /// `kernel_gva = kernel_base_va() + (gpa - BASE_ADDRESS)`.
183    ///
184    /// Lives in canonical low-half so the host process can
185    /// mmap-shadow this region at the same VA.
186    #[inline]
187    pub(crate) fn kernel_base_va() -> u64 {
188        nub_host_common::layout::guest_va_base() + nub_host_common::layout::KERNEL_OFFSET
189    }
190
191    // the offset into a sandbox's input/output buffer where the stack starts
192    pub(crate) const STACK_POINTER_SIZE_BYTES: u64 = 8;
193
194    /// Create a new `SandboxMemoryLayout` with the given
195    /// `SandboxConfiguration`, code size and stack/heap size.
196    #[instrument(err(Debug), skip_all, parent = Span::current(), level= "Trace")]
197    pub(crate) fn new(
198        cfg: SandboxConfiguration,
199        code_size: usize,
200        init_data_size: usize,
201        init_data_permissions: Option<MemoryRegionFlags>,
202    ) -> Result<Self> {
203        let heap_size = usize::try_from(cfg.get_heap_size())?;
204        let scratch_size = cfg.get_scratch_size();
205        if scratch_size > Self::MAX_MEMORY_SIZE {
206            return Err(MemoryRequestTooBig(scratch_size, Self::MAX_MEMORY_SIZE));
207        }
208        let min_scratch_size = nub_host_common::layout::min_scratch_size(
209            cfg.get_input_data_size(),
210            cfg.get_output_data_size(),
211        ) + Self::parallel_invoke_slots_size(cfg.get_vcpu_count())
212            + Self::exception_stack_size(cfg.get_vcpu_count());
213        if scratch_size < min_scratch_size {
214            return Err(MemoryRequestTooSmall(scratch_size, min_scratch_size));
215        }
216
217        let guest_code_offset = 0;
218        // The following offsets are to the fields of the PEB struct itself!
219        let peb_offset = code_size.next_multiple_of(PAGE_SIZE_USIZE);
220        let peb_input_data_offset = peb_offset + offset_of!(HyperlightPEB, input_stack);
221        let peb_output_data_offset = peb_offset + offset_of!(HyperlightPEB, output_stack);
222        let peb_init_data_offset = peb_offset + offset_of!(HyperlightPEB, init_data);
223        let peb_heap_data_offset = peb_offset + offset_of!(HyperlightPEB, guest_heap);
224
225        // The following offsets are the actual values that relate to memory layout,
226        // which are written to PEB struct
227        let peb_address = Self::BASE_ADDRESS + peb_offset;
228        // make sure heap buffer starts at 4K boundary.
229        // The heap starts at the next page boundary after the PEB struct.
230        let guest_heap_buffer_offset =
231            (peb_offset + size_of::<HyperlightPEB>()).next_multiple_of(PAGE_SIZE_USIZE);
232
233        // make sure init data starts at 4K boundary
234        let init_data_offset =
235            (guest_heap_buffer_offset + heap_size).next_multiple_of(PAGE_SIZE_USIZE);
236        let mut ret = Self {
237            peb_offset,
238            heap_size,
239            peb_input_data_offset,
240            peb_output_data_offset,
241            peb_init_data_offset,
242            peb_heap_data_offset,
243            sandbox_memory_config: cfg,
244            code_size,
245            guest_heap_buffer_offset,
246            peb_address,
247            guest_code_offset,
248            init_data_offset,
249            init_data_size,
250            init_data_permissions,
251            pt_size: None,
252            scratch_size,
253            snapshot_size: 0,
254        };
255        ret.set_snapshot_size(ret.get_memory_size()?);
256        Ok(ret)
257    }
258
259    /// Get the offset in guest memory to the output data size
260    #[instrument(skip_all, parent = Span::current(), level= "Trace")]
261    pub(super) fn get_output_data_size_offset(&self) -> usize {
262        // The size field is the first field in the `OutputData` struct
263        self.peb_output_data_offset
264    }
265
266    /// Get the offset in guest memory to the init data size
267    #[instrument(skip_all, parent = Span::current(), level= "Trace")]
268    pub(super) fn get_init_data_size_offset(&self) -> usize {
269        // The init data size is the first field in the `GuestMemoryRegion` struct
270        self.peb_init_data_offset
271    }
272
273    #[instrument(skip_all, parent = Span::current(), level= "Trace")]
274    pub(crate) fn get_scratch_size(&self) -> usize {
275        self.scratch_size
276    }
277
278    /// Get the offset in guest memory to the output data pointer.
279    #[instrument(skip_all, parent = Span::current(), level= "Trace")]
280    fn get_output_data_pointer_offset(&self) -> usize {
281        // This field is immediately after the output data size field,
282        // which is a `u64`.
283        self.get_output_data_size_offset() + size_of::<u64>()
284    }
285
286    /// Get the offset in guest memory to the init data pointer.
287    #[instrument(skip_all, parent = Span::current(), level= "Trace")]
288    pub(super) fn get_init_data_pointer_offset(&self) -> usize {
289        // The init data pointer is immediately after the init data size field,
290        // which is a `u64`.
291        self.get_init_data_size_offset() + size_of::<u64>()
292    }
293
294    /// Get the guest virtual address of the start of output data.
295    #[instrument(skip_all, parent = Span::current(), level= "Trace")]
296    pub(crate) fn get_output_data_buffer_gva(&self) -> u64 {
297        nub_host_common::layout::scratch_base_gva(self.scratch_size)
298            + self.sandbox_memory_config.get_input_data_size() as u64
299    }
300
301    /// Get the offset into the host scratch buffer of the start of
302    /// the output data.
303    #[instrument(skip_all, parent = Span::current(), level= "Trace")]
304    pub(crate) fn get_output_data_buffer_scratch_host_offset(&self) -> usize {
305        self.sandbox_memory_config.get_input_data_size()
306    }
307
308    /// Get the offset in guest memory to the input data size.
309    #[instrument(skip_all, parent = Span::current(), level= "Trace")]
310    pub(super) fn get_input_data_size_offset(&self) -> usize {
311        // The input data size is the first field in the input stack's `GuestMemoryRegion` struct
312        self.peb_input_data_offset
313    }
314
315    /// Get the offset in guest memory to the input data pointer.
316    #[instrument(skip_all, parent = Span::current(), level= "Trace")]
317    fn get_input_data_pointer_offset(&self) -> usize {
318        // The input data pointer is immediately after the input
319        // data size field in the input data `GuestMemoryRegion` struct which is a `u64`.
320        self.get_input_data_size_offset() + size_of::<u64>()
321    }
322
323    /// Get the guest virtual address of the start of input data
324    #[instrument(skip_all, parent = Span::current(), level= "Trace")]
325    fn get_input_data_buffer_gva(&self) -> u64 {
326        nub_host_common::layout::scratch_base_gva(self.scratch_size)
327    }
328
329    /// Get the offset into the host scratch buffer of the start of
330    /// the input data
331    #[instrument(skip_all, parent = Span::current(), level= "Trace")]
332    pub(crate) fn get_input_data_buffer_scratch_host_offset(&self) -> usize {
333        0
334    }
335
336    pub(crate) fn parallel_invoke_slots_size(vcpu_count: usize) -> usize {
337        (vcpu_count.max(1) * PARALLEL_INVOKE_SLOT_BYTES)
338            .next_multiple_of(nub_host_common::vmem::PAGE_SIZE)
339    }
340
341    pub(crate) fn exception_stack_size(vcpu_count: usize) -> usize {
342        (vcpu_count.max(1) as u64 * nub_host_common::layout::VCPU_EXCEPTION_STACK_STRIDE) as usize
343    }
344
345    pub(crate) fn get_parallel_invoke_slots_size(&self) -> usize {
346        Self::parallel_invoke_slots_size(self.sandbox_memory_config.get_vcpu_count())
347    }
348
349    pub(crate) fn get_vcpu_count(&self) -> usize {
350        self.sandbox_memory_config.get_vcpu_count()
351    }
352
353    pub(crate) fn get_parallel_invoke_slots_scratch_host_offset(&self) -> usize {
354        self.sandbox_memory_config.get_input_data_size()
355            + self.sandbox_memory_config.get_output_data_size()
356    }
357
358    #[allow(dead_code)]
359    pub(crate) fn get_parallel_invoke_slot_scratch_host_offset(&self, lane: usize) -> usize {
360        self.get_parallel_invoke_slots_scratch_host_offset() + lane * PARALLEL_INVOKE_SLOT_BYTES
361    }
362
363    #[allow(dead_code)]
364    pub(crate) fn get_parallel_invoke_slots_gva(&self) -> u64 {
365        nub_host_common::layout::scratch_base_gva(self.scratch_size)
366            + self.get_parallel_invoke_slots_scratch_host_offset() as u64
367    }
368
369    /// Get the offset from the beginning of the scratch region to the
370    /// location where page tables will be eagerly copied on restore
371    #[instrument(skip_all, parent = Span::current(), level= "Trace")]
372    pub(crate) fn get_pt_base_scratch_offset(&self) -> usize {
373        (self.sandbox_memory_config.get_input_data_size()
374            + self.sandbox_memory_config.get_output_data_size()
375            + self.get_parallel_invoke_slots_size())
376        .next_multiple_of(nub_host_common::vmem::PAGE_SIZE)
377    }
378
379    /// Get the base GPA to which the page tables will be eagerly
380    /// copied on restore
381    #[instrument(skip_all, parent = Span::current(), level= "Trace")]
382    pub(crate) fn get_pt_base_gpa(&self) -> u64 {
383        nub_host_common::layout::scratch_base_gpa(self.scratch_size)
384            + self.get_pt_base_scratch_offset() as u64
385    }
386
387    /// Get the first GPA of the scratch region that the host hasn't
388    /// used for something else
389    #[instrument(skip_all, parent = Span::current(), level= "Trace")]
390    pub(crate) fn get_first_free_scratch_gpa(&self) -> u64 {
391        self.get_pt_base_gpa() + self.pt_size.unwrap_or(0) as u64
392    }
393
394    /// Get the offset in guest memory to the heap size
395    #[instrument(skip_all, parent = Span::current(), level= "Trace")]
396    fn get_heap_size_offset(&self) -> usize {
397        self.peb_heap_data_offset
398    }
399
400    /// Get the offset of the heap pointer in guest memory,
401    #[instrument(skip_all, parent = Span::current(), level= "Trace")]
402    fn get_heap_pointer_offset(&self) -> usize {
403        // The heap pointer is immediately after the
404        // heap size field in the guest heap's `GuestMemoryRegion` struct which is a `u64`.
405        self.get_heap_size_offset() + size_of::<u64>()
406    }
407
408    /// Get the total size of guest memory in `self`'s memory
409    /// layout.
410    #[instrument(skip_all, parent = Span::current(), level= "Trace")]
411    fn get_unaligned_memory_size(&self) -> usize {
412        self.init_data_offset + self.init_data_size
413    }
414
415    /// get the code offset
416    /// This is the offset in the sandbox memory where the code starts
417    #[instrument(skip_all, parent = Span::current(), level= "Trace")]
418    pub(crate) fn get_guest_code_offset(&self) -> usize {
419        self.guest_code_offset
420    }
421
422    /// Get the guest address of the code section in the sandbox
423    #[instrument(skip_all, parent = Span::current(), level= "Trace")]
424    pub(crate) fn get_guest_code_address(&self) -> usize {
425        Self::BASE_ADDRESS + self.guest_code_offset
426    }
427
428    /// Get the total size of guest memory in `self`'s memory
429    /// layout aligned to page size boundaries.
430    #[instrument(skip_all, parent = Span::current(), level= "Trace")]
431    pub(crate) fn get_memory_size(&self) -> Result<usize> {
432        let total_memory = self.get_unaligned_memory_size();
433
434        // Size should be a multiple of page size.
435        let remainder = total_memory % PAGE_SIZE_USIZE;
436        let multiples = total_memory / PAGE_SIZE_USIZE;
437        let size = match remainder {
438            0 => total_memory,
439            _ => (multiples + 1) * PAGE_SIZE_USIZE,
440        };
441
442        if size > Self::MAX_MEMORY_SIZE {
443            Err(MemoryRequestTooBig(size, Self::MAX_MEMORY_SIZE))
444        } else {
445            Ok(size)
446        }
447    }
448
449    /// Sets the size of the memory region used for page tables
450    #[instrument(skip_all, parent = Span::current(), level= "Trace")]
451    pub(crate) fn set_pt_size(&mut self, size: usize) -> Result<()> {
452        let min_fixed_scratch = nub_host_common::layout::min_scratch_size(
453            self.sandbox_memory_config.get_input_data_size(),
454            self.sandbox_memory_config.get_output_data_size(),
455        ) + self.get_parallel_invoke_slots_size()
456            + Self::exception_stack_size(self.sandbox_memory_config.get_vcpu_count());
457        let min_scratch = min_fixed_scratch + size;
458        if self.scratch_size < min_scratch {
459            return Err(MemoryRequestTooSmall(self.scratch_size, min_scratch));
460        }
461        let old_pt_size = self.pt_size.unwrap_or(0);
462        self.snapshot_size = self.snapshot_size - old_pt_size + size;
463        self.pt_size = Some(size);
464        Ok(())
465    }
466
467    #[instrument(skip_all, parent = Span::current(), level= "Trace")]
468    pub(crate) fn set_snapshot_size(&mut self, new_size: usize) {
469        self.snapshot_size = new_size;
470    }
471
472    /// Get the size of the memory region used for page tables
473    #[instrument(skip_all, parent = Span::current(), level= "Trace")]
474    pub(crate) fn get_pt_size(&self) -> usize {
475        self.pt_size.unwrap_or(0)
476    }
477
478    /// Returns the memory regions associated with this memory layout,
479    /// suitable for passing to a hypervisor for mapping into memory
480    pub(crate) fn get_memory_regions_<K: MemoryRegionKind>(
481        &self,
482        host_base: K::HostBaseType,
483    ) -> Result<Vec<MemoryRegion_<K>>> {
484        let mut builder = MemoryRegionVecBuilder::new(Self::BASE_ADDRESS, host_base);
485
486        // code
487        let peb_offset = builder.push_page_aligned(
488            self.code_size,
489            MemoryRegionFlags::READ | MemoryRegionFlags::WRITE | MemoryRegionFlags::EXECUTE,
490            Code,
491        );
492
493        let expected_peb_offset = TryInto::<usize>::try_into(self.peb_offset)?;
494
495        if peb_offset != expected_peb_offset {
496            return Err(new_error!(
497                "PEB offset does not match expected PEB offset expected:  {}, actual:  {}",
498                expected_peb_offset,
499                peb_offset
500            ));
501        }
502
503        // PEB
504        let heap_offset =
505            builder.push_page_aligned(size_of::<HyperlightPEB>(), MemoryRegionFlags::READ, Peb);
506
507        let expected_heap_offset = TryInto::<usize>::try_into(self.guest_heap_buffer_offset)?;
508
509        if heap_offset != expected_heap_offset {
510            return Err(new_error!(
511                "Guest Heap offset does not match expected Guest Heap offset expected:  {}, actual:  {}",
512                expected_heap_offset,
513                heap_offset
514            ));
515        }
516
517        // heap
518        #[cfg(feature = "executable_heap")]
519        let init_data_offset = builder.push_page_aligned(
520            self.heap_size,
521            MemoryRegionFlags::READ | MemoryRegionFlags::WRITE | MemoryRegionFlags::EXECUTE,
522            Heap,
523        );
524        #[cfg(not(feature = "executable_heap"))]
525        let init_data_offset = builder.push_page_aligned(
526            self.heap_size,
527            MemoryRegionFlags::READ | MemoryRegionFlags::WRITE,
528            Heap,
529        );
530
531        let expected_init_data_offset = TryInto::<usize>::try_into(self.init_data_offset)?;
532
533        if init_data_offset != expected_init_data_offset {
534            return Err(new_error!(
535                "Init Data offset does not match expected Init Data offset expected:  {}, actual:  {}",
536                expected_init_data_offset,
537                init_data_offset
538            ));
539        }
540
541        // init data
542        let after_init_offset = if self.init_data_size > 0 {
543            let mem_flags = self
544                .init_data_permissions
545                .unwrap_or(DEFAULT_GUEST_BLOB_MEM_FLAGS);
546            builder.push_page_aligned(self.init_data_size, mem_flags, InitData)
547        } else {
548            init_data_offset
549        };
550
551        let final_offset = after_init_offset;
552
553        let expected_final_offset = TryInto::<usize>::try_into(self.get_memory_size()?)?;
554
555        if final_offset != expected_final_offset {
556            return Err(new_error!(
557                "Final offset does not match expected Final offset expected:  {}, actual:  {}",
558                expected_final_offset,
559                final_offset
560            ));
561        }
562
563        Ok(builder.build())
564    }
565
566    #[instrument(err(Debug), skip_all, parent = Span::current(), level= "Trace")]
567    pub(crate) fn write_init_data(&self, out: &mut [u8], bytes: &[u8]) -> Result<()> {
568        out[self.init_data_offset..self.init_data_offset + self.init_data_size]
569            .copy_from_slice(bytes);
570        Ok(())
571    }
572
573    /// Write the finished memory layout to `mem` and return `Ok` if
574    /// successful.
575    ///
576    /// Note: `mem` may have been modified, even if `Err` was returned
577    /// from this function.
578    #[instrument(err(Debug), skip_all, parent = Span::current(), level= "Trace")]
579    pub(crate) fn write_peb(&self, mem: &mut [u8]) -> Result<()> {
580        // (No `guest_offset` here — Stage F kernel relocation replaced
581        // the legacy `get_address!` macro with `get_gva!` for the
582        // kernel-half PEB pointers, and the input/output buffer
583        // pointers compute their GVAs via `get_*_gva()`.)
584
585        fn write_u64(mem: &mut [u8], offset: usize, value: u64) -> Result<()> {
586            if offset + 8 > mem.len() {
587                return Err(new_error!(
588                    "Cannot write to offset {} in slice of len {}",
589                    offset,
590                    mem.len()
591                ));
592            }
593            mem[offset..offset + 8].copy_from_slice(&u64::to_ne_bytes(value));
594            Ok(())
595        }
596
597        // Returns the kernel-half GVA for a PEB-pointer field. Used for
598        // `init_data.ptr` / `guest_heap.ptr` — the guest dereferences
599        // these and expects high-VA pointers after Stage F kernel
600        // relocation. The legacy `get_address!` macro (GPA-returning)
601        // is gone with the file-mapping API.
602        macro_rules! get_gva {
603            ($something:ident) => {
604                Self::kernel_base_va() + (self.$something as u64)
605            };
606        }
607
608        // Start of setting up the PEB. The following are in the order of the PEB fields
609
610        // Set up input buffer pointer
611        write_u64(
612            mem,
613            self.get_input_data_size_offset(),
614            self.sandbox_memory_config
615                .get_input_data_size()
616                .try_into()?,
617        )?;
618        write_u64(
619            mem,
620            self.get_input_data_pointer_offset(),
621            self.get_input_data_buffer_gva(),
622        )?;
623
624        // Set up output buffer pointer
625        write_u64(
626            mem,
627            self.get_output_data_size_offset(),
628            self.sandbox_memory_config
629                .get_output_data_size()
630                .try_into()?,
631        )?;
632        write_u64(
633            mem,
634            self.get_output_data_pointer_offset(),
635            self.get_output_data_buffer_gva(),
636        )?;
637
638        // Set up init data pointer (high GVA — guest dereferences via
639        // the kernel-half mapping).
640        write_u64(
641            mem,
642            self.get_init_data_size_offset(),
643            (self.get_unaligned_memory_size() - self.init_data_offset).try_into()?,
644        )?;
645        let addr = get_gva!(init_data_offset);
646        write_u64(mem, self.get_init_data_pointer_offset(), addr)?;
647
648        // Set up heap buffer pointer (high GVA — talc's `claim` reads
649        // this directly from `(*peb).guest_heap.ptr`).
650        let addr = get_gva!(guest_heap_buffer_offset);
651        write_u64(mem, self.get_heap_size_offset(), self.heap_size.try_into()?)?;
652        write_u64(mem, self.get_heap_pointer_offset(), addr)?;
653
654        // End of setting up the PEB
655
656        // The input and output data regions do not have their layout
657        // initialised here, because they are in the scratch
658        // region---they are instead set in
659        // [`SandboxMemoryManager::update_scratch_bookkeeping`].
660
661        Ok(())
662    }
663}
664
665#[cfg(test)]
666mod tests {
667    use nub_host_common::mem::PAGE_SIZE_USIZE;
668
669    use super::*;
670
671    // helper func for testing
672    fn get_expected_memory_size(layout: &SandboxMemoryLayout) -> usize {
673        let mut expected_size = 0;
674        // in order of layout
675        expected_size += layout.code_size;
676
677        let peb_and_array = size_of::<HyperlightPEB>();
678        expected_size += peb_and_array.next_multiple_of(PAGE_SIZE_USIZE);
679
680        expected_size += layout.heap_size.next_multiple_of(PAGE_SIZE_USIZE);
681
682        expected_size
683    }
684
685    #[test]
686    fn test_get_memory_size() {
687        let sbox_cfg = SandboxConfiguration::default();
688        let sbox_mem_layout = SandboxMemoryLayout::new(sbox_cfg, 4096, 0, None).unwrap();
689        assert_eq!(
690            sbox_mem_layout.get_memory_size().unwrap(),
691            get_expected_memory_size(&sbox_mem_layout)
692        );
693    }
694
695    #[test]
696    fn test_max_memory_sandbox() {
697        let mut cfg = SandboxConfiguration::default();
698        // scratch_size exceeds 16 GiB limit
699        cfg.set_scratch_size(17 * 1024 * 1024 * 1024);
700        cfg.set_input_data_size(16 * 1024 * 1024 * 1024);
701        let layout = SandboxMemoryLayout::new(cfg, 4096, 4096, None);
702        assert!(matches!(layout.unwrap_err(), MemoryRequestTooBig(..)));
703    }
704}