1use 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 pub(super) heap_size: usize,
84 init_data_size: usize,
85
86 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 pub(crate) peb_address: usize,
100 code_size: usize,
101 guest_code_offset: usize,
103 pub(crate) init_data_permissions: Option<MemoryRegionFlags>,
104
105 scratch_size: usize,
108 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 const MAX_MEMORY_SIZE: usize = (16 * 1024 * 1024 * 1024) - Self::BASE_ADDRESS; pub(crate) const BASE_ADDRESS: usize = 0x1000;
177
178 #[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 pub(crate) const STACK_POINTER_SIZE_BYTES: u64 = 8;
193
194 #[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 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 let peb_address = Self::BASE_ADDRESS + peb_offset;
228 let guest_heap_buffer_offset =
231 (peb_offset + size_of::<HyperlightPEB>()).next_multiple_of(PAGE_SIZE_USIZE);
232
233 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 #[instrument(skip_all, parent = Span::current(), level= "Trace")]
261 pub(super) fn get_output_data_size_offset(&self) -> usize {
262 self.peb_output_data_offset
264 }
265
266 #[instrument(skip_all, parent = Span::current(), level= "Trace")]
268 pub(super) fn get_init_data_size_offset(&self) -> usize {
269 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 #[instrument(skip_all, parent = Span::current(), level= "Trace")]
280 fn get_output_data_pointer_offset(&self) -> usize {
281 self.get_output_data_size_offset() + size_of::<u64>()
284 }
285
286 #[instrument(skip_all, parent = Span::current(), level= "Trace")]
288 pub(super) fn get_init_data_pointer_offset(&self) -> usize {
289 self.get_init_data_size_offset() + size_of::<u64>()
292 }
293
294 #[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 #[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 #[instrument(skip_all, parent = Span::current(), level= "Trace")]
310 pub(super) fn get_input_data_size_offset(&self) -> usize {
311 self.peb_input_data_offset
313 }
314
315 #[instrument(skip_all, parent = Span::current(), level= "Trace")]
317 fn get_input_data_pointer_offset(&self) -> usize {
318 self.get_input_data_size_offset() + size_of::<u64>()
321 }
322
323 #[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 #[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 #[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 #[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 #[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 #[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 #[instrument(skip_all, parent = Span::current(), level= "Trace")]
402 fn get_heap_pointer_offset(&self) -> usize {
403 self.get_heap_size_offset() + size_of::<u64>()
406 }
407
408 #[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 #[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 #[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 #[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 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 #[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 #[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 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 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 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 #[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 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 #[instrument(err(Debug), skip_all, parent = Span::current(), level= "Trace")]
579 pub(crate) fn write_peb(&self, mem: &mut [u8]) -> Result<()> {
580 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 macro_rules! get_gva {
603 ($something:ident) => {
604 Self::kernel_base_va() + (self.$something as u64)
605 };
606 }
607
608 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 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 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 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 Ok(())
662 }
663}
664
665#[cfg(test)]
666mod tests {
667 use nub_host_common::mem::PAGE_SIZE_USIZE;
668
669 use super::*;
670
671 fn get_expected_memory_size(layout: &SandboxMemoryLayout) -> usize {
673 let mut expected_size = 0;
674 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 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}