From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Seungil Jeon <jeon1691951@gmail.com>
Date: Wed, 26 Mar 2026 00:00:00 +0000
Subject: [PATCH] KVM: nSVM: Snapshot vmcb12 save.rip to prevent TOCTOU race

vmcb12->save.rip is read three separate times from guest-writable memory
during nested VMRUN processing, without being snapshotted into the save
area cache. A malicious L1 guest with multiple vCPUs can concurrently
modify vmcb12->save.rip through another vCPU (since the vmcb12 page is
mapped as a direct HVA into guest physical memory with no write
protection), producing a three-way RIP inconsistency across:

  1) svm->soft_int_old_rip and svm->soft_int_next_rip (set in
     nested_vmcb02_prepare_control() from vmcb12->save.rip)
  2) vcpu->arch.regs[VCPU_REGS_RIP] (set via kvm_rip_write() in
     nested_vmcb02_prepare_save())
  3) vmcb02->save.rip (set directly in nested_vmcb02_prepare_save())

This is the save-area analog of CVE-2021-29657, which exploited the
same TOCTOU pattern on vmcb12->control.intercept to achieve full host
code execution. The CVE-2021-29657 fix introduced
nested_copy_vmcb_save_to_cache() but deliberately excluded rip, rsp,
and rax from the cached fields, leaving the save area vulnerable to
the same class of concurrent-modification attacks.

When combined with soft interrupt injection via event_inj, the three-way
RIP split causes KVM to record inconsistent state for interrupt
re-injection: soft_int_old_rip holds one value (from read #1), the vCPU
register state holds another (from read #2), and the hardware VMCB holds
a third (from read #3). If delivery of the soft interrupt faults (e.g.,
#PF on IDT access), svm_complete_interrupts() uses soft_int_old_rip to
reconstruct the pre-injection state, which no longer matches the actual
vCPU or hardware state. This can trigger WARN_ON assertions in KVM's
interrupt handling, cause instruction emulation at wrong addresses, or
lead to information disclosure through confused emulation.

Fix this by adding rip, rsp, and rax to vmcb_save_area_cached, copying
them in __nested_copy_vmcb_save_to_cache(), and replacing all direct
reads of vmcb12->save.{rip,rsp,rax} with reads from the cached copy.
This ensures that all consumers within a single nested VMRUN see a
consistent snapshot of these registers, closing the TOCTOU window.

The same approach is used for rsp and rax, which suffer from analogous
(though currently two-read rather than three-read) TOCTOU patterns in
nested_vmcb02_prepare_save().

Cc: stable@vger.kernel.org
Reported-by: Seungil Jeon <jeon1691951@gmail.com>
Signed-off-by: Seungil Jeon <jeon1691951@gmail.com>
---
 arch/x86/kvm/svm/nested.c | 22 +++++++++++-----------
 arch/x86/kvm/svm/svm.h    |  3 +++
 2 files changed, 14 insertions(+), 11 deletions(-)

diff --git a/arch/x86/kvm/svm/svm.h b/arch/x86/kvm/svm/svm.h
index XXXXXXX..XXXXXXX 100644
--- a/arch/x86/kvm/svm/svm.h
+++ b/arch/x86/kvm/svm/svm.h
@@ -XXX,XX +XXX,XX @@ struct vmcb_save_area_cached {
 	u64 cr4;
 	u64 dr6;
 	u64 dr7;
+	u64 rip;
+	u64 rsp;
+	u64 rax;
 };

 struct vmcb_ctrl_area_cached {
diff --git a/arch/x86/kvm/svm/nested.c b/arch/x86/kvm/svm/nested.c
index XXXXXXX..XXXXXXX 100644
--- a/arch/x86/kvm/svm/nested.c
+++ b/arch/x86/kvm/svm/nested.c
@@ -XXX,XX +XXX,XX @@ static void __nested_copy_vmcb_save_to_cache(struct vmcb_save_area_cached *to,
 					      struct vmcb_save_area *from)
 {
 	/*
-	 * Copy only fields that are validated, as we need them
-	 * to avoid TOC/TOU races.
+	 * Copy fields that are either validated or read multiple times,
+	 * as we need a consistent snapshot to avoid TOC/TOU races.
+	 * rip/rsp/rax are read multiple times from guest-writable
+	 * memory in enter_svm_guest_mode() and must be snapshotted.
 	 */
 	to->efer = from->efer;
 	to->cr0  = from->cr0;
 	to->cr3  = from->cr3;
 	to->cr4  = from->cr4;
 	to->dr6  = from->dr6;
 	to->dr7  = from->dr7;
+	to->rip  = from->rip;
+	to->rsp  = from->rsp;
+	to->rax  = from->rax;
 }

 /*
@@ -XXX,XX +XXX,XX @@ static void nested_vmcb02_prepare_save(struct vcpu_svm *svm,
 					struct vmcb *vmcb12)
 {
+	struct vmcb_save_area_cached *save = &svm->nested.save;
 	struct kvm_vcpu *vcpu = &svm->vcpu;

 	/* ... segment register copies from vmcb12 (unchanged) ... */

-	kvm_rip_write(vcpu, vmcb12->save.rip);
-	vmcb02->save.rip = vmcb12->save.rip;
+	kvm_rip_write(vcpu, save->rip);
+	vmcb02->save.rip = save->rip;

-	kvm_rsp_write(vcpu, vmcb12->save.rsp);
-	vmcb02->save.rsp = vmcb12->save.rsp;
+	kvm_rsp_write(vcpu, save->rsp);
+	vmcb02->save.rsp = save->rsp;

-	kvm_rax_write(vcpu, vmcb12->save.rax);
-	vmcb02->save.rax = vmcb12->save.rax;
+	kvm_rax_write(vcpu, save->rax);
+	vmcb02->save.rax = save->rax;

 	/* ... remainder of function unchanged ... */
 }
@@ -XXX,XX +XXX,XX @@ int enter_svm_guest_mode(struct vcpu_svm *svm,
 			 struct vmcb *vmcb12, bool from_vmrun)
 {
+	struct vmcb_save_area_cached *save = &svm->nested.save;
 	struct vmcb *vmcb02 = svm->nested.vmcb02.ptr;

 	/* ... existing code ... */

-	nested_vmcb02_prepare_control(svm, vmcb12->save.rip,
-				      vmcb12->save.cs.base);
+	nested_vmcb02_prepare_control(svm, save->rip,
+				      vmcb12->save.cs.base);

 	nested_vmcb02_prepare_save(svm, vmcb12);

 	/* ... remainder of function unchanged ... */
 }
--
2.43.0
