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

CVE-2026-46071

CVE-2026-46071

Description

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

KVM: nSVM: Avoid clearing VMCB_LBR in vmcb12

svm_copy_lbrs() always marks VMCB_LBR dirty in the destination VMCB. However, nested_svm_vmexit() uses it to copy LBRs to vmcb12, and clearing clean bits in vmcb12 is not architecturally defined.

Move vmcb_mark_dirty() to callers and drop it for vmcb12.

This also facilitates incoming refactoring that does not pass the entire VMCB to svm_copy_lbrs().

AI Insight

LLM-synthesized narrative grounded in this CVE's description and references.

A flaw in KVM's nested SVM LBR handling clears VMCB clean bits in vmcb12, causing undefined behavior during VM exit.

Vulnerability

In the Linux kernel's KVM subsystem, the nested SVM (nSVM) implementation contains a bug in the svm_copy_lbrs() function. This function is called during nested VM exits to copy Last Branch Record (LBR) state to vmcb12. However, svm_copy_lbrs() unconditionally marks the VMCB_LBR bit as dirty in the destination VMCB. For vmcb12, clearing architecturally defined clean bits is not specified and leads to undefined behavior. The issue affects Linux kernel versions prior to the stable commit a3f0981a5a0e [1].

Exploitation

An attacker must have the ability to run nested virtualization (SVM) and trigger VM exits that cause LBR state to be copied. The attacker needs sufficient privileges to create nested VMs. During a nested VM exit, the kernel incorrectly clears the VMCB_LBR clean bit in vmcb12, potentially leading to inconsistent VMCB state. No user interaction beyond normal nested VM operation is required.

Impact

Successful exploitation results in undefined behavior due to the clearing of architecturally defined clean bits in vmcb12. This can lead to incorrect LBR state, which may cause information disclosure (e.g., leaking host or guest memory contents) or denial of service (e.g., kernel panic or incorrect virtualization behavior). The compromise occurs at the host kernel level, but the attacker operates from a nested guest context.

Mitigation

The vulnerability is fixed in Linux kernel commit a3f0981a5a0e [1]. Users should update to a kernel version that includes this fix. No workaround is available. The issue is not listed on the CISA Known Exploited Vulnerabilities (KEV) catalog.

AI Insight generated on May 27, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.

Affected products

1

Patches

6
9efe23568806

KVM: nSVM: Avoid clearing VMCB_LBR in vmcb12

4 files changed · +10 10
  • arch/x86/kvm/svm/nested.c+5 2 modified
    diff --git a/arch/x86/kvm/svm/nested.c b/arch/x86/kvm/svm/nested.c
    index 13de99f07be806..da9b3208feae27 100644
    --- a/arch/x86/kvm/svm/nested.c
    +++ b/arch/x86/kvm/svm/nested.c
    @@ -726,6 +726,7 @@ static void nested_vmcb02_prepare_save(struct vcpu_svm *svm, struct vmcb *vmcb12
     	} else {
     		svm_copy_lbrs(vmcb02, vmcb01);
     	}
    +	vmcb_mark_dirty(vmcb02, VMCB_LBR);
     	svm_update_lbrv(&svm->vcpu);
     }
     
    @@ -1242,10 +1243,12 @@ int nested_svm_vmexit(struct vcpu_svm *svm)
     		kvm_make_request(KVM_REQ_EVENT, &svm->vcpu);
     
     	if (unlikely(guest_cpu_cap_has(vcpu, X86_FEATURE_LBRV) &&
    -		     (svm->nested.ctl.virt_ext & LBR_CTL_ENABLE_MASK)))
    +		     (svm->nested.ctl.virt_ext & LBR_CTL_ENABLE_MASK))) {
     		svm_copy_lbrs(vmcb12, vmcb02);
    -	else
    +	} else {
     		svm_copy_lbrs(vmcb01, vmcb02);
    +		vmcb_mark_dirty(vmcb01, VMCB_LBR);
    +	}
     
     	svm_update_lbrv(vcpu);
     
    
  • arch/x86/kvm/svm/nested.c+5 2 modified
    diff --git a/arch/x86/kvm/svm/nested.c b/arch/x86/kvm/svm/nested.c
    index 13de99f07be806..da9b3208feae27 100644
    --- a/arch/x86/kvm/svm/nested.c
    +++ b/arch/x86/kvm/svm/nested.c
    @@ -726,6 +726,7 @@ static void nested_vmcb02_prepare_save(struct vcpu_svm *svm, struct vmcb *vmcb12
     	} else {
     		svm_copy_lbrs(vmcb02, vmcb01);
     	}
    +	vmcb_mark_dirty(vmcb02, VMCB_LBR);
     	svm_update_lbrv(&svm->vcpu);
     }
     
    @@ -1242,10 +1243,12 @@ int nested_svm_vmexit(struct vcpu_svm *svm)
     		kvm_make_request(KVM_REQ_EVENT, &svm->vcpu);
     
     	if (unlikely(guest_cpu_cap_has(vcpu, X86_FEATURE_LBRV) &&
    -		     (svm->nested.ctl.virt_ext & LBR_CTL_ENABLE_MASK)))
    +		     (svm->nested.ctl.virt_ext & LBR_CTL_ENABLE_MASK))) {
     		svm_copy_lbrs(vmcb12, vmcb02);
    -	else
    +	} else {
     		svm_copy_lbrs(vmcb01, vmcb02);
    +		vmcb_mark_dirty(vmcb01, VMCB_LBR);
    +	}
     
     	svm_update_lbrv(vcpu);
     
    
  • arch/x86/kvm/svm/svm.c+0 3 modified
    diff --git a/arch/x86/kvm/svm/svm.c b/arch/x86/kvm/svm/svm.c
    index c0cad55c78db50..cebd93682dd5c2 100644
    --- a/arch/x86/kvm/svm/svm.c
    +++ b/arch/x86/kvm/svm/svm.c
    @@ -848,8 +848,6 @@ void svm_copy_lbrs(struct vmcb *to_vmcb, struct vmcb *from_vmcb)
     	to_vmcb->save.br_to		= from_vmcb->save.br_to;
     	to_vmcb->save.last_excp_from	= from_vmcb->save.last_excp_from;
     	to_vmcb->save.last_excp_to	= from_vmcb->save.last_excp_to;
    -
    -	vmcb_mark_dirty(to_vmcb, VMCB_LBR);
     }
     
     static void __svm_enable_lbrv(struct kvm_vcpu *vcpu)
    -- 
    cgit 1.3-korg
    
    
    
  • arch/x86/kvm/svm/svm.c+0 3 modified
    diff --git a/arch/x86/kvm/svm/svm.c b/arch/x86/kvm/svm/svm.c
    index c0cad55c78db50..cebd93682dd5c2 100644
    --- a/arch/x86/kvm/svm/svm.c
    +++ b/arch/x86/kvm/svm/svm.c
    @@ -848,8 +848,6 @@ void svm_copy_lbrs(struct vmcb *to_vmcb, struct vmcb *from_vmcb)
     	to_vmcb->save.br_to		= from_vmcb->save.br_to;
     	to_vmcb->save.last_excp_from	= from_vmcb->save.last_excp_from;
     	to_vmcb->save.last_excp_to	= from_vmcb->save.last_excp_to;
    -
    -	vmcb_mark_dirty(to_vmcb, VMCB_LBR);
     }
     
     static void __svm_enable_lbrv(struct kvm_vcpu *vcpu)
    -- 
    cgit 1.3-korg
    
    
    
a3f0981a5a0e

KVM: nSVM: Avoid clearing VMCB_LBR in vmcb12

4 files changed · +10 10
  • arch/x86/kvm/svm/nested.c+5 2 modified
    diff --git a/arch/x86/kvm/svm/nested.c b/arch/x86/kvm/svm/nested.c
    index 6243e7d91c7582..947f3b961ce6fd 100644
    --- a/arch/x86/kvm/svm/nested.c
    +++ b/arch/x86/kvm/svm/nested.c
    @@ -684,6 +684,7 @@ static void nested_vmcb02_prepare_save(struct vcpu_svm *svm, struct vmcb *vmcb12
     	} else {
     		svm_copy_lbrs(vmcb02, vmcb01);
     	}
    +	vmcb_mark_dirty(vmcb02, VMCB_LBR);
     	svm_update_lbrv(&svm->vcpu);
     }
     
    @@ -1188,10 +1189,12 @@ int nested_svm_vmexit(struct vcpu_svm *svm)
     		kvm_make_request(KVM_REQ_EVENT, &svm->vcpu);
     
     	if (unlikely(guest_cpu_cap_has(vcpu, X86_FEATURE_LBRV) &&
    -		     (svm->nested.ctl.virt_ext & LBR_CTL_ENABLE_MASK)))
    +		     (svm->nested.ctl.virt_ext & LBR_CTL_ENABLE_MASK))) {
     		svm_copy_lbrs(vmcb12, vmcb02);
    -	else
    +	} else {
     		svm_copy_lbrs(vmcb01, vmcb02);
    +		vmcb_mark_dirty(vmcb01, VMCB_LBR);
    +	}
     
     	svm_update_lbrv(vcpu);
     
    
  • arch/x86/kvm/svm/nested.c+5 2 modified
    diff --git a/arch/x86/kvm/svm/nested.c b/arch/x86/kvm/svm/nested.c
    index 6243e7d91c7582..947f3b961ce6fd 100644
    --- a/arch/x86/kvm/svm/nested.c
    +++ b/arch/x86/kvm/svm/nested.c
    @@ -684,6 +684,7 @@ static void nested_vmcb02_prepare_save(struct vcpu_svm *svm, struct vmcb *vmcb12
     	} else {
     		svm_copy_lbrs(vmcb02, vmcb01);
     	}
    +	vmcb_mark_dirty(vmcb02, VMCB_LBR);
     	svm_update_lbrv(&svm->vcpu);
     }
     
    @@ -1188,10 +1189,12 @@ int nested_svm_vmexit(struct vcpu_svm *svm)
     		kvm_make_request(KVM_REQ_EVENT, &svm->vcpu);
     
     	if (unlikely(guest_cpu_cap_has(vcpu, X86_FEATURE_LBRV) &&
    -		     (svm->nested.ctl.virt_ext & LBR_CTL_ENABLE_MASK)))
    +		     (svm->nested.ctl.virt_ext & LBR_CTL_ENABLE_MASK))) {
     		svm_copy_lbrs(vmcb12, vmcb02);
    -	else
    +	} else {
     		svm_copy_lbrs(vmcb01, vmcb02);
    +		vmcb_mark_dirty(vmcb01, VMCB_LBR);
    +	}
     
     	svm_update_lbrv(vcpu);
     
    
  • arch/x86/kvm/svm/svm.c+0 3 modified
    diff --git a/arch/x86/kvm/svm/svm.c b/arch/x86/kvm/svm/svm.c
    index a8e8c76c023b84..055d3f64d06bed 100644
    --- a/arch/x86/kvm/svm/svm.c
    +++ b/arch/x86/kvm/svm/svm.c
    @@ -811,8 +811,6 @@ void svm_copy_lbrs(struct vmcb *to_vmcb, struct vmcb *from_vmcb)
     	to_vmcb->save.br_to		= from_vmcb->save.br_to;
     	to_vmcb->save.last_excp_from	= from_vmcb->save.last_excp_from;
     	to_vmcb->save.last_excp_to	= from_vmcb->save.last_excp_to;
    -
    -	vmcb_mark_dirty(to_vmcb, VMCB_LBR);
     }
     
     static void __svm_enable_lbrv(struct kvm_vcpu *vcpu)
    -- 
    cgit 1.3-korg
    
    
    
  • arch/x86/kvm/svm/svm.c+0 3 modified
    diff --git a/arch/x86/kvm/svm/svm.c b/arch/x86/kvm/svm/svm.c
    index a8e8c76c023b84..055d3f64d06bed 100644
    --- a/arch/x86/kvm/svm/svm.c
    +++ b/arch/x86/kvm/svm/svm.c
    @@ -811,8 +811,6 @@ void svm_copy_lbrs(struct vmcb *to_vmcb, struct vmcb *from_vmcb)
     	to_vmcb->save.br_to		= from_vmcb->save.br_to;
     	to_vmcb->save.last_excp_from	= from_vmcb->save.last_excp_from;
     	to_vmcb->save.last_excp_to	= from_vmcb->save.last_excp_to;
    -
    -	vmcb_mark_dirty(to_vmcb, VMCB_LBR);
     }
     
     static void __svm_enable_lbrv(struct kvm_vcpu *vcpu)
    -- 
    cgit 1.3-korg
    
    
    
b53ab5167a81

KVM: nSVM: Avoid clearing VMCB_LBR in vmcb12

4 files changed · +10 10
  • arch/x86/kvm/svm/nested.c+5 2 modified
    diff --git a/arch/x86/kvm/svm/nested.c b/arch/x86/kvm/svm/nested.c
    index 3e2841598a36c4..0a35c815f4d266 100644
    --- a/arch/x86/kvm/svm/nested.c
    +++ b/arch/x86/kvm/svm/nested.c
    @@ -715,6 +715,7 @@ static void nested_vmcb02_prepare_save(struct vcpu_svm *svm, struct vmcb *vmcb12
     	} else {
     		svm_copy_lbrs(vmcb02, vmcb01);
     	}
    +	vmcb_mark_dirty(vmcb02, VMCB_LBR);
     	svm_update_lbrv(&svm->vcpu);
     }
     
    @@ -1231,10 +1232,12 @@ int nested_svm_vmexit(struct vcpu_svm *svm)
     		kvm_make_request(KVM_REQ_EVENT, &svm->vcpu);
     
     	if (unlikely(guest_cpu_cap_has(vcpu, X86_FEATURE_LBRV) &&
    -		     (svm->nested.ctl.virt_ext & LBR_CTL_ENABLE_MASK)))
    +		     (svm->nested.ctl.virt_ext & LBR_CTL_ENABLE_MASK))) {
     		svm_copy_lbrs(vmcb12, vmcb02);
    -	else
    +	} else {
     		svm_copy_lbrs(vmcb01, vmcb02);
    +		vmcb_mark_dirty(vmcb01, VMCB_LBR);
    +	}
     
     	svm_update_lbrv(vcpu);
     
    
  • arch/x86/kvm/svm/nested.c+5 2 modified
    diff --git a/arch/x86/kvm/svm/nested.c b/arch/x86/kvm/svm/nested.c
    index 3e2841598a36c4..0a35c815f4d266 100644
    --- a/arch/x86/kvm/svm/nested.c
    +++ b/arch/x86/kvm/svm/nested.c
    @@ -715,6 +715,7 @@ static void nested_vmcb02_prepare_save(struct vcpu_svm *svm, struct vmcb *vmcb12
     	} else {
     		svm_copy_lbrs(vmcb02, vmcb01);
     	}
    +	vmcb_mark_dirty(vmcb02, VMCB_LBR);
     	svm_update_lbrv(&svm->vcpu);
     }
     
    @@ -1231,10 +1232,12 @@ int nested_svm_vmexit(struct vcpu_svm *svm)
     		kvm_make_request(KVM_REQ_EVENT, &svm->vcpu);
     
     	if (unlikely(guest_cpu_cap_has(vcpu, X86_FEATURE_LBRV) &&
    -		     (svm->nested.ctl.virt_ext & LBR_CTL_ENABLE_MASK)))
    +		     (svm->nested.ctl.virt_ext & LBR_CTL_ENABLE_MASK))) {
     		svm_copy_lbrs(vmcb12, vmcb02);
    -	else
    +	} else {
     		svm_copy_lbrs(vmcb01, vmcb02);
    +		vmcb_mark_dirty(vmcb01, VMCB_LBR);
    +	}
     
     	svm_update_lbrv(vcpu);
     
    
  • arch/x86/kvm/svm/svm.c+0 3 modified
    diff --git a/arch/x86/kvm/svm/svm.c b/arch/x86/kvm/svm/svm.c
    index 543f9f3f966e39..9b4f5a46d55061 100644
    --- a/arch/x86/kvm/svm/svm.c
    +++ b/arch/x86/kvm/svm/svm.c
    @@ -848,8 +848,6 @@ void svm_copy_lbrs(struct vmcb *to_vmcb, struct vmcb *from_vmcb)
     	to_vmcb->save.br_to		= from_vmcb->save.br_to;
     	to_vmcb->save.last_excp_from	= from_vmcb->save.last_excp_from;
     	to_vmcb->save.last_excp_to	= from_vmcb->save.last_excp_to;
    -
    -	vmcb_mark_dirty(to_vmcb, VMCB_LBR);
     }
     
     static void __svm_enable_lbrv(struct kvm_vcpu *vcpu)
    -- 
    cgit 1.3-korg
    
    
    
  • arch/x86/kvm/svm/svm.c+0 3 modified
    diff --git a/arch/x86/kvm/svm/svm.c b/arch/x86/kvm/svm/svm.c
    index 543f9f3f966e39..9b4f5a46d55061 100644
    --- a/arch/x86/kvm/svm/svm.c
    +++ b/arch/x86/kvm/svm/svm.c
    @@ -848,8 +848,6 @@ void svm_copy_lbrs(struct vmcb *to_vmcb, struct vmcb *from_vmcb)
     	to_vmcb->save.br_to		= from_vmcb->save.br_to;
     	to_vmcb->save.last_excp_from	= from_vmcb->save.last_excp_from;
     	to_vmcb->save.last_excp_to	= from_vmcb->save.last_excp_to;
    -
    -	vmcb_mark_dirty(to_vmcb, VMCB_LBR);
     }
     
     static void __svm_enable_lbrv(struct kvm_vcpu *vcpu)
    -- 
    cgit 1.3-korg
    
    
    
b53ab5167a81

KVM: nSVM: Avoid clearing VMCB_LBR in vmcb12

4 files changed · +10 10
  • arch/x86/kvm/svm/nested.c+5 2 modified
    diff --git a/arch/x86/kvm/svm/nested.c b/arch/x86/kvm/svm/nested.c
    index 3e2841598a36c4..0a35c815f4d266 100644
    --- a/arch/x86/kvm/svm/nested.c
    +++ b/arch/x86/kvm/svm/nested.c
    @@ -715,6 +715,7 @@ static void nested_vmcb02_prepare_save(struct vcpu_svm *svm, struct vmcb *vmcb12
     	} else {
     		svm_copy_lbrs(vmcb02, vmcb01);
     	}
    +	vmcb_mark_dirty(vmcb02, VMCB_LBR);
     	svm_update_lbrv(&svm->vcpu);
     }
     
    @@ -1231,10 +1232,12 @@ int nested_svm_vmexit(struct vcpu_svm *svm)
     		kvm_make_request(KVM_REQ_EVENT, &svm->vcpu);
     
     	if (unlikely(guest_cpu_cap_has(vcpu, X86_FEATURE_LBRV) &&
    -		     (svm->nested.ctl.virt_ext & LBR_CTL_ENABLE_MASK)))
    +		     (svm->nested.ctl.virt_ext & LBR_CTL_ENABLE_MASK))) {
     		svm_copy_lbrs(vmcb12, vmcb02);
    -	else
    +	} else {
     		svm_copy_lbrs(vmcb01, vmcb02);
    +		vmcb_mark_dirty(vmcb01, VMCB_LBR);
    +	}
     
     	svm_update_lbrv(vcpu);
     
    
  • arch/x86/kvm/svm/nested.c+5 2 modified
    diff --git a/arch/x86/kvm/svm/nested.c b/arch/x86/kvm/svm/nested.c
    index 3e2841598a36c4..0a35c815f4d266 100644
    --- a/arch/x86/kvm/svm/nested.c
    +++ b/arch/x86/kvm/svm/nested.c
    @@ -715,6 +715,7 @@ static void nested_vmcb02_prepare_save(struct vcpu_svm *svm, struct vmcb *vmcb12
     	} else {
     		svm_copy_lbrs(vmcb02, vmcb01);
     	}
    +	vmcb_mark_dirty(vmcb02, VMCB_LBR);
     	svm_update_lbrv(&svm->vcpu);
     }
     
    @@ -1231,10 +1232,12 @@ int nested_svm_vmexit(struct vcpu_svm *svm)
     		kvm_make_request(KVM_REQ_EVENT, &svm->vcpu);
     
     	if (unlikely(guest_cpu_cap_has(vcpu, X86_FEATURE_LBRV) &&
    -		     (svm->nested.ctl.virt_ext & LBR_CTL_ENABLE_MASK)))
    +		     (svm->nested.ctl.virt_ext & LBR_CTL_ENABLE_MASK))) {
     		svm_copy_lbrs(vmcb12, vmcb02);
    -	else
    +	} else {
     		svm_copy_lbrs(vmcb01, vmcb02);
    +		vmcb_mark_dirty(vmcb01, VMCB_LBR);
    +	}
     
     	svm_update_lbrv(vcpu);
     
    
  • arch/x86/kvm/svm/svm.c+0 3 modified
    diff --git a/arch/x86/kvm/svm/svm.c b/arch/x86/kvm/svm/svm.c
    index 543f9f3f966e39..9b4f5a46d55061 100644
    --- a/arch/x86/kvm/svm/svm.c
    +++ b/arch/x86/kvm/svm/svm.c
    @@ -848,8 +848,6 @@ void svm_copy_lbrs(struct vmcb *to_vmcb, struct vmcb *from_vmcb)
     	to_vmcb->save.br_to		= from_vmcb->save.br_to;
     	to_vmcb->save.last_excp_from	= from_vmcb->save.last_excp_from;
     	to_vmcb->save.last_excp_to	= from_vmcb->save.last_excp_to;
    -
    -	vmcb_mark_dirty(to_vmcb, VMCB_LBR);
     }
     
     static void __svm_enable_lbrv(struct kvm_vcpu *vcpu)
    -- 
    cgit 1.3-korg
    
    
    
  • arch/x86/kvm/svm/svm.c+0 3 modified
    diff --git a/arch/x86/kvm/svm/svm.c b/arch/x86/kvm/svm/svm.c
    index 543f9f3f966e39..9b4f5a46d55061 100644
    --- a/arch/x86/kvm/svm/svm.c
    +++ b/arch/x86/kvm/svm/svm.c
    @@ -848,8 +848,6 @@ void svm_copy_lbrs(struct vmcb *to_vmcb, struct vmcb *from_vmcb)
     	to_vmcb->save.br_to		= from_vmcb->save.br_to;
     	to_vmcb->save.last_excp_from	= from_vmcb->save.last_excp_from;
     	to_vmcb->save.last_excp_to	= from_vmcb->save.last_excp_to;
    -
    -	vmcb_mark_dirty(to_vmcb, VMCB_LBR);
     }
     
     static void __svm_enable_lbrv(struct kvm_vcpu *vcpu)
    -- 
    cgit 1.3-korg
    
    
    
a3f0981a5a0e

KVM: nSVM: Avoid clearing VMCB_LBR in vmcb12

4 files changed · +10 10
  • arch/x86/kvm/svm/nested.c+5 2 modified
    diff --git a/arch/x86/kvm/svm/nested.c b/arch/x86/kvm/svm/nested.c
    index 6243e7d91c7582..947f3b961ce6fd 100644
    --- a/arch/x86/kvm/svm/nested.c
    +++ b/arch/x86/kvm/svm/nested.c
    @@ -684,6 +684,7 @@ static void nested_vmcb02_prepare_save(struct vcpu_svm *svm, struct vmcb *vmcb12
     	} else {
     		svm_copy_lbrs(vmcb02, vmcb01);
     	}
    +	vmcb_mark_dirty(vmcb02, VMCB_LBR);
     	svm_update_lbrv(&svm->vcpu);
     }
     
    @@ -1188,10 +1189,12 @@ int nested_svm_vmexit(struct vcpu_svm *svm)
     		kvm_make_request(KVM_REQ_EVENT, &svm->vcpu);
     
     	if (unlikely(guest_cpu_cap_has(vcpu, X86_FEATURE_LBRV) &&
    -		     (svm->nested.ctl.virt_ext & LBR_CTL_ENABLE_MASK)))
    +		     (svm->nested.ctl.virt_ext & LBR_CTL_ENABLE_MASK))) {
     		svm_copy_lbrs(vmcb12, vmcb02);
    -	else
    +	} else {
     		svm_copy_lbrs(vmcb01, vmcb02);
    +		vmcb_mark_dirty(vmcb01, VMCB_LBR);
    +	}
     
     	svm_update_lbrv(vcpu);
     
    
  • arch/x86/kvm/svm/nested.c+5 2 modified
    diff --git a/arch/x86/kvm/svm/nested.c b/arch/x86/kvm/svm/nested.c
    index 6243e7d91c7582..947f3b961ce6fd 100644
    --- a/arch/x86/kvm/svm/nested.c
    +++ b/arch/x86/kvm/svm/nested.c
    @@ -684,6 +684,7 @@ static void nested_vmcb02_prepare_save(struct vcpu_svm *svm, struct vmcb *vmcb12
     	} else {
     		svm_copy_lbrs(vmcb02, vmcb01);
     	}
    +	vmcb_mark_dirty(vmcb02, VMCB_LBR);
     	svm_update_lbrv(&svm->vcpu);
     }
     
    @@ -1188,10 +1189,12 @@ int nested_svm_vmexit(struct vcpu_svm *svm)
     		kvm_make_request(KVM_REQ_EVENT, &svm->vcpu);
     
     	if (unlikely(guest_cpu_cap_has(vcpu, X86_FEATURE_LBRV) &&
    -		     (svm->nested.ctl.virt_ext & LBR_CTL_ENABLE_MASK)))
    +		     (svm->nested.ctl.virt_ext & LBR_CTL_ENABLE_MASK))) {
     		svm_copy_lbrs(vmcb12, vmcb02);
    -	else
    +	} else {
     		svm_copy_lbrs(vmcb01, vmcb02);
    +		vmcb_mark_dirty(vmcb01, VMCB_LBR);
    +	}
     
     	svm_update_lbrv(vcpu);
     
    
  • arch/x86/kvm/svm/svm.c+0 3 modified
    diff --git a/arch/x86/kvm/svm/svm.c b/arch/x86/kvm/svm/svm.c
    index a8e8c76c023b84..055d3f64d06bed 100644
    --- a/arch/x86/kvm/svm/svm.c
    +++ b/arch/x86/kvm/svm/svm.c
    @@ -811,8 +811,6 @@ void svm_copy_lbrs(struct vmcb *to_vmcb, struct vmcb *from_vmcb)
     	to_vmcb->save.br_to		= from_vmcb->save.br_to;
     	to_vmcb->save.last_excp_from	= from_vmcb->save.last_excp_from;
     	to_vmcb->save.last_excp_to	= from_vmcb->save.last_excp_to;
    -
    -	vmcb_mark_dirty(to_vmcb, VMCB_LBR);
     }
     
     static void __svm_enable_lbrv(struct kvm_vcpu *vcpu)
    -- 
    cgit 1.3-korg
    
    
    
  • arch/x86/kvm/svm/svm.c+0 3 modified
    diff --git a/arch/x86/kvm/svm/svm.c b/arch/x86/kvm/svm/svm.c
    index a8e8c76c023b84..055d3f64d06bed 100644
    --- a/arch/x86/kvm/svm/svm.c
    +++ b/arch/x86/kvm/svm/svm.c
    @@ -811,8 +811,6 @@ void svm_copy_lbrs(struct vmcb *to_vmcb, struct vmcb *from_vmcb)
     	to_vmcb->save.br_to		= from_vmcb->save.br_to;
     	to_vmcb->save.last_excp_from	= from_vmcb->save.last_excp_from;
     	to_vmcb->save.last_excp_to	= from_vmcb->save.last_excp_to;
    -
    -	vmcb_mark_dirty(to_vmcb, VMCB_LBR);
     }
     
     static void __svm_enable_lbrv(struct kvm_vcpu *vcpu)
    -- 
    cgit 1.3-korg
    
    
    
9efe23568806

KVM: nSVM: Avoid clearing VMCB_LBR in vmcb12

4 files changed · +10 10
  • arch/x86/kvm/svm/nested.c+5 2 modified
    diff --git a/arch/x86/kvm/svm/nested.c b/arch/x86/kvm/svm/nested.c
    index 13de99f07be806..da9b3208feae27 100644
    --- a/arch/x86/kvm/svm/nested.c
    +++ b/arch/x86/kvm/svm/nested.c
    @@ -726,6 +726,7 @@ static void nested_vmcb02_prepare_save(struct vcpu_svm *svm, struct vmcb *vmcb12
     	} else {
     		svm_copy_lbrs(vmcb02, vmcb01);
     	}
    +	vmcb_mark_dirty(vmcb02, VMCB_LBR);
     	svm_update_lbrv(&svm->vcpu);
     }
     
    @@ -1242,10 +1243,12 @@ int nested_svm_vmexit(struct vcpu_svm *svm)
     		kvm_make_request(KVM_REQ_EVENT, &svm->vcpu);
     
     	if (unlikely(guest_cpu_cap_has(vcpu, X86_FEATURE_LBRV) &&
    -		     (svm->nested.ctl.virt_ext & LBR_CTL_ENABLE_MASK)))
    +		     (svm->nested.ctl.virt_ext & LBR_CTL_ENABLE_MASK))) {
     		svm_copy_lbrs(vmcb12, vmcb02);
    -	else
    +	} else {
     		svm_copy_lbrs(vmcb01, vmcb02);
    +		vmcb_mark_dirty(vmcb01, VMCB_LBR);
    +	}
     
     	svm_update_lbrv(vcpu);
     
    
  • arch/x86/kvm/svm/nested.c+5 2 modified
    diff --git a/arch/x86/kvm/svm/nested.c b/arch/x86/kvm/svm/nested.c
    index 13de99f07be806..da9b3208feae27 100644
    --- a/arch/x86/kvm/svm/nested.c
    +++ b/arch/x86/kvm/svm/nested.c
    @@ -726,6 +726,7 @@ static void nested_vmcb02_prepare_save(struct vcpu_svm *svm, struct vmcb *vmcb12
     	} else {
     		svm_copy_lbrs(vmcb02, vmcb01);
     	}
    +	vmcb_mark_dirty(vmcb02, VMCB_LBR);
     	svm_update_lbrv(&svm->vcpu);
     }
     
    @@ -1242,10 +1243,12 @@ int nested_svm_vmexit(struct vcpu_svm *svm)
     		kvm_make_request(KVM_REQ_EVENT, &svm->vcpu);
     
     	if (unlikely(guest_cpu_cap_has(vcpu, X86_FEATURE_LBRV) &&
    -		     (svm->nested.ctl.virt_ext & LBR_CTL_ENABLE_MASK)))
    +		     (svm->nested.ctl.virt_ext & LBR_CTL_ENABLE_MASK))) {
     		svm_copy_lbrs(vmcb12, vmcb02);
    -	else
    +	} else {
     		svm_copy_lbrs(vmcb01, vmcb02);
    +		vmcb_mark_dirty(vmcb01, VMCB_LBR);
    +	}
     
     	svm_update_lbrv(vcpu);
     
    
  • arch/x86/kvm/svm/svm.c+0 3 modified
    diff --git a/arch/x86/kvm/svm/svm.c b/arch/x86/kvm/svm/svm.c
    index c0cad55c78db50..cebd93682dd5c2 100644
    --- a/arch/x86/kvm/svm/svm.c
    +++ b/arch/x86/kvm/svm/svm.c
    @@ -848,8 +848,6 @@ void svm_copy_lbrs(struct vmcb *to_vmcb, struct vmcb *from_vmcb)
     	to_vmcb->save.br_to		= from_vmcb->save.br_to;
     	to_vmcb->save.last_excp_from	= from_vmcb->save.last_excp_from;
     	to_vmcb->save.last_excp_to	= from_vmcb->save.last_excp_to;
    -
    -	vmcb_mark_dirty(to_vmcb, VMCB_LBR);
     }
     
     static void __svm_enable_lbrv(struct kvm_vcpu *vcpu)
    -- 
    cgit 1.3-korg
    
    
    
  • arch/x86/kvm/svm/svm.c+0 3 modified
    diff --git a/arch/x86/kvm/svm/svm.c b/arch/x86/kvm/svm/svm.c
    index c0cad55c78db50..cebd93682dd5c2 100644
    --- a/arch/x86/kvm/svm/svm.c
    +++ b/arch/x86/kvm/svm/svm.c
    @@ -848,8 +848,6 @@ void svm_copy_lbrs(struct vmcb *to_vmcb, struct vmcb *from_vmcb)
     	to_vmcb->save.br_to		= from_vmcb->save.br_to;
     	to_vmcb->save.last_excp_from	= from_vmcb->save.last_excp_from;
     	to_vmcb->save.last_excp_to	= from_vmcb->save.last_excp_to;
    -
    -	vmcb_mark_dirty(to_vmcb, VMCB_LBR);
     }
     
     static void __svm_enable_lbrv(struct kvm_vcpu *vcpu)
    -- 
    cgit 1.3-korg
    
    
    

Vulnerability mechanics

Root cause

"svm_copy_lbrs() unconditionally calls vmcb_mark_dirty(to_vmcb, VMCB_LBR), which clears the VMCB_LBR clean bit in vmcb12 during nested_svm_vmexit(), an operation that is not architecturally defined for the guest's VMCB."

Attack vector

An attacker running a nested (L2) guest on an AMD SVM host with nested LBR virtualization enabled (LBR_CTL_ENABLE_MASK set) can trigger this bug. When the L2 guest exits back to L1 (nested_svm_vmexit()), the kernel copies LBR state into vmcb12 via svm_copy_lbrs(). Because svm_copy_lbrs() unconditionally called vmcb_mark_dirty() on the destination VMCB, the VMCB_LBR clean bit in vmcb12 was spuriously cleared. This violates architectural expectations for the L1 hypervisor's VMCB state and could cause incorrect behavior or missed optimizations in the L1 hypervisor.

Affected code

The vulnerable code is in arch/x86/kvm/svm/svm.c in the svm_copy_lbrs() function, which unconditionally called vmcb_mark_dirty(to_vmcb, VMCB_LBR). The callers in arch/x86/kvm/svm/nested.c are nested_svm_vmexit() (which passes vmcb12 as the destination) and nested_vmcb02_prepare_save() (which passes vmcb02).

What the fix does

The fix removes the vmcb_mark_dirty(VMCB_LBR) call from inside svm_copy_lbrs() in arch/x86/kvm/svm/svm.c [patch_id=2659955]. Instead, the dirty marking is moved to the callers in arch/x86/kvm/svm/nested.c: nested_vmcb02_prepare_save() now calls vmcb_mark_dirty(vmcb02, VMCB_LBR) after copying LBRs to vmcb02, and the else branch of nested_svm_vmexit() calls vmcb_mark_dirty(vmcb01, VMCB_LBR) after copying LBRs to vmcb01. Crucially, the if branch that copies LBRs to vmcb12 does NOT add a vmcb_mark_dirty() call, because clearing clean bits in the guest's vmcb12 is not architecturally defined [patch_id=2659955].

Preconditions

  • configNested virtualization (nSVM) must be enabled on the host.
  • configThe L2 guest must have LBR virtualization enabled (LBR_CTL_ENABLE_MASK set in virt_ext).
  • inputAn L2 guest exit must occur, triggering nested_svm_vmexit() which copies LBR state to vmcb12.

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

References

3

News mentions

0

No linked articles in our index yet.