VYPR
Unrated severityNVD Advisory· Published May 27, 2026· Updated May 27, 2026

CVE-2026-46076

CVE-2026-46076

Description

In the Linux kernel, the following vulnerability has been resolved:

KVM: nSVM: Raise #UD if unhandled VMMCALL isn't intercepted by L1

Explicitly synthesize a #UD for VMMCALL if L2 is active, L1 does NOT want to intercept VMMCALL, nested_svm_l2_tlb_flush_enabled() is true, and the hypercall is something other than one of the supported Hyper-V hypercalls. When all of the above conditions are met, KVM will intercept VMMCALL but never forward it to L1, i.e. will let L2 make hypercalls as if it were L1.

The TLFS says a whole lot of nothing about this scenario, so go with the architectural behavior, which says that VMMCALL #UDs if it's not intercepted.

Opportunistically do a 2-for-1 stub trade by stub-ifying the new API instead of the helpers it uses. The last remaining "single" stub will soon be dropped as well.

[sean: rewrite changelog and comment, tag for stable, remove defunct stubs]

Affected products

2

Patches

8
c36991c6f8d2

KVM: nSVM: Raise #UD if unhandled VMMCALL isn't intercepted by L1

4 files changed · +30 13
  • arch/x86/kvm/hyperv.h+0 8 modified
    diff --git a/arch/x86/kvm/hyperv.h b/arch/x86/kvm/hyperv.h
    index 6ce160ffa67865..6301f79fcbae7c 100644
    --- a/arch/x86/kvm/hyperv.h
    +++ b/arch/x86/kvm/hyperv.h
    @@ -305,14 +305,6 @@ static inline bool kvm_hv_has_stimer_pending(struct kvm_vcpu *vcpu)
     {
     	return false;
     }
    -static inline bool kvm_hv_is_tlb_flush_hcall(struct kvm_vcpu *vcpu)
    -{
    -	return false;
    -}
    -static inline bool guest_hv_cpuid_has_l2_tlb_flush(struct kvm_vcpu *vcpu)
    -{
    -	return false;
    -}
     static inline int kvm_hv_verify_vp_assist(struct kvm_vcpu *vcpu)
     {
     	return 0;
    
  • arch/x86/kvm/svm/hyperv.h+11 0 modified
    diff --git a/arch/x86/kvm/svm/hyperv.h b/arch/x86/kvm/svm/hyperv.h
    index d3f8bfc05832ee..9af03970d40c28 100644
    --- a/arch/x86/kvm/svm/hyperv.h
    +++ b/arch/x86/kvm/svm/hyperv.h
    @@ -41,6 +41,13 @@ static inline bool nested_svm_l2_tlb_flush_enabled(struct kvm_vcpu *vcpu)
     	return hv_vcpu->vp_assist_page.nested_control.features.directhypercall;
     }
     
    +static inline bool nested_svm_is_l2_tlb_flush_hcall(struct kvm_vcpu *vcpu)
    +{
    +	return guest_hv_cpuid_has_l2_tlb_flush(vcpu) &&
    +	       nested_svm_l2_tlb_flush_enabled(vcpu) &&
    +	       kvm_hv_is_tlb_flush_hcall(vcpu);
    +}
    +
     void svm_hv_inject_synthetic_vmexit_post_tlb_flush(struct kvm_vcpu *vcpu);
     #else /* CONFIG_KVM_HYPERV */
     static inline void nested_svm_hv_update_vm_vp_ids(struct kvm_vcpu *vcpu) {}
    @@ -48,6 +55,10 @@ static inline bool nested_svm_l2_tlb_flush_enabled(struct kvm_vcpu *vcpu)
     {
     	return false;
     }
    +static inline bool nested_svm_is_l2_tlb_flush_hcall(struct kvm_vcpu *vcpu)
    +{
    +	return false;
    +}
     static inline void svm_hv_inject_synthetic_vmexit_post_tlb_flush(struct kvm_vcpu *vcpu) {}
     #endif /* CONFIG_KVM_HYPERV */
     
    
  • arch/x86/kvm/svm/nested.c+1 3 modified
    diff --git a/arch/x86/kvm/svm/nested.c b/arch/x86/kvm/svm/nested.c
    index 5b639d98bf09d8..0f7893a7cb040a 100644
    --- a/arch/x86/kvm/svm/nested.c
    +++ b/arch/x86/kvm/svm/nested.c
    @@ -1738,9 +1738,7 @@ int nested_svm_exit_special(struct vcpu_svm *svm)
     	}
     	case SVM_EXIT_VMMCALL:
     		/* Hyper-V L2 TLB flush hypercall is handled by L0 */
    -		if (guest_hv_cpuid_has_l2_tlb_flush(vcpu) &&
    -		    nested_svm_l2_tlb_flush_enabled(vcpu) &&
    -		    kvm_hv_is_tlb_flush_hcall(vcpu))
    +		if (nested_svm_is_l2_tlb_flush_hcall(vcpu))
     			return NESTED_EXIT_HOST;
     		break;
     	default:
    
  • arch/x86/kvm/svm/svm.c+18 2 modified
    diff --git a/arch/x86/kvm/svm/svm.c b/arch/x86/kvm/svm/svm.c
    index 7efa7170929232..9e6864cf58d34e 100644
    --- a/arch/x86/kvm/svm/svm.c
    +++ b/arch/x86/kvm/svm/svm.c
    @@ -52,6 +52,7 @@
     #include "svm.h"
     #include "svm_ops.h"
     
    +#include "hyperv.h"
     #include "kvm_onhyperv.h"
     #include "svm_onhyperv.h"
     
    @@ -3248,6 +3249,22 @@ static int bus_lock_exit(struct kvm_vcpu *vcpu)
     	return 0;
     }
     
    +static int vmmcall_interception(struct kvm_vcpu *vcpu)
    +{
    +	/*
    +	 * Inject a #UD if L2 is active and the VMMCALL isn't a Hyper-V TLB
    +	 * hypercall, as VMMCALL #UDs if it's not intercepted, and this path is
    +	 * reachable if and only if L1 doesn't want to intercept VMMCALL or has
    +	 * enabled L0 (KVM) handling of Hyper-V L2 TLB flush hypercalls.
    +	 */
    +	if (is_guest_mode(vcpu) && !nested_svm_is_l2_tlb_flush_hcall(vcpu)) {
    +		kvm_queue_exception(vcpu, UD_VECTOR);
    +		return 1;
    +	}
    +
    +	return kvm_emulate_hypercall(vcpu);
    +}
    +
     static int (*const svm_exit_handlers[])(struct kvm_vcpu *vcpu) = {
     	[SVM_EXIT_READ_CR0]			= cr_interception,
     	[SVM_EXIT_READ_CR3]			= cr_interception,
    @@ -3298,7 +3315,7 @@ static int (*const svm_exit_handlers[])(struct kvm_vcpu *vcpu) = {
     	[SVM_EXIT_TASK_SWITCH]			= task_switch_interception,
     	[SVM_EXIT_SHUTDOWN]			= shutdown_interception,
     	[SVM_EXIT_VMRUN]			= vmrun_interception,
    -	[SVM_EXIT_VMMCALL]			= kvm_emulate_hypercall,
    +	[SVM_EXIT_VMMCALL]			= vmmcall_interception,
     	[SVM_EXIT_VMLOAD]			= vmload_interception,
     	[SVM_EXIT_VMSAVE]			= vmsave_interception,
     	[SVM_EXIT_STGI]				= stgi_interception,
    -- 
    cgit 1.3-korg
    
    
    
924d721fae95

KVM: nSVM: Raise #UD if unhandled VMMCALL isn't intercepted by L1

4 files changed · +30 13
  • arch/x86/kvm/hyperv.h+0 8 modified
    diff --git a/arch/x86/kvm/hyperv.h b/arch/x86/kvm/hyperv.h
    index 913bfc96959cbf..8d1ae8277038cd 100644
    --- a/arch/x86/kvm/hyperv.h
    +++ b/arch/x86/kvm/hyperv.h
    @@ -304,14 +304,6 @@ static inline bool kvm_hv_has_stimer_pending(struct kvm_vcpu *vcpu)
     {
     	return false;
     }
    -static inline bool kvm_hv_is_tlb_flush_hcall(struct kvm_vcpu *vcpu)
    -{
    -	return false;
    -}
    -static inline bool guest_hv_cpuid_has_l2_tlb_flush(struct kvm_vcpu *vcpu)
    -{
    -	return false;
    -}
     static inline int kvm_hv_verify_vp_assist(struct kvm_vcpu *vcpu)
     {
     	return 0;
    
  • arch/x86/kvm/svm/hyperv.h+11 0 modified
    diff --git a/arch/x86/kvm/svm/hyperv.h b/arch/x86/kvm/svm/hyperv.h
    index d3f8bfc05832ee..9af03970d40c28 100644
    --- a/arch/x86/kvm/svm/hyperv.h
    +++ b/arch/x86/kvm/svm/hyperv.h
    @@ -41,6 +41,13 @@ static inline bool nested_svm_l2_tlb_flush_enabled(struct kvm_vcpu *vcpu)
     	return hv_vcpu->vp_assist_page.nested_control.features.directhypercall;
     }
     
    +static inline bool nested_svm_is_l2_tlb_flush_hcall(struct kvm_vcpu *vcpu)
    +{
    +	return guest_hv_cpuid_has_l2_tlb_flush(vcpu) &&
    +	       nested_svm_l2_tlb_flush_enabled(vcpu) &&
    +	       kvm_hv_is_tlb_flush_hcall(vcpu);
    +}
    +
     void svm_hv_inject_synthetic_vmexit_post_tlb_flush(struct kvm_vcpu *vcpu);
     #else /* CONFIG_KVM_HYPERV */
     static inline void nested_svm_hv_update_vm_vp_ids(struct kvm_vcpu *vcpu) {}
    @@ -48,6 +55,10 @@ static inline bool nested_svm_l2_tlb_flush_enabled(struct kvm_vcpu *vcpu)
     {
     	return false;
     }
    +static inline bool nested_svm_is_l2_tlb_flush_hcall(struct kvm_vcpu *vcpu)
    +{
    +	return false;
    +}
     static inline void svm_hv_inject_synthetic_vmexit_post_tlb_flush(struct kvm_vcpu *vcpu) {}
     #endif /* CONFIG_KVM_HYPERV */
     
    
  • arch/x86/kvm/svm/nested.c+1 3 modified
    diff --git a/arch/x86/kvm/svm/nested.c b/arch/x86/kvm/svm/nested.c
    index 4956833b70193b..6eb0e840a565fd 100644
    --- a/arch/x86/kvm/svm/nested.c
    +++ b/arch/x86/kvm/svm/nested.c
    @@ -1532,9 +1532,7 @@ int nested_svm_exit_special(struct vcpu_svm *svm)
     	}
     	case SVM_EXIT_VMMCALL:
     		/* Hyper-V L2 TLB flush hypercall is handled by L0 */
    -		if (guest_hv_cpuid_has_l2_tlb_flush(vcpu) &&
    -		    nested_svm_l2_tlb_flush_enabled(vcpu) &&
    -		    kvm_hv_is_tlb_flush_hcall(vcpu))
    +		if (nested_svm_is_l2_tlb_flush_hcall(vcpu))
     			return NESTED_EXIT_HOST;
     		break;
     	default:
    
  • arch/x86/kvm/svm/svm.c+18 2 modified
    diff --git a/arch/x86/kvm/svm/svm.c b/arch/x86/kvm/svm/svm.c
    index 86d829220e80fa..6ca9bd96c34e79 100644
    --- a/arch/x86/kvm/svm/svm.c
    +++ b/arch/x86/kvm/svm/svm.c
    @@ -49,6 +49,7 @@
     #include "svm.h"
     #include "svm_ops.h"
     
    +#include "hyperv.h"
     #include "kvm_onhyperv.h"
     #include "svm_onhyperv.h"
     
    @@ -3377,6 +3378,22 @@ static int invpcid_interception(struct kvm_vcpu *vcpu)
     	return kvm_handle_invpcid(vcpu, type, gva);
     }
     
    +static int vmmcall_interception(struct kvm_vcpu *vcpu)
    +{
    +	/*
    +	 * Inject a #UD if L2 is active and the VMMCALL isn't a Hyper-V TLB
    +	 * hypercall, as VMMCALL #UDs if it's not intercepted, and this path is
    +	 * reachable if and only if L1 doesn't want to intercept VMMCALL or has
    +	 * enabled L0 (KVM) handling of Hyper-V L2 TLB flush hypercalls.
    +	 */
    +	if (is_guest_mode(vcpu) && !nested_svm_is_l2_tlb_flush_hcall(vcpu)) {
    +		kvm_queue_exception(vcpu, UD_VECTOR);
    +		return 1;
    +	}
    +
    +	return kvm_emulate_hypercall(vcpu);
    +}
    +
     static int (*const svm_exit_handlers[])(struct kvm_vcpu *vcpu) = {
     	[SVM_EXIT_READ_CR0]			= cr_interception,
     	[SVM_EXIT_READ_CR3]			= cr_interception,
    @@ -3427,7 +3444,7 @@ static int (*const svm_exit_handlers[])(struct kvm_vcpu *vcpu) = {
     	[SVM_EXIT_TASK_SWITCH]			= task_switch_interception,
     	[SVM_EXIT_SHUTDOWN]			= shutdown_interception,
     	[SVM_EXIT_VMRUN]			= vmrun_interception,
    -	[SVM_EXIT_VMMCALL]			= kvm_emulate_hypercall,
    +	[SVM_EXIT_VMMCALL]			= vmmcall_interception,
     	[SVM_EXIT_VMLOAD]			= vmload_interception,
     	[SVM_EXIT_VMSAVE]			= vmsave_interception,
     	[SVM_EXIT_STGI]				= stgi_interception,
    -- 
    cgit 1.3-korg
    
    
    
009c0f726abe

KVM: nSVM: Raise #UD if unhandled VMMCALL isn't intercepted by L1

4 files changed · +30 13
  • arch/x86/kvm/hyperv.h+0 8 modified
    diff --git a/arch/x86/kvm/hyperv.h b/arch/x86/kvm/hyperv.h
    index 6ce160ffa67865..6301f79fcbae7c 100644
    --- a/arch/x86/kvm/hyperv.h
    +++ b/arch/x86/kvm/hyperv.h
    @@ -305,14 +305,6 @@ static inline bool kvm_hv_has_stimer_pending(struct kvm_vcpu *vcpu)
     {
     	return false;
     }
    -static inline bool kvm_hv_is_tlb_flush_hcall(struct kvm_vcpu *vcpu)
    -{
    -	return false;
    -}
    -static inline bool guest_hv_cpuid_has_l2_tlb_flush(struct kvm_vcpu *vcpu)
    -{
    -	return false;
    -}
     static inline int kvm_hv_verify_vp_assist(struct kvm_vcpu *vcpu)
     {
     	return 0;
    
  • arch/x86/kvm/svm/hyperv.h+11 0 modified
    diff --git a/arch/x86/kvm/svm/hyperv.h b/arch/x86/kvm/svm/hyperv.h
    index d3f8bfc05832ee..9af03970d40c28 100644
    --- a/arch/x86/kvm/svm/hyperv.h
    +++ b/arch/x86/kvm/svm/hyperv.h
    @@ -41,6 +41,13 @@ static inline bool nested_svm_l2_tlb_flush_enabled(struct kvm_vcpu *vcpu)
     	return hv_vcpu->vp_assist_page.nested_control.features.directhypercall;
     }
     
    +static inline bool nested_svm_is_l2_tlb_flush_hcall(struct kvm_vcpu *vcpu)
    +{
    +	return guest_hv_cpuid_has_l2_tlb_flush(vcpu) &&
    +	       nested_svm_l2_tlb_flush_enabled(vcpu) &&
    +	       kvm_hv_is_tlb_flush_hcall(vcpu);
    +}
    +
     void svm_hv_inject_synthetic_vmexit_post_tlb_flush(struct kvm_vcpu *vcpu);
     #else /* CONFIG_KVM_HYPERV */
     static inline void nested_svm_hv_update_vm_vp_ids(struct kvm_vcpu *vcpu) {}
    @@ -48,6 +55,10 @@ static inline bool nested_svm_l2_tlb_flush_enabled(struct kvm_vcpu *vcpu)
     {
     	return false;
     }
    +static inline bool nested_svm_is_l2_tlb_flush_hcall(struct kvm_vcpu *vcpu)
    +{
    +	return false;
    +}
     static inline void svm_hv_inject_synthetic_vmexit_post_tlb_flush(struct kvm_vcpu *vcpu) {}
     #endif /* CONFIG_KVM_HYPERV */
     
    
  • arch/x86/kvm/svm/nested.c+1 3 modified
    diff --git a/arch/x86/kvm/svm/nested.c b/arch/x86/kvm/svm/nested.c
    index 1105c62c2103eb..bb250449fd4141 100644
    --- a/arch/x86/kvm/svm/nested.c
    +++ b/arch/x86/kvm/svm/nested.c
    @@ -1672,9 +1672,7 @@ int nested_svm_exit_special(struct vcpu_svm *svm)
     	}
     	case SVM_EXIT_VMMCALL:
     		/* Hyper-V L2 TLB flush hypercall is handled by L0 */
    -		if (guest_hv_cpuid_has_l2_tlb_flush(vcpu) &&
    -		    nested_svm_l2_tlb_flush_enabled(vcpu) &&
    -		    kvm_hv_is_tlb_flush_hcall(vcpu))
    +		if (nested_svm_is_l2_tlb_flush_hcall(vcpu))
     			return NESTED_EXIT_HOST;
     		break;
     	default:
    
  • arch/x86/kvm/svm/svm.c+18 2 modified
    diff --git a/arch/x86/kvm/svm/svm.c b/arch/x86/kvm/svm/svm.c
    index 35e24f8f060ad5..7795e4777da7e9 100644
    --- a/arch/x86/kvm/svm/svm.c
    +++ b/arch/x86/kvm/svm/svm.c
    @@ -52,6 +52,7 @@
     #include "svm.h"
     #include "svm_ops.h"
     
    +#include "hyperv.h"
     #include "kvm_onhyperv.h"
     #include "svm_onhyperv.h"
     
    @@ -3177,6 +3178,22 @@ static int bus_lock_exit(struct kvm_vcpu *vcpu)
     	return 0;
     }
     
    +static int vmmcall_interception(struct kvm_vcpu *vcpu)
    +{
    +	/*
    +	 * Inject a #UD if L2 is active and the VMMCALL isn't a Hyper-V TLB
    +	 * hypercall, as VMMCALL #UDs if it's not intercepted, and this path is
    +	 * reachable if and only if L1 doesn't want to intercept VMMCALL or has
    +	 * enabled L0 (KVM) handling of Hyper-V L2 TLB flush hypercalls.
    +	 */
    +	if (is_guest_mode(vcpu) && !nested_svm_is_l2_tlb_flush_hcall(vcpu)) {
    +		kvm_queue_exception(vcpu, UD_VECTOR);
    +		return 1;
    +	}
    +
    +	return kvm_emulate_hypercall(vcpu);
    +}
    +
     static int (*const svm_exit_handlers[])(struct kvm_vcpu *vcpu) = {
     	[SVM_EXIT_READ_CR0]			= cr_interception,
     	[SVM_EXIT_READ_CR3]			= cr_interception,
    @@ -3227,7 +3244,7 @@ static int (*const svm_exit_handlers[])(struct kvm_vcpu *vcpu) = {
     	[SVM_EXIT_TASK_SWITCH]			= task_switch_interception,
     	[SVM_EXIT_SHUTDOWN]			= shutdown_interception,
     	[SVM_EXIT_VMRUN]			= vmrun_interception,
    -	[SVM_EXIT_VMMCALL]			= kvm_emulate_hypercall,
    +	[SVM_EXIT_VMMCALL]			= vmmcall_interception,
     	[SVM_EXIT_VMLOAD]			= vmload_interception,
     	[SVM_EXIT_VMSAVE]			= vmsave_interception,
     	[SVM_EXIT_STGI]				= stgi_interception,
    -- 
    cgit 1.3-korg
    
    
    
5fb4a5f36156

KVM: nSVM: Raise #UD if unhandled VMMCALL isn't intercepted by L1

4 files changed · +30 13
  • arch/x86/kvm/hyperv.h+0 8 modified
    diff --git a/arch/x86/kvm/hyperv.h b/arch/x86/kvm/hyperv.h
    index 6ce160ffa67865..6301f79fcbae7c 100644
    --- a/arch/x86/kvm/hyperv.h
    +++ b/arch/x86/kvm/hyperv.h
    @@ -305,14 +305,6 @@ static inline bool kvm_hv_has_stimer_pending(struct kvm_vcpu *vcpu)
     {
     	return false;
     }
    -static inline bool kvm_hv_is_tlb_flush_hcall(struct kvm_vcpu *vcpu)
    -{
    -	return false;
    -}
    -static inline bool guest_hv_cpuid_has_l2_tlb_flush(struct kvm_vcpu *vcpu)
    -{
    -	return false;
    -}
     static inline int kvm_hv_verify_vp_assist(struct kvm_vcpu *vcpu)
     {
     	return 0;
    
  • arch/x86/kvm/svm/hyperv.h+11 0 modified
    diff --git a/arch/x86/kvm/svm/hyperv.h b/arch/x86/kvm/svm/hyperv.h
    index d3f8bfc05832ee..9af03970d40c28 100644
    --- a/arch/x86/kvm/svm/hyperv.h
    +++ b/arch/x86/kvm/svm/hyperv.h
    @@ -41,6 +41,13 @@ static inline bool nested_svm_l2_tlb_flush_enabled(struct kvm_vcpu *vcpu)
     	return hv_vcpu->vp_assist_page.nested_control.features.directhypercall;
     }
     
    +static inline bool nested_svm_is_l2_tlb_flush_hcall(struct kvm_vcpu *vcpu)
    +{
    +	return guest_hv_cpuid_has_l2_tlb_flush(vcpu) &&
    +	       nested_svm_l2_tlb_flush_enabled(vcpu) &&
    +	       kvm_hv_is_tlb_flush_hcall(vcpu);
    +}
    +
     void svm_hv_inject_synthetic_vmexit_post_tlb_flush(struct kvm_vcpu *vcpu);
     #else /* CONFIG_KVM_HYPERV */
     static inline void nested_svm_hv_update_vm_vp_ids(struct kvm_vcpu *vcpu) {}
    @@ -48,6 +55,10 @@ static inline bool nested_svm_l2_tlb_flush_enabled(struct kvm_vcpu *vcpu)
     {
     	return false;
     }
    +static inline bool nested_svm_is_l2_tlb_flush_hcall(struct kvm_vcpu *vcpu)
    +{
    +	return false;
    +}
     static inline void svm_hv_inject_synthetic_vmexit_post_tlb_flush(struct kvm_vcpu *vcpu) {}
     #endif /* CONFIG_KVM_HYPERV */
     
    
  • arch/x86/kvm/svm/nested.c+1 3 modified
    diff --git a/arch/x86/kvm/svm/nested.c b/arch/x86/kvm/svm/nested.c
    index 0db2a5b0734485..4f249e3713ef60 100644
    --- a/arch/x86/kvm/svm/nested.c
    +++ b/arch/x86/kvm/svm/nested.c
    @@ -1711,9 +1711,7 @@ int nested_svm_exit_special(struct vcpu_svm *svm)
     	}
     	case SVM_EXIT_VMMCALL:
     		/* Hyper-V L2 TLB flush hypercall is handled by L0 */
    -		if (guest_hv_cpuid_has_l2_tlb_flush(vcpu) &&
    -		    nested_svm_l2_tlb_flush_enabled(vcpu) &&
    -		    kvm_hv_is_tlb_flush_hcall(vcpu))
    +		if (nested_svm_is_l2_tlb_flush_hcall(vcpu))
     			return NESTED_EXIT_HOST;
     		break;
     	default:
    
  • arch/x86/kvm/svm/svm.c+18 2 modified
    diff --git a/arch/x86/kvm/svm/svm.c b/arch/x86/kvm/svm/svm.c
    index 5c5f0ad206deb2..cb0a89da99d097 100644
    --- a/arch/x86/kvm/svm/svm.c
    +++ b/arch/x86/kvm/svm/svm.c
    @@ -52,6 +52,7 @@
     #include "svm.h"
     #include "svm_ops.h"
     
    +#include "hyperv.h"
     #include "kvm_onhyperv.h"
     #include "svm_onhyperv.h"
     
    @@ -3249,6 +3250,22 @@ static int bus_lock_exit(struct kvm_vcpu *vcpu)
     	return 0;
     }
     
    +static int vmmcall_interception(struct kvm_vcpu *vcpu)
    +{
    +	/*
    +	 * Inject a #UD if L2 is active and the VMMCALL isn't a Hyper-V TLB
    +	 * hypercall, as VMMCALL #UDs if it's not intercepted, and this path is
    +	 * reachable if and only if L1 doesn't want to intercept VMMCALL or has
    +	 * enabled L0 (KVM) handling of Hyper-V L2 TLB flush hypercalls.
    +	 */
    +	if (is_guest_mode(vcpu) && !nested_svm_is_l2_tlb_flush_hcall(vcpu)) {
    +		kvm_queue_exception(vcpu, UD_VECTOR);
    +		return 1;
    +	}
    +
    +	return kvm_emulate_hypercall(vcpu);
    +}
    +
     static int (*const svm_exit_handlers[])(struct kvm_vcpu *vcpu) = {
     	[SVM_EXIT_READ_CR0]			= cr_interception,
     	[SVM_EXIT_READ_CR3]			= cr_interception,
    @@ -3299,7 +3316,7 @@ static int (*const svm_exit_handlers[])(struct kvm_vcpu *vcpu) = {
     	[SVM_EXIT_TASK_SWITCH]			= task_switch_interception,
     	[SVM_EXIT_SHUTDOWN]			= shutdown_interception,
     	[SVM_EXIT_VMRUN]			= vmrun_interception,
    -	[SVM_EXIT_VMMCALL]			= kvm_emulate_hypercall,
    +	[SVM_EXIT_VMMCALL]			= vmmcall_interception,
     	[SVM_EXIT_VMLOAD]			= vmload_interception,
     	[SVM_EXIT_VMSAVE]			= vmsave_interception,
     	[SVM_EXIT_STGI]				= stgi_interception,
    -- 
    cgit 1.3-korg
    
    
    
009c0f726abe

KVM: nSVM: Raise #UD if unhandled VMMCALL isn't intercepted by L1

4 files changed · +30 13
  • arch/x86/kvm/hyperv.h+0 8 modified
    diff --git a/arch/x86/kvm/hyperv.h b/arch/x86/kvm/hyperv.h
    index 6ce160ffa67865..6301f79fcbae7c 100644
    --- a/arch/x86/kvm/hyperv.h
    +++ b/arch/x86/kvm/hyperv.h
    @@ -305,14 +305,6 @@ static inline bool kvm_hv_has_stimer_pending(struct kvm_vcpu *vcpu)
     {
     	return false;
     }
    -static inline bool kvm_hv_is_tlb_flush_hcall(struct kvm_vcpu *vcpu)
    -{
    -	return false;
    -}
    -static inline bool guest_hv_cpuid_has_l2_tlb_flush(struct kvm_vcpu *vcpu)
    -{
    -	return false;
    -}
     static inline int kvm_hv_verify_vp_assist(struct kvm_vcpu *vcpu)
     {
     	return 0;
    
  • arch/x86/kvm/svm/hyperv.h+11 0 modified
    diff --git a/arch/x86/kvm/svm/hyperv.h b/arch/x86/kvm/svm/hyperv.h
    index d3f8bfc05832ee..9af03970d40c28 100644
    --- a/arch/x86/kvm/svm/hyperv.h
    +++ b/arch/x86/kvm/svm/hyperv.h
    @@ -41,6 +41,13 @@ static inline bool nested_svm_l2_tlb_flush_enabled(struct kvm_vcpu *vcpu)
     	return hv_vcpu->vp_assist_page.nested_control.features.directhypercall;
     }
     
    +static inline bool nested_svm_is_l2_tlb_flush_hcall(struct kvm_vcpu *vcpu)
    +{
    +	return guest_hv_cpuid_has_l2_tlb_flush(vcpu) &&
    +	       nested_svm_l2_tlb_flush_enabled(vcpu) &&
    +	       kvm_hv_is_tlb_flush_hcall(vcpu);
    +}
    +
     void svm_hv_inject_synthetic_vmexit_post_tlb_flush(struct kvm_vcpu *vcpu);
     #else /* CONFIG_KVM_HYPERV */
     static inline void nested_svm_hv_update_vm_vp_ids(struct kvm_vcpu *vcpu) {}
    @@ -48,6 +55,10 @@ static inline bool nested_svm_l2_tlb_flush_enabled(struct kvm_vcpu *vcpu)
     {
     	return false;
     }
    +static inline bool nested_svm_is_l2_tlb_flush_hcall(struct kvm_vcpu *vcpu)
    +{
    +	return false;
    +}
     static inline void svm_hv_inject_synthetic_vmexit_post_tlb_flush(struct kvm_vcpu *vcpu) {}
     #endif /* CONFIG_KVM_HYPERV */
     
    
  • arch/x86/kvm/svm/nested.c+1 3 modified
    diff --git a/arch/x86/kvm/svm/nested.c b/arch/x86/kvm/svm/nested.c
    index 1105c62c2103eb..bb250449fd4141 100644
    --- a/arch/x86/kvm/svm/nested.c
    +++ b/arch/x86/kvm/svm/nested.c
    @@ -1672,9 +1672,7 @@ int nested_svm_exit_special(struct vcpu_svm *svm)
     	}
     	case SVM_EXIT_VMMCALL:
     		/* Hyper-V L2 TLB flush hypercall is handled by L0 */
    -		if (guest_hv_cpuid_has_l2_tlb_flush(vcpu) &&
    -		    nested_svm_l2_tlb_flush_enabled(vcpu) &&
    -		    kvm_hv_is_tlb_flush_hcall(vcpu))
    +		if (nested_svm_is_l2_tlb_flush_hcall(vcpu))
     			return NESTED_EXIT_HOST;
     		break;
     	default:
    
  • arch/x86/kvm/svm/svm.c+18 2 modified
    diff --git a/arch/x86/kvm/svm/svm.c b/arch/x86/kvm/svm/svm.c
    index 35e24f8f060ad5..7795e4777da7e9 100644
    --- a/arch/x86/kvm/svm/svm.c
    +++ b/arch/x86/kvm/svm/svm.c
    @@ -52,6 +52,7 @@
     #include "svm.h"
     #include "svm_ops.h"
     
    +#include "hyperv.h"
     #include "kvm_onhyperv.h"
     #include "svm_onhyperv.h"
     
    @@ -3177,6 +3178,22 @@ static int bus_lock_exit(struct kvm_vcpu *vcpu)
     	return 0;
     }
     
    +static int vmmcall_interception(struct kvm_vcpu *vcpu)
    +{
    +	/*
    +	 * Inject a #UD if L2 is active and the VMMCALL isn't a Hyper-V TLB
    +	 * hypercall, as VMMCALL #UDs if it's not intercepted, and this path is
    +	 * reachable if and only if L1 doesn't want to intercept VMMCALL or has
    +	 * enabled L0 (KVM) handling of Hyper-V L2 TLB flush hypercalls.
    +	 */
    +	if (is_guest_mode(vcpu) && !nested_svm_is_l2_tlb_flush_hcall(vcpu)) {
    +		kvm_queue_exception(vcpu, UD_VECTOR);
    +		return 1;
    +	}
    +
    +	return kvm_emulate_hypercall(vcpu);
    +}
    +
     static int (*const svm_exit_handlers[])(struct kvm_vcpu *vcpu) = {
     	[SVM_EXIT_READ_CR0]			= cr_interception,
     	[SVM_EXIT_READ_CR3]			= cr_interception,
    @@ -3227,7 +3244,7 @@ static int (*const svm_exit_handlers[])(struct kvm_vcpu *vcpu) = {
     	[SVM_EXIT_TASK_SWITCH]			= task_switch_interception,
     	[SVM_EXIT_SHUTDOWN]			= shutdown_interception,
     	[SVM_EXIT_VMRUN]			= vmrun_interception,
    -	[SVM_EXIT_VMMCALL]			= kvm_emulate_hypercall,
    +	[SVM_EXIT_VMMCALL]			= vmmcall_interception,
     	[SVM_EXIT_VMLOAD]			= vmload_interception,
     	[SVM_EXIT_VMSAVE]			= vmsave_interception,
     	[SVM_EXIT_STGI]				= stgi_interception,
    -- 
    cgit 1.3-korg
    
    
    
5fb4a5f36156

KVM: nSVM: Raise #UD if unhandled VMMCALL isn't intercepted by L1

4 files changed · +30 13
  • arch/x86/kvm/hyperv.h+0 8 modified
    diff --git a/arch/x86/kvm/hyperv.h b/arch/x86/kvm/hyperv.h
    index 6ce160ffa67865..6301f79fcbae7c 100644
    --- a/arch/x86/kvm/hyperv.h
    +++ b/arch/x86/kvm/hyperv.h
    @@ -305,14 +305,6 @@ static inline bool kvm_hv_has_stimer_pending(struct kvm_vcpu *vcpu)
     {
     	return false;
     }
    -static inline bool kvm_hv_is_tlb_flush_hcall(struct kvm_vcpu *vcpu)
    -{
    -	return false;
    -}
    -static inline bool guest_hv_cpuid_has_l2_tlb_flush(struct kvm_vcpu *vcpu)
    -{
    -	return false;
    -}
     static inline int kvm_hv_verify_vp_assist(struct kvm_vcpu *vcpu)
     {
     	return 0;
    
  • arch/x86/kvm/svm/hyperv.h+11 0 modified
    diff --git a/arch/x86/kvm/svm/hyperv.h b/arch/x86/kvm/svm/hyperv.h
    index d3f8bfc05832ee..9af03970d40c28 100644
    --- a/arch/x86/kvm/svm/hyperv.h
    +++ b/arch/x86/kvm/svm/hyperv.h
    @@ -41,6 +41,13 @@ static inline bool nested_svm_l2_tlb_flush_enabled(struct kvm_vcpu *vcpu)
     	return hv_vcpu->vp_assist_page.nested_control.features.directhypercall;
     }
     
    +static inline bool nested_svm_is_l2_tlb_flush_hcall(struct kvm_vcpu *vcpu)
    +{
    +	return guest_hv_cpuid_has_l2_tlb_flush(vcpu) &&
    +	       nested_svm_l2_tlb_flush_enabled(vcpu) &&
    +	       kvm_hv_is_tlb_flush_hcall(vcpu);
    +}
    +
     void svm_hv_inject_synthetic_vmexit_post_tlb_flush(struct kvm_vcpu *vcpu);
     #else /* CONFIG_KVM_HYPERV */
     static inline void nested_svm_hv_update_vm_vp_ids(struct kvm_vcpu *vcpu) {}
    @@ -48,6 +55,10 @@ static inline bool nested_svm_l2_tlb_flush_enabled(struct kvm_vcpu *vcpu)
     {
     	return false;
     }
    +static inline bool nested_svm_is_l2_tlb_flush_hcall(struct kvm_vcpu *vcpu)
    +{
    +	return false;
    +}
     static inline void svm_hv_inject_synthetic_vmexit_post_tlb_flush(struct kvm_vcpu *vcpu) {}
     #endif /* CONFIG_KVM_HYPERV */
     
    
  • arch/x86/kvm/svm/nested.c+1 3 modified
    diff --git a/arch/x86/kvm/svm/nested.c b/arch/x86/kvm/svm/nested.c
    index 0db2a5b0734485..4f249e3713ef60 100644
    --- a/arch/x86/kvm/svm/nested.c
    +++ b/arch/x86/kvm/svm/nested.c
    @@ -1711,9 +1711,7 @@ int nested_svm_exit_special(struct vcpu_svm *svm)
     	}
     	case SVM_EXIT_VMMCALL:
     		/* Hyper-V L2 TLB flush hypercall is handled by L0 */
    -		if (guest_hv_cpuid_has_l2_tlb_flush(vcpu) &&
    -		    nested_svm_l2_tlb_flush_enabled(vcpu) &&
    -		    kvm_hv_is_tlb_flush_hcall(vcpu))
    +		if (nested_svm_is_l2_tlb_flush_hcall(vcpu))
     			return NESTED_EXIT_HOST;
     		break;
     	default:
    
  • arch/x86/kvm/svm/svm.c+18 2 modified
    diff --git a/arch/x86/kvm/svm/svm.c b/arch/x86/kvm/svm/svm.c
    index 5c5f0ad206deb2..cb0a89da99d097 100644
    --- a/arch/x86/kvm/svm/svm.c
    +++ b/arch/x86/kvm/svm/svm.c
    @@ -52,6 +52,7 @@
     #include "svm.h"
     #include "svm_ops.h"
     
    +#include "hyperv.h"
     #include "kvm_onhyperv.h"
     #include "svm_onhyperv.h"
     
    @@ -3249,6 +3250,22 @@ static int bus_lock_exit(struct kvm_vcpu *vcpu)
     	return 0;
     }
     
    +static int vmmcall_interception(struct kvm_vcpu *vcpu)
    +{
    +	/*
    +	 * Inject a #UD if L2 is active and the VMMCALL isn't a Hyper-V TLB
    +	 * hypercall, as VMMCALL #UDs if it's not intercepted, and this path is
    +	 * reachable if and only if L1 doesn't want to intercept VMMCALL or has
    +	 * enabled L0 (KVM) handling of Hyper-V L2 TLB flush hypercalls.
    +	 */
    +	if (is_guest_mode(vcpu) && !nested_svm_is_l2_tlb_flush_hcall(vcpu)) {
    +		kvm_queue_exception(vcpu, UD_VECTOR);
    +		return 1;
    +	}
    +
    +	return kvm_emulate_hypercall(vcpu);
    +}
    +
     static int (*const svm_exit_handlers[])(struct kvm_vcpu *vcpu) = {
     	[SVM_EXIT_READ_CR0]			= cr_interception,
     	[SVM_EXIT_READ_CR3]			= cr_interception,
    @@ -3299,7 +3316,7 @@ static int (*const svm_exit_handlers[])(struct kvm_vcpu *vcpu) = {
     	[SVM_EXIT_TASK_SWITCH]			= task_switch_interception,
     	[SVM_EXIT_SHUTDOWN]			= shutdown_interception,
     	[SVM_EXIT_VMRUN]			= vmrun_interception,
    -	[SVM_EXIT_VMMCALL]			= kvm_emulate_hypercall,
    +	[SVM_EXIT_VMMCALL]			= vmmcall_interception,
     	[SVM_EXIT_VMLOAD]			= vmload_interception,
     	[SVM_EXIT_VMSAVE]			= vmsave_interception,
     	[SVM_EXIT_STGI]				= stgi_interception,
    -- 
    cgit 1.3-korg
    
    
    
924d721fae95

KVM: nSVM: Raise #UD if unhandled VMMCALL isn't intercepted by L1

4 files changed · +30 13
  • arch/x86/kvm/hyperv.h+0 8 modified
    diff --git a/arch/x86/kvm/hyperv.h b/arch/x86/kvm/hyperv.h
    index 913bfc96959cbf..8d1ae8277038cd 100644
    --- a/arch/x86/kvm/hyperv.h
    +++ b/arch/x86/kvm/hyperv.h
    @@ -304,14 +304,6 @@ static inline bool kvm_hv_has_stimer_pending(struct kvm_vcpu *vcpu)
     {
     	return false;
     }
    -static inline bool kvm_hv_is_tlb_flush_hcall(struct kvm_vcpu *vcpu)
    -{
    -	return false;
    -}
    -static inline bool guest_hv_cpuid_has_l2_tlb_flush(struct kvm_vcpu *vcpu)
    -{
    -	return false;
    -}
     static inline int kvm_hv_verify_vp_assist(struct kvm_vcpu *vcpu)
     {
     	return 0;
    
  • arch/x86/kvm/svm/hyperv.h+11 0 modified
    diff --git a/arch/x86/kvm/svm/hyperv.h b/arch/x86/kvm/svm/hyperv.h
    index d3f8bfc05832ee..9af03970d40c28 100644
    --- a/arch/x86/kvm/svm/hyperv.h
    +++ b/arch/x86/kvm/svm/hyperv.h
    @@ -41,6 +41,13 @@ static inline bool nested_svm_l2_tlb_flush_enabled(struct kvm_vcpu *vcpu)
     	return hv_vcpu->vp_assist_page.nested_control.features.directhypercall;
     }
     
    +static inline bool nested_svm_is_l2_tlb_flush_hcall(struct kvm_vcpu *vcpu)
    +{
    +	return guest_hv_cpuid_has_l2_tlb_flush(vcpu) &&
    +	       nested_svm_l2_tlb_flush_enabled(vcpu) &&
    +	       kvm_hv_is_tlb_flush_hcall(vcpu);
    +}
    +
     void svm_hv_inject_synthetic_vmexit_post_tlb_flush(struct kvm_vcpu *vcpu);
     #else /* CONFIG_KVM_HYPERV */
     static inline void nested_svm_hv_update_vm_vp_ids(struct kvm_vcpu *vcpu) {}
    @@ -48,6 +55,10 @@ static inline bool nested_svm_l2_tlb_flush_enabled(struct kvm_vcpu *vcpu)
     {
     	return false;
     }
    +static inline bool nested_svm_is_l2_tlb_flush_hcall(struct kvm_vcpu *vcpu)
    +{
    +	return false;
    +}
     static inline void svm_hv_inject_synthetic_vmexit_post_tlb_flush(struct kvm_vcpu *vcpu) {}
     #endif /* CONFIG_KVM_HYPERV */
     
    
  • arch/x86/kvm/svm/nested.c+1 3 modified
    diff --git a/arch/x86/kvm/svm/nested.c b/arch/x86/kvm/svm/nested.c
    index 4956833b70193b..6eb0e840a565fd 100644
    --- a/arch/x86/kvm/svm/nested.c
    +++ b/arch/x86/kvm/svm/nested.c
    @@ -1532,9 +1532,7 @@ int nested_svm_exit_special(struct vcpu_svm *svm)
     	}
     	case SVM_EXIT_VMMCALL:
     		/* Hyper-V L2 TLB flush hypercall is handled by L0 */
    -		if (guest_hv_cpuid_has_l2_tlb_flush(vcpu) &&
    -		    nested_svm_l2_tlb_flush_enabled(vcpu) &&
    -		    kvm_hv_is_tlb_flush_hcall(vcpu))
    +		if (nested_svm_is_l2_tlb_flush_hcall(vcpu))
     			return NESTED_EXIT_HOST;
     		break;
     	default:
    
  • arch/x86/kvm/svm/svm.c+18 2 modified
    diff --git a/arch/x86/kvm/svm/svm.c b/arch/x86/kvm/svm/svm.c
    index 86d829220e80fa..6ca9bd96c34e79 100644
    --- a/arch/x86/kvm/svm/svm.c
    +++ b/arch/x86/kvm/svm/svm.c
    @@ -49,6 +49,7 @@
     #include "svm.h"
     #include "svm_ops.h"
     
    +#include "hyperv.h"
     #include "kvm_onhyperv.h"
     #include "svm_onhyperv.h"
     
    @@ -3377,6 +3378,22 @@ static int invpcid_interception(struct kvm_vcpu *vcpu)
     	return kvm_handle_invpcid(vcpu, type, gva);
     }
     
    +static int vmmcall_interception(struct kvm_vcpu *vcpu)
    +{
    +	/*
    +	 * Inject a #UD if L2 is active and the VMMCALL isn't a Hyper-V TLB
    +	 * hypercall, as VMMCALL #UDs if it's not intercepted, and this path is
    +	 * reachable if and only if L1 doesn't want to intercept VMMCALL or has
    +	 * enabled L0 (KVM) handling of Hyper-V L2 TLB flush hypercalls.
    +	 */
    +	if (is_guest_mode(vcpu) && !nested_svm_is_l2_tlb_flush_hcall(vcpu)) {
    +		kvm_queue_exception(vcpu, UD_VECTOR);
    +		return 1;
    +	}
    +
    +	return kvm_emulate_hypercall(vcpu);
    +}
    +
     static int (*const svm_exit_handlers[])(struct kvm_vcpu *vcpu) = {
     	[SVM_EXIT_READ_CR0]			= cr_interception,
     	[SVM_EXIT_READ_CR3]			= cr_interception,
    @@ -3427,7 +3444,7 @@ static int (*const svm_exit_handlers[])(struct kvm_vcpu *vcpu) = {
     	[SVM_EXIT_TASK_SWITCH]			= task_switch_interception,
     	[SVM_EXIT_SHUTDOWN]			= shutdown_interception,
     	[SVM_EXIT_VMRUN]			= vmrun_interception,
    -	[SVM_EXIT_VMMCALL]			= kvm_emulate_hypercall,
    +	[SVM_EXIT_VMMCALL]			= vmmcall_interception,
     	[SVM_EXIT_VMLOAD]			= vmload_interception,
     	[SVM_EXIT_VMSAVE]			= vmsave_interception,
     	[SVM_EXIT_STGI]				= stgi_interception,
    -- 
    cgit 1.3-korg
    
    
    
c36991c6f8d2

KVM: nSVM: Raise #UD if unhandled VMMCALL isn't intercepted by L1

4 files changed · +30 13
  • arch/x86/kvm/hyperv.h+0 8 modified
    diff --git a/arch/x86/kvm/hyperv.h b/arch/x86/kvm/hyperv.h
    index 6ce160ffa67865..6301f79fcbae7c 100644
    --- a/arch/x86/kvm/hyperv.h
    +++ b/arch/x86/kvm/hyperv.h
    @@ -305,14 +305,6 @@ static inline bool kvm_hv_has_stimer_pending(struct kvm_vcpu *vcpu)
     {
     	return false;
     }
    -static inline bool kvm_hv_is_tlb_flush_hcall(struct kvm_vcpu *vcpu)
    -{
    -	return false;
    -}
    -static inline bool guest_hv_cpuid_has_l2_tlb_flush(struct kvm_vcpu *vcpu)
    -{
    -	return false;
    -}
     static inline int kvm_hv_verify_vp_assist(struct kvm_vcpu *vcpu)
     {
     	return 0;
    
  • arch/x86/kvm/svm/hyperv.h+11 0 modified
    diff --git a/arch/x86/kvm/svm/hyperv.h b/arch/x86/kvm/svm/hyperv.h
    index d3f8bfc05832ee..9af03970d40c28 100644
    --- a/arch/x86/kvm/svm/hyperv.h
    +++ b/arch/x86/kvm/svm/hyperv.h
    @@ -41,6 +41,13 @@ static inline bool nested_svm_l2_tlb_flush_enabled(struct kvm_vcpu *vcpu)
     	return hv_vcpu->vp_assist_page.nested_control.features.directhypercall;
     }
     
    +static inline bool nested_svm_is_l2_tlb_flush_hcall(struct kvm_vcpu *vcpu)
    +{
    +	return guest_hv_cpuid_has_l2_tlb_flush(vcpu) &&
    +	       nested_svm_l2_tlb_flush_enabled(vcpu) &&
    +	       kvm_hv_is_tlb_flush_hcall(vcpu);
    +}
    +
     void svm_hv_inject_synthetic_vmexit_post_tlb_flush(struct kvm_vcpu *vcpu);
     #else /* CONFIG_KVM_HYPERV */
     static inline void nested_svm_hv_update_vm_vp_ids(struct kvm_vcpu *vcpu) {}
    @@ -48,6 +55,10 @@ static inline bool nested_svm_l2_tlb_flush_enabled(struct kvm_vcpu *vcpu)
     {
     	return false;
     }
    +static inline bool nested_svm_is_l2_tlb_flush_hcall(struct kvm_vcpu *vcpu)
    +{
    +	return false;
    +}
     static inline void svm_hv_inject_synthetic_vmexit_post_tlb_flush(struct kvm_vcpu *vcpu) {}
     #endif /* CONFIG_KVM_HYPERV */
     
    
  • arch/x86/kvm/svm/nested.c+1 3 modified
    diff --git a/arch/x86/kvm/svm/nested.c b/arch/x86/kvm/svm/nested.c
    index 5b639d98bf09d8..0f7893a7cb040a 100644
    --- a/arch/x86/kvm/svm/nested.c
    +++ b/arch/x86/kvm/svm/nested.c
    @@ -1738,9 +1738,7 @@ int nested_svm_exit_special(struct vcpu_svm *svm)
     	}
     	case SVM_EXIT_VMMCALL:
     		/* Hyper-V L2 TLB flush hypercall is handled by L0 */
    -		if (guest_hv_cpuid_has_l2_tlb_flush(vcpu) &&
    -		    nested_svm_l2_tlb_flush_enabled(vcpu) &&
    -		    kvm_hv_is_tlb_flush_hcall(vcpu))
    +		if (nested_svm_is_l2_tlb_flush_hcall(vcpu))
     			return NESTED_EXIT_HOST;
     		break;
     	default:
    
  • arch/x86/kvm/svm/svm.c+18 2 modified
    diff --git a/arch/x86/kvm/svm/svm.c b/arch/x86/kvm/svm/svm.c
    index 7efa7170929232..9e6864cf58d34e 100644
    --- a/arch/x86/kvm/svm/svm.c
    +++ b/arch/x86/kvm/svm/svm.c
    @@ -52,6 +52,7 @@
     #include "svm.h"
     #include "svm_ops.h"
     
    +#include "hyperv.h"
     #include "kvm_onhyperv.h"
     #include "svm_onhyperv.h"
     
    @@ -3248,6 +3249,22 @@ static int bus_lock_exit(struct kvm_vcpu *vcpu)
     	return 0;
     }
     
    +static int vmmcall_interception(struct kvm_vcpu *vcpu)
    +{
    +	/*
    +	 * Inject a #UD if L2 is active and the VMMCALL isn't a Hyper-V TLB
    +	 * hypercall, as VMMCALL #UDs if it's not intercepted, and this path is
    +	 * reachable if and only if L1 doesn't want to intercept VMMCALL or has
    +	 * enabled L0 (KVM) handling of Hyper-V L2 TLB flush hypercalls.
    +	 */
    +	if (is_guest_mode(vcpu) && !nested_svm_is_l2_tlb_flush_hcall(vcpu)) {
    +		kvm_queue_exception(vcpu, UD_VECTOR);
    +		return 1;
    +	}
    +
    +	return kvm_emulate_hypercall(vcpu);
    +}
    +
     static int (*const svm_exit_handlers[])(struct kvm_vcpu *vcpu) = {
     	[SVM_EXIT_READ_CR0]			= cr_interception,
     	[SVM_EXIT_READ_CR3]			= cr_interception,
    @@ -3298,7 +3315,7 @@ static int (*const svm_exit_handlers[])(struct kvm_vcpu *vcpu) = {
     	[SVM_EXIT_TASK_SWITCH]			= task_switch_interception,
     	[SVM_EXIT_SHUTDOWN]			= shutdown_interception,
     	[SVM_EXIT_VMRUN]			= vmrun_interception,
    -	[SVM_EXIT_VMMCALL]			= kvm_emulate_hypercall,
    +	[SVM_EXIT_VMMCALL]			= vmmcall_interception,
     	[SVM_EXIT_VMLOAD]			= vmload_interception,
     	[SVM_EXIT_VMSAVE]			= vmsave_interception,
     	[SVM_EXIT_STGI]				= stgi_interception,
    -- 
    cgit 1.3-korg
    
    
    

Vulnerability mechanics

Root cause

"Missing #UD injection when KVM intercepts a VMMCALL from L2 that is not a Hyper-V TLB flush hypercall and L1 does not want to intercept VMMCALL, allowing L2 to execute arbitrary hypercalls as if it were L1."

Attack vector

An attacker controlling a nested L2 guest can issue a VMMCALL instruction that is not a recognized Hyper-V TLB flush hypercall. When L1 has not set the VMMCALL intercept bit but KVM has enabled Hyper-V L2 TLB flush handling, KVM intercepts the VMMCALL but previously forwarded it directly to kvm_emulate_hypercall() without checking whether L1 actually wanted to intercept it [patch_id=2659913]. This lets L2 execute arbitrary hypercalls with L1's privileges, bypassing L1's intended interception policy. The architectural behavior per AMD is that VMMCALL should #UD if not intercepted.

Affected code

The vulnerable code path is in arch/x86/kvm/svm/svm.c where the SVM_EXIT_VMMCALL exit handler was directly set to kvm_emulate_hypercall. The fix introduces a new vmmcall_interception() function in svm.c that checks is_guest_mode() and nested_svm_is_l2_tlb_flush_hcall() before deciding whether to inject #UD or emulate the hypercall [patch_id=2659913]. The helper nested_svm_is_l2_tlb_flush_hcall() is added in arch/x86/kvm/svm/hyperv.h, and the existing open-coded check in arch/x86/kvm/svm/nested.c is refactored to use it.

What the fix does

The patch replaces the direct assignment of kvm_emulate_hypercall to the SVM_EXIT_VMMCALL handler with a new vmmcall_interception() wrapper [patch_id=2659913]. This wrapper first checks if L2 is active (is_guest_mode) and whether the VMMCALL is a recognized Hyper-V L2 TLB flush hypercall via the new nested_svm_is_l2_tlb_flush_hcall() helper. If L2 is active and the hypercall is not a TLB flush hypercall, it injects a #UD exception (kvm_queue_exception(vcpu, UD_VECTOR)), matching the architectural behavior that VMMCALL #UDs when not intercepted. The patch also removes the now-unnecessary stubs for kvm_hv_is_tlb_flush_hcall and guest_hv_cpuid_has_l2_tlb_flush from the !CONFIG_KVM_HYPERV path in hyperv.h, as the new nested_svm_is_l2_tlb_flush_hcall stub handles that case.

Preconditions

  • configKVM must be running on an AMD SVM host with nested virtualization enabled.
  • configL1 must have enabled Hyper-V L2 TLB flush handling (nested_svm_l2_tlb_flush_enabled() returns true) and L1 must NOT have set the VMMCALL intercept bit.
  • authAttacker must have the ability to execute code inside a nested L2 guest.

Generated on May 27, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

4

News mentions

0

No linked articles in our index yet.