CVE-2026-46032
Description
In the Linux kernel, the following vulnerability has been resolved:
KVM: nSVM: Triple fault if restore host CR3 fails on nested #VMEXIT
If loading L1's CR3 fails on a nested #VMEXIT, nested_svm_vmexit() returns an error code that is ignored by most callers, and continues to run L1 with corrupted state. A sane recovery is not possible in this case, and HW behavior is to cause a shutdown. Inject a triple fault instead, and do not return early from nested_svm_vmexit(). Continue cleaning up the vCPU state (e.g. clear pending exceptions), to handle the failure as gracefully as possible.
From the APM:
Upon #VMEXIT, the processor performs the following actions in order to return to the host execution context:
...
if (illegal host state loaded, or exception while loading host state) shutdown else execute first host instruction following the VMRUN
Remove the return value of nested_svm_vmexit(), which is mostly unchecked anyway.
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
Unchecked error on nested VMEXIT can leave L1 with corrupted state; kernel patch injects triple fault instead.
Vulnerability
The vulnerability resides in the Linux kernel's KVM implementation for nested SVM (nSVM). In nested_svm_vmexit(), if loading the host (L1) CR3 fails during a nested #VMEXIT, the function returns an error code. However, several callers ignore this return value, allowing execution to continue in L1 with corrupted state. The affected code is present in stable kernel versions prior to the fix commit 9a738cf170a4 [1].
Exploitation
To trigger this issue, an attacker must be able to run nested virtual machines (i.e., have access to SVM virtualization) and cause a scenario where restoring L1's CR3 fails on a #VMEXIT. This requires specific conditions such as invalid host state or an exception during host state loading. No special authentication or network access is needed beyond the ability to configure nested virtualization.
Impact
If the failure is ignored, L1 continues execution with a corrupted control register, potentially leading to undefined behavior, crashes, or security breaches (e.g., privilege escalation or information disclosure). The documented hardware behavior for such a failure is a shutdown (triple fault). The fix ensures that a triple fault is injected, halting the guest safely instead of proceeding with corrupted state.
Mitigation
The fix was committed to the Linux kernel stable branch as commit 9a738cf170a4 [1]. Administrators should apply the patch or update to a kernel version containing this commit. No workaround is available, as the error handling is purely in the hypervisor code. The vulnerability is not currently listed on CISA's KEV.
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
2Patches
45d291ef0585eKVM: nSVM: Triple fault if restore host CR3 fails on nested #VMEXIT
3 files changed · +8 −20
arch/x86/kvm/svm/nested.c+3 −7 modifieddiff --git a/arch/x86/kvm/svm/nested.c b/arch/x86/kvm/svm/nested.c index 30c99bbe9927dd..5e0feeb50ba326 100644 --- a/arch/x86/kvm/svm/nested.c +++ b/arch/x86/kvm/svm/nested.c @@ -1192,12 +1192,11 @@ static int nested_svm_vmexit_update_vmcb12(struct kvm_vcpu *vcpu) return 0; } -int nested_svm_vmexit(struct vcpu_svm *svm) +void nested_svm_vmexit(struct vcpu_svm *svm) { struct kvm_vcpu *vcpu = &svm->vcpu; struct vmcb *vmcb01 = svm->vmcb01.ptr; struct vmcb *vmcb02 = svm->nested.vmcb02.ptr; - int rc; if (nested_svm_vmexit_update_vmcb12(vcpu)) kvm_make_request(KVM_REQ_TRIPLE_FAULT, vcpu); @@ -1316,9 +1315,8 @@ int nested_svm_vmexit(struct vcpu_svm *svm) nested_svm_uninit_mmu_context(vcpu); - rc = nested_svm_load_cr3(vcpu, vmcb01->save.cr3, false, true); - if (rc) - return 1; + if (nested_svm_load_cr3(vcpu, vmcb01->save.cr3, false, true)) + kvm_make_request(KVM_REQ_TRIPLE_FAULT, vcpu); /* * Drop what we picked up for L2 via svm_complete_interrupts() so it @@ -1343,8 +1341,6 @@ int nested_svm_vmexit(struct vcpu_svm *svm) */ if (kvm_apicv_activated(vcpu->kvm)) __kvm_vcpu_update_apicv(vcpu); - - return 0; } static void nested_svm_triple_fault(struct kvm_vcpu *vcpu)
arch/x86/kvm/svm/svm.c+2 −9 modifieddiff --git a/arch/x86/kvm/svm/svm.c b/arch/x86/kvm/svm/svm.c index e97c56df41f632..7efa7170929232 100644 --- a/arch/x86/kvm/svm/svm.c +++ b/arch/x86/kvm/svm/svm.c @@ -2234,13 +2234,9 @@ static int emulate_svm_instr(struct kvm_vcpu *vcpu, int opcode) [SVM_INSTR_VMSAVE] = vmsave_interception, }; struct vcpu_svm *svm = to_svm(vcpu); - int ret; if (is_guest_mode(vcpu)) { - /* Returns '1' or -errno on failure, '0' on success. */ - ret = nested_svm_simple_vmexit(svm, guest_mode_exit_codes[opcode]); - if (ret) - return ret; + nested_svm_simple_vmexit(svm, guest_mode_exit_codes[opcode]); return 1; } return svm_instr_handlers[opcode](vcpu); @@ -4871,7 +4867,6 @@ static int svm_enter_smm(struct kvm_vcpu *vcpu, union kvm_smram *smram) { struct vcpu_svm *svm = to_svm(vcpu); struct kvm_host_map map_save; - int ret; if (!is_guest_mode(vcpu)) return 0; @@ -4891,9 +4886,7 @@ static int svm_enter_smm(struct kvm_vcpu *vcpu, union kvm_smram *smram) svm->vmcb->save.rsp = vcpu->arch.regs[VCPU_REGS_RSP]; svm->vmcb->save.rip = vcpu->arch.regs[VCPU_REGS_RIP]; - ret = nested_svm_simple_vmexit(svm, SVM_EXIT_SW); - if (ret) - return ret; + nested_svm_simple_vmexit(svm, SVM_EXIT_SW); /* * KVM uses VMCB01 to store L1 host state while L2 runs but
arch/x86/kvm/svm/svm.h+3 −4 modifieddiff --git a/arch/x86/kvm/svm/svm.h b/arch/x86/kvm/svm/svm.h index 44d767cd1d25a9..7629cb37c9302d 100644 --- a/arch/x86/kvm/svm/svm.h +++ b/arch/x86/kvm/svm/svm.h @@ -793,14 +793,14 @@ int nested_svm_vmrun(struct kvm_vcpu *vcpu); void svm_copy_vmrun_state(struct vmcb_save_area *to_save, struct vmcb_save_area *from_save); void svm_copy_vmloadsave_state(struct vmcb *to_vmcb, struct vmcb *from_vmcb); -int nested_svm_vmexit(struct vcpu_svm *svm); +void nested_svm_vmexit(struct vcpu_svm *svm); -static inline int nested_svm_simple_vmexit(struct vcpu_svm *svm, u32 exit_code) +static inline void nested_svm_simple_vmexit(struct vcpu_svm *svm, u32 exit_code) { svm->vmcb->control.exit_code = exit_code; svm->vmcb->control.exit_info_1 = 0; svm->vmcb->control.exit_info_2 = 0; - return nested_svm_vmexit(svm); + nested_svm_vmexit(svm); } int nested_svm_exit_handled(struct vcpu_svm *svm); -- cgit 1.3-korg
9a738cf170a4KVM: nSVM: Triple fault if restore host CR3 fails on nested #VMEXIT
3 files changed · +8 −20
arch/x86/kvm/svm/nested.c+3 −7 modifieddiff --git a/arch/x86/kvm/svm/nested.c b/arch/x86/kvm/svm/nested.c index f20cc23ba73ec2..6a3668a7fafdbf 100644 --- a/arch/x86/kvm/svm/nested.c +++ b/arch/x86/kvm/svm/nested.c @@ -1202,12 +1202,11 @@ static int nested_svm_vmexit_update_vmcb12(struct kvm_vcpu *vcpu) return 0; } -int nested_svm_vmexit(struct vcpu_svm *svm) +void nested_svm_vmexit(struct vcpu_svm *svm) { struct kvm_vcpu *vcpu = &svm->vcpu; struct vmcb *vmcb01 = svm->vmcb01.ptr; struct vmcb *vmcb02 = svm->nested.vmcb02.ptr; - int rc; rc = nested_svm_vmexit_update_vmcb12(vcpu); if (rc) { @@ -1330,9 +1329,8 @@ int nested_svm_vmexit(struct vcpu_svm *svm) nested_svm_uninit_mmu_context(vcpu); - rc = nested_svm_load_cr3(vcpu, vmcb01->save.cr3, false, true); - if (rc) - return 1; + if (nested_svm_load_cr3(vcpu, vmcb01->save.cr3, false, true)) + kvm_make_request(KVM_REQ_TRIPLE_FAULT, vcpu); /* * Drop what we picked up for L2 via svm_complete_interrupts() so it @@ -1357,8 +1355,6 @@ int nested_svm_vmexit(struct vcpu_svm *svm) */ if (kvm_apicv_activated(vcpu->kvm)) __kvm_vcpu_update_apicv(vcpu); - - return 0; } static void nested_svm_triple_fault(struct kvm_vcpu *vcpu)
arch/x86/kvm/svm/svm.c+2 −9 modifieddiff --git a/arch/x86/kvm/svm/svm.c b/arch/x86/kvm/svm/svm.c index 1f5b8bab27a07f..5c5f0ad206deb2 100644 --- a/arch/x86/kvm/svm/svm.c +++ b/arch/x86/kvm/svm/svm.c @@ -2233,13 +2233,9 @@ static int emulate_svm_instr(struct kvm_vcpu *vcpu, int opcode) [SVM_INSTR_VMSAVE] = vmsave_interception, }; struct vcpu_svm *svm = to_svm(vcpu); - int ret; if (is_guest_mode(vcpu)) { - /* Returns '1' or -errno on failure, '0' on success. */ - ret = nested_svm_simple_vmexit(svm, guest_mode_exit_codes[opcode]); - if (ret) - return ret; + nested_svm_simple_vmexit(svm, guest_mode_exit_codes[opcode]); return 1; } return svm_instr_handlers[opcode](vcpu); @@ -4872,7 +4868,6 @@ static int svm_enter_smm(struct kvm_vcpu *vcpu, union kvm_smram *smram) { struct vcpu_svm *svm = to_svm(vcpu); struct kvm_host_map map_save; - int ret; if (!is_guest_mode(vcpu)) return 0; @@ -4892,9 +4887,7 @@ static int svm_enter_smm(struct kvm_vcpu *vcpu, union kvm_smram *smram) svm->vmcb->save.rsp = vcpu->arch.regs[VCPU_REGS_RSP]; svm->vmcb->save.rip = vcpu->arch.regs[VCPU_REGS_RIP]; - ret = nested_svm_simple_vmexit(svm, SVM_EXIT_SW); - if (ret) - return ret; + nested_svm_simple_vmexit(svm, SVM_EXIT_SW); /* * KVM uses VMCB01 to store L1 host state while L2 runs but
arch/x86/kvm/svm/svm.h+3 −4 modifieddiff --git a/arch/x86/kvm/svm/svm.h b/arch/x86/kvm/svm/svm.h index 7e57810821604d..69996be0182946 100644 --- a/arch/x86/kvm/svm/svm.h +++ b/arch/x86/kvm/svm/svm.h @@ -793,14 +793,14 @@ int nested_svm_vmrun(struct kvm_vcpu *vcpu); void svm_copy_vmrun_state(struct vmcb_save_area *to_save, struct vmcb_save_area *from_save); void svm_copy_vmloadsave_state(struct vmcb *to_vmcb, struct vmcb *from_vmcb); -int nested_svm_vmexit(struct vcpu_svm *svm); +void nested_svm_vmexit(struct vcpu_svm *svm); -static inline int nested_svm_simple_vmexit(struct vcpu_svm *svm, u32 exit_code) +static inline void nested_svm_simple_vmexit(struct vcpu_svm *svm, u32 exit_code) { svm->vmcb->control.exit_code = exit_code; svm->vmcb->control.exit_info_1 = 0; svm->vmcb->control.exit_info_2 = 0; - return nested_svm_vmexit(svm); + nested_svm_vmexit(svm); } int nested_svm_exit_handled(struct vcpu_svm *svm); -- cgit 1.3-korg
5d291ef0585eKVM: nSVM: Triple fault if restore host CR3 fails on nested #VMEXIT
3 files changed · +8 −20
arch/x86/kvm/svm/nested.c+3 −7 modifieddiff --git a/arch/x86/kvm/svm/nested.c b/arch/x86/kvm/svm/nested.c index 30c99bbe9927dd..5e0feeb50ba326 100644 --- a/arch/x86/kvm/svm/nested.c +++ b/arch/x86/kvm/svm/nested.c @@ -1192,12 +1192,11 @@ static int nested_svm_vmexit_update_vmcb12(struct kvm_vcpu *vcpu) return 0; } -int nested_svm_vmexit(struct vcpu_svm *svm) +void nested_svm_vmexit(struct vcpu_svm *svm) { struct kvm_vcpu *vcpu = &svm->vcpu; struct vmcb *vmcb01 = svm->vmcb01.ptr; struct vmcb *vmcb02 = svm->nested.vmcb02.ptr; - int rc; if (nested_svm_vmexit_update_vmcb12(vcpu)) kvm_make_request(KVM_REQ_TRIPLE_FAULT, vcpu); @@ -1316,9 +1315,8 @@ int nested_svm_vmexit(struct vcpu_svm *svm) nested_svm_uninit_mmu_context(vcpu); - rc = nested_svm_load_cr3(vcpu, vmcb01->save.cr3, false, true); - if (rc) - return 1; + if (nested_svm_load_cr3(vcpu, vmcb01->save.cr3, false, true)) + kvm_make_request(KVM_REQ_TRIPLE_FAULT, vcpu); /* * Drop what we picked up for L2 via svm_complete_interrupts() so it @@ -1343,8 +1341,6 @@ int nested_svm_vmexit(struct vcpu_svm *svm) */ if (kvm_apicv_activated(vcpu->kvm)) __kvm_vcpu_update_apicv(vcpu); - - return 0; } static void nested_svm_triple_fault(struct kvm_vcpu *vcpu)
arch/x86/kvm/svm/svm.c+2 −9 modifieddiff --git a/arch/x86/kvm/svm/svm.c b/arch/x86/kvm/svm/svm.c index e97c56df41f632..7efa7170929232 100644 --- a/arch/x86/kvm/svm/svm.c +++ b/arch/x86/kvm/svm/svm.c @@ -2234,13 +2234,9 @@ static int emulate_svm_instr(struct kvm_vcpu *vcpu, int opcode) [SVM_INSTR_VMSAVE] = vmsave_interception, }; struct vcpu_svm *svm = to_svm(vcpu); - int ret; if (is_guest_mode(vcpu)) { - /* Returns '1' or -errno on failure, '0' on success. */ - ret = nested_svm_simple_vmexit(svm, guest_mode_exit_codes[opcode]); - if (ret) - return ret; + nested_svm_simple_vmexit(svm, guest_mode_exit_codes[opcode]); return 1; } return svm_instr_handlers[opcode](vcpu); @@ -4871,7 +4867,6 @@ static int svm_enter_smm(struct kvm_vcpu *vcpu, union kvm_smram *smram) { struct vcpu_svm *svm = to_svm(vcpu); struct kvm_host_map map_save; - int ret; if (!is_guest_mode(vcpu)) return 0; @@ -4891,9 +4886,7 @@ static int svm_enter_smm(struct kvm_vcpu *vcpu, union kvm_smram *smram) svm->vmcb->save.rsp = vcpu->arch.regs[VCPU_REGS_RSP]; svm->vmcb->save.rip = vcpu->arch.regs[VCPU_REGS_RIP]; - ret = nested_svm_simple_vmexit(svm, SVM_EXIT_SW); - if (ret) - return ret; + nested_svm_simple_vmexit(svm, SVM_EXIT_SW); /* * KVM uses VMCB01 to store L1 host state while L2 runs but
arch/x86/kvm/svm/svm.h+3 −4 modifieddiff --git a/arch/x86/kvm/svm/svm.h b/arch/x86/kvm/svm/svm.h index 44d767cd1d25a9..7629cb37c9302d 100644 --- a/arch/x86/kvm/svm/svm.h +++ b/arch/x86/kvm/svm/svm.h @@ -793,14 +793,14 @@ int nested_svm_vmrun(struct kvm_vcpu *vcpu); void svm_copy_vmrun_state(struct vmcb_save_area *to_save, struct vmcb_save_area *from_save); void svm_copy_vmloadsave_state(struct vmcb *to_vmcb, struct vmcb *from_vmcb); -int nested_svm_vmexit(struct vcpu_svm *svm); +void nested_svm_vmexit(struct vcpu_svm *svm); -static inline int nested_svm_simple_vmexit(struct vcpu_svm *svm, u32 exit_code) +static inline void nested_svm_simple_vmexit(struct vcpu_svm *svm, u32 exit_code) { svm->vmcb->control.exit_code = exit_code; svm->vmcb->control.exit_info_1 = 0; svm->vmcb->control.exit_info_2 = 0; - return nested_svm_vmexit(svm); + nested_svm_vmexit(svm); } int nested_svm_exit_handled(struct vcpu_svm *svm); -- cgit 1.3-korg
9a738cf170a4KVM: nSVM: Triple fault if restore host CR3 fails on nested #VMEXIT
3 files changed · +8 −20
arch/x86/kvm/svm/nested.c+3 −7 modifieddiff --git a/arch/x86/kvm/svm/nested.c b/arch/x86/kvm/svm/nested.c index f20cc23ba73ec2..6a3668a7fafdbf 100644 --- a/arch/x86/kvm/svm/nested.c +++ b/arch/x86/kvm/svm/nested.c @@ -1202,12 +1202,11 @@ static int nested_svm_vmexit_update_vmcb12(struct kvm_vcpu *vcpu) return 0; } -int nested_svm_vmexit(struct vcpu_svm *svm) +void nested_svm_vmexit(struct vcpu_svm *svm) { struct kvm_vcpu *vcpu = &svm->vcpu; struct vmcb *vmcb01 = svm->vmcb01.ptr; struct vmcb *vmcb02 = svm->nested.vmcb02.ptr; - int rc; rc = nested_svm_vmexit_update_vmcb12(vcpu); if (rc) { @@ -1330,9 +1329,8 @@ int nested_svm_vmexit(struct vcpu_svm *svm) nested_svm_uninit_mmu_context(vcpu); - rc = nested_svm_load_cr3(vcpu, vmcb01->save.cr3, false, true); - if (rc) - return 1; + if (nested_svm_load_cr3(vcpu, vmcb01->save.cr3, false, true)) + kvm_make_request(KVM_REQ_TRIPLE_FAULT, vcpu); /* * Drop what we picked up for L2 via svm_complete_interrupts() so it @@ -1357,8 +1355,6 @@ int nested_svm_vmexit(struct vcpu_svm *svm) */ if (kvm_apicv_activated(vcpu->kvm)) __kvm_vcpu_update_apicv(vcpu); - - return 0; } static void nested_svm_triple_fault(struct kvm_vcpu *vcpu)
arch/x86/kvm/svm/svm.c+2 −9 modifieddiff --git a/arch/x86/kvm/svm/svm.c b/arch/x86/kvm/svm/svm.c index 1f5b8bab27a07f..5c5f0ad206deb2 100644 --- a/arch/x86/kvm/svm/svm.c +++ b/arch/x86/kvm/svm/svm.c @@ -2233,13 +2233,9 @@ static int emulate_svm_instr(struct kvm_vcpu *vcpu, int opcode) [SVM_INSTR_VMSAVE] = vmsave_interception, }; struct vcpu_svm *svm = to_svm(vcpu); - int ret; if (is_guest_mode(vcpu)) { - /* Returns '1' or -errno on failure, '0' on success. */ - ret = nested_svm_simple_vmexit(svm, guest_mode_exit_codes[opcode]); - if (ret) - return ret; + nested_svm_simple_vmexit(svm, guest_mode_exit_codes[opcode]); return 1; } return svm_instr_handlers[opcode](vcpu); @@ -4872,7 +4868,6 @@ static int svm_enter_smm(struct kvm_vcpu *vcpu, union kvm_smram *smram) { struct vcpu_svm *svm = to_svm(vcpu); struct kvm_host_map map_save; - int ret; if (!is_guest_mode(vcpu)) return 0; @@ -4892,9 +4887,7 @@ static int svm_enter_smm(struct kvm_vcpu *vcpu, union kvm_smram *smram) svm->vmcb->save.rsp = vcpu->arch.regs[VCPU_REGS_RSP]; svm->vmcb->save.rip = vcpu->arch.regs[VCPU_REGS_RIP]; - ret = nested_svm_simple_vmexit(svm, SVM_EXIT_SW); - if (ret) - return ret; + nested_svm_simple_vmexit(svm, SVM_EXIT_SW); /* * KVM uses VMCB01 to store L1 host state while L2 runs but
arch/x86/kvm/svm/svm.h+3 −4 modifieddiff --git a/arch/x86/kvm/svm/svm.h b/arch/x86/kvm/svm/svm.h index 7e57810821604d..69996be0182946 100644 --- a/arch/x86/kvm/svm/svm.h +++ b/arch/x86/kvm/svm/svm.h @@ -793,14 +793,14 @@ int nested_svm_vmrun(struct kvm_vcpu *vcpu); void svm_copy_vmrun_state(struct vmcb_save_area *to_save, struct vmcb_save_area *from_save); void svm_copy_vmloadsave_state(struct vmcb *to_vmcb, struct vmcb *from_vmcb); -int nested_svm_vmexit(struct vcpu_svm *svm); +void nested_svm_vmexit(struct vcpu_svm *svm); -static inline int nested_svm_simple_vmexit(struct vcpu_svm *svm, u32 exit_code) +static inline void nested_svm_simple_vmexit(struct vcpu_svm *svm, u32 exit_code) { svm->vmcb->control.exit_code = exit_code; svm->vmcb->control.exit_info_1 = 0; svm->vmcb->control.exit_info_2 = 0; - return nested_svm_vmexit(svm); + nested_svm_vmexit(svm); } int nested_svm_exit_handled(struct vcpu_svm *svm); -- cgit 1.3-korg
Vulnerability mechanics
Root cause
"Missing error handling when restoring L1's CR3 fails during a nested #VMEXIT; the error return was ignored by most callers, leaving L1 running with corrupted state."
Attack vector
An attacker who can run a nested virtual machine (L2) on an AMD SVM host can trigger a failure when KVM attempts to reload L1's CR3 during a nested #VMEXIT. This failure can occur due to memory pressure or an invalid host CR3 value. Before the fix, nested_svm_vmexit() returned an error code that callers such as emulate_svm_instr() and svm_enter_smm() ignored, causing L1 to continue execution with a corrupted page-table root [patch_id=2660276]. The hardware specification (APM) states that an exception while loading host state should cause a shutdown, not silent continuation.
Affected code
The vulnerability is in arch/x86/kvm/svm/nested.c in the nested_svm_vmexit() function, specifically where nested_svm_load_cr3() is called to restore L1's CR3 [patch_id=2660276]. The callers in arch/x86/kvm/svm/svm.c (emulate_svm_instr() and svm_enter_smm()) and the wrapper nested_svm_simple_vmexit() in arch/x86/kvm/svm/svm.h are also affected because they ignored the error return [patch_id=2660276].
What the fix does
The patch changes nested_svm_vmexit() from returning an int to returning void, and replaces the early return on CR3 load failure with kvm_make_request(KVM_REQ_TRIPLE_FAULT, vcpu) [patch_id=2660276][patch_id=2660277]. This ensures that when nested_svm_load_cr3() fails, a triple fault is injected into the vCPU rather than allowing L1 to run with a broken CR3. The function continues executing past the failure to clean up vCPU state (e.g. clearing pending exceptions), handling the failure as gracefully as possible. All callers that previously checked the return value are simplified to unconditionally proceed, since the triple fault request will be serviced later.
Preconditions
- configThe host must be running an AMD SVM-capable CPU with nested virtualization (nSVM) enabled.
- inputThe attacker must be able to run a nested guest (L2) and cause nested_svm_load_cr3() to fail, e.g. through memory pressure or by corrupting L1's host CR3.
Generated on May 27, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
2News mentions
0No linked articles in our index yet.