[PATCH 03/24] KVM: x86: hyperv: Introduce memory fault on hcalls with bad ingpas
From: Paolo Bonzini
Date: Thu Jul 16 2026 - 14:16:36 EST
From: Nicolas Saenz Julienne <nsaenz@xxxxxxxxxx>
Signed-off-by: Nicolas Saenz Julienne <nsaenz@xxxxxxxxxx>
Signed-off-by: Paolo Bonzini <pbonzini@xxxxxxxxxx>
---
arch/x86/include/asm/kvm_host.h | 1 +
arch/x86/kvm/hyperv.c | 32 ++++++++++++++++++++++++++++++++
arch/x86/kvm/x86.c | 7 +++++++
include/uapi/linux/kvm.h | 1 +
4 files changed, 41 insertions(+)
diff --git a/arch/x86/include/asm/kvm_host.h b/arch/x86/include/asm/kvm_host.h
index b517257a6315..d0c1d0ab03c2 100644
--- a/arch/x86/include/asm/kvm_host.h
+++ b/arch/x86/include/asm/kvm_host.h
@@ -1274,6 +1274,7 @@ struct kvm_hv {
struct kvm_hv_syndbg hv_syndbg;
bool xsaves_xsavec_checked;
+ bool hcall_fault_exit;
};
#endif
diff --git a/arch/x86/kvm/hyperv.c b/arch/x86/kvm/hyperv.c
index 1ee0d23f8949..d688759df081 100644
--- a/arch/x86/kvm/hyperv.c
+++ b/arch/x86/kvm/hyperv.c
@@ -2531,11 +2531,30 @@ static bool hv_check_hypercall_access(struct kvm_vcpu_hv *hv_vcpu, u16 code)
return true;
}
+static unsigned int kvm_hv_hypercall_mem_access(u16 code)
+{
+ switch (code) {
+ case HVCALL_SIGNAL_EVENT:
+ case HVCALL_FLUSH_VIRTUAL_ADDRESS_LIST:
+ case HVCALL_FLUSH_VIRTUAL_ADDRESS_LIST_EX:
+ case HVCALL_FLUSH_VIRTUAL_ADDRESS_SPACE:
+ case HVCALL_FLUSH_VIRTUAL_ADDRESS_SPACE_EX:
+ case HVCALL_SEND_IPI:
+ case HVCALL_SEND_IPI_EX:
+ return KVM_MEMORY_EXIT_FLAG_READ;
+ }
+
+ return KVM_MEMORY_EXIT_FLAG_WRITE;
+}
+
int kvm_hv_hypercall(struct kvm_vcpu *vcpu)
{
struct kvm_vcpu_hv *hv_vcpu = to_hv_vcpu(vcpu);
+ struct kvm *kvm = vcpu->kvm;
struct kvm_hv_hcall hc;
u64 ret = HV_STATUS_SUCCESS;
+ unsigned int access;
+ unsigned long addr;
/*
* hypercall generates UD from non zero cpl and real mode
@@ -2590,6 +2609,19 @@ int kvm_hv_hypercall(struct kvm_vcpu *vcpu)
kvm_hv_hypercall_read_xmm(&hc);
}
+ if (!hc.fast && kvm->arch.hyperv.hcall_fault_exit) {
+ bool writable = true;
+ access = kvm_hv_hypercall_mem_access(hc.code);
+ addr = kvm_vcpu_gfn_to_hva_prot(vcpu, gpa_to_gfn(hc.ingpa), &writable);
+ if (addr == KVM_HVA_ERR_BAD ||
+ (access == KVM_MEMORY_EXIT_FLAG_WRITE && !writable)) {
+ kvm_prepare_memory_fault_exit(vcpu, hc.ingpa, PAGE_SIZE,
+ access == KVM_MEMORY_EXIT_FLAG_WRITE,
+ false, false);
+ return -EFAULT;
+ }
+ }
+
switch (hc.code) {
case HVCALL_NOTIFY_LONG_SPIN_WAIT:
if (unlikely(hc.rep || hc.var_cnt)) {
diff --git a/arch/x86/kvm/x86.c b/arch/x86/kvm/x86.c
index 0626e835e9eb..352d289076f6 100644
--- a/arch/x86/kvm/x86.c
+++ b/arch/x86/kvm/x86.c
@@ -2224,6 +2224,7 @@ int kvm_vm_ioctl_check_extension(struct kvm *kvm, long ext)
case KVM_CAP_HYPERV_CPUID:
case KVM_CAP_HYPERV_ENFORCE_CPUID:
case KVM_CAP_SYS_HYPERV_CPUID:
+ case KVM_CAP_HYPERV_HCALL_FAULT_EXIT:
#endif
case KVM_CAP_PCI_SEGMENT:
case KVM_CAP_DEBUGREGS:
@@ -4190,6 +4191,12 @@ int kvm_vm_ioctl_enable_cap(struct kvm *kvm,
mutex_unlock(&kvm->lock);
break;
}
+#ifdef CONFIG_KVM_HYPERV
+ case KVM_CAP_HYPERV_HCALL_FAULT_EXIT:
+ kvm->arch.hyperv.hcall_fault_exit = cap->args[0];
+ r = 0;
+ break;
+#endif
default:
r = -EINVAL;
break;
diff --git a/include/uapi/linux/kvm.h b/include/uapi/linux/kvm.h
index 720b1ffe880b..3b235e953923 100644
--- a/include/uapi/linux/kvm.h
+++ b/include/uapi/linux/kvm.h
@@ -1000,6 +1000,7 @@ struct kvm_enable_cap {
#define KVM_CAP_S390_KEYOP 247
#define KVM_CAP_S390_VSIE_ESAMODE 248
#define KVM_CAP_S390_HPAGE_2G 249
+#define KVM_CAP_HYPERV_HCALL_FAULT_EXIT 250
struct kvm_irq_routing_irqchip {
__u32 irqchip;
--
2.52.0