[PATCH 39/45] KVM: VMX: mark VM dead on shadow VMCS or eVMCS access failures

From: Paolo Bonzini

Date: Mon Nov 02 2026 - 13:05:05 EST


KVM ignores failures when reading or writing L1's in-memory VMCSs (shadow
VMCS, eVMCS), allowing execution to continue with stale shadow VMCS values
or silently losing updates. Check both the HVA cache initialization
and the cached memory accesses, and mark the VM dead on failure; these
failures can only occur when guest memory permissions deny the access,
and terminating the VM is safe (though it precludes using memory attributes
as a replacement for e.g. userfaultfd, and only leaves the feature usable
for security purposes).

Likewise, treat a failed read of an enabled VP assist page as fatal too,
otherwise KVM continues by disabling enlightened VMCS.

Signed-off-by: Paolo Bonzini <pbonzini@xxxxxxxxxx>
---
arch/x86/kvm/vmx/hyperv.c | 7 ++++++-
arch/x86/kvm/vmx/nested.c | 21 ++++++++++++++-------
2 files changed, 20 insertions(+), 8 deletions(-)

diff --git a/arch/x86/kvm/vmx/hyperv.c b/arch/x86/kvm/vmx/hyperv.c
index fa41d036acd4..746540e503e3 100644
--- a/arch/x86/kvm/vmx/hyperv.c
+++ b/arch/x86/kvm/vmx/hyperv.c
@@ -18,9 +18,14 @@ u64 nested_get_evmptr(struct kvm_vcpu *vcpu)
{
struct kvm_vcpu_hv *hv_vcpu = to_hv_vcpu(vcpu);

- if (unlikely(kvm_hv_get_assist_page(vcpu)))
+ if (unlikely(!kvm_hv_assist_page_enabled(vcpu)))
return EVMPTR_INVALID;

+ if (unlikely(kvm_hv_get_assist_page(vcpu))) {
+ kvm_vm_dead(vcpu->kvm);
+ return EVMPTR_INVALID;
+ }
+
if (unlikely(!hv_vcpu->vp_assist_page.enlighten_vmentry))
return EVMPTR_INVALID;

diff --git a/arch/x86/kvm/vmx/nested.c b/arch/x86/kvm/vmx/nested.c
index 2ac4a366ace4..bde151de0bf7 100644
--- a/arch/x86/kvm/vmx/nested.c
+++ b/arch/x86/kvm/vmx/nested.c
@@ -861,10 +861,13 @@ static void nested_cache_shadow_vmcs12(struct kvm_vcpu *vcpu,
if (ghc->gpa != vmcs12->vmcs_link_pointer &&
kvm_gfn_to_hva_cache_init(vcpu->kvm, ghc,
vmcs12->vmcs_link_pointer, VMCS12_SIZE))
- return;
+ goto error;

- kvm_read_guest_cached(vcpu->kvm, ghc, get_shadow_vmcs12(vcpu),
- VMCS12_SIZE);
+ if (!kvm_read_guest_cached(vcpu->kvm, ghc, get_shadow_vmcs12(vcpu),
+ VMCS12_SIZE))
+ return;
+error:
+ kvm_vm_dead(vcpu->kvm);
}

static void nested_flush_cached_shadow_vmcs12(struct kvm_vcpu *vcpu,
@@ -880,10 +883,13 @@ static void nested_flush_cached_shadow_vmcs12(struct kvm_vcpu *vcpu,
if (ghc->gpa != vmcs12->vmcs_link_pointer &&
kvm_gfn_to_hva_cache_init(vcpu->kvm, ghc,
vmcs12->vmcs_link_pointer, VMCS12_SIZE))
- return;
+ goto error;

- kvm_write_guest_cached(vcpu->kvm, ghc, get_shadow_vmcs12(vcpu),
- VMCS12_SIZE);
+ if (!kvm_write_guest_cached(vcpu->kvm, ghc, get_shadow_vmcs12(vcpu),
+ VMCS12_SIZE))
+ return;
+error:
+ kvm_vm_dead(vcpu->kvm);
}

/*
@@ -5091,7 +5097,8 @@ void __nested_vmx_vmexit(struct kvm_vcpu *vcpu, u32 vm_exit_reason,
* do that when something is forcing L2->L1 exit prior to
* the first L2 run.
*/
- (void)nested_get_evmcs_page(vcpu);
+ if (!nested_get_evmcs_page(vcpu))
+ kvm_vm_dead(vcpu->kvm);
#endif
}

--
2.52.0