From: Sean Christopherson <seanjc@google.com>
To: Kyle Zeng <kylebot@openai.com>
Cc: kvm@vger.kernel.org, Paolo Bonzini <pbonzini@redhat.com>,
Thomas Gleixner <tglx@kernel.org>,
Ingo Molnar <mingo@redhat.com>, Borislav Petkov <bp@alien8.de>,
Dave Hansen <dave.hansen@linux.intel.com>,
x86@kernel.org, Chris Ayoub <cayoub@openai.com>,
Oleg Boiko <oboiko@openai.com>
Subject: Re: [PATCH] KVM: x86: Restrict saved GPA writes to hardware write faults
Date: Fri, 28 Aug 2026 16:12:51 -0700 [thread overview]
Message-ID: <apIV8151LGvValMh@google.com> (raw)
In-Reply-To: <20260828193055.59623-1-kylebot@openai.com>
On Fri, Aug 28, 2026, Kyle Zeng wrote:
> A GPA supplied by a hardware page fault describes the access that
> faulted, not an arbitrary instruction decoded afterwards. A guest can
> change an MMIO read into a store before KVM fetches the instruction. The
> saved-GPA shortcut then skips the write-aware guest page-table walk and
> can issue a write through a guest-read-only mapping.
>
> Carry final-write fault information into the emulator and retain it
> alongside the saved GPA. Only reuse that GPA for a write when hardware
> reported a final data write. Reads, faults with unknown access direction,
> and implicit guest page-table writes do not authorize an emulated write;
> fall back to the existing permission-aware translation in those cases.
Why not? As noted above, the GPA is the faulting GPA, not the final GPA. If the
guest kernel is crazy/broken enough to try and put its page tables in emulated
MMIO space, then IMO it's a-ok to assume the guest has given userspace permission
to generate writes to those addresses.
Then it doesn't matter what protections the final translation has, and so the SEV
problem goes away.
> diff --git a/arch/x86/kvm/mmu/mmu.c b/arch/x86/kvm/mmu/mmu.c
> index 064ecc33b926..223698cbb9be 100644
> --- a/arch/x86/kvm/mmu/mmu.c
> +++ b/arch/x86/kvm/mmu/mmu.c
> @@ -6632,6 +6632,16 @@ int noinline kvm_mmu_page_fault(struct kvm_vcpu *vcpu, gpa_t cr2_or_gpa, u64 err
> return r;
>
> emulate:
> + /*
> + * A write during a guest page walk does not authorize the instruction
> + * itself to write to the faulting GPA. Require a final write access
> + * before allowing the emulator to reuse the GPA for writes.
> + */
> + if (direct && (error_code & PFERR_WRITE_MASK) &&
> + (error_code & PFERR_GUEST_FINAL_MASK) &&
> + !(error_code & PFERR_GUEST_PAGE_MASK))
> + emulation_type |= EMULTYPE_PF_WRITE;
> +
> return x86_emulate_instruction(vcpu, cr2_or_gpa, emulation_type, insn,
> insn_len);
> }
> diff --git a/arch/x86/kvm/x86.c b/arch/x86/kvm/x86.c
> index 79468ddfe473..8c46aa3468c2 100644
> --- a/arch/x86/kvm/x86.c
> +++ b/arch/x86/kvm/x86.c
> @@ -5088,8 +5088,13 @@ static int emulator_read_write_onepage(unsigned long addr, void *val,
> * Note, this cannot be used on string operations since string
> * operation using rep will only have the initial GPA from the NPF
> * occurred.
> + *
> + * The guest may have changed the instruction since the fault. Reuse
> + * the GPA for writes only if hardware reported a final write access;
> + * otherwise, recheck permissions for the decoded access.
> */
> - if (ctxt->gpa_available && emulator_can_use_gpa(ctxt) &&
> + if (ctxt->gpa_available && (!write || ctxt->gpa_write) &&
LOL, because I can't help myself. In the spirit of evil bitwise operations when
doing paging protection checks, what if we do:
diff --git a/arch/x86/kvm/kvm_emulate.h b/arch/x86/kvm/kvm_emulate.h
index 3e375af15c03..5b74fcd183f5 100644
--- a/arch/x86/kvm/kvm_emulate.h
+++ b/arch/x86/kvm/kvm_emulate.h
@@ -355,7 +355,7 @@ struct x86_emulate_ctxt {
struct x86_exception exception;
/* GPA available */
- bool gpa_available;
+ u64 gpa_access;
gpa_t gpa_val;
/*
diff --git a/arch/x86/kvm/x86.c b/arch/x86/kvm/x86.c
index 4b3681796c75..7470ff762975 100644
--- a/arch/x86/kvm/x86.c
+++ b/arch/x86/kvm/x86.c
@@ -5089,7 +5089,7 @@ static int emulator_read_write_onepage(unsigned long addr, void *val,
* operation using rep will only have the initial GPA from the NPF
* occurred.
*/
- if (ctxt->gpa_available && emulator_can_use_gpa(ctxt) &&
+ if (ctxt->gpa_access & BIT(write) && emulator_can_use_gpa(ctxt) &&
(addr & ~PAGE_MASK) == (ctxt->gpa_val & ~PAGE_MASK)) {
gpa = ctxt->gpa_val;
ret = vcpu_is_mmio_gpa(vcpu, addr, gpa, write);
@@ -5938,7 +5938,7 @@ static void init_emulate_ctxt(struct kvm_vcpu *vcpu)
kvm_x86_call(get_cs_db_l_bits)(vcpu, &cs_db, &cs_l);
- ctxt->gpa_available = false;
+ ctxt->gpa_access = 0;
ctxt->eflags = kvm_get_rflags(vcpu);
ctxt->tf = (ctxt->eflags & X86_EFLAGS_TF) != 0;
@@ -6454,7 +6454,10 @@ int x86_emulate_instruction(struct kvm_vcpu *vcpu, gpa_t cr2_or_gpa,
/* With shadow page tables, cr2 contains a GVA or nGPA. */
if (vcpu->arch.mmu->root_role.direct) {
- ctxt->gpa_available = true;
+ if (emulation_type & EMULTYPE_PF_WRITE)
+ ctxt->gpa_access = ACC_WRITE_MASK;
+ else
+ ctxt->gpa_access = ACC_READ_MASK;
ctxt->gpa_val = cr2_or_gpa;
}
} else {
Or a slightly less evil version:
diff --git a/arch/x86/kvm/kvm_emulate.h b/arch/x86/kvm/kvm_emulate.h
index 3e375af15c03..5b74fcd183f5 100644
--- a/arch/x86/kvm/kvm_emulate.h
+++ b/arch/x86/kvm/kvm_emulate.h
@@ -355,7 +355,7 @@ struct x86_emulate_ctxt {
struct x86_exception exception;
/* GPA available */
- bool gpa_available;
+ u64 gpa_access;
gpa_t gpa_val;
/*
diff --git a/arch/x86/kvm/x86.c b/arch/x86/kvm/x86.c
index 4b3681796c75..d6522e38008a 100644
--- a/arch/x86/kvm/x86.c
+++ b/arch/x86/kvm/x86.c
@@ -5089,7 +5089,8 @@ static int emulator_read_write_onepage(unsigned long addr, void *val,
* operation using rep will only have the initial GPA from the NPF
* occurred.
*/
- if (ctxt->gpa_available && emulator_can_use_gpa(ctxt) &&
+ if (ctxt->gpa_access & (write ? ACC_WRITE_MASK : ACC_READ_MASK) &&
+ emulator_can_use_gpa(ctxt) &&
(addr & ~PAGE_MASK) == (ctxt->gpa_val & ~PAGE_MASK)) {
gpa = ctxt->gpa_val;
ret = vcpu_is_mmio_gpa(vcpu, addr, gpa, write);
@@ -5938,7 +5939,7 @@ static void init_emulate_ctxt(struct kvm_vcpu *vcpu)
kvm_x86_call(get_cs_db_l_bits)(vcpu, &cs_db, &cs_l);
- ctxt->gpa_available = false;
+ ctxt->gpa_access = 0;
ctxt->eflags = kvm_get_rflags(vcpu);
ctxt->tf = (ctxt->eflags & X86_EFLAGS_TF) != 0;
@@ -6454,7 +6455,10 @@ int x86_emulate_instruction(struct kvm_vcpu *vcpu, gpa_t cr2_or_gpa,
/* With shadow page tables, cr2 contains a GVA or nGPA. */
if (vcpu->arch.mmu->root_role.direct) {
- ctxt->gpa_available = true;
+ if (emulation_type & EMULTYPE_PF_WRITE)
+ ctxt->gpa_access = ACC_WRITE_MASK;
+ else
+ ctxt->gpa_access = ACC_READ_MASK;
ctxt->gpa_val = cr2_or_gpa;
}
} else {
prev parent reply other threads:[~2026-08-28 23:12 UTC|newest]
Thread overview: 4+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-08-28 19:30 [PATCH] KVM: x86: Restrict saved GPA writes to hardware write faults Kyle Zeng
2026-08-28 19:49 ` sashiko-bot
2026-08-28 21:02 ` Kyle Zeng
2026-08-28 23:12 ` Sean Christopherson [this message]
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=apIV8151LGvValMh@google.com \
--to=seanjc@google.com \
--cc=bp@alien8.de \
--cc=cayoub@openai.com \
--cc=dave.hansen@linux.intel.com \
--cc=kvm@vger.kernel.org \
--cc=kylebot@openai.com \
--cc=mingo@redhat.com \
--cc=oboiko@openai.com \
--cc=pbonzini@redhat.com \
--cc=tglx@kernel.org \
--cc=x86@kernel.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.