1use hyperlight_common::flatbuffer_wrappers::guest_log_data::GuestLogData;
17use nub_arch_x86_abi::ParallelInvokeSlot;
18use nub_host_common::vmem::{self, PAGE_TABLE_SIZE};
19use std::mem::{align_of, offset_of, size_of};
20use tracing::{Span, instrument};
21
22use super::layout::SandboxMemoryLayout;
23use super::shared_mem::{
24 ExclusiveSharedMemory, GuestSharedMemory, HostSharedMemory, ReadonlySharedMemory, SharedMemory,
25};
26use crate::Result;
27use crate::sandbox::snapshot::{NextAction, Snapshot};
28
29mod unused_hack {
35 use crate::mem::shared_mem::ReadonlySharedMemory;
36 use crate::mem::shared_mem::SharedMemory;
37 pub trait SnapshotSharedMemoryT {
38 type T<S: SharedMemory>;
39 }
40 pub struct SnapshotSharedMemory_;
41 impl SnapshotSharedMemoryT for SnapshotSharedMemory_ {
42 type T<S: SharedMemory> = ReadonlySharedMemory;
43 }
44 pub type SnapshotSharedMemory<S> = <SnapshotSharedMemory_ as SnapshotSharedMemoryT>::T<S>;
45}
46impl ReadonlySharedMemory {
47 pub(crate) fn to_mgr_snapshot_mem(
48 &self,
49 ) -> Result<SnapshotSharedMemory<ExclusiveSharedMemory>> {
50 let ret = self.clone();
51 Ok(ret)
52 }
53}
54pub(crate) use unused_hack::SnapshotSharedMemory;
55#[derive(Clone)]
58pub(crate) struct SandboxMemoryManager<S: SharedMemory> {
59 pub(crate) shared_mem: SnapshotSharedMemory<S>,
61 pub(crate) scratch_mem: S,
63 pub(crate) layout: SandboxMemoryLayout,
65 pub(crate) entrypoint: NextAction,
67 pub(crate) mapped_rgns: u64,
69 pub(crate) abort_buffer: Vec<u8>,
71 pub(crate) snapshot_count: u64,
77}
78
79pub(crate) struct GuestPageTableBuffer {
83 buffer: std::cell::RefCell<Vec<u8>>,
84 phys_base: usize,
85 root: std::cell::Cell<u64>,
90}
91
92impl vmem::TableReadOps for GuestPageTableBuffer {
93 type TableAddr = u64;
94
95 fn entry_addr(addr: u64, offset: u64) -> u64 {
96 addr + offset
97 }
98
99 unsafe fn read_entry(&self, addr: u64) -> vmem::PageTableEntry {
100 let buffer = self.buffer.borrow();
101 let byte_offset = addr as usize - self.phys_base;
102 let pte_size = core::mem::size_of::<vmem::PageTableEntry>();
103 let Some(bytes) = buffer.get(byte_offset..byte_offset + pte_size) else {
104 return 0;
105 };
106 let mut buf = [0u8; 8];
107 buf[..pte_size].copy_from_slice(bytes);
108 vmem::PageTableEntry::from_le_bytes(buf[..pte_size].try_into().unwrap_or_default())
109 }
110
111 fn to_phys(addr: u64) -> vmem::PhysAddr {
112 addr as vmem::PhysAddr
113 }
114
115 fn from_phys(addr: vmem::PhysAddr) -> u64 {
116 #[allow(clippy::unnecessary_cast)]
117 {
118 addr as u64
119 }
120 }
121
122 fn root_table(&self) -> u64 {
123 self.root.get()
124 }
125}
126
127impl vmem::TableOps for GuestPageTableBuffer {
128 type TableMovability = vmem::MayNotMoveTable;
129
130 unsafe fn alloc_table(&self) -> u64 {
131 let mut b = self.buffer.borrow_mut();
132 let offset = b.len();
133 b.resize(offset + PAGE_TABLE_SIZE, 0);
134 (self.phys_base + offset) as u64
135 }
136
137 unsafe fn write_entry(&self, addr: u64, entry: vmem::PageTableEntry) -> Option<vmem::Void> {
138 let mut b = self.buffer.borrow_mut();
139 let byte_offset = addr as usize - self.phys_base;
140 let pte_size = core::mem::size_of::<vmem::PageTableEntry>();
141 if let Some(slice) = b.get_mut(byte_offset..byte_offset + pte_size) {
142 slice.copy_from_slice(&entry.to_le_bytes()[..pte_size]);
143 }
144 None
145 }
146
147 unsafe fn update_root(&self, impossible: vmem::Void) {
148 match impossible {}
149 }
150}
151
152impl core::convert::AsRef<GuestPageTableBuffer> for GuestPageTableBuffer {
153 fn as_ref(&self) -> &Self {
154 self
155 }
156}
157
158impl GuestPageTableBuffer {
159 pub(crate) fn new(phys_base: usize) -> Self {
163 GuestPageTableBuffer {
164 buffer: std::cell::RefCell::new(vec![0u8; PAGE_TABLE_SIZE]),
165 phys_base,
166 root: std::cell::Cell::new(phys_base as u64),
167 }
168 }
169
170 pub(crate) fn into_bytes(self) -> Box<[u8]> {
171 self.buffer.into_inner().into_boxed_slice()
172 }
173}
174
175impl<S> SandboxMemoryManager<S>
176where
177 S: SharedMemory,
178{
179 #[instrument(skip_all, parent = Span::current(), level= "Trace")]
181 pub(crate) fn new(
182 layout: SandboxMemoryLayout,
183 shared_mem: SnapshotSharedMemory<S>,
184 scratch_mem: S,
185 entrypoint: NextAction,
186 ) -> Self {
187 Self {
188 layout,
189 shared_mem,
190 scratch_mem,
191 entrypoint,
192 mapped_rgns: 0,
193 abort_buffer: Vec::new(),
194 snapshot_count: 0,
195 }
196 }
197
198 pub(crate) fn get_abort_buffer_mut(&mut self) -> &mut Vec<u8> {
200 &mut self.abort_buffer
201 }
202}
203
204impl SandboxMemoryManager<ExclusiveSharedMemory> {
205 pub(crate) fn from_snapshot(s: &Snapshot) -> Result<Self> {
206 let layout = *s.layout();
207 let shared_mem = s.memory().to_mgr_snapshot_mem()?;
208 let scratch_mem = ExclusiveSharedMemory::new(s.layout().get_scratch_size())?;
209 let entrypoint = s.entrypoint();
210 Ok(Self::new(layout, shared_mem, scratch_mem, entrypoint))
211 }
212
213 pub fn build(
224 self,
225 ) -> Result<(
226 SandboxMemoryManager<HostSharedMemory>,
227 SandboxMemoryManager<GuestSharedMemory>,
228 )> {
229 let (hshm, gshm) = self.shared_mem.build();
230 let (hscratch, gscratch) = self.scratch_mem.build();
231 let mut host_mgr = SandboxMemoryManager {
232 shared_mem: hshm,
233 scratch_mem: hscratch,
234 layout: self.layout,
235 entrypoint: self.entrypoint,
236 mapped_rgns: self.mapped_rgns,
237 abort_buffer: self.abort_buffer,
238 snapshot_count: self.snapshot_count,
239 };
240 let guest_mgr = SandboxMemoryManager {
241 shared_mem: gshm,
242 scratch_mem: gscratch,
243 layout: self.layout,
244 entrypoint: self.entrypoint,
245 mapped_rgns: self.mapped_rgns,
246 abort_buffer: Vec::new(), snapshot_count: self.snapshot_count,
248 };
249 host_mgr.update_scratch_bookkeeping()?;
250 Ok((host_mgr, guest_mgr))
251 }
252}
253
254impl SandboxMemoryManager<HostSharedMemory> {
255 #[allow(dead_code)]
256 pub(crate) fn parallel_invoke_slots_gva(&self) -> u64 {
257 self.layout.get_parallel_invoke_slots_gva()
258 }
259
260 pub(crate) fn parallel_invoke_slot_scratch_host_offset(&self, lane: usize) -> usize {
261 self.layout
262 .get_parallel_invoke_slot_scratch_host_offset(lane)
263 }
264
265 pub(crate) fn parallel_invoke_slot_host_ptr(
266 &self,
267 lane: usize,
268 ) -> Result<*mut ParallelInvokeSlot> {
269 let offset = self.parallel_invoke_slot_scratch_host_offset(lane);
270 let len = size_of::<ParallelInvokeSlot>();
271 if offset
272 .checked_add(len)
273 .is_none_or(|end| end > self.scratch_mem.mem_size())
274 {
275 return Err(crate::new_error!(
276 "parallel invoke slot {} is outside scratch memory",
277 lane
278 ));
279 }
280 let ptr = self.scratch_mem.base_ptr().wrapping_add(offset) as *mut ParallelInvokeSlot;
281 debug_assert_eq!(
282 (ptr as usize) % align_of::<ParallelInvokeSlot>(),
283 0,
284 "parallel invoke slot is not ABI-aligned"
285 );
286 Ok(ptr)
287 }
288
289 fn parallel_invoke_slot_field_offset(&self, lane: usize, field_offset: usize) -> usize {
290 self.parallel_invoke_slot_scratch_host_offset(lane) + field_offset
291 }
292
293 pub(crate) fn read_parallel_invoke_status(&self, lane: usize) -> Result<u32> {
294 self.scratch_mem.read::<u32>(
295 self.parallel_invoke_slot_field_offset(lane, offset_of!(ParallelInvokeSlot, status)),
296 )
297 }
298
299 pub(crate) fn write_parallel_invoke_status(&self, lane: usize, status: u32) -> Result<()> {
300 self.scratch_mem.write::<u32>(
301 self.parallel_invoke_slot_field_offset(lane, offset_of!(ParallelInvokeSlot, status)),
302 status,
303 )
304 }
305
306 #[instrument(err(Debug), skip_all, parent = Span::current(), level= "Trace")]
309 pub(crate) fn write_guest_function_call_raw(&mut self, buffer: &[u8]) -> Result<()> {
310 self.scratch_mem.push_buffer(
311 self.layout.get_input_data_buffer_scratch_host_offset(),
312 self.layout.sandbox_memory_config.get_input_data_size(),
313 buffer,
314 )
315 }
316
317 #[instrument(err(Debug), skip_all, parent = Span::current(), level= "Trace")]
320 pub(crate) fn read_guest_function_call_result_raw(&mut self) -> Result<Vec<u8>> {
321 self.scratch_mem.try_pop_buffer_raw(
322 self.layout.get_output_data_buffer_scratch_host_offset(),
323 self.layout.sandbox_memory_config.get_output_data_size(),
324 )
325 }
326
327 #[instrument(err(Debug), skip_all, parent = Span::current(), level= "Trace")]
330 pub(crate) fn read_host_function_call_raw(&mut self) -> Result<Vec<u8>> {
331 self.scratch_mem.try_pop_buffer_raw(
332 self.layout.get_output_data_buffer_scratch_host_offset(),
333 self.layout.sandbox_memory_config.get_output_data_size(),
334 )
335 }
336
337 #[instrument(err(Debug), skip_all, parent = Span::current(), level= "Trace")]
340 pub(crate) fn write_host_function_response_raw(&mut self, buffer: &[u8]) -> Result<()> {
341 self.scratch_mem.push_buffer(
342 self.layout.get_input_data_buffer_scratch_host_offset(),
343 self.layout.sandbox_memory_config.get_input_data_size(),
344 buffer,
345 )
346 }
347
348 #[instrument(err(Debug), skip_all, parent = Span::current(), level= "Trace")]
350 pub(crate) fn read_guest_log_data(&mut self) -> Result<GuestLogData> {
351 self.scratch_mem.try_pop_buffer_into::<GuestLogData>(
352 self.layout.get_output_data_buffer_scratch_host_offset(),
353 self.layout.sandbox_memory_config.get_output_data_size(),
354 )
355 }
356
357 pub(crate) fn clear_io_buffers(&mut self) {
358 loop {
360 let Ok(_) = self.scratch_mem.try_pop_buffer_into::<Vec<u8>>(
361 self.layout.get_output_data_buffer_scratch_host_offset(),
362 self.layout.sandbox_memory_config.get_output_data_size(),
363 ) else {
364 break;
365 };
366 }
367 loop {
369 let Ok(_) = self.scratch_mem.try_pop_buffer_into::<Vec<u8>>(
370 self.layout.get_input_data_buffer_scratch_host_offset(),
371 self.layout.sandbox_memory_config.get_input_data_size(),
372 ) else {
373 break;
374 };
375 }
376 }
377
378 #[inline]
379 fn update_scratch_bookkeeping_item(&mut self, offset: u64, value: u64) -> Result<()> {
380 let scratch_size = self.scratch_mem.mem_size();
381 let base_offset = scratch_size - offset as usize;
382 self.scratch_mem.write::<u64>(base_offset, value)
383 }
384
385 fn update_scratch_bookkeeping(&mut self) -> Result<()> {
386 use nub_host_common::layout::*;
387 let scratch_size = self.scratch_mem.mem_size();
388 self.update_scratch_bookkeeping_item(SCRATCH_TOP_SIZE_OFFSET, scratch_size as u64)?;
389 self.update_scratch_bookkeeping_item(
390 SCRATCH_TOP_ALLOCATOR_OFFSET,
391 self.layout.get_first_free_scratch_gpa(),
392 )?;
393 self.update_scratch_bookkeeping_item(
402 SCRATCH_TOP_SNAPSHOT_PT_GPA_BASE_OFFSET,
403 self.layout.get_pt_base_gpa(),
404 )?;
405 self.update_scratch_bookkeeping_item(
406 SCRATCH_TOP_SNAPSHOT_GENERATION_OFFSET,
407 self.snapshot_count,
408 )?;
409
410 self.scratch_mem.write::<u64>(
413 self.layout.get_input_data_buffer_scratch_host_offset(),
414 SandboxMemoryLayout::STACK_POINTER_SIZE_BYTES,
415 )?;
416 self.scratch_mem.write::<u64>(
417 self.layout.get_output_data_buffer_scratch_host_offset(),
418 SandboxMemoryLayout::STACK_POINTER_SIZE_BYTES,
419 )?;
420
421 let slot_start = self.layout.get_parallel_invoke_slots_scratch_host_offset();
422 let slot_end = slot_start + self.layout.get_parallel_invoke_slots_size();
423 self.scratch_mem.with_exclusivity(|scratch| {
424 scratch.as_mut_slice()[slot_start..slot_end].fill(0);
425 })?;
426
427 let snapshot_pt_end = self.shared_mem.mem_size();
434 let snapshot_pt_size = self.layout.get_pt_size();
435 let snapshot_pt_start = snapshot_pt_end - snapshot_pt_size;
436 self.scratch_mem.with_exclusivity(|scratch| {
437 let bytes = &self.shared_mem.as_slice()[snapshot_pt_start..snapshot_pt_end];
438 #[allow(clippy::needless_borrow)]
439 scratch.copy_from_slice(&bytes, self.layout.get_pt_base_scratch_offset())
440 })??;
441
442 Ok(())
443 }
444}