Skip to content

Commit 3d20a45

Browse files
committed
Use WHvResetPartition on windows to clear guest state
Signed-off-by: Ludvig Liljenberg <4257730+ludfjig@users.noreply.github.com>
1 parent de25f36 commit 3d20a45

File tree

4 files changed

+120
-107
lines changed

4 files changed

+120
-107
lines changed

Justfile

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -315,6 +315,11 @@ clippy target=default-target: (witguest-wit)
315315
clippyw target=default-target: (witguest-wit)
316316
{{ cargo-cmd }} clippy --all-targets --all-features --target x86_64-pc-windows-gnu --profile={{ if target == "debug" { "dev" } else { target } }} -- -D warnings
317317

318+
# Cross-check for linux from a windows host using clippy (no linking needed).
319+
# Only checks lib targets to avoid dev-dependencies (criterion->alloca) that need a C cross-compiler.
320+
clippyl target=default-target:
321+
{{ cargo-cmd }} clippy --lib --all-features --target x86_64-unknown-linux-gnu --profile={{ if target == "debug" { "dev" } else { target } }} -- -D warnings
322+
318323
clippy-guests target=default-target: (witguest-wit) (ensure-cargo-hyperlight)
319324
cd src/tests/rust_guests/simpleguest && cargo hyperlight clippy --profile={{ if target == "debug" { "dev" } else { target } }} -- -D warnings
320325
cd src/tests/rust_guests/witguest && cargo hyperlight clippy --profile={{ if target == "debug" { "dev" } else { target } }} -- -D warnings

src/hyperlight_host/src/hypervisor/hyperlight_vm/x86_64.rs

Lines changed: 86 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -40,9 +40,9 @@ use crate::hypervisor::gdb::{
4040
};
4141
#[cfg(gdb)]
4242
use crate::hypervisor::gdb::{DebugError, DebugMemoryAccessError};
43-
use crate::hypervisor::regs::{
44-
CommonDebugRegs, CommonFpu, CommonRegisters, CommonSpecialRegisters,
45-
};
43+
#[cfg(not(target_os = "windows"))]
44+
use crate::hypervisor::regs::CommonDebugRegs;
45+
use crate::hypervisor::regs::{CommonFpu, CommonRegisters, CommonSpecialRegisters};
4646
#[cfg(not(gdb))]
4747
use crate::hypervisor::virtual_machine::VirtualMachine;
4848
#[cfg(kvm)]
@@ -330,22 +330,29 @@ impl HyperlightVm {
330330
}
331331

332332
/// Resets the following vCPU state:
333-
/// - General purpose registers
334-
/// - Debug registers
335-
/// - XSAVE (includes FPU/SSE state with proper FCW and MXCSR defaults)
336-
/// - Special registers (restored from snapshot, with CR3 updated to new page table location)
333+
/// - On Windows: calls WHvResetPartition (resets all per-VP state including
334+
/// GP registers, debug registers, XSAVE, MSRs, APIC, etc.)
335+
/// - On Linux: explicitly resets GP registers, debug registers, and XSAVE
336+
/// - On all platforms: restores special registers from snapshot with CR3
337+
/// updated to new page table location
337338
// TODO: check if other state needs to be reset
338339
pub(crate) fn reset_vcpu(
339340
&mut self,
340341
cr3: u64,
341342
sregs: &CommonSpecialRegisters,
342343
) -> std::result::Result<(), RegisterError> {
343-
self.vm.set_regs(&CommonRegisters {
344-
rflags: 1 << 1, // Reserved bit always set
345-
..Default::default()
346-
})?;
347-
self.vm.set_debug_regs(&CommonDebugRegs::default())?;
348-
self.vm.reset_xsave()?;
344+
#[cfg(target_os = "windows")]
345+
self.vm.reset_partition()?;
346+
347+
#[cfg(not(target_os = "windows"))]
348+
{
349+
self.vm.set_regs(&CommonRegisters {
350+
rflags: 1 << 1, // Reserved bit always set
351+
..Default::default()
352+
})?;
353+
self.vm.set_debug_regs(&CommonDebugRegs::default())?;
354+
self.vm.reset_xsave()?;
355+
}
349356

350357
#[cfg(not(feature = "nanvix-unstable"))]
351358
{
@@ -880,7 +887,9 @@ mod tests {
880887
use super::*;
881888
#[cfg(kvm)]
882889
use crate::hypervisor::regs::FP_CONTROL_WORD_DEFAULT;
883-
use crate::hypervisor::regs::{CommonSegmentRegister, CommonTableRegister, MXCSR_DEFAULT};
890+
use crate::hypervisor::regs::{
891+
CommonDebugRegs, CommonSegmentRegister, CommonTableRegister, MXCSR_DEFAULT,
892+
};
884893
use crate::hypervisor::virtual_machine::VirtualMachine;
885894
use crate::mem::layout::SandboxMemoryLayout;
886895
use crate::mem::memory_region::{GuestMemoryRegion, MemoryRegionFlags};
@@ -1184,9 +1193,18 @@ mod tests {
11841193
// Assertion Helpers - Verify vCPU state after reset
11851194
// ==========================================================================
11861195

1187-
/// Assert that debug registers are in reset state.
1188-
/// Reserved bits in DR6/DR7 are read-only (set by CPU), so we only check
1189-
/// that writable bits are cleared to 0 and DR0-DR3 are zeroed.
1196+
/// Assert that debug registers are in architectural reset state.
1197+
///
1198+
/// On Linux (KVM/MSHV): reset_vcpu explicitly zeroes debug registers.
1199+
///
1200+
/// On Windows: WHvResetPartition resets to power-on defaults per
1201+
/// Intel SDM Vol. 3, Table 10-1:
1202+
/// DR0-DR3 = 0 (breakpoint addresses cleared)
1203+
/// DR6 = 0xFFFF0FF0 (reserved bits set, writable bits cleared)
1204+
/// DR7 = 0x00000400 (reserved bit 10 set, all enables cleared)
1205+
///
1206+
/// Reserved bits in DR6/DR7 are read-only and CPU-dependent, so we only
1207+
/// verify that writable bits are cleared to 0 and DR0-DR3 are zeroed.
11901208
fn assert_debug_regs_reset(vm: &dyn VirtualMachine) {
11911209
let debug_regs = vm.debug_regs().unwrap();
11921210
let expected = CommonDebugRegs {
@@ -1201,19 +1219,58 @@ mod tests {
12011219
}
12021220

12031221
/// Assert that general-purpose registers are in reset state.
1204-
/// After reset, all registers should be zeroed except rflags which has
1205-
/// reserved bit 1 always set.
1222+
///
1223+
/// On Linux (KVM/MSHV): reset_vcpu explicitly zeroes all GP regs and sets
1224+
/// rflags = 0x2, so we verify all-zeros.
1225+
///
1226+
/// On Windows: WHvResetPartition sets architectural power-on defaults
1227+
/// per Intel SDM Vol. 3, Table 10-1:
1228+
/// RIP = 0xFFF0 (reset vector)
1229+
/// RDX = CPUID signature (CPU-dependent stepping/model/family)
1230+
/// RFLAGS = 0x2 (only reserved bit 1 set)
1231+
/// All other GP regs = 0
1232+
/// These are overwritten by dispatch_call_from_host before guest execution,
1233+
/// but we still verify the power-on state is correct.
12061234
fn assert_regs_reset(vm: &dyn VirtualMachine) {
1235+
let regs = vm.regs().unwrap();
1236+
#[cfg(not(target_os = "windows"))]
12071237
assert_eq!(
1208-
vm.regs().unwrap(),
1238+
regs,
12091239
CommonRegisters {
1210-
rflags: 1 << 1, // Reserved bit 1 is always set
1240+
rflags: 1 << 1,
12111241
..Default::default()
12121242
}
12131243
);
1244+
#[cfg(target_os = "windows")]
1245+
{
1246+
// WHvResetPartition sets x86 power-on reset values
1247+
// (Intel SDM Vol. 3, Table 10-1)
1248+
let expected = CommonRegisters {
1249+
rip: 0xFFF0, // Reset vector
1250+
rdx: regs.rdx, // CPUID signature (CPU-dependent)
1251+
rflags: 0x2, // Reserved bit 1
1252+
..Default::default()
1253+
};
1254+
assert_ne!(
1255+
regs.rdx, 0x4444444444444444,
1256+
"RDX should not retain dirty value"
1257+
);
1258+
assert_eq!(regs, expected);
1259+
}
12141260
}
12151261

12161262
/// Assert that FPU state is in reset state.
1263+
///
1264+
/// On Linux (KVM/MSHV): reset_vcpu calls reset_xsave which zeroes FPU state
1265+
/// and sets FCW/MXCSR to defaults.
1266+
///
1267+
/// On Windows: WHvResetPartition resets to power-on defaults per
1268+
/// Intel SDM Vol. 3, Table 10-1 (FINIT-equivalent state):
1269+
/// FCW = 0x037F (all exceptions masked, precision=64-bit, round=nearest)
1270+
/// FSW = 0, FTW = 0 (all empty), FOP = 0, FIP = 0, FDP = 0
1271+
/// MXCSR = 0x1F80 (all SIMD exceptions masked, round=nearest)
1272+
/// ST0-ST7 = 0, XMM0-XMM15 = 0
1273+
///
12171274
/// Handles hypervisor-specific quirks (KVM MXCSR, empty FPU registers).
12181275
fn assert_fpu_reset(vm: &dyn VirtualMachine) {
12191276
let fpu = vm.fpu().unwrap();
@@ -1223,8 +1280,14 @@ mod tests {
12231280
assert_eq!(fpu, expected_fpu);
12241281
}
12251282

1226-
/// Assert that special registers are in reset state.
1227-
/// Handles hypervisor-specific differences in hidden descriptor cache fields.
1283+
/// Assert that special registers match the expected snapshot state.
1284+
///
1285+
/// After reset_vcpu, sregs are explicitly restored from the snapshot
1286+
/// (with CR3 updated). This verifies they match the expected 64-bit
1287+
/// long mode configuration from CommonSpecialRegisters::standard_64bit_defaults.
1288+
///
1289+
/// Handles hypervisor-specific differences in hidden descriptor cache fields
1290+
/// (unusable, granularity, type_ for unused segments).
12281291
fn assert_sregs_reset(vm: &dyn VirtualMachine, pml4_addr: u64) {
12291292
let defaults = CommonSpecialRegisters::standard_64bit_defaults(pml4_addr);
12301293
let sregs = vm.sregs().unwrap();

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

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ pub(crate) enum HypervisorType {
103103
/// Minimum XSAVE buffer size: 512 bytes legacy region + 64 bytes header.
104104
/// Only used by MSHV and WHP which use compacted XSAVE format and need to
105105
/// validate buffer size before accessing XCOMP_BV.
106-
#[cfg(any(mshv3, target_os = "windows"))]
106+
#[cfg(mshv3)]
107107
pub(crate) const XSAVE_MIN_SIZE: usize = 576;
108108

109109
/// Standard XSAVE buffer size (4KB) used by KVM and MSHV.
@@ -240,6 +240,9 @@ pub enum RegisterError {
240240
#[cfg(target_os = "windows")]
241241
#[error("Failed to convert WHP registers: {0}")]
242242
ConversionFailed(String),
243+
#[cfg(target_os = "windows")]
244+
#[error("Failed to reset partition: {0}")]
245+
ResetPartition(HypervisorError),
243246
}
244247

245248
/// Map memory error
@@ -337,18 +340,32 @@ pub(crate) trait VirtualMachine: Debug + Send {
337340
#[allow(dead_code)]
338341
fn debug_regs(&self) -> std::result::Result<CommonDebugRegs, RegisterError>;
339342
/// Set the debug registers of the vCPU
343+
#[allow(dead_code)]
340344
fn set_debug_regs(&self, drs: &CommonDebugRegs) -> std::result::Result<(), RegisterError>;
341345

342346
/// Get xsave
343347
#[allow(dead_code)]
344348
fn xsave(&self) -> std::result::Result<Vec<u8>, RegisterError>;
345349
/// Reset xsave to default state
350+
#[cfg(not(target_os = "windows"))]
346351
fn reset_xsave(&self) -> std::result::Result<(), RegisterError>;
347352
/// Set xsave - only used for tests
348353
#[cfg(test)]
349354
#[cfg(not(feature = "nanvix-unstable"))]
350355
fn set_xsave(&self, xsave: &[u32]) -> std::result::Result<(), RegisterError>;
351356

357+
/// Reset the partition using WHvResetPartition.
358+
///
359+
/// Resets all per-VP state to architectural defaults: GP registers, segment
360+
/// registers, control registers, EFER, MSRs, debug registers, full XSAVE
361+
/// state (x87/SSE/AVX/AVX-512/AMX), XCR0, APIC/LAPIC, pending interrupts,
362+
/// and VMCS/VMCB internals.
363+
///
364+
/// Does NOT reset: GPA memory mappings or contents, partition configuration,
365+
/// notification ports, or VPCI device state.
366+
#[cfg(target_os = "windows")]
367+
fn reset_partition(&self) -> std::result::Result<(), RegisterError>;
368+
352369
/// Get partition handle
353370
#[cfg(target_os = "windows")]
354371
fn partition_handle(&self) -> windows::Win32::System::Hypervisor::WHV_PARTITION_HANDLE;

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

Lines changed: 11 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -31,15 +31,14 @@ use windows_result::HRESULT;
3131
use crate::hypervisor::gdb::{DebugError, DebuggableVm};
3232
use crate::hypervisor::regs::{
3333
Align16, CommonDebugRegs, CommonFpu, CommonRegisters, CommonSpecialRegisters,
34-
FP_CONTROL_WORD_DEFAULT, MXCSR_DEFAULT, WHP_DEBUG_REGS_NAMES, WHP_DEBUG_REGS_NAMES_LEN,
35-
WHP_FPU_NAMES, WHP_FPU_NAMES_LEN, WHP_REGS_NAMES, WHP_REGS_NAMES_LEN, WHP_SREGS_NAMES,
36-
WHP_SREGS_NAMES_LEN,
34+
WHP_DEBUG_REGS_NAMES, WHP_DEBUG_REGS_NAMES_LEN, WHP_FPU_NAMES, WHP_FPU_NAMES_LEN,
35+
WHP_REGS_NAMES, WHP_REGS_NAMES_LEN, WHP_SREGS_NAMES, WHP_SREGS_NAMES_LEN,
3736
};
3837
use crate::hypervisor::surrogate_process::SurrogateProcess;
3938
use crate::hypervisor::surrogate_process_manager::get_surrogate_process_manager;
4039
use crate::hypervisor::virtual_machine::{
4140
CreateVmError, HypervisorError, MapMemoryError, RegisterError, RunVcpuError, UnmapMemoryError,
42-
VirtualMachine, VmExit, XSAVE_MIN_SIZE,
41+
VirtualMachine, VmExit,
4342
};
4443
use crate::hypervisor::wrappers::HandleWrapper;
4544
use crate::mem::memory_region::{MemoryRegion, MemoryRegionFlags, MemoryRegionType};
@@ -549,85 +548,6 @@ impl VirtualMachine for WhpVm {
549548
Ok(xsave_buffer)
550549
}
551550

552-
fn reset_xsave(&self) -> std::result::Result<(), RegisterError> {
553-
// WHP uses compacted XSAVE format (bit 63 of XCOMP_BV set).
554-
// We cannot just zero out the xsave area, we need to preserve the XCOMP_BV.
555-
556-
// Get the required buffer size by calling with NULL buffer.
557-
let mut buffer_size_needed: u32 = 0;
558-
559-
let result = unsafe {
560-
WHvGetVirtualProcessorXsaveState(
561-
self.partition,
562-
0,
563-
std::ptr::null_mut(),
564-
0,
565-
&mut buffer_size_needed,
566-
)
567-
};
568-
569-
// Expect insufficient buffer error; any other error is unexpected
570-
if let Err(e) = result
571-
&& e.code() != windows::Win32::Foundation::WHV_E_INSUFFICIENT_BUFFER
572-
{
573-
return Err(RegisterError::GetXsaveSize(e.into()));
574-
}
575-
576-
if buffer_size_needed < XSAVE_MIN_SIZE as u32 {
577-
return Err(RegisterError::XsaveSizeMismatch {
578-
expected: XSAVE_MIN_SIZE as u32,
579-
actual: buffer_size_needed,
580-
});
581-
}
582-
583-
// Create a buffer to hold the current state (to get the correct XCOMP_BV)
584-
let mut current_state = vec![0u8; buffer_size_needed as usize];
585-
let mut written_bytes = 0;
586-
unsafe {
587-
WHvGetVirtualProcessorXsaveState(
588-
self.partition,
589-
0,
590-
current_state.as_mut_ptr() as *mut std::ffi::c_void,
591-
buffer_size_needed,
592-
&mut written_bytes,
593-
)
594-
.map_err(|e| RegisterError::GetXsave(e.into()))?;
595-
};
596-
597-
// Zero out most of the buffer, preserving only XCOMP_BV (520-528).
598-
// Extended components with XSTATE_BV bit=0 will use their init values.
599-
//
600-
// - Legacy region (0-512): x87 FPU + SSE state
601-
// - XSTATE_BV (512-520): Feature bitmap
602-
// - XCOMP_BV (520-528): Compaction bitmap + format bit (KEEP)
603-
// - Reserved (528-576): Header padding
604-
// - Extended (576+): AVX, AVX-512, MPX, PKRU, AMX, etc.
605-
current_state[0..520].fill(0);
606-
current_state[528..].fill(0);
607-
608-
// XSAVE area layout from Intel SDM Vol. 1 Section 13.4.1:
609-
// - Bytes 0-1: FCW (x87 FPU Control Word)
610-
// - Bytes 24-27: MXCSR
611-
// - Bytes 512-519: XSTATE_BV (bitmap of valid state components)
612-
current_state[0..2].copy_from_slice(&FP_CONTROL_WORD_DEFAULT.to_le_bytes());
613-
current_state[24..28].copy_from_slice(&MXCSR_DEFAULT.to_le_bytes());
614-
// XSTATE_BV = 0x3: bits 0,1 = x87 + SSE valid. Explicitly tell hypervisor
615-
// to apply the legacy region from this buffer for consistent behavior.
616-
current_state[512..520].copy_from_slice(&0x3u64.to_le_bytes());
617-
618-
unsafe {
619-
WHvSetVirtualProcessorXsaveState(
620-
self.partition,
621-
0,
622-
current_state.as_ptr() as *const std::ffi::c_void,
623-
buffer_size_needed,
624-
)
625-
.map_err(|e| RegisterError::SetXsave(e.into()))?;
626-
}
627-
628-
Ok(())
629-
}
630-
631551
#[cfg(test)]
632552
#[cfg(not(feature = "nanvix-unstable"))]
633553
fn set_xsave(&self, xsave: &[u32]) -> std::result::Result<(), RegisterError> {
@@ -674,6 +594,14 @@ impl VirtualMachine for WhpVm {
674594
Ok(())
675595
}
676596

597+
fn reset_partition(&self) -> std::result::Result<(), RegisterError> {
598+
unsafe {
599+
WHvResetPartition(self.partition)
600+
.map_err(|e| RegisterError::ResetPartition(e.into()))?;
601+
}
602+
Ok(())
603+
}
604+
677605
/// Get the partition handle for this VM
678606
fn partition_handle(&self) -> WHV_PARTITION_HANDLE {
679607
self.partition

0 commit comments

Comments
 (0)