Create sandboxes directly from snapshots#1459
Conversation
There was a problem hiding this comment.
Pull request overview
Adds the ability to construct a MultiUseSandbox directly from an Arc<Snapshot>, bypassing UninitializedSandbox + evolve(). Snapshots now record the host functions registered at capture time, and the new constructor validates that the provided host-function set is a superset of those required. The gdb entry-point breakpoint mechanism is reworked into a one-shot tracked on the VM so it works for both Initialise and Call snapshots.
Changes:
- New
HostFunctionsnewtype (withdefault()pre-registeringHostPrintandnew()empty) andMultiUseSandbox::from_snapshot(snap, host_funcs, cfg). Snapshotnow carriesHostFunctionDetailsand exposesvalidate_host_functionsto reject missing/mismatched signatures; pre-init snapshots accept any registry.- Gdb support: replaces
VcpuStopReason::EntryPointBpwith aone_shot_entry_bpfield cleared by the run loop; entry breakpoint installed for bothInitialiseandCallentries; new gdb e2e test for snapshot path.
Reviewed changes
Copilot reviewed 14 out of 14 changed files in this pull request and generated 7 comments.
Show a summary per file
| File | Description |
|---|---|
| src/hyperlight_host/src/sandbox/initialized_multi_use.rs | Implements from_snapshot, layout-override warning, snapshot host-function plumbing, and tests. |
| src/hyperlight_host/src/sandbox/snapshot/mod.rs | Adds host_functions field and validate_host_functions; updates constructors and tests. |
| src/hyperlight_host/src/sandbox/host_funcs.rs | Adds HostFunctions newtype and with_default_host_print; changes register_host_function to infallible; tweaks From impl. |
| src/hyperlight_host/src/sandbox/uninitialized.rs | Uses with_default_host_print and removes redundant register_print call in new. |
| src/hyperlight_host/src/func/host_functions.rs | Adapts Registerable impls to infallible registry call; adds HostFunctions impl. |
| src/hyperlight_host/src/mem/mgr.rs | Plumbs HostFunctionDetails into snapshot(); inherits snapshot_count in from_snapshot. |
| src/hyperlight_host/src/mem/layout.rs | Widens visibility of several layout fields and MAX_MEMORY_SIZE. |
| src/hyperlight_host/src/lib.rs | Re-exports HostFunctions. |
| src/hyperlight_host/src/hypervisor/hyperlight_vm/x86_64.rs | Sets up one-shot entry breakpoint covering Initialise and Call. |
| src/hyperlight_host/src/hypervisor/hyperlight_vm/mod.rs | Adds one_shot_entry_bp field and run-loop logic to clear it on first hit. |
| src/hyperlight_host/src/hypervisor/gdb/mod.rs | Removes VcpuStopReason::EntryPointBp variant. |
| src/hyperlight_host/src/hypervisor/gdb/event_loop.rs | Drops handling for the removed variant. |
| src/hyperlight_host/src/hypervisor/gdb/arch.rs | vcpu_stop_reason no longer takes/uses the entrypoint, becomes side-effect-free classifier. |
| src/hyperlight_host/examples/guest-debugging/main.rs | Extracts gdb test helpers and adds test_gdb_from_snapshot. |
Signed-off-by: Ludvig Liljenberg <4257730+ludfjig@users.noreply.github.com>
Signed-off-by: Ludvig Liljenberg <4257730+ludfjig@users.noreply.github.com>
Signed-off-by: Ludvig Liljenberg <4257730+ludfjig@users.noreply.github.com>
Signed-off-by: Ludvig Liljenberg <4257730+ludfjig@users.noreply.github.com>
jsturtevant
left a comment
There was a problem hiding this comment.
LGTM, I've left a few comments and one missing testing that needs a fix
|
|
||
| // Validate that the provided host functions are a superset of | ||
| // those required by the snapshot. | ||
| snapshot.validate_host_functions(&host_funcs)?; |
There was a problem hiding this comment.
I assume the snapshot manager does other validation? Should this validation live with that validation? if not are we missing validation? Can a maliciously crafted snapshot bypass any assumptions we have about the memory layout?
There was a problem hiding this comment.
There's no way to create malicious snapshots (yet). Right now they are completely opaque structs to the user. This will change when we load them from disk though (future PR, where I add a lot of validation)
There was a problem hiding this comment.
(future PR, where I add a lot of validation)
Glad to hear we are planning this. What if a malicious guest does something (writes some handcrafted assembly) and we take a snapshot with that in there?
| } | ||
|
|
||
| /// Create a snapshot with the given mapped regions | ||
| #[allow(clippy::too_many_arguments)] |
There was a problem hiding this comment.
Should we consider a snapshot builder to avoid this?
There was a problem hiding this comment.
Hmm I don't think so, none of these are really optional in that sense
| let mgr = crate::mem::mgr::SandboxMemoryManager::from_snapshot(&snapshot)?; | ||
| let (mut hshm, gshm) = mgr.build()?; | ||
|
|
||
| let page_size = u32::try_from(page_size::get())? as usize; |
There was a problem hiding this comment.
Hm--just noticed set set_up_hypervisor_partition takes a usize page_size but vm.initialise takes a u32. Can we improve this to avoid the casts?
There was a problem hiding this comment.
good point, but I'd prefer to fix this in follow up. I don't think we should require both methods to take in page size as well, that seems strange
| /// produced by a test-only constructor) accepts any `provided` | ||
| /// set. | ||
| pub(crate) fn validate_host_functions(&self, provided: &crate::HostFunctions) -> Result<()> { | ||
| let required = match &self.host_functions.host_functions { |
There was a problem hiding this comment.
nit: host_functions.host_functions
There was a problem hiding this comment.
Agree. I can fix this in follow up as well
| for req in required { | ||
| match provided_funcs | ||
| .iter() | ||
| .find(|f| f.function_name == req.function_name) |
There was a problem hiding this comment.
Can we improve this lookup? For every req function, we look at all provided functions (i.e., O(req * provided)). What if we built a HashMap for the provided functions? This way we'd have a O(provided + req)?
There was a problem hiding this comment.
good point let me investigate. HostFunc is already based on HashMap
| let sregs = snapshot.sregs().ok_or_else(|| { | ||
| crate::new_error!("snapshot with NextAction::Call must have captured sregs") | ||
| })?; | ||
| vm.apply_sregs(hshm.layout.get_pt_base_gpa(), sregs) |
There was a problem hiding this comment.
Might be worth adding a comment that this is overwriting the apply_sregs from vm.initialise?
There was a problem hiding this comment.
it's not, vm.initialize is a noop
|
|
||
| /// Construct the (out_file_path, cmd_file_path, manifest_dir) | ||
| /// triple every gdb test needs. | ||
| fn gdb_test_paths(name: &str) -> (String, String, String) { |
Signed-off-by: Ludvig Liljenberg <4257730+ludfjig@users.noreply.github.com>
Signed-off-by: Ludvig Liljenberg <4257730+ludfjig@users.noreply.github.com>
Signed-off-by: Ludvig Liljenberg <4257730+ludfjig@users.noreply.github.com>
Builds a ready-to-use MultiUseSandbox directly from an
Arc<Snapshot>without going throughUninitializedSandbox+evolve(). Building block for OCI-backed snapshot loading on a follow-up branch.Adds:
HostFunctionsnewtype around the internal FunctionRegistry. default() pre-registers HostPrint, empty() starts empty.MultiUseSandbox::from_snapshot(snap, host_funcs, cfg). Snapshot is the source of truth for layout. cfg is honored for non-layout fields (timeouts, debug info, interrupt retry delay).Recommend to review commit-by-commit