Skip to content

Commit e8ce0d3

Browse files
committed
Persist host function metadata in Snapshot
Signed-off-by: Ludvig Liljenberg <4257730+ludfjig@users.noreply.github.com>
1 parent ea6c44b commit e8ce0d3

3 files changed

Lines changed: 88 additions & 0 deletions

File tree

src/hyperlight_host/src/mem/mgr.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ use hyperlight_common::flatbuffer_wrappers::function_call::{
2222
};
2323
use hyperlight_common::flatbuffer_wrappers::function_types::FunctionCallResult;
2424
use hyperlight_common::flatbuffer_wrappers::guest_log_data::GuestLogData;
25+
use hyperlight_common::flatbuffer_wrappers::host_function_details::HostFunctionDetails;
2526
use hyperlight_common::vmem::{self, PAGE_TABLE_SIZE};
2627
#[cfg(all(feature = "crashdump", not(feature = "i686-guest")))]
2728
use hyperlight_common::vmem::{BasicMapping, MappingKind};
@@ -298,6 +299,7 @@ where
298299
}
299300

300301
/// Create a snapshot with the given mapped regions
302+
#[allow(clippy::too_many_arguments)]
301303
pub(crate) fn snapshot(
302304
&mut self,
303305
sandbox_id: u64,
@@ -306,6 +308,7 @@ where
306308
rsp_gva: u64,
307309
sregs: CommonSpecialRegisters,
308310
entrypoint: NextAction,
311+
host_functions: HostFunctionDetails,
309312
) -> Result<Snapshot> {
310313
self.snapshot_count += 1;
311314
Snapshot::new(
@@ -320,6 +323,7 @@ where
320323
sregs,
321324
entrypoint,
322325
self.snapshot_count,
326+
host_functions,
323327
)
324328
}
325329
}

src/hyperlight_host/src/sandbox/initialized_multi_use.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -207,13 +207,19 @@ impl MultiUseSandbox {
207207
.get_snapshot_sregs()
208208
.map_err(|e| HyperlightError::HyperlightVmError(e.into()))?;
209209
let entrypoint = self.vm.get_entrypoint();
210+
let host_functions = (&*self.host_funcs.try_lock().map_err(|e| {
211+
crate::new_error!("Error locking host_funcs at {}:{}: {}", file!(), line!(), e)
212+
})?)
213+
.into();
214+
210215
let memory_snapshot = self.mem_mgr.snapshot(
211216
self.id,
212217
mapped_regions_vec,
213218
&root_pt_gpas,
214219
stack_top_gpa,
215220
sregs,
216221
entrypoint,
222+
host_functions,
217223
)?;
218224
let snapshot = Arc::new(memory_snapshot);
219225
self.snapshot = Some(snapshot.clone());

src/hyperlight_host/src/sandbox/snapshot/mod.rs

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ limitations under the License.
1717
use std::collections::{BTreeMap, HashMap};
1818
use std::sync::atomic::{AtomicU64, Ordering};
1919

20+
use hyperlight_common::flatbuffer_wrappers::host_function_details::HostFunctionDetails;
2021
use hyperlight_common::layout::{scratch_base_gpa, scratch_base_gva};
2122
use hyperlight_common::vmem;
2223
use hyperlight_common::vmem::{
@@ -115,6 +116,16 @@ pub struct Snapshot {
115116
/// restored sandbox's guest-visible counter so the guest can tell
116117
/// which snapshot it is currently a clone of.
117118
snapshot_generation: u64,
119+
120+
/// Names and signatures of host functions registered on the
121+
/// sandbox at the time this snapshot was taken. Persisted to disk
122+
/// so that [`crate::MultiUseSandbox::from_snapshot`] can reject
123+
/// a `HostFunctions` set that is missing required functions or
124+
/// has mismatched signatures.
125+
///
126+
/// Empty for snapshots created via test-only constructors that
127+
/// bypass the normal sandbox path.
128+
host_functions: HostFunctionDetails,
118129
}
119130
impl core::convert::AsRef<Snapshot> for Snapshot {
120131
fn as_ref(&self) -> &Self {
@@ -423,6 +434,9 @@ impl Snapshot {
423434
sregs: None,
424435
entrypoint: NextAction::Initialise(load_addr + entrypoint_va - base_va),
425436
snapshot_generation: 0,
437+
host_functions: HostFunctionDetails {
438+
host_functions: None,
439+
},
426440
})
427441
}
428442

@@ -447,6 +461,7 @@ impl Snapshot {
447461
sregs: CommonSpecialRegisters,
448462
entrypoint: NextAction,
449463
snapshot_generation: u64,
464+
host_functions: HostFunctionDetails,
450465
) -> Result<Self> {
451466
let mut phys_seen = HashMap::<u64, usize>::new();
452467
let scratch_gva = scratch_base_gva(layout.get_scratch_size());
@@ -610,6 +625,7 @@ impl Snapshot {
610625
sregs: Some(sregs),
611626
entrypoint,
612627
snapshot_generation,
628+
host_functions,
613629
})
614630
}
615631

@@ -663,6 +679,65 @@ impl Snapshot {
663679
pub(crate) fn entrypoint(&self) -> NextAction {
664680
self.entrypoint
665681
}
682+
683+
/// Validate that `provided` is a superset of the host functions
684+
/// recorded in this snapshot: every function that was registered
685+
/// at snapshot time must also be present in `provided` with a
686+
/// matching signature. Extras in `provided` are allowed.
687+
///
688+
/// Snapshots created in-memory before this field was wired in
689+
/// (or by test-only constructors) carry an empty function set,
690+
/// in which case any `provided` set is accepted.
691+
pub(crate) fn validate_host_functions(&self, provided: &crate::HostFunctions) -> Result<()> {
692+
let required = match &self.host_functions.host_functions {
693+
Some(v) => v,
694+
None => return Ok(()),
695+
};
696+
if required.is_empty() {
697+
return Ok(());
698+
}
699+
700+
// Build a HostFunctionDetails view of the provided registry
701+
// using the existing `From<&FunctionRegistry>` impl.
702+
let provided_details: HostFunctionDetails = provided.inner().into();
703+
let provided_funcs = provided_details.host_functions.unwrap_or_default();
704+
705+
let mut missing: Vec<String> = Vec::new();
706+
let mut signature_mismatches: Vec<String> = Vec::new();
707+
708+
for req in required {
709+
match provided_funcs
710+
.iter()
711+
.find(|f| f.function_name == req.function_name)
712+
{
713+
None => missing.push(req.function_name.clone()),
714+
Some(found)
715+
if found.parameter_types != req.parameter_types
716+
|| found.return_type != req.return_type =>
717+
{
718+
signature_mismatches.push(format!(
719+
"{}: snapshot has {:?} -> {:?}, registered {:?} -> {:?}",
720+
req.function_name,
721+
req.parameter_types,
722+
req.return_type,
723+
found.parameter_types,
724+
found.return_type,
725+
));
726+
}
727+
Some(_) => {}
728+
}
729+
}
730+
731+
if missing.is_empty() && signature_mismatches.is_empty() {
732+
return Ok(());
733+
}
734+
735+
Err(crate::new_error!(
736+
"snapshot host function mismatch: missing={:?}, signature_mismatches={:?}",
737+
missing,
738+
signature_mismatches
739+
))
740+
}
666741
}
667742

668743
impl PartialEq for Snapshot {
@@ -674,6 +749,7 @@ impl PartialEq for Snapshot {
674749
#[cfg(test)]
675750
#[cfg(not(feature = "i686-guest"))]
676751
mod tests {
752+
use hyperlight_common::flatbuffer_wrappers::host_function_details::HostFunctionDetails;
677753
use hyperlight_common::vmem::{self, BasicMapping, Mapping, MappingKind, PAGE_SIZE};
678754

679755
use crate::hypervisor::regs::CommonSpecialRegisters;
@@ -747,6 +823,7 @@ mod tests {
747823
default_sregs(),
748824
super::NextAction::None,
749825
1,
826+
HostFunctionDetails::default(),
750827
)
751828
.unwrap();
752829

@@ -764,6 +841,7 @@ mod tests {
764841
default_sregs(),
765842
super::NextAction::None,
766843
2,
844+
HostFunctionDetails::default(),
767845
)
768846
.unwrap();
769847

0 commit comments

Comments
 (0)