CVM register validation#3960
Conversation
|
Whats the reason for needing this validation? Are we seeing callers pass bad values in and we can't just fix the callers? Should we not respect their decision to crash themselves? |
The issue is that if the guest writes invalid values to its registers, the next time the hypervisor tries to run the VMSA VMRUN fails with an invalid VMSA error. This is particularly bad as the host cannot read the VMSA to see what is invalid, and the VP cannot run. |
|
We should probably trace a warn on any of these invalid writes then |
I thought about it but thought it might be too much given how many occur though we can use rate limiting. The error will cause the guest to see a #GP, which is the right place for the error to surface to a misbehaving guest |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.
Comments suppressed due to low confidence (1)
openhcl/virt_mshv_vtl/src/processor/hardware_cvm/mod.rs:705
- CR0 normalization here clears some reserved low bits, but it still allows architecturally-invalid state to slip through:
- CR0 bits 63:32 are reserved and should be forced to 0 (or rejected).
- CR0.ET (bit 4) is architecturally fixed to 1 on x86_64 (TDX path already forces this via
value | X64_CR0_ET).
Leaving either case unhandled can still result in bad guest state being committed viaset_registers()and later failing at entry/run time.
HvX64RegisterName::Cr0 | HvX64RegisterName::IntermediateCr0 => {
let mut cr0 = reg.value.as_u64();
cr0 &= !X64_CR0_RESERVED_BITS_MASK_LOW32;
// Bit 6 must be zero; PG requires PE; NW requires CD.
if cr0 & X64_CR0_RSVDZ1_MASK != 0
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 5 out of 5 changed files in this pull request and generated 1 comment.
Comments suppressed due to low confidence (1)
openhcl/virt_mshv_vtl/src/processor/hardware_cvm/mod.rs:708
- CR0 validation/normalization only clears a subset of reserved bits in the low 32 bits, but it leaves any bits set in CR0[63:32] untouched. Those upper bits are architecturally reserved and can propagate into the backing state via
registers.cr0 = reg.value.as_u64(), potentially reintroducing the invalid state this validation is trying to prevent. Consider clearing the upper 32 bits (or rejecting nonzero upper bits) during normalization.
HvX64RegisterName::Cr0 | HvX64RegisterName::IntermediateCr0 => {
let mut cr0 = reg.value.as_u64();
cr0 &= !X64_CR0_RESERVED_BITS_MASK_LOW32;
// Bit 6 must be zero; PG requires PE; NW requires CD.
if cr0 & X64_CR0_RSVDZ1_MASK != 0
|| (cr0 & X64_CR0_PG != 0 && cr0 & X64_CR0_PE == 0)
|| (cr0 & X64_CR0_NW != 0 && cr0 & X64_CR0_CD == 0)
{
return Err(HvError::InvalidRegisterValue);
}
reg.value = HvRegisterValue::from(cr0);
}
| const CVM_GUEST_VA_BITS_48: u32 = 48; | ||
| const CVM_GUEST_VA_BITS_57: u32 = 57; | ||
| const X64_CR0_RESERVED_BITS_MASK_LOW32: u64 = 0x1FFA_FF80; | ||
| const X64_CR0_RSVDZ1_MASK: u64 = 1 << 6; |
There was a problem hiding this comment.
Should these be in x86defs?
| x86defs::X64_MSR_KERNEL_GS_BASE | ||
| | x86defs::X86X_MSR_LSTAR | ||
| | x86defs::X86X_MSR_CSTAR | ||
| | x86defs::X86X_MSR_SYSENTER_EIP | ||
| | x86defs::X86X_MSR_SYSENTER_ESP | ||
| | x86defs::X86X_MSR_INTERRUPT_SSP_TABLE_ADDR => { | ||
| if !is_canonical_address(value, CVM_GUEST_VA_BITS_57) { | ||
| return Err(MsrError::InvalidAccess); | ||
| } |
| | HvX64RegisterName::InterruptSspTableAddr => { | ||
| if !is_canonical_address(reg.value.as_u64(), CVM_GUEST_VA_BITS_57) { | ||
| return Err(HvError::InvalidRegisterValue); | ||
| } | ||
| } |
Add per-register / per-MSR validation on the SetVpRegisters and WRMSR paths to help prevent invalid VMSA state which cause failures at VMRUN