Skip to content

Commit 54bc12a

Browse files
committed
PR feedback
Signed-off-by: Ludvig Liljenberg <4257730+ludfjig@users.noreply.github.com>
1 parent b737fca commit 54bc12a

File tree

8 files changed

+41
-22
lines changed

8 files changed

+41
-22
lines changed

.github/workflows/ValidatePullRequest.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ jobs:
6767
- docs-pr
6868
- build-guests
6969
strategy:
70-
fail-fast: false
70+
fail-fast: true
7171
matrix:
7272
hypervisor: [hyperv, 'hyperv-ws2025', mshv3, kvm]
7373
cpu: [amd, intel]

src/hyperlight_host/src/error.rs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -327,9 +327,8 @@ impl HyperlightError {
327327
| HyperlightError::MemoryAccessViolation(_, _, _)
328328
| HyperlightError::SnapshotSizeMismatch(_, _)
329329
| HyperlightError::MemoryRegionSizeMismatch(_, _, _)
330-
// HyperlightVmError::Restore doesn't technically need to poison the sandbox,
331-
// because if MultiUseSandbox::restore() fails, the sandbox is still in the same
332-
// poisoned state as before.
330+
// HyperlightVmError::Restore is already handled manually in restore(), but we mark it
331+
// as poisoning here too for defense in depth.
333332
| HyperlightError::HyperlightVmError(HyperlightVmError::Restore(_)) => true,
334333

335334
// HyperlightVmError::DispatchGuestCall may poison the sandbox

src/hyperlight_host/src/hypervisor/hyperlight_vm.rs

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -875,11 +875,11 @@ impl HyperlightVm {
875875
Ok(())
876876
}
877877

878-
// Resets the following vCPU state:
879-
// - General purpose registers
880-
// - Debug registers
881-
// - XSAVE (includes FPU/SSE state with proper FCW and MXCSR defaults)
882-
// - Special registers (with saved PML4 if feature enabled)
878+
/// Resets the following vCPU state:
879+
/// - General purpose registers
880+
/// - Debug registers
881+
/// - XSAVE (includes FPU/SSE state with proper FCW and MXCSR defaults)
882+
/// - Special registers (with saved PML4 if feature enabled)
883883
// TODO: check if other state needs to be reset
884884
pub(crate) fn reset_vcpu(&self) -> std::result::Result<(), RegisterError> {
885885
self.vm.set_regs(&CommonRegisters {
@@ -2344,7 +2344,8 @@ mod tests {
23442344
normalize_fpu_mxcsr_for_kvm(&mut expected_dirty, fpu.mxcsr);
23452345
assert_eq!(fpu, expected_dirty);
23462346

2347-
// Verify MXCSR via xsave on KVM (since fpu() doesn't return it)
2347+
// KVM's get_fpu/set_fpu ioctls don't include MXCSR (it's in the SSE state,
2348+
// not x87 FPU state). We must use xsave to verify MXCSR on KVM.
23482349
#[cfg(kvm)]
23492350
if available_hv == HypervisorType::Kvm {
23502351
let xsave = hyperlight_vm.vm.xsave().unwrap();
@@ -2358,7 +2359,7 @@ mod tests {
23582359
// Check FPU is reset to defaults
23592360
assert_fpu_reset(hyperlight_vm.vm.as_ref());
23602361

2361-
// Verify MXCSR via xsave on KVM
2362+
// Verify MXCSR via xsave on KVM (fpu() doesn't include it)
23622363
#[cfg(kvm)]
23632364
if available_hv == HypervisorType::Kvm {
23642365
let xsave = hyperlight_vm.vm.xsave().unwrap();

src/hyperlight_host/src/hypervisor/virtual_machine/kvm.rs

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -255,16 +255,21 @@ impl VirtualMachine for KvmVm {
255255
fn reset_xsave(&self) -> std::result::Result<(), RegisterError> {
256256
let mut xsave = kvm_xsave::default(); // default is zeroed 4KB buffer with no FAM
257257

258-
// XSAVE area layout from Intel SDM Vol. 1 Section 13.4.1:
259-
// - Bytes 0-1: FCW (x87 FPU Control Word)
258+
// XSAVE legacy region layout (Intel SDM Vol. 1 Section 13.4.1):
259+
// - Bytes 0-1: FCW, 2-3: FSW
260260
// - Bytes 24-27: MXCSR
261-
// - Bytes 512-519: XSTATE_BV (bitmap of valid state components)
261+
// - Bytes 512-519: XSTATE_BV
262+
// - Bytes 520-527: XCOMP_BV (compaction format indicator)
263+
//
264+
// kvm_xsave.region is [u32], so region[0] covers FCW (low 16) and FSW (high 16, stays 0).
262265
xsave.region[0] = FP_CONTROL_WORD_DEFAULT as u32;
263266
xsave.region[6] = MXCSR_DEFAULT;
264267
// XSTATE_BV = 0x3: bits 0,1 = x87 + SSE valid. This tells KVM to apply
265268
// the legacy region from this buffer. Without this, some KVM versions
266269
// may ignore set_xsave entirely when XSTATE_BV=0.
267270
xsave.region[128] = 0x3;
271+
// Note: Unlike MSHV/WHP, we don't preserve XCOMP_BV because KVM uses
272+
// standard (non-compacted) XSAVE format where XCOMP_BV remains 0.
268273

269274
// SAFETY: No dynamic features enabled, 4KB is sufficient
270275
unsafe {

src/hyperlight_host/src/hypervisor/virtual_machine/mod.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,12 @@ pub(crate) enum HypervisorType {
100100
Whp,
101101
}
102102

103+
/// Minimum XSAVE buffer size: 512 bytes legacy region + 64 bytes header.
104+
/// Only used by MSHV and WHP which use compacted XSAVE format and need to
105+
/// validate buffer size before accessing XCOMP_BV.
106+
#[cfg(any(mshv3, target_os = "windows"))]
107+
pub(crate) const XSAVE_MIN_SIZE: usize = 576;
108+
103109
// Compiler error if no hypervisor type is available
104110
#[cfg(not(any(kvm, mshv3, target_os = "windows")))]
105111
compile_error!(

src/hyperlight_host/src/hypervisor/virtual_machine/mshv.rs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ use crate::hypervisor::regs::{
4141
};
4242
use crate::hypervisor::virtual_machine::{
4343
CreateVmError, MapMemoryError, RegisterError, RunVcpuError, UnmapMemoryError, VirtualMachine,
44-
VmExit,
44+
VmExit, XSAVE_MIN_SIZE,
4545
};
4646
use crate::mem::memory_region::{MemoryRegion, MemoryRegionFlags};
4747
#[cfg(feature = "trace_guest")]
@@ -293,8 +293,6 @@ impl VirtualMachine for MshvVm {
293293
}
294294

295295
fn reset_xsave(&self) -> std::result::Result<(), RegisterError> {
296-
const XSAVE_MIN_SIZE: usize = 576; // 512 legacy + 64 header
297-
298296
let current_xsave = self
299297
.vcpu_fd
300298
.get_xsave()

src/hyperlight_host/src/hypervisor/virtual_machine/whp.rs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ use crate::hypervisor::surrogate_process::SurrogateProcess;
3737
use crate::hypervisor::surrogate_process_manager::get_surrogate_process_manager;
3838
use crate::hypervisor::virtual_machine::{
3939
CreateVmError, HypervisorError, MapMemoryError, RegisterError, RunVcpuError, UnmapMemoryError,
40-
VirtualMachine, VmExit,
40+
VirtualMachine, VmExit, XSAVE_MIN_SIZE,
4141
};
4242
use crate::mem::memory_region::{MemoryRegion, MemoryRegionFlags};
4343
#[cfg(feature = "trace_guest")]
@@ -506,7 +506,6 @@ impl VirtualMachine for WhpVm {
506506
}
507507

508508
fn reset_xsave(&self) -> std::result::Result<(), RegisterError> {
509-
const XSAVE_MIN_SIZE: usize = 576; // 512 legacy + 64 header
510509
// WHP uses compacted XSAVE format (bit 63 of XCOMP_BV set).
511510
// We cannot just zero out the xsave area, we need to preserve the XCOMP_BV.
512511

@@ -611,7 +610,7 @@ impl VirtualMachine for WhpVm {
611610
}
612611

613612
let provided_size = std::mem::size_of_val(xsave) as u32;
614-
if buffer_size_needed > provided_size {
613+
if provided_size != buffer_size_needed {
615614
return Err(RegisterError::XsaveSizeMismatch {
616615
expected: buffer_size_needed,
617616
actual: provided_size,

src/hyperlight_host/src/sandbox/initialized_multi_use.rs

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,12 @@ impl MultiUseSandbox {
197197
/// - **Locked mutexes** - All lock state is reset
198198
/// - **Partial updates** - Data structures restored to their pre-execution state
199199
///
200+
/// # Errors
201+
///
202+
/// If this method returns an error, the sandbox will be poisoned regardless of its
203+
/// previous state. This is because a failed restore may leave the sandbox in an
204+
/// inconsistent state (e.g., memory partially restored but vCPU state not reset).
205+
/// The sandbox should not be used after a failed restore.
200206
///
201207
/// # Examples
202208
///
@@ -289,8 +295,13 @@ impl MultiUseSandbox {
289295
unsafe { self.vm.map_region(region) }.map_err(HyperlightVmError::MapRegion)?;
290296
}
291297

292-
// if this fails, then the sandbox will not have been restored so keep it poisoned (if it is)
293-
self.vm.reset_vcpu().map_err(HyperlightVmError::Restore)?;
298+
// Reset vCPU state (registers, FPU, debug registers, etc.)
299+
// If this fails, we're in an inconsistent state (memory restored but vCPU not reset),
300+
// so we must poison the sandbox.
301+
self.vm.reset_vcpu().map_err(|e| {
302+
self.poisoned = true;
303+
HyperlightVmError::Restore(e)
304+
})?;
294305

295306
// The restored snapshot is now our most current snapshot
296307
self.snapshot = Some(snapshot.clone());

0 commit comments

Comments
 (0)