Skip to content

Commit fd7f733

Browse files
committed
WIP
Signed-off-by: Ludvig Liljenberg <4257730+ludfjig@users.noreply.github.com>
1 parent 9011420 commit fd7f733

12 files changed

Lines changed: 177 additions & 12 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
55
## [Prerelease] - Unreleased
66

77
### Added
8+
* Add `RngRestorePolicy` to `SandboxConfiguration` to preserve or reseed the guest libc PRNG on snapshot restore.
89

910
### Changed
1011
* **Breaking:** Guest MSR state is now saved and restored across snapshots.

src/hyperlight_common/src/layout.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ pub const SCRATCH_TOP_SIZE_OFFSET: u64 = 0x08;
2727
pub const SCRATCH_TOP_ALLOCATOR_OFFSET: u64 = 0x10;
2828
pub const SCRATCH_TOP_SNAPSHOT_PT_GPA_BASE_OFFSET: u64 = 0x18;
2929
pub const SCRATCH_TOP_SNAPSHOT_GENERATION_OFFSET: u64 = 0x20;
30+
pub const SCRATCH_TOP_LIBC_RNG_SEED_OFFSET: u64 = 0x28;
3031
pub const SCRATCH_TOP_EXN_STACK_OFFSET: u64 = 0x30;
3132

3233
pub fn scratch_base_gpa(size: usize) -> u64 {

src/hyperlight_guest/src/layout.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,4 +35,8 @@ pub fn snapshot_generation_gva() -> *mut u64 {
3535
use hyperlight_common::layout::{SCRATCH_TOP_GVA, SCRATCH_TOP_SNAPSHOT_GENERATION_OFFSET};
3636
(SCRATCH_TOP_GVA as u64 - SCRATCH_TOP_SNAPSHOT_GENERATION_OFFSET + 1) as *mut u64
3737
}
38+
pub fn libc_rng_seed_gva() -> *mut u64 {
39+
use hyperlight_common::layout::{SCRATCH_TOP_GVA, SCRATCH_TOP_LIBC_RNG_SEED_OFFSET};
40+
(SCRATCH_TOP_GVA as u64 - SCRATCH_TOP_LIBC_RNG_SEED_OFFSET + 1) as *mut u64
41+
}
3842
pub use arch::{scratch_base_gpa, scratch_base_gva};

src/hyperlight_guest_bin/src/guest_function/call.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,9 @@ pub(crate) fn call_guest_function(function_call: FunctionCall) -> Result<Vec<u8>
8686
}
8787

8888
pub(crate) fn internal_dispatch_function() {
89+
#[cfg(feature = "libc")]
90+
crate::refresh_libc_rng();
91+
8992
// Read the current TSC to report it to the host with the spans/events
9093
// This helps calculating the timestamps relative to the guest call
9194
#[cfg(all(feature = "trace_guest", target_arch = "x86_64"))]

src/hyperlight_guest_bin/src/lib.rs

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,23 @@ unsafe extern "C" {
210210
fn srand(seed: u32);
211211
}
212212

213+
#[cfg(feature = "libc")]
214+
fn fold_libc_seed(seed: u64) -> u32 {
215+
(seed ^ (seed >> 32)) as u32
216+
}
217+
218+
#[cfg(feature = "libc")]
219+
pub(crate) fn refresh_libc_rng() {
220+
let seed_ptr = hyperlight_guest::layout::libc_rng_seed_gva();
221+
let seed = unsafe { seed_ptr.read_volatile() };
222+
if seed != 0 {
223+
unsafe {
224+
seed_ptr.write_volatile(0);
225+
srand(fold_libc_seed(seed));
226+
}
227+
}
228+
}
229+
213230
#[tracing::instrument(skip_all, parent = tracing::Span::current(), level= "Trace")]
214231
extern "C" fn hyperlight_main_default() {
215232
// no-op
@@ -254,8 +271,7 @@ pub(crate) extern "C" fn generic_init(
254271

255272
#[cfg(feature = "libc")]
256273
unsafe {
257-
let srand_seed = (((peb_address << 8) ^ (_seed >> 4)) >> 32) as u32;
258-
srand(srand_seed);
274+
srand(fold_libc_seed(_seed));
259275
}
260276

261277
unsafe {

src/hyperlight_host/src/mem/mgr.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -531,6 +531,13 @@ impl SandboxMemoryManager<HostSharedMemory> {
531531
self.scratch_mem.write::<u64>(base_offset, value)
532532
}
533533

534+
pub(crate) fn request_libc_rng_reseed(&mut self, seed: u64) -> Result<()> {
535+
self.update_scratch_bookkeeping_item(
536+
hyperlight_common::layout::SCRATCH_TOP_LIBC_RNG_SEED_OFFSET,
537+
seed,
538+
)
539+
}
540+
534541
fn update_scratch_bookkeeping(&mut self) -> Result<()> {
535542
use hyperlight_common::layout::*;
536543
let scratch_size = self.scratch_mem.mem_size();
@@ -555,6 +562,7 @@ impl SandboxMemoryManager<HostSharedMemory> {
555562
SCRATCH_TOP_SNAPSHOT_GENERATION_OFFSET,
556563
self.snapshot_count,
557564
)?;
565+
self.update_scratch_bookkeeping_item(SCRATCH_TOP_LIBC_RNG_SEED_OFFSET, 0)?;
558566

559567
// Initialise the guest input and output data buffers in
560568
// scratch memory. TODO: remove the need for this.

src/hyperlight_host/src/sandbox/config.rs

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,19 @@ pub enum GuestMsrError {
4141
},
4242
}
4343

44+
/// Controls Hyperlight-managed guest randomness across snapshot restores.
45+
#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)]
46+
#[repr(u8)]
47+
pub enum RngRestorePolicy {
48+
/// Reseed the guest libc PRNG before execution resumes.
49+
///
50+
/// Snapshots taken before Hyperlight v0.17.0 ignore this policy and preserve their PRNG state.
51+
Refresh,
52+
/// Preserve the guest libc PRNG state captured by the snapshot.
53+
#[default]
54+
Preserve,
55+
}
56+
4457
/// The complete set of configuration needed to create a Sandbox
4558
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
4659
#[repr(C)]
@@ -92,6 +105,8 @@ pub struct SandboxConfiguration {
92105
/// Number of valid entries in `guest_msrs`.
93106
#[cfg(target_arch = "x86_64")]
94107
guest_msrs_count: usize,
108+
/// How snapshot restores handle the guest libc PRNG
109+
rng_restore_policy: RngRestorePolicy,
95110
}
96111

97112
impl SandboxConfiguration {
@@ -137,6 +152,7 @@ impl SandboxConfiguration {
137152
scratch_size,
138153
interrupt_retry_delay,
139154
interrupt_vcpu_sigrtmin_offset,
155+
rng_restore_policy: RngRestorePolicy::default(),
140156
#[cfg(gdb)]
141157
guest_debug_info,
142158
#[cfg(crashdump)]
@@ -299,6 +315,16 @@ impl SandboxConfiguration {
299315
self.scratch_size = scratch_size;
300316
}
301317

318+
/// Set how snapshot restores handle the guest libc PRNG.
319+
pub fn set_rng_restore_policy(&mut self, policy: RngRestorePolicy) {
320+
self.rng_restore_policy = policy;
321+
}
322+
323+
/// Get how snapshot restores handle the guest libc PRNG.
324+
pub fn get_rng_restore_policy(&self) -> RngRestorePolicy {
325+
self.rng_restore_policy
326+
}
327+
302328
#[cfg(crashdump)]
303329
#[instrument(skip_all, parent = Span::current(), level= "Trace")]
304330
pub(crate) fn get_guest_core_dump(&self) -> bool {
@@ -347,7 +373,16 @@ impl Default for SandboxConfiguration {
347373
mod tests {
348374
#[cfg(target_arch = "x86_64")]
349375
use super::GuestMsrError;
350-
use super::SandboxConfiguration;
376+
use super::{RngRestorePolicy, SandboxConfiguration};
377+
378+
#[test]
379+
fn rng_restore_policy_defaults_to_preserve_and_can_be_refreshed() {
380+
let mut config = SandboxConfiguration::default();
381+
assert_eq!(config.get_rng_restore_policy(), RngRestorePolicy::Preserve);
382+
383+
config.set_rng_restore_policy(RngRestorePolicy::Refresh);
384+
assert_eq!(config.get_rng_restore_policy(), RngRestorePolicy::Refresh);
385+
}
351386

352387
#[test]
353388
#[cfg(target_arch = "x86_64")]

src/hyperlight_host/src/sandbox/initialized_multi_use.rs

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,16 @@ use crate::metrics::{
4242
};
4343
use crate::{HyperlightError, Result, log_then_return};
4444

45+
fn libc_rng_seed() -> u64 {
46+
loop {
47+
let seed = rand::random::<u64>();
48+
// Zero means no reseed request in scratch memory.
49+
if seed != 0 {
50+
return seed;
51+
}
52+
}
53+
}
54+
4555
/// A fully initialized sandbox that can execute guest functions multiple times.
4656
///
4757
/// Guest functions can be called repeatedly while maintaining state between calls.
@@ -95,6 +105,7 @@ pub struct MultiUseSandbox {
95105
/// Given (snapshot_mem, scratch_mem, cr3), returns a list of root GPAs.
96106
/// If not set, only CR3 is used as the single root.
97107
pt_root_finder: Option<PtRootFinder>,
108+
rng_restore_policy: crate::sandbox::RngRestorePolicy,
98109
}
99110

100111
/// Callback for discovering page table roots from guest memory.
@@ -120,6 +131,7 @@ impl MultiUseSandbox {
120131
host_funcs: Arc<Mutex<FunctionRegistry>>,
121132
mgr: SandboxMemoryManager<HostSharedMemory>,
122133
vm: HyperlightVm,
134+
rng_restore_policy: crate::sandbox::RngRestorePolicy,
123135
#[cfg(gdb)] dbg_mem_access_fn: Arc<Mutex<SandboxMemoryManager<HostSharedMemory>>>,
124136
) -> MultiUseSandbox {
125137
Self {
@@ -131,6 +143,7 @@ impl MultiUseSandbox {
131143
dbg_mem_access_fn,
132144
snapshot: None,
133145
pt_root_finder: None,
146+
rng_restore_policy,
134147
}
135148
}
136149

@@ -210,8 +223,6 @@ impl MultiUseSandbox {
210223
host_funcs: crate::HostFunctions,
211224
config: Option<crate::sandbox::SandboxConfiguration>,
212225
) -> Result<Self> {
213-
use rand::RngExt;
214-
215226
use crate::mem::ptr::RawPtr;
216227
use crate::sandbox::uninitialized_evolve::set_up_hypervisor_partition;
217228

@@ -275,10 +286,7 @@ impl MultiUseSandbox {
275286
load_info,
276287
)?;
277288

278-
let seed = {
279-
let mut rng = rand::rng();
280-
rng.random::<u64>()
281-
};
289+
let seed = libc_rng_seed();
282290
let peb_addr = RawPtr::from(u64::try_from(hshm.layout.peb_address())?);
283291

284292
#[cfg(gdb)]
@@ -296,6 +304,12 @@ impl MultiUseSandbox {
296304
)
297305
.map_err(crate::hypervisor::hyperlight_vm::HyperlightVmError::Initialize)?;
298306

307+
if config.get_rng_restore_policy() == crate::sandbox::RngRestorePolicy::Refresh
308+
&& matches!(snapshot.next_action(), super::snapshot::NextAction::Call(_))
309+
{
310+
hshm.request_libc_rng_reseed(seed)?;
311+
}
312+
299313
// If the snapshot was taken from an already-initialized guest
300314
// (NextAction::Call), apply the captured special registers so
301315
// the guest resumes in the correct CPU state.
@@ -326,6 +340,7 @@ impl MultiUseSandbox {
326340
host_funcs,
327341
hshm,
328342
vm,
343+
config.get_rng_restore_policy(),
329344
#[cfg(gdb)]
330345
dbg_mem_wrapper,
331346
);
@@ -553,6 +568,9 @@ impl MultiUseSandbox {
553568
}
554569

555570
let (gsnapshot, gscratch) = self.mem_mgr.restore_snapshot(&snapshot)?;
571+
if self.rng_restore_policy == crate::sandbox::RngRestorePolicy::Refresh {
572+
self.mem_mgr.request_libc_rng_reseed(libc_rng_seed())?;
573+
}
556574
if let Some(gsnapshot) = gsnapshot {
557575
self.vm
558576
.update_snapshot_mapping(gsnapshot)

src/hyperlight_host/src/sandbox/mod.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,8 @@ pub(crate) mod trace;
4343

4444
/// Trait used by the macros to paper over the differences between hyperlight and hyperlight-wasm
4545
pub use callable::Callable;
46-
/// Re-export for `SandboxConfiguration` type
47-
pub use config::SandboxConfiguration;
46+
/// Re-export for sandbox configuration types
47+
pub use config::{RngRestorePolicy, SandboxConfiguration};
4848
/// Re-export for the `MultiUseSandbox` type
4949
pub use initialized_multi_use::{MultiUseSandbox, PtRootFinder};
5050
/// Re-export for `GuestBinary` type

src/hyperlight_host/src/sandbox/snapshot/file_tests.rs

Lines changed: 75 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ limitations under the License.
2020

2121
use std::sync::Arc;
2222

23-
use hyperlight_testing::simple_guest_as_pathbuf;
23+
use hyperlight_testing::{c_simple_guest_as_pathbuf, simple_guest_as_pathbuf};
2424
use serde_json::Value;
2525
use sha2::{Digest as _, Sha256};
2626

@@ -37,6 +37,24 @@ fn create_test_sandbox() -> MultiUseSandbox {
3737
.unwrap()
3838
}
3939

40+
fn create_c_test_sandbox() -> MultiUseSandbox {
41+
create_c_test_sandbox_with_config(None)
42+
}
43+
44+
fn create_c_test_sandbox_with_config(
45+
config: Option<crate::sandbox::SandboxConfiguration>,
46+
) -> MultiUseSandbox {
47+
let path = c_simple_guest_as_pathbuf();
48+
UninitializedSandbox::new(GuestBinary::FilePath(path), config)
49+
.unwrap()
50+
.evolve()
51+
.unwrap()
52+
}
53+
54+
fn random_sequence(sandbox: &mut MultiUseSandbox) -> [i32; 4] {
55+
std::array::from_fn(|_| sandbox.call("NextRandom", ()).unwrap())
56+
}
57+
4058
fn create_snapshot() -> Arc<Snapshot> {
4159
let mut sbox = create_test_sandbox();
4260
sbox.snapshot().unwrap()
@@ -3175,6 +3193,62 @@ fn from_snapshot_silently_ignores_layout_overrides() {
31753193
assert_eq!(new_snap.layout().get_scratch_size(), original_scratch);
31763194
}
31773195

3196+
#[test]
3197+
fn from_snapshot_preserves_guest_libc_rng_by_default() {
3198+
let mut sandbox = create_c_test_sandbox();
3199+
let snapshot = sandbox.snapshot().unwrap();
3200+
3201+
let mut first =
3202+
MultiUseSandbox::from_snapshot(snapshot.clone(), HostFunctions::default(), None).unwrap();
3203+
let mut second =
3204+
MultiUseSandbox::from_snapshot(snapshot, HostFunctions::default(), None).unwrap();
3205+
3206+
assert_eq!(random_sequence(&mut first), random_sequence(&mut second));
3207+
}
3208+
3209+
#[test]
3210+
fn from_snapshot_reseeds_guest_libc_rng_when_requested() {
3211+
use crate::sandbox::{RngRestorePolicy, SandboxConfiguration};
3212+
3213+
let mut sandbox = create_c_test_sandbox();
3214+
let snapshot = sandbox.snapshot().unwrap();
3215+
let mut config = SandboxConfiguration::default();
3216+
config.set_rng_restore_policy(RngRestorePolicy::Refresh);
3217+
let mut first =
3218+
MultiUseSandbox::from_snapshot(snapshot.clone(), HostFunctions::default(), Some(config))
3219+
.unwrap();
3220+
let mut second =
3221+
MultiUseSandbox::from_snapshot(snapshot, HostFunctions::default(), Some(config)).unwrap();
3222+
3223+
assert_ne!(random_sequence(&mut first), random_sequence(&mut second));
3224+
}
3225+
3226+
#[test]
3227+
fn restore_preserves_guest_libc_rng_by_default() {
3228+
let mut sandbox = create_c_test_sandbox();
3229+
let snapshot = sandbox.snapshot().unwrap();
3230+
let expected = random_sequence(&mut sandbox);
3231+
3232+
sandbox.restore(snapshot).unwrap();
3233+
3234+
assert_eq!(random_sequence(&mut sandbox), expected);
3235+
}
3236+
3237+
#[test]
3238+
fn restore_reseeds_guest_libc_rng_when_requested() {
3239+
use crate::sandbox::{RngRestorePolicy, SandboxConfiguration};
3240+
3241+
let mut config = SandboxConfiguration::default();
3242+
config.set_rng_restore_policy(RngRestorePolicy::Refresh);
3243+
let mut sandbox = create_c_test_sandbox_with_config(Some(config));
3244+
let snapshot = sandbox.snapshot().unwrap();
3245+
let captured = random_sequence(&mut sandbox);
3246+
3247+
sandbox.restore(snapshot).unwrap();
3248+
3249+
assert_ne!(random_sequence(&mut sandbox), captured);
3250+
}
3251+
31783252
/// `from_snapshot` honors `guest_core_dump=true` so that
31793253
/// `generate_crashdump_to_dir` writes a file.
31803254
#[test]

0 commit comments

Comments
 (0)