* [PATCH v2 00/22] KVM: x86: Fix multiple #PF RO infinite loop bugs
@ 2024-08-31 0:15 Sean Christopherson
2024-08-31 0:15 ` [PATCH v2 01/22] KVM: VMX: Set PFERR_GUEST_{FINAL,PAGE}_MASK if and only if the GVA is valid Sean Christopherson
` (22 more replies)
0 siblings, 23 replies; 26+ messages in thread
From: Sean Christopherson @ 2024-08-31 0:15 UTC (permalink / raw)
To: Sean Christopherson, Paolo Bonzini; +Cc: kvm, linux-kernel, Yuan Yao, Yuan Yao
Fix an amusing number of minor bugs that can lead to KVM putting the guest into
an infinite "retry #PF" loop, and cleanup and consolidate the unprotect+retry
paths (there are four-ish).
As a bonus, adding RET_PF_WRITE_PROTECTED obviates the need for
kvm_lookup_pfn()[*].
[*] https://lore.kernel.org/all/63c41e25-2523-4397-96b4-557394281443@redhat.com
v2:
- Gather reviews.
- Rewrite the comment for the nested NPT "fast" unprotect path to explain what
it actually handles. [Yuan]
- Drop Cc: stable for the remaining patches, as it's extremely unlikely these
fixes would actually prevent a guest from dying. [Paolo]
- Move the patch to replace PFERR_NESTED_GUEST_PAGE much earlier. [Paolo]
- Wrap indirect_shadow_pages with READ_ONCE(). [Paolo]
- Massage a few changelogs to (hopefully) fix weird wordings. [Paolo]
- Always refer directly to last_retry_{addr,eip} in comments, instead of
indirectly referencing them as "infinite loop protection". [Paolo]
- WARN if write-protection hits the MMIO cache. [Yuan]
v1: https://lore.kernel.org/all/20240809190319.1710470-1-seanjc@google.com
Sean Christopherson (22):
KVM: VMX: Set PFERR_GUEST_{FINAL,PAGE}_MASK if and only if the GVA is
valid
KVM: x86/mmu: Replace PFERR_NESTED_GUEST_PAGE with a more descriptive
helper
KVM: x86/mmu: Trigger unprotect logic only on write-protection page
faults
KVM: x86/mmu: Skip emulation on page fault iff 1+ SPs were unprotected
KVM: x86: Retry to-be-emulated insn in "slow" unprotect path iff sp is
zapped
KVM: x86: Get RIP from vCPU state when storing it to last_retry_eip
KVM: x86: Store gpa as gpa_t, not unsigned long, when unprotecting for
retry
KVM: x86/mmu: Apply retry protection to "fast nTDP unprotect" path
KVM: x86/mmu: Try "unprotect for retry" iff there are indirect SPs
KVM: x86: Move EMULTYPE_ALLOW_RETRY_PF to x86_emulate_instruction()
KVM: x86: Fold retry_instruction() into x86_emulate_instruction()
KVM: x86/mmu: Don't try to unprotect an INVALID_GPA
KVM: x86/mmu: Always walk guest PTEs with WRITE access when
unprotecting
KVM: x86/mmu: Move event re-injection unprotect+retry into common path
KVM: x86: Remove manual pfn lookup when retrying #PF after failed
emulation
KVM: x86: Check EMULTYPE_WRITE_PF_TO_SP before unprotecting gfn
KVM: x86: Apply retry protection to "unprotect on failure" path
KVM: x86: Update retry protection fields when forcing retry on
emulation failure
KVM: x86: Rename
reexecute_instruction()=>kvm_unprotect_and_retry_on_failure()
KVM: x86/mmu: Subsume kvm_mmu_unprotect_page() into the and_retry()
version
KVM: x86/mmu: Detect if unprotect will do anything based on
invalid_list
KVM: x86/mmu: WARN on MMIO cache hit when emulating write-protected
gfn
arch/x86/include/asm/kvm_host.h | 14 ++-
arch/x86/kvm/mmu/mmu.c | 200 +++++++++++++++++++++++---------
arch/x86/kvm/mmu/mmu_internal.h | 3 +
arch/x86/kvm/mmu/mmutrace.h | 1 +
arch/x86/kvm/mmu/paging_tmpl.h | 2 +-
arch/x86/kvm/mmu/tdp_mmu.c | 6 +-
arch/x86/kvm/vmx/vmx.c | 5 +-
arch/x86/kvm/x86.c | 133 ++++++---------------
8 files changed, 198 insertions(+), 166 deletions(-)
base-commit: a1206bc992c3cd3f758a9b46117dfc7e59e8c10f
--
2.46.0.469.g59c65b2a67-goog
^ permalink raw reply [flat|nested] 26+ messages in thread
* [PATCH v2 01/22] KVM: VMX: Set PFERR_GUEST_{FINAL,PAGE}_MASK if and only if the GVA is valid
2024-08-31 0:15 [PATCH v2 00/22] KVM: x86: Fix multiple #PF RO infinite loop bugs Sean Christopherson
@ 2024-08-31 0:15 ` Sean Christopherson
2024-08-31 0:15 ` [PATCH v2 02/22] KVM: x86/mmu: Replace PFERR_NESTED_GUEST_PAGE with a more descriptive helper Sean Christopherson
` (21 subsequent siblings)
22 siblings, 0 replies; 26+ messages in thread
From: Sean Christopherson @ 2024-08-31 0:15 UTC (permalink / raw)
To: Sean Christopherson, Paolo Bonzini; +Cc: kvm, linux-kernel, Yuan Yao, Yuan Yao
Set PFERR_GUEST_{FINAL,PAGE}_MASK based on EPT_VIOLATION_GVA_TRANSLATED if
and only if EPT_VIOLATION_GVA_IS_VALID is also set in exit qualification.
Per the SDM, bit 8 (EPT_VIOLATION_GVA_TRANSLATED) is valid if and only if
bit 7 (EPT_VIOLATION_GVA_IS_VALID) is set, and is '0' if bit 7 is '0'.
Bit 7 (a.k.a. EPT_VIOLATION_GVA_IS_VALID)
Set if the guest linear-address field is valid. The guest linear-address
field is valid for all EPT violations except those resulting from an
attempt to load the guest PDPTEs as part of the execution of the MOV CR
instruction and those due to trace-address pre-translation
Bit 8 (a.k.a. EPT_VIOLATION_GVA_TRANSLATED)
If bit 7 is 1:
• Set if the access causing the EPT violation is to a guest-physical
address that is the translation of a linear address.
• Clear if the access causing the EPT violation is to a paging-structure
entry as part of a page walk or the update of an accessed or dirty bit.
Reserved if bit 7 is 0 (cleared to 0).
Failure to guard the logic on GVA_IS_VALID results in KVM marking the page
fault as PFERR_GUEST_PAGE_MASK when there is no known GVA, which can put
the vCPU into an infinite loop due to kvm_mmu_page_fault() getting false
positive on its PFERR_NESTED_GUEST_PAGE logic (though only because that
logic is also buggy/flawed).
In practice, this is largely a non-issue because so GVA_IS_VALID is almost
always set. However, when TDX comes along, GVA_IS_VALID will *never* be
set, as the TDX Module deliberately clears bits 12:7 in exit qualification,
e.g. so that the faulting virtual address and other metadata that aren't
practically useful for the hypervisor aren't leaked to the untrusted host.
When exit is due to EPT violation, bits 12-7 of the exit qualification
are cleared to 0.
Fixes: eebed2438923 ("kvm: nVMX: Add support for fast unprotection of nested guest page tables")
Reviewed-by: Yuan Yao <yuan.yao@intel.com>
Signed-off-by: Sean Christopherson <seanjc@google.com>
---
arch/x86/kvm/vmx/vmx.c | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/arch/x86/kvm/vmx/vmx.c b/arch/x86/kvm/vmx/vmx.c
index f9fbc299126c..ad5c3f149fd3 100644
--- a/arch/x86/kvm/vmx/vmx.c
+++ b/arch/x86/kvm/vmx/vmx.c
@@ -5800,8 +5800,9 @@ static int handle_ept_violation(struct kvm_vcpu *vcpu)
error_code |= (exit_qualification & EPT_VIOLATION_RWX_MASK)
? PFERR_PRESENT_MASK : 0;
- error_code |= (exit_qualification & EPT_VIOLATION_GVA_TRANSLATED) != 0 ?
- PFERR_GUEST_FINAL_MASK : PFERR_GUEST_PAGE_MASK;
+ if (error_code & EPT_VIOLATION_GVA_IS_VALID)
+ error_code |= (exit_qualification & EPT_VIOLATION_GVA_TRANSLATED) ?
+ PFERR_GUEST_FINAL_MASK : PFERR_GUEST_PAGE_MASK;
/*
* Check that the GPA doesn't exceed physical memory limits, as that is
--
2.46.0.469.g59c65b2a67-goog
^ permalink raw reply [flat|nested] 26+ messages in thread
* [PATCH v2 02/22] KVM: x86/mmu: Replace PFERR_NESTED_GUEST_PAGE with a more descriptive helper
2024-08-31 0:15 [PATCH v2 00/22] KVM: x86: Fix multiple #PF RO infinite loop bugs Sean Christopherson
2024-08-31 0:15 ` [PATCH v2 01/22] KVM: VMX: Set PFERR_GUEST_{FINAL,PAGE}_MASK if and only if the GVA is valid Sean Christopherson
@ 2024-08-31 0:15 ` Sean Christopherson
2024-08-31 0:15 ` [PATCH v2 03/22] KVM: x86/mmu: Trigger unprotect logic only on write-protection page faults Sean Christopherson
` (20 subsequent siblings)
22 siblings, 0 replies; 26+ messages in thread
From: Sean Christopherson @ 2024-08-31 0:15 UTC (permalink / raw)
To: Sean Christopherson, Paolo Bonzini; +Cc: kvm, linux-kernel, Yuan Yao, Yuan Yao
Drop the globally visible PFERR_NESTED_GUEST_PAGE and replace it with a
more appropriately named is_write_to_guest_page_table(). The macro name
is misleading, because while all nNPT walks match PAGE|WRITE|PRESENT, the
reverse is not true.
No functional change intended.
Signed-off-by: Sean Christopherson <seanjc@google.com>
---
arch/x86/include/asm/kvm_host.h | 4 ----
arch/x86/kvm/mmu/mmu.c | 9 ++++++++-
2 files changed, 8 insertions(+), 5 deletions(-)
diff --git a/arch/x86/include/asm/kvm_host.h b/arch/x86/include/asm/kvm_host.h
index 1811a42fa093..62d19403d63c 100644
--- a/arch/x86/include/asm/kvm_host.h
+++ b/arch/x86/include/asm/kvm_host.h
@@ -280,10 +280,6 @@ enum x86_intercept_stage;
#define PFERR_PRIVATE_ACCESS BIT_ULL(49)
#define PFERR_SYNTHETIC_MASK (PFERR_IMPLICIT_ACCESS | PFERR_PRIVATE_ACCESS)
-#define PFERR_NESTED_GUEST_PAGE (PFERR_GUEST_PAGE_MASK | \
- PFERR_WRITE_MASK | \
- PFERR_PRESENT_MASK)
-
/* apic attention bits */
#define KVM_APIC_CHECK_VAPIC 0
/*
diff --git a/arch/x86/kvm/mmu/mmu.c b/arch/x86/kvm/mmu/mmu.c
index d25c2b395116..4ca01256143e 100644
--- a/arch/x86/kvm/mmu/mmu.c
+++ b/arch/x86/kvm/mmu/mmu.c
@@ -5947,6 +5947,13 @@ void kvm_mmu_track_write(struct kvm_vcpu *vcpu, gpa_t gpa, const u8 *new,
write_unlock(&vcpu->kvm->mmu_lock);
}
+static bool is_write_to_guest_page_table(u64 error_code)
+{
+ const u64 mask = PFERR_GUEST_PAGE_MASK | PFERR_WRITE_MASK | PFERR_PRESENT_MASK;
+
+ return (error_code & mask) == mask;
+}
+
int noinline kvm_mmu_page_fault(struct kvm_vcpu *vcpu, gpa_t cr2_or_gpa, u64 error_code,
void *insn, int insn_len)
{
@@ -6010,7 +6017,7 @@ int noinline kvm_mmu_page_fault(struct kvm_vcpu *vcpu, gpa_t cr2_or_gpa, u64 err
* and resume the guest.
*/
if (vcpu->arch.mmu->root_role.direct &&
- (error_code & PFERR_NESTED_GUEST_PAGE) == PFERR_NESTED_GUEST_PAGE) {
+ is_write_to_guest_page_table(error_code)) {
kvm_mmu_unprotect_page(vcpu->kvm, gpa_to_gfn(cr2_or_gpa));
return 1;
}
--
2.46.0.469.g59c65b2a67-goog
^ permalink raw reply [flat|nested] 26+ messages in thread
* [PATCH v2 03/22] KVM: x86/mmu: Trigger unprotect logic only on write-protection page faults
2024-08-31 0:15 [PATCH v2 00/22] KVM: x86: Fix multiple #PF RO infinite loop bugs Sean Christopherson
2024-08-31 0:15 ` [PATCH v2 01/22] KVM: VMX: Set PFERR_GUEST_{FINAL,PAGE}_MASK if and only if the GVA is valid Sean Christopherson
2024-08-31 0:15 ` [PATCH v2 02/22] KVM: x86/mmu: Replace PFERR_NESTED_GUEST_PAGE with a more descriptive helper Sean Christopherson
@ 2024-08-31 0:15 ` Sean Christopherson
2024-08-31 0:15 ` [PATCH v2 04/22] KVM: x86/mmu: Skip emulation on page fault iff 1+ SPs were unprotected Sean Christopherson
` (19 subsequent siblings)
22 siblings, 0 replies; 26+ messages in thread
From: Sean Christopherson @ 2024-08-31 0:15 UTC (permalink / raw)
To: Sean Christopherson, Paolo Bonzini; +Cc: kvm, linux-kernel, Yuan Yao, Yuan Yao
Trigger KVM's various "unprotect gfn" paths if and only if the page fault
was a write to a write-protected gfn. To do so, add a new page fault
return code, RET_PF_WRITE_PROTECTED, to explicitly and precisely track
such page faults.
If a page fault requires emulation for any MMIO (or any reason besides
write-protection), trying to unprotect the gfn is pointless and risks
putting the vCPU into an infinite loop. E.g. KVM will put the vCPU into
an infinite loop if the vCPU manages to trigger MMIO on a page table walk.
Fixes: 147277540bbc ("kvm: svm: Add support for additional SVM NPF error codes")
Reviewed-by: Yuan Yao <yuan.yao@intel.com>
Signed-off-by: Sean Christopherson <seanjc@google.com>
---
arch/x86/kvm/mmu/mmu.c | 75 +++++++++++++++++++--------------
arch/x86/kvm/mmu/mmu_internal.h | 3 ++
arch/x86/kvm/mmu/mmutrace.h | 1 +
arch/x86/kvm/mmu/paging_tmpl.h | 2 +-
arch/x86/kvm/mmu/tdp_mmu.c | 6 +--
5 files changed, 50 insertions(+), 37 deletions(-)
diff --git a/arch/x86/kvm/mmu/mmu.c b/arch/x86/kvm/mmu/mmu.c
index 4ca01256143e..57692d873f76 100644
--- a/arch/x86/kvm/mmu/mmu.c
+++ b/arch/x86/kvm/mmu/mmu.c
@@ -2896,10 +2896,8 @@ static int mmu_set_spte(struct kvm_vcpu *vcpu, struct kvm_memory_slot *slot,
trace_kvm_mmu_set_spte(level, gfn, sptep);
}
- if (wrprot) {
- if (write_fault)
- ret = RET_PF_EMULATE;
- }
+ if (wrprot && write_fault)
+ ret = RET_PF_WRITE_PROTECTED;
if (flush)
kvm_flush_remote_tlbs_gfn(vcpu->kvm, gfn, level);
@@ -4531,7 +4529,7 @@ static int direct_page_fault(struct kvm_vcpu *vcpu, struct kvm_page_fault *fault
return RET_PF_RETRY;
if (page_fault_handle_page_track(vcpu, fault))
- return RET_PF_EMULATE;
+ return RET_PF_WRITE_PROTECTED;
r = fast_page_fault(vcpu, fault);
if (r != RET_PF_INVALID)
@@ -4624,7 +4622,7 @@ static int kvm_tdp_mmu_page_fault(struct kvm_vcpu *vcpu,
int r;
if (page_fault_handle_page_track(vcpu, fault))
- return RET_PF_EMULATE;
+ return RET_PF_WRITE_PROTECTED;
r = fast_page_fault(vcpu, fault);
if (r != RET_PF_INVALID)
@@ -4703,6 +4701,7 @@ static int kvm_tdp_map_page(struct kvm_vcpu *vcpu, gpa_t gpa, u64 error_code,
switch (r) {
case RET_PF_FIXED:
case RET_PF_SPURIOUS:
+ case RET_PF_WRITE_PROTECTED:
return 0;
case RET_PF_EMULATE:
@@ -5954,6 +5953,40 @@ static bool is_write_to_guest_page_table(u64 error_code)
return (error_code & mask) == mask;
}
+static int kvm_mmu_write_protect_fault(struct kvm_vcpu *vcpu, gpa_t cr2_or_gpa,
+ u64 error_code, int *emulation_type)
+{
+ bool direct = vcpu->arch.mmu->root_role.direct;
+
+ /*
+ * Before emulating the instruction, check if the error code
+ * was due to a RO violation while translating the guest page.
+ * This can occur when using nested virtualization with nested
+ * paging in both guests. If true, we simply unprotect the page
+ * and resume the guest.
+ */
+ if (direct && is_write_to_guest_page_table(error_code)) {
+ kvm_mmu_unprotect_page(vcpu->kvm, gpa_to_gfn(cr2_or_gpa));
+ return RET_PF_RETRY;
+ }
+
+ /*
+ * The gfn is write-protected, but if emulation fails we can still
+ * optimistically try to just unprotect the page and let the processor
+ * re-execute the instruction that caused the page fault. Do not allow
+ * retrying MMIO emulation, as it's not only pointless but could also
+ * cause us to enter an infinite loop because the processor will keep
+ * faulting on the non-existent MMIO address. Retrying an instruction
+ * from a nested guest is also pointless and dangerous as we are only
+ * explicitly shadowing L1's page tables, i.e. unprotecting something
+ * for L1 isn't going to magically fix whatever issue cause L2 to fail.
+ */
+ if (!mmio_info_in_cache(vcpu, cr2_or_gpa, direct) && !is_guest_mode(vcpu))
+ *emulation_type |= EMULTYPE_ALLOW_RETRY_PF;
+
+ return RET_PF_EMULATE;
+}
+
int noinline kvm_mmu_page_fault(struct kvm_vcpu *vcpu, gpa_t cr2_or_gpa, u64 error_code,
void *insn, int insn_len)
{
@@ -5999,6 +6032,10 @@ int noinline kvm_mmu_page_fault(struct kvm_vcpu *vcpu, gpa_t cr2_or_gpa, u64 err
if (r < 0)
return r;
+ if (r == RET_PF_WRITE_PROTECTED)
+ r = kvm_mmu_write_protect_fault(vcpu, cr2_or_gpa, error_code,
+ &emulation_type);
+
if (r == RET_PF_FIXED)
vcpu->stat.pf_fixed++;
else if (r == RET_PF_EMULATE)
@@ -6009,32 +6046,6 @@ int noinline kvm_mmu_page_fault(struct kvm_vcpu *vcpu, gpa_t cr2_or_gpa, u64 err
if (r != RET_PF_EMULATE)
return 1;
- /*
- * Before emulating the instruction, check if the error code
- * was due to a RO violation while translating the guest page.
- * This can occur when using nested virtualization with nested
- * paging in both guests. If true, we simply unprotect the page
- * and resume the guest.
- */
- if (vcpu->arch.mmu->root_role.direct &&
- is_write_to_guest_page_table(error_code)) {
- kvm_mmu_unprotect_page(vcpu->kvm, gpa_to_gfn(cr2_or_gpa));
- return 1;
- }
-
- /*
- * vcpu->arch.mmu.page_fault returned RET_PF_EMULATE, but we can still
- * optimistically try to just unprotect the page and let the processor
- * re-execute the instruction that caused the page fault. Do not allow
- * retrying MMIO emulation, as it's not only pointless but could also
- * cause us to enter an infinite loop because the processor will keep
- * faulting on the non-existent MMIO address. Retrying an instruction
- * from a nested guest is also pointless and dangerous as we are only
- * explicitly shadowing L1's page tables, i.e. unprotecting something
- * for L1 isn't going to magically fix whatever issue cause L2 to fail.
- */
- if (!mmio_info_in_cache(vcpu, cr2_or_gpa, direct) && !is_guest_mode(vcpu))
- emulation_type |= EMULTYPE_ALLOW_RETRY_PF;
emulate:
return x86_emulate_instruction(vcpu, cr2_or_gpa, emulation_type, insn,
insn_len);
diff --git a/arch/x86/kvm/mmu/mmu_internal.h b/arch/x86/kvm/mmu/mmu_internal.h
index 1721d97743e9..50d2624111f8 100644
--- a/arch/x86/kvm/mmu/mmu_internal.h
+++ b/arch/x86/kvm/mmu/mmu_internal.h
@@ -258,6 +258,8 @@ int kvm_tdp_page_fault(struct kvm_vcpu *vcpu, struct kvm_page_fault *fault);
* RET_PF_CONTINUE: So far, so good, keep handling the page fault.
* RET_PF_RETRY: let CPU fault again on the address.
* RET_PF_EMULATE: mmio page fault, emulate the instruction directly.
+ * RET_PF_WRITE_PROTECTED: the gfn is write-protected, either unprotected the
+ * gfn and retry, or emulate the instruction directly.
* RET_PF_INVALID: the spte is invalid, let the real page fault path update it.
* RET_PF_FIXED: The faulting entry has been fixed.
* RET_PF_SPURIOUS: The faulting entry was already fixed, e.g. by another vCPU.
@@ -274,6 +276,7 @@ enum {
RET_PF_CONTINUE = 0,
RET_PF_RETRY,
RET_PF_EMULATE,
+ RET_PF_WRITE_PROTECTED,
RET_PF_INVALID,
RET_PF_FIXED,
RET_PF_SPURIOUS,
diff --git a/arch/x86/kvm/mmu/mmutrace.h b/arch/x86/kvm/mmu/mmutrace.h
index 195d98bc8de8..f35a830ce469 100644
--- a/arch/x86/kvm/mmu/mmutrace.h
+++ b/arch/x86/kvm/mmu/mmutrace.h
@@ -57,6 +57,7 @@
TRACE_DEFINE_ENUM(RET_PF_CONTINUE);
TRACE_DEFINE_ENUM(RET_PF_RETRY);
TRACE_DEFINE_ENUM(RET_PF_EMULATE);
+TRACE_DEFINE_ENUM(RET_PF_WRITE_PROTECTED);
TRACE_DEFINE_ENUM(RET_PF_INVALID);
TRACE_DEFINE_ENUM(RET_PF_FIXED);
TRACE_DEFINE_ENUM(RET_PF_SPURIOUS);
diff --git a/arch/x86/kvm/mmu/paging_tmpl.h b/arch/x86/kvm/mmu/paging_tmpl.h
index 69941cebb3a8..a722a3c96af9 100644
--- a/arch/x86/kvm/mmu/paging_tmpl.h
+++ b/arch/x86/kvm/mmu/paging_tmpl.h
@@ -805,7 +805,7 @@ static int FNAME(page_fault)(struct kvm_vcpu *vcpu, struct kvm_page_fault *fault
if (page_fault_handle_page_track(vcpu, fault)) {
shadow_page_table_clear_flood(vcpu, fault->addr);
- return RET_PF_EMULATE;
+ return RET_PF_WRITE_PROTECTED;
}
r = mmu_topup_memory_caches(vcpu, true);
diff --git a/arch/x86/kvm/mmu/tdp_mmu.c b/arch/x86/kvm/mmu/tdp_mmu.c
index 3c55955bcaf8..3b996c1fdaab 100644
--- a/arch/x86/kvm/mmu/tdp_mmu.c
+++ b/arch/x86/kvm/mmu/tdp_mmu.c
@@ -1046,10 +1046,8 @@ static int tdp_mmu_map_handle_target_level(struct kvm_vcpu *vcpu,
* protected, emulation is needed. If the emulation was skipped,
* the vCPU would have the same fault again.
*/
- if (wrprot) {
- if (fault->write)
- ret = RET_PF_EMULATE;
- }
+ if (wrprot && fault->write)
+ ret = RET_PF_WRITE_PROTECTED;
/* If a MMIO SPTE is installed, the MMIO will need to be emulated. */
if (unlikely(is_mmio_spte(vcpu->kvm, new_spte))) {
--
2.46.0.469.g59c65b2a67-goog
^ permalink raw reply [flat|nested] 26+ messages in thread
* [PATCH v2 04/22] KVM: x86/mmu: Skip emulation on page fault iff 1+ SPs were unprotected
2024-08-31 0:15 [PATCH v2 00/22] KVM: x86: Fix multiple #PF RO infinite loop bugs Sean Christopherson
` (2 preceding siblings ...)
2024-08-31 0:15 ` [PATCH v2 03/22] KVM: x86/mmu: Trigger unprotect logic only on write-protection page faults Sean Christopherson
@ 2024-08-31 0:15 ` Sean Christopherson
2024-09-06 6:22 ` Yuan Yao
2024-08-31 0:15 ` [PATCH v2 05/22] KVM: x86: Retry to-be-emulated insn in "slow" unprotect path iff sp is zapped Sean Christopherson
` (18 subsequent siblings)
22 siblings, 1 reply; 26+ messages in thread
From: Sean Christopherson @ 2024-08-31 0:15 UTC (permalink / raw)
To: Sean Christopherson, Paolo Bonzini; +Cc: kvm, linux-kernel, Yuan Yao, Yuan Yao
When doing "fast unprotection" of nested TDP page tables, skip emulation
if and only if at least one gfn was unprotected, i.e. continue with
emulation if simply resuming is likely to hit the same fault and risk
putting the vCPU into an infinite loop.
Note, it's entirely possible to get a false negative, e.g. if a different
vCPU faults on the same gfn and unprotects the gfn first, but that's a
relatively rare edge case, and emulating is still functionally ok, i.e.
saving a few cycles by avoiding emulation isn't worth the risk of putting
the vCPU into an infinite loop.
Opportunistically rewrite the relevant comment to document in gory detail
exactly what scenario the "fast unprotect" logic is handling.
Fixes: 147277540bbc ("kvm: svm: Add support for additional SVM NPF error codes")
Cc: Yuan Yao <yuan.yao@intel.com>
Signed-off-by: Sean Christopherson <seanjc@google.com>
---
arch/x86/kvm/mmu/mmu.c | 37 +++++++++++++++++++++++++++++--------
1 file changed, 29 insertions(+), 8 deletions(-)
diff --git a/arch/x86/kvm/mmu/mmu.c b/arch/x86/kvm/mmu/mmu.c
index 57692d873f76..6b5f80f38a95 100644
--- a/arch/x86/kvm/mmu/mmu.c
+++ b/arch/x86/kvm/mmu/mmu.c
@@ -5959,16 +5959,37 @@ static int kvm_mmu_write_protect_fault(struct kvm_vcpu *vcpu, gpa_t cr2_or_gpa,
bool direct = vcpu->arch.mmu->root_role.direct;
/*
- * Before emulating the instruction, check if the error code
- * was due to a RO violation while translating the guest page.
- * This can occur when using nested virtualization with nested
- * paging in both guests. If true, we simply unprotect the page
- * and resume the guest.
+ * Before emulating the instruction, check to see if the access was due
+ * to a read-only violation while the CPU was walking non-nested NPT
+ * page tables, i.e. for a direct MMU, for _guest_ page tables in L1.
+ * If L1 is sharing (a subset of) its page tables with L2, e.g. by
+ * having nCR3 share lower level page tables with hCR3, then when KVM
+ * (L0) write-protects the nested NPTs, i.e. npt12 entries, KVM is also
+ * unknowingly write-protecting L1's guest page tables, which KVM isn't
+ * shadowing.
+ *
+ * Because the CPU (by default) walks NPT page tables using a write
+ * access (to ensure the CPU can do A/D updates), page walks in L1 can
+ * trigger write faults for the above case even when L1 isn't modifying
+ * PTEs. As a result, KVM will unnecessarily emulate (or at least, try
+ * to emulate) an excessive number of L1 instructions; because L1's MMU
+ * isn't shadowed by KVM, there is no need to write-protect L1's gPTEs
+ * and thus no need to emulate in order to guarantee forward progress.
+ *
+ * Try to unprotect the gfn, i.e. zap any shadow pages, so that L1 can
+ * proceed without triggering emulation. If one or more shadow pages
+ * was zapped, skip emulation and resume L1 to let it natively execute
+ * the instruction. If no shadow pages were zapped, then the write-
+ * fault is due to something else entirely, i.e. KVM needs to emulate,
+ * as resuming the guest will put it into an infinite loop.
+ *
+ * Note, this code also applies to Intel CPUs, even though it is *very*
+ * unlikely that an L1 will share its page tables (IA32/PAE/paging64
+ * format) with L2's page tables (EPT format).
*/
- if (direct && is_write_to_guest_page_table(error_code)) {
- kvm_mmu_unprotect_page(vcpu->kvm, gpa_to_gfn(cr2_or_gpa));
+ if (direct && is_write_to_guest_page_table(error_code) &&
+ kvm_mmu_unprotect_page(vcpu->kvm, gpa_to_gfn(cr2_or_gpa)))
return RET_PF_RETRY;
- }
/*
* The gfn is write-protected, but if emulation fails we can still
--
2.46.0.469.g59c65b2a67-goog
^ permalink raw reply [flat|nested] 26+ messages in thread
* [PATCH v2 05/22] KVM: x86: Retry to-be-emulated insn in "slow" unprotect path iff sp is zapped
2024-08-31 0:15 [PATCH v2 00/22] KVM: x86: Fix multiple #PF RO infinite loop bugs Sean Christopherson
` (3 preceding siblings ...)
2024-08-31 0:15 ` [PATCH v2 04/22] KVM: x86/mmu: Skip emulation on page fault iff 1+ SPs were unprotected Sean Christopherson
@ 2024-08-31 0:15 ` Sean Christopherson
2024-09-06 8:17 ` Yuan Yao
2024-08-31 0:15 ` [PATCH v2 06/22] KVM: x86: Get RIP from vCPU state when storing it to last_retry_eip Sean Christopherson
` (17 subsequent siblings)
22 siblings, 1 reply; 26+ messages in thread
From: Sean Christopherson @ 2024-08-31 0:15 UTC (permalink / raw)
To: Sean Christopherson, Paolo Bonzini; +Cc: kvm, linux-kernel, Yuan Yao, Yuan Yao
Resume the guest and thus skip emulation of a non-PTE-writing instruction
if and only if unprotecting the gfn actually zapped at least one shadow
page. If the gfn is write-protected for some reason other than shadow
paging, attempting to unprotect the gfn will effectively fail, and thus
retrying the instruction is all but guaranteed to be pointless. This bug
has existed for a long time, but was effectively fudged around by the
retry RIP+address anti-loop detection.
Signed-off-by: Sean Christopherson <seanjc@google.com>
---
arch/x86/kvm/x86.c | 12 ++++++------
1 file changed, 6 insertions(+), 6 deletions(-)
diff --git a/arch/x86/kvm/x86.c b/arch/x86/kvm/x86.c
index 966fb301d44b..c4cb6c6d605b 100644
--- a/arch/x86/kvm/x86.c
+++ b/arch/x86/kvm/x86.c
@@ -8961,14 +8961,14 @@ static bool retry_instruction(struct x86_emulate_ctxt *ctxt,
if (ctxt->eip == last_retry_eip && last_retry_addr == cr2_or_gpa)
return false;
+ if (!vcpu->arch.mmu->root_role.direct)
+ gpa = kvm_mmu_gva_to_gpa_write(vcpu, cr2_or_gpa, NULL);
+
+ if (!kvm_mmu_unprotect_page(vcpu->kvm, gpa_to_gfn(gpa)))
+ return false;
+
vcpu->arch.last_retry_eip = ctxt->eip;
vcpu->arch.last_retry_addr = cr2_or_gpa;
-
- if (!vcpu->arch.mmu->root_role.direct)
- gpa = kvm_mmu_gva_to_gpa_write(vcpu, cr2_or_gpa, NULL);
-
- kvm_mmu_unprotect_page(vcpu->kvm, gpa_to_gfn(gpa));
-
return true;
}
--
2.46.0.469.g59c65b2a67-goog
^ permalink raw reply [flat|nested] 26+ messages in thread
* [PATCH v2 06/22] KVM: x86: Get RIP from vCPU state when storing it to last_retry_eip
2024-08-31 0:15 [PATCH v2 00/22] KVM: x86: Fix multiple #PF RO infinite loop bugs Sean Christopherson
` (4 preceding siblings ...)
2024-08-31 0:15 ` [PATCH v2 05/22] KVM: x86: Retry to-be-emulated insn in "slow" unprotect path iff sp is zapped Sean Christopherson
@ 2024-08-31 0:15 ` Sean Christopherson
2024-08-31 0:15 ` [PATCH v2 07/22] KVM: x86: Store gpa as gpa_t, not unsigned long, when unprotecting for retry Sean Christopherson
` (16 subsequent siblings)
22 siblings, 0 replies; 26+ messages in thread
From: Sean Christopherson @ 2024-08-31 0:15 UTC (permalink / raw)
To: Sean Christopherson, Paolo Bonzini; +Cc: kvm, linux-kernel, Yuan Yao, Yuan Yao
Read RIP from vCPU state instead of pulling it from the emulation context
when filling last_retry_eip, which is part of the anti-infinite-loop
protection used when unprotecting and retrying instructions that hit a
write-protected gfn.
This will allow reusing the anti-infinite-loop protection in flows that
never make it into the emulator.
No functional change intended, as ctxt->eip is set to kvm_rip_read() in
init_emulate_ctxt(), and EMULTYPE_PF emulation is mutually exclusive with
EMULTYPE_NO_DECODE and EMULTYPE_SKIP, i.e. always goes through
x86_decode_emulated_instruction() and hasn't advanced ctxt->eip (yet).
Signed-off-by: Sean Christopherson <seanjc@google.com>
---
arch/x86/kvm/x86.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/arch/x86/kvm/x86.c b/arch/x86/kvm/x86.c
index c4cb6c6d605b..a1f0f4dede55 100644
--- a/arch/x86/kvm/x86.c
+++ b/arch/x86/kvm/x86.c
@@ -8967,7 +8967,7 @@ static bool retry_instruction(struct x86_emulate_ctxt *ctxt,
if (!kvm_mmu_unprotect_page(vcpu->kvm, gpa_to_gfn(gpa)))
return false;
- vcpu->arch.last_retry_eip = ctxt->eip;
+ vcpu->arch.last_retry_eip = kvm_rip_read(vcpu);
vcpu->arch.last_retry_addr = cr2_or_gpa;
return true;
}
--
2.46.0.469.g59c65b2a67-goog
^ permalink raw reply [flat|nested] 26+ messages in thread
* [PATCH v2 07/22] KVM: x86: Store gpa as gpa_t, not unsigned long, when unprotecting for retry
2024-08-31 0:15 [PATCH v2 00/22] KVM: x86: Fix multiple #PF RO infinite loop bugs Sean Christopherson
` (5 preceding siblings ...)
2024-08-31 0:15 ` [PATCH v2 06/22] KVM: x86: Get RIP from vCPU state when storing it to last_retry_eip Sean Christopherson
@ 2024-08-31 0:15 ` Sean Christopherson
2024-08-31 0:15 ` [PATCH v2 08/22] KVM: x86/mmu: Apply retry protection to "fast nTDP unprotect" path Sean Christopherson
` (15 subsequent siblings)
22 siblings, 0 replies; 26+ messages in thread
From: Sean Christopherson @ 2024-08-31 0:15 UTC (permalink / raw)
To: Sean Christopherson, Paolo Bonzini; +Cc: kvm, linux-kernel, Yuan Yao, Yuan Yao
Store the gpa used to unprotect the faulting gfn for retry as a gpa_t, not
an unsigned long. This fixes a bug where 32-bit KVM would unprotect and
retry the wrong gfn if the gpa had bits 63:32!=0. In practice, this bug
is functionally benign, as unprotecting the wrong gfn is purely a
performance issue (thanks to the anti-infinite-loop logic). And of course,
almost no one runs 32-bit KVM these days.
Signed-off-by: Sean Christopherson <seanjc@google.com>
---
arch/x86/kvm/x86.c | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/arch/x86/kvm/x86.c b/arch/x86/kvm/x86.c
index a1f0f4dede55..c84f57e1a888 100644
--- a/arch/x86/kvm/x86.c
+++ b/arch/x86/kvm/x86.c
@@ -8928,7 +8928,8 @@ static bool retry_instruction(struct x86_emulate_ctxt *ctxt,
gpa_t cr2_or_gpa, int emulation_type)
{
struct kvm_vcpu *vcpu = emul_to_vcpu(ctxt);
- unsigned long last_retry_eip, last_retry_addr, gpa = cr2_or_gpa;
+ unsigned long last_retry_eip, last_retry_addr;
+ gpa_t gpa = cr2_or_gpa;
last_retry_eip = vcpu->arch.last_retry_eip;
last_retry_addr = vcpu->arch.last_retry_addr;
--
2.46.0.469.g59c65b2a67-goog
^ permalink raw reply [flat|nested] 26+ messages in thread
* [PATCH v2 08/22] KVM: x86/mmu: Apply retry protection to "fast nTDP unprotect" path
2024-08-31 0:15 [PATCH v2 00/22] KVM: x86: Fix multiple #PF RO infinite loop bugs Sean Christopherson
` (6 preceding siblings ...)
2024-08-31 0:15 ` [PATCH v2 07/22] KVM: x86: Store gpa as gpa_t, not unsigned long, when unprotecting for retry Sean Christopherson
@ 2024-08-31 0:15 ` Sean Christopherson
2024-08-31 0:15 ` [PATCH v2 09/22] KVM: x86/mmu: Try "unprotect for retry" iff there are indirect SPs Sean Christopherson
` (14 subsequent siblings)
22 siblings, 0 replies; 26+ messages in thread
From: Sean Christopherson @ 2024-08-31 0:15 UTC (permalink / raw)
To: Sean Christopherson, Paolo Bonzini; +Cc: kvm, linux-kernel, Yuan Yao, Yuan Yao
Move the anti-infinite-loop protection provided by last_retry_{eip,addr}
into kvm_mmu_write_protect_fault() so that it guards unprotect+retry that
never hits the emulator, as well as reexecute_instruction(), which is the
last ditch "might as well try it" logic that kicks in when emulation fails
on an instruction that faulted on a write-protected gfn.
Add a new helper, kvm_mmu_unprotect_gfn_and_retry(), to set the retry
fields and deduplicate other code (with more to come).
Signed-off-by: Sean Christopherson <seanjc@google.com>
---
arch/x86/include/asm/kvm_host.h | 1 +
arch/x86/kvm/mmu/mmu.c | 39 ++++++++++++++++++++++++++++++++-
arch/x86/kvm/x86.c | 27 +----------------------
3 files changed, 40 insertions(+), 27 deletions(-)
diff --git a/arch/x86/include/asm/kvm_host.h b/arch/x86/include/asm/kvm_host.h
index 62d19403d63c..2c3f28331118 100644
--- a/arch/x86/include/asm/kvm_host.h
+++ b/arch/x86/include/asm/kvm_host.h
@@ -2135,6 +2135,7 @@ int kvm_get_nr_pending_nmis(struct kvm_vcpu *vcpu);
void kvm_update_dr7(struct kvm_vcpu *vcpu);
int kvm_mmu_unprotect_page(struct kvm *kvm, gfn_t gfn);
+bool kvm_mmu_unprotect_gfn_and_retry(struct kvm_vcpu *vcpu, gpa_t cr2_or_gpa);
void kvm_mmu_free_roots(struct kvm *kvm, struct kvm_mmu *mmu,
ulong roots_to_free);
void kvm_mmu_free_guest_mode_roots(struct kvm *kvm, struct kvm_mmu *mmu);
diff --git a/arch/x86/kvm/mmu/mmu.c b/arch/x86/kvm/mmu/mmu.c
index 6b5f80f38a95..c34c8bbd61c8 100644
--- a/arch/x86/kvm/mmu/mmu.c
+++ b/arch/x86/kvm/mmu/mmu.c
@@ -2713,6 +2713,22 @@ int kvm_mmu_unprotect_page(struct kvm *kvm, gfn_t gfn)
return r;
}
+bool kvm_mmu_unprotect_gfn_and_retry(struct kvm_vcpu *vcpu, gpa_t cr2_or_gpa)
+{
+ gpa_t gpa = cr2_or_gpa;
+ bool r;
+
+ if (!vcpu->arch.mmu->root_role.direct)
+ gpa = kvm_mmu_gva_to_gpa_write(vcpu, cr2_or_gpa, NULL);
+
+ r = kvm_mmu_unprotect_page(vcpu->kvm, gpa_to_gfn(gpa));
+ if (r) {
+ vcpu->arch.last_retry_eip = kvm_rip_read(vcpu);
+ vcpu->arch.last_retry_addr = cr2_or_gpa;
+ }
+ return r;
+}
+
static int kvm_mmu_unprotect_page_virt(struct kvm_vcpu *vcpu, gva_t gva)
{
gpa_t gpa;
@@ -5958,6 +5974,27 @@ static int kvm_mmu_write_protect_fault(struct kvm_vcpu *vcpu, gpa_t cr2_or_gpa,
{
bool direct = vcpu->arch.mmu->root_role.direct;
+ /*
+ * Do not try to unprotect and retry if the vCPU re-faulted on the same
+ * RIP with the same address that was previously unprotected, as doing
+ * so will likely put the vCPU into an infinite. E.g. if the vCPU uses
+ * a non-page-table modifying instruction on the PDE that points to the
+ * instruction, then unprotecting the gfn will unmap the instruction's
+ * code, i.e. make it impossible for the instruction to ever complete.
+ */
+ if (vcpu->arch.last_retry_eip == kvm_rip_read(vcpu) &&
+ vcpu->arch.last_retry_addr == cr2_or_gpa)
+ return RET_PF_EMULATE;
+
+ /*
+ * Reset the unprotect+retry values that guard against infinite loops.
+ * The values will be refreshed if KVM explicitly unprotects a gfn and
+ * retries, in all other cases it's safe to retry in the future even if
+ * the next page fault happens on the same RIP+address.
+ */
+ vcpu->arch.last_retry_eip = 0;
+ vcpu->arch.last_retry_addr = 0;
+
/*
* Before emulating the instruction, check to see if the access was due
* to a read-only violation while the CPU was walking non-nested NPT
@@ -5988,7 +6025,7 @@ static int kvm_mmu_write_protect_fault(struct kvm_vcpu *vcpu, gpa_t cr2_or_gpa,
* format) with L2's page tables (EPT format).
*/
if (direct && is_write_to_guest_page_table(error_code) &&
- kvm_mmu_unprotect_page(vcpu->kvm, gpa_to_gfn(cr2_or_gpa)))
+ kvm_mmu_unprotect_gfn_and_retry(vcpu, cr2_or_gpa))
return RET_PF_RETRY;
/*
diff --git a/arch/x86/kvm/x86.c b/arch/x86/kvm/x86.c
index c84f57e1a888..862eed96cfd5 100644
--- a/arch/x86/kvm/x86.c
+++ b/arch/x86/kvm/x86.c
@@ -8928,27 +8928,13 @@ static bool retry_instruction(struct x86_emulate_ctxt *ctxt,
gpa_t cr2_or_gpa, int emulation_type)
{
struct kvm_vcpu *vcpu = emul_to_vcpu(ctxt);
- unsigned long last_retry_eip, last_retry_addr;
- gpa_t gpa = cr2_or_gpa;
-
- last_retry_eip = vcpu->arch.last_retry_eip;
- last_retry_addr = vcpu->arch.last_retry_addr;
/*
* If the emulation is caused by #PF and it is non-page_table
* writing instruction, it means the VM-EXIT is caused by shadow
* page protected, we can zap the shadow page and retry this
* instruction directly.
- *
- * Note: if the guest uses a non-page-table modifying instruction
- * on the PDE that points to the instruction, then we will unmap
- * the instruction and go to an infinite loop. So, we cache the
- * last retried eip and the last fault address, if we meet the eip
- * and the address again, we can break out of the potential infinite
- * loop.
*/
- vcpu->arch.last_retry_eip = vcpu->arch.last_retry_addr = 0;
-
if (!(emulation_type & EMULTYPE_ALLOW_RETRY_PF))
return false;
@@ -8959,18 +8945,7 @@ static bool retry_instruction(struct x86_emulate_ctxt *ctxt,
if (x86_page_table_writing_insn(ctxt))
return false;
- if (ctxt->eip == last_retry_eip && last_retry_addr == cr2_or_gpa)
- return false;
-
- if (!vcpu->arch.mmu->root_role.direct)
- gpa = kvm_mmu_gva_to_gpa_write(vcpu, cr2_or_gpa, NULL);
-
- if (!kvm_mmu_unprotect_page(vcpu->kvm, gpa_to_gfn(gpa)))
- return false;
-
- vcpu->arch.last_retry_eip = kvm_rip_read(vcpu);
- vcpu->arch.last_retry_addr = cr2_or_gpa;
- return true;
+ return kvm_mmu_unprotect_gfn_and_retry(vcpu, cr2_or_gpa);
}
static int complete_emulated_mmio(struct kvm_vcpu *vcpu);
--
2.46.0.469.g59c65b2a67-goog
^ permalink raw reply [flat|nested] 26+ messages in thread
* [PATCH v2 09/22] KVM: x86/mmu: Try "unprotect for retry" iff there are indirect SPs
2024-08-31 0:15 [PATCH v2 00/22] KVM: x86: Fix multiple #PF RO infinite loop bugs Sean Christopherson
` (7 preceding siblings ...)
2024-08-31 0:15 ` [PATCH v2 08/22] KVM: x86/mmu: Apply retry protection to "fast nTDP unprotect" path Sean Christopherson
@ 2024-08-31 0:15 ` Sean Christopherson
2024-08-31 0:15 ` [PATCH v2 10/22] KVM: x86: Move EMULTYPE_ALLOW_RETRY_PF to x86_emulate_instruction() Sean Christopherson
` (13 subsequent siblings)
22 siblings, 0 replies; 26+ messages in thread
From: Sean Christopherson @ 2024-08-31 0:15 UTC (permalink / raw)
To: Sean Christopherson, Paolo Bonzini; +Cc: kvm, linux-kernel, Yuan Yao, Yuan Yao
Try to unprotect shadow pages if and only if indirect_shadow_pages is non-
zero, i.e. iff there is at least one protected such shadow page. Pre-
checking indirect_shadow_pages avoids taking mmu_lock for write when the
gfn is write-protected by a third party, i.e. not for KVM shadow paging,
and in the *extremely* unlikely case that a different task has already
unprotected the last shadow page.
Signed-off-by: Sean Christopherson <seanjc@google.com>
---
arch/x86/kvm/mmu/mmu.c | 11 +++++++++++
1 file changed, 11 insertions(+)
diff --git a/arch/x86/kvm/mmu/mmu.c b/arch/x86/kvm/mmu/mmu.c
index c34c8bbd61c8..dd62bd1e7657 100644
--- a/arch/x86/kvm/mmu/mmu.c
+++ b/arch/x86/kvm/mmu/mmu.c
@@ -2718,6 +2718,17 @@ bool kvm_mmu_unprotect_gfn_and_retry(struct kvm_vcpu *vcpu, gpa_t cr2_or_gpa)
gpa_t gpa = cr2_or_gpa;
bool r;
+ /*
+ * Bail early if there aren't any write-protected shadow pages to avoid
+ * unnecessarily taking mmu_lock lock, e.g. if the gfn is write-tracked
+ * by a third party. Reading indirect_shadow_pages without holding
+ * mmu_lock is safe, as this is purely an optimization, i.e. a false
+ * positive is benign, and a false negative will simply result in KVM
+ * skipping the unprotect+retry path, which is also an optimization.
+ */
+ if (!READ_ONCE(vcpu->kvm->arch.indirect_shadow_pages))
+ return false;
+
if (!vcpu->arch.mmu->root_role.direct)
gpa = kvm_mmu_gva_to_gpa_write(vcpu, cr2_or_gpa, NULL);
--
2.46.0.469.g59c65b2a67-goog
^ permalink raw reply [flat|nested] 26+ messages in thread
* [PATCH v2 10/22] KVM: x86: Move EMULTYPE_ALLOW_RETRY_PF to x86_emulate_instruction()
2024-08-31 0:15 [PATCH v2 00/22] KVM: x86: Fix multiple #PF RO infinite loop bugs Sean Christopherson
` (8 preceding siblings ...)
2024-08-31 0:15 ` [PATCH v2 09/22] KVM: x86/mmu: Try "unprotect for retry" iff there are indirect SPs Sean Christopherson
@ 2024-08-31 0:15 ` Sean Christopherson
2024-08-31 0:15 ` [PATCH v2 11/22] KVM: x86: Fold retry_instruction() into x86_emulate_instruction() Sean Christopherson
` (12 subsequent siblings)
22 siblings, 0 replies; 26+ messages in thread
From: Sean Christopherson @ 2024-08-31 0:15 UTC (permalink / raw)
To: Sean Christopherson, Paolo Bonzini; +Cc: kvm, linux-kernel, Yuan Yao, Yuan Yao
Move the sanity checks for EMULTYPE_ALLOW_RETRY_PF to the top of
x86_emulate_instruction(). In addition to deduplicating a small amount
of code, this makes the connection between EMULTYPE_ALLOW_RETRY_PF and
EMULTYPE_PF even more explicit, and will allow dropping retry_instruction()
entirely.
Signed-off-by: Sean Christopherson <seanjc@google.com>
---
arch/x86/kvm/x86.c | 13 +++++--------
1 file changed, 5 insertions(+), 8 deletions(-)
diff --git a/arch/x86/kvm/x86.c b/arch/x86/kvm/x86.c
index 862eed96cfd5..7ddca8edf91b 100644
--- a/arch/x86/kvm/x86.c
+++ b/arch/x86/kvm/x86.c
@@ -8866,10 +8866,6 @@ static bool reexecute_instruction(struct kvm_vcpu *vcpu, gpa_t cr2_or_gpa,
if (!(emulation_type & EMULTYPE_ALLOW_RETRY_PF))
return false;
- if (WARN_ON_ONCE(is_guest_mode(vcpu)) ||
- WARN_ON_ONCE(!(emulation_type & EMULTYPE_PF)))
- return false;
-
if (!vcpu->arch.mmu->root_role.direct) {
/*
* Write permission should be allowed since only
@@ -8938,10 +8934,6 @@ static bool retry_instruction(struct x86_emulate_ctxt *ctxt,
if (!(emulation_type & EMULTYPE_ALLOW_RETRY_PF))
return false;
- if (WARN_ON_ONCE(is_guest_mode(vcpu)) ||
- WARN_ON_ONCE(!(emulation_type & EMULTYPE_PF)))
- return false;
-
if (x86_page_table_writing_insn(ctxt))
return false;
@@ -9144,6 +9136,11 @@ int x86_emulate_instruction(struct kvm_vcpu *vcpu, gpa_t cr2_or_gpa,
struct x86_emulate_ctxt *ctxt = vcpu->arch.emulate_ctxt;
bool writeback = true;
+ if ((emulation_type & EMULTYPE_ALLOW_RETRY_PF) &&
+ (WARN_ON_ONCE(is_guest_mode(vcpu)) ||
+ WARN_ON_ONCE(!(emulation_type & EMULTYPE_PF))))
+ emulation_type &= ~EMULTYPE_ALLOW_RETRY_PF;
+
r = kvm_check_emulate_insn(vcpu, emulation_type, insn, insn_len);
if (r != X86EMUL_CONTINUE) {
if (r == X86EMUL_RETRY_INSTR || r == X86EMUL_PROPAGATE_FAULT)
--
2.46.0.469.g59c65b2a67-goog
^ permalink raw reply [flat|nested] 26+ messages in thread
* [PATCH v2 11/22] KVM: x86: Fold retry_instruction() into x86_emulate_instruction()
2024-08-31 0:15 [PATCH v2 00/22] KVM: x86: Fix multiple #PF RO infinite loop bugs Sean Christopherson
` (9 preceding siblings ...)
2024-08-31 0:15 ` [PATCH v2 10/22] KVM: x86: Move EMULTYPE_ALLOW_RETRY_PF to x86_emulate_instruction() Sean Christopherson
@ 2024-08-31 0:15 ` Sean Christopherson
2024-08-31 0:15 ` [PATCH v2 12/22] KVM: x86/mmu: Don't try to unprotect an INVALID_GPA Sean Christopherson
` (11 subsequent siblings)
22 siblings, 0 replies; 26+ messages in thread
From: Sean Christopherson @ 2024-08-31 0:15 UTC (permalink / raw)
To: Sean Christopherson, Paolo Bonzini; +Cc: kvm, linux-kernel, Yuan Yao, Yuan Yao
Now that retry_instruction() is reasonably tiny, fold it into its sole
caller, x86_emulate_instruction(). In addition to getting rid of the
absurdly confusing retry_instruction() name, handling the retry in
x86_emulate_instruction() pairs it back up with the code that resets
last_retry_{eip,address}.
No functional change intended.
Signed-off-by: Sean Christopherson <seanjc@google.com>
---
arch/x86/kvm/x86.c | 30 +++++++++---------------------
1 file changed, 9 insertions(+), 21 deletions(-)
diff --git a/arch/x86/kvm/x86.c b/arch/x86/kvm/x86.c
index 7ddca8edf91b..c873a587769a 100644
--- a/arch/x86/kvm/x86.c
+++ b/arch/x86/kvm/x86.c
@@ -8920,26 +8920,6 @@ static bool reexecute_instruction(struct kvm_vcpu *vcpu, gpa_t cr2_or_gpa,
return !(emulation_type & EMULTYPE_WRITE_PF_TO_SP);
}
-static bool retry_instruction(struct x86_emulate_ctxt *ctxt,
- gpa_t cr2_or_gpa, int emulation_type)
-{
- struct kvm_vcpu *vcpu = emul_to_vcpu(ctxt);
-
- /*
- * If the emulation is caused by #PF and it is non-page_table
- * writing instruction, it means the VM-EXIT is caused by shadow
- * page protected, we can zap the shadow page and retry this
- * instruction directly.
- */
- if (!(emulation_type & EMULTYPE_ALLOW_RETRY_PF))
- return false;
-
- if (x86_page_table_writing_insn(ctxt))
- return false;
-
- return kvm_mmu_unprotect_gfn_and_retry(vcpu, cr2_or_gpa);
-}
-
static int complete_emulated_mmio(struct kvm_vcpu *vcpu);
static int complete_emulated_pio(struct kvm_vcpu *vcpu);
@@ -9219,7 +9199,15 @@ int x86_emulate_instruction(struct kvm_vcpu *vcpu, gpa_t cr2_or_gpa,
return 1;
}
- if (retry_instruction(ctxt, cr2_or_gpa, emulation_type))
+ /*
+ * If emulation was caused by a write-protection #PF on a non-page_table
+ * writing instruction, try to unprotect the gfn, i.e. zap shadow pages,
+ * and retry the instruction, as the vCPU is likely no longer using the
+ * gfn as a page table.
+ */
+ if ((emulation_type & EMULTYPE_ALLOW_RETRY_PF) &&
+ !x86_page_table_writing_insn(ctxt) &&
+ kvm_mmu_unprotect_gfn_and_retry(vcpu, cr2_or_gpa))
return 1;
/* this is needed for vmware backdoor interface to work since it
--
2.46.0.469.g59c65b2a67-goog
^ permalink raw reply [flat|nested] 26+ messages in thread
* [PATCH v2 12/22] KVM: x86/mmu: Don't try to unprotect an INVALID_GPA
2024-08-31 0:15 [PATCH v2 00/22] KVM: x86: Fix multiple #PF RO infinite loop bugs Sean Christopherson
` (10 preceding siblings ...)
2024-08-31 0:15 ` [PATCH v2 11/22] KVM: x86: Fold retry_instruction() into x86_emulate_instruction() Sean Christopherson
@ 2024-08-31 0:15 ` Sean Christopherson
2024-08-31 0:15 ` [PATCH v2 13/22] KVM: x86/mmu: Always walk guest PTEs with WRITE access when unprotecting Sean Christopherson
` (10 subsequent siblings)
22 siblings, 0 replies; 26+ messages in thread
From: Sean Christopherson @ 2024-08-31 0:15 UTC (permalink / raw)
To: Sean Christopherson, Paolo Bonzini; +Cc: kvm, linux-kernel, Yuan Yao, Yuan Yao
If getting the gpa for a gva fails, e.g. because the gva isn't mapped in
the guest page tables, don't try to unprotect the invalid gfn. This is
mostly a performance fix (avoids unnecessarily taking mmu_lock), as
for_each_gfn_valid_sp_with_gptes() won't explode on garbage input, it's
simply pointless.
Signed-off-by: Sean Christopherson <seanjc@google.com>
---
arch/x86/kvm/mmu/mmu.c | 7 ++++++-
1 file changed, 6 insertions(+), 1 deletion(-)
diff --git a/arch/x86/kvm/mmu/mmu.c b/arch/x86/kvm/mmu/mmu.c
index dd62bd1e7657..ee288f8370de 100644
--- a/arch/x86/kvm/mmu/mmu.c
+++ b/arch/x86/kvm/mmu/mmu.c
@@ -2729,8 +2729,11 @@ bool kvm_mmu_unprotect_gfn_and_retry(struct kvm_vcpu *vcpu, gpa_t cr2_or_gpa)
if (!READ_ONCE(vcpu->kvm->arch.indirect_shadow_pages))
return false;
- if (!vcpu->arch.mmu->root_role.direct)
+ if (!vcpu->arch.mmu->root_role.direct) {
gpa = kvm_mmu_gva_to_gpa_write(vcpu, cr2_or_gpa, NULL);
+ if (gpa == INVALID_GPA)
+ return false;
+ }
r = kvm_mmu_unprotect_page(vcpu->kvm, gpa_to_gfn(gpa));
if (r) {
@@ -2749,6 +2752,8 @@ static int kvm_mmu_unprotect_page_virt(struct kvm_vcpu *vcpu, gva_t gva)
return 0;
gpa = kvm_mmu_gva_to_gpa_read(vcpu, gva, NULL);
+ if (gpa == INVALID_GPA)
+ return 0;
r = kvm_mmu_unprotect_page(vcpu->kvm, gpa >> PAGE_SHIFT);
--
2.46.0.469.g59c65b2a67-goog
^ permalink raw reply [flat|nested] 26+ messages in thread
* [PATCH v2 13/22] KVM: x86/mmu: Always walk guest PTEs with WRITE access when unprotecting
2024-08-31 0:15 [PATCH v2 00/22] KVM: x86: Fix multiple #PF RO infinite loop bugs Sean Christopherson
` (11 preceding siblings ...)
2024-08-31 0:15 ` [PATCH v2 12/22] KVM: x86/mmu: Don't try to unprotect an INVALID_GPA Sean Christopherson
@ 2024-08-31 0:15 ` Sean Christopherson
2024-08-31 0:15 ` [PATCH v2 14/22] KVM: x86/mmu: Move event re-injection unprotect+retry into common path Sean Christopherson
` (9 subsequent siblings)
22 siblings, 0 replies; 26+ messages in thread
From: Sean Christopherson @ 2024-08-31 0:15 UTC (permalink / raw)
To: Sean Christopherson, Paolo Bonzini; +Cc: kvm, linux-kernel, Yuan Yao, Yuan Yao
When getting a gpa from a gva to unprotect the associated gfn when an
event is awating reinjection, walk the guest PTEs for WRITE as there's no
point in unprotecting the gfn if the guest is unable to write the page,
i.e. if write-protection can't trigger emulation.
Note, the entire flow should be guarded on the access being a write, and
even better should be conditioned on actually triggering a write-protect
fault. This will be addressed in a future commit.
Signed-off-by: Sean Christopherson <seanjc@google.com>
---
arch/x86/kvm/mmu/mmu.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/arch/x86/kvm/mmu/mmu.c b/arch/x86/kvm/mmu/mmu.c
index ee288f8370de..b89e2c63b435 100644
--- a/arch/x86/kvm/mmu/mmu.c
+++ b/arch/x86/kvm/mmu/mmu.c
@@ -2751,7 +2751,7 @@ static int kvm_mmu_unprotect_page_virt(struct kvm_vcpu *vcpu, gva_t gva)
if (vcpu->arch.mmu->root_role.direct)
return 0;
- gpa = kvm_mmu_gva_to_gpa_read(vcpu, gva, NULL);
+ gpa = kvm_mmu_gva_to_gpa_write(vcpu, gva, NULL);
if (gpa == INVALID_GPA)
return 0;
--
2.46.0.469.g59c65b2a67-goog
^ permalink raw reply [flat|nested] 26+ messages in thread
* [PATCH v2 14/22] KVM: x86/mmu: Move event re-injection unprotect+retry into common path
2024-08-31 0:15 [PATCH v2 00/22] KVM: x86: Fix multiple #PF RO infinite loop bugs Sean Christopherson
` (12 preceding siblings ...)
2024-08-31 0:15 ` [PATCH v2 13/22] KVM: x86/mmu: Always walk guest PTEs with WRITE access when unprotecting Sean Christopherson
@ 2024-08-31 0:15 ` Sean Christopherson
2024-08-31 0:15 ` [PATCH v2 15/22] KVM: x86: Remove manual pfn lookup when retrying #PF after failed emulation Sean Christopherson
` (8 subsequent siblings)
22 siblings, 0 replies; 26+ messages in thread
From: Sean Christopherson @ 2024-08-31 0:15 UTC (permalink / raw)
To: Sean Christopherson, Paolo Bonzini; +Cc: kvm, linux-kernel, Yuan Yao, Yuan Yao
Move the event re-injection unprotect+retry logic into
kvm_mmu_write_protect_fault(), i.e. unprotect and retry if and only if
the #PF actually hit a write-protected gfn. Note, there is a small
possibility that the gfn was unprotected by a different tasking between
hitting the #PF and acquiring mmu_lock, but in that case, KVM will resume
the guest immediately anyways because KVM will treat the fault as spurious.
As a bonus, unprotecting _after_ handling the page fault also addresses the
case where the installing a SPTE to handle fault encounters a shadowed PTE,
i.e. *creates* a read-only SPTE.
Opportunstically add a comment explaining what on earth the intent of the
code is, as based on the changelog from commit 577bdc496614 ("KVM: Avoid
instruction emulation when event delivery is pending").
Signed-off-by: Sean Christopherson <seanjc@google.com>
---
arch/x86/kvm/mmu/mmu.c | 28 ++++++++--------------------
1 file changed, 8 insertions(+), 20 deletions(-)
diff --git a/arch/x86/kvm/mmu/mmu.c b/arch/x86/kvm/mmu/mmu.c
index b89e2c63b435..4910ac3d7f83 100644
--- a/arch/x86/kvm/mmu/mmu.c
+++ b/arch/x86/kvm/mmu/mmu.c
@@ -2743,23 +2743,6 @@ bool kvm_mmu_unprotect_gfn_and_retry(struct kvm_vcpu *vcpu, gpa_t cr2_or_gpa)
return r;
}
-static int kvm_mmu_unprotect_page_virt(struct kvm_vcpu *vcpu, gva_t gva)
-{
- gpa_t gpa;
- int r;
-
- if (vcpu->arch.mmu->root_role.direct)
- return 0;
-
- gpa = kvm_mmu_gva_to_gpa_write(vcpu, gva, NULL);
- if (gpa == INVALID_GPA)
- return 0;
-
- r = kvm_mmu_unprotect_page(vcpu->kvm, gpa >> PAGE_SHIFT);
-
- return r;
-}
-
static void kvm_unsync_page(struct kvm *kvm, struct kvm_mmu_page *sp)
{
trace_kvm_mmu_unsync_page(sp);
@@ -4630,8 +4613,6 @@ int kvm_handle_page_fault(struct kvm_vcpu *vcpu, u64 error_code,
if (!flags) {
trace_kvm_page_fault(vcpu, fault_address, error_code);
- if (kvm_event_needs_reinjection(vcpu))
- kvm_mmu_unprotect_page_virt(vcpu, fault_address);
r = kvm_mmu_page_fault(vcpu, fault_address, error_code, insn,
insn_len);
} else if (flags & KVM_PV_REASON_PAGE_NOT_PRESENT) {
@@ -6039,8 +6020,15 @@ static int kvm_mmu_write_protect_fault(struct kvm_vcpu *vcpu, gpa_t cr2_or_gpa,
* Note, this code also applies to Intel CPUs, even though it is *very*
* unlikely that an L1 will share its page tables (IA32/PAE/paging64
* format) with L2's page tables (EPT format).
+ *
+ * For indirect MMUs, i.e. if KVM is shadowing the current MMU, try to
+ * unprotect the gfn and retry if an event is awaiting reinjection. If
+ * KVM emulates multiple instructions before completing event injection,
+ * the event could be delayed beyond what is architecturally allowed,
+ * e.g. KVM could inject an IRQ after the TPR has been raised.
*/
- if (direct && is_write_to_guest_page_table(error_code) &&
+ if (((direct && is_write_to_guest_page_table(error_code)) ||
+ (!direct && kvm_event_needs_reinjection(vcpu))) &&
kvm_mmu_unprotect_gfn_and_retry(vcpu, cr2_or_gpa))
return RET_PF_RETRY;
--
2.46.0.469.g59c65b2a67-goog
^ permalink raw reply [flat|nested] 26+ messages in thread
* [PATCH v2 15/22] KVM: x86: Remove manual pfn lookup when retrying #PF after failed emulation
2024-08-31 0:15 [PATCH v2 00/22] KVM: x86: Fix multiple #PF RO infinite loop bugs Sean Christopherson
` (13 preceding siblings ...)
2024-08-31 0:15 ` [PATCH v2 14/22] KVM: x86/mmu: Move event re-injection unprotect+retry into common path Sean Christopherson
@ 2024-08-31 0:15 ` Sean Christopherson
2024-08-31 0:15 ` [PATCH v2 16/22] KVM: x86: Check EMULTYPE_WRITE_PF_TO_SP before unprotecting gfn Sean Christopherson
` (7 subsequent siblings)
22 siblings, 0 replies; 26+ messages in thread
From: Sean Christopherson @ 2024-08-31 0:15 UTC (permalink / raw)
To: Sean Christopherson, Paolo Bonzini; +Cc: kvm, linux-kernel, Yuan Yao, Yuan Yao
Drop the manual pfn look when retrying an instruction that KVM failed to
emulation in response to a #PF due to a write-protected gfn. Now that KVM
sets EMULTYPE_ALLOW_RETRY_PF if and only if the page fault hit a write-
protected gfn, i.e. if and only if there's a writable memslot, there's no
need to redo the lookup to avoid retrying an instruction that failed on
emulated MMIO (no slot, or a write to a read-only slot).
I.e. KVM will never attempt to retry an instruction that failed on
emulated MMIO, whereas that was not the case prior to the introduction of
RET_PF_WRITE_PROTECTED.
Signed-off-by: Sean Christopherson <seanjc@google.com>
---
arch/x86/kvm/x86.c | 18 ------------------
1 file changed, 18 deletions(-)
diff --git a/arch/x86/kvm/x86.c b/arch/x86/kvm/x86.c
index c873a587769a..23be5384d5a5 100644
--- a/arch/x86/kvm/x86.c
+++ b/arch/x86/kvm/x86.c
@@ -8861,7 +8861,6 @@ static bool reexecute_instruction(struct kvm_vcpu *vcpu, gpa_t cr2_or_gpa,
int emulation_type)
{
gpa_t gpa = cr2_or_gpa;
- kvm_pfn_t pfn;
if (!(emulation_type & EMULTYPE_ALLOW_RETRY_PF))
return false;
@@ -8881,23 +8880,6 @@ static bool reexecute_instruction(struct kvm_vcpu *vcpu, gpa_t cr2_or_gpa,
return true;
}
- /*
- * Do not retry the unhandleable instruction if it faults on the
- * readonly host memory, otherwise it will goto a infinite loop:
- * retry instruction -> write #PF -> emulation fail -> retry
- * instruction -> ...
- */
- pfn = gfn_to_pfn(vcpu->kvm, gpa_to_gfn(gpa));
-
- /*
- * If the instruction failed on the error pfn, it can not be fixed,
- * report the error to userspace.
- */
- if (is_error_noslot_pfn(pfn))
- return false;
-
- kvm_release_pfn_clean(pfn);
-
/*
* If emulation may have been triggered by a write to a shadowed page
* table, unprotect the gfn (zap any relevant SPTEs) and re-enter the
--
2.46.0.469.g59c65b2a67-goog
^ permalink raw reply [flat|nested] 26+ messages in thread
* [PATCH v2 16/22] KVM: x86: Check EMULTYPE_WRITE_PF_TO_SP before unprotecting gfn
2024-08-31 0:15 [PATCH v2 00/22] KVM: x86: Fix multiple #PF RO infinite loop bugs Sean Christopherson
` (14 preceding siblings ...)
2024-08-31 0:15 ` [PATCH v2 15/22] KVM: x86: Remove manual pfn lookup when retrying #PF after failed emulation Sean Christopherson
@ 2024-08-31 0:15 ` Sean Christopherson
2024-08-31 0:15 ` [PATCH v2 17/22] KVM: x86: Apply retry protection to "unprotect on failure" path Sean Christopherson
` (6 subsequent siblings)
22 siblings, 0 replies; 26+ messages in thread
From: Sean Christopherson @ 2024-08-31 0:15 UTC (permalink / raw)
To: Sean Christopherson, Paolo Bonzini; +Cc: kvm, linux-kernel, Yuan Yao, Yuan Yao
Don't bother unprotecting the target gfn if EMULTYPE_WRITE_PF_TO_SP is
set, as KVM will simply report the emulation failure to userspace. This
will allow converting reexecute_instruction() to use
kvm_mmu_unprotect_gfn_instead_retry() instead of kvm_mmu_unprotect_page().
Signed-off-by: Sean Christopherson <seanjc@google.com>
---
arch/x86/kvm/x86.c | 28 +++++++++++++++++++---------
1 file changed, 19 insertions(+), 9 deletions(-)
diff --git a/arch/x86/kvm/x86.c b/arch/x86/kvm/x86.c
index 23be5384d5a5..ad457487971c 100644
--- a/arch/x86/kvm/x86.c
+++ b/arch/x86/kvm/x86.c
@@ -8865,6 +8865,19 @@ static bool reexecute_instruction(struct kvm_vcpu *vcpu, gpa_t cr2_or_gpa,
if (!(emulation_type & EMULTYPE_ALLOW_RETRY_PF))
return false;
+ /*
+ * If the failed instruction faulted on an access to page tables that
+ * are used to translate any part of the instruction, KVM can't resolve
+ * the issue by unprotecting the gfn, as zapping the shadow page will
+ * result in the instruction taking a !PRESENT page fault and thus put
+ * the vCPU into an infinite loop of page faults. E.g. KVM will create
+ * a SPTE and write-protect the gfn to resolve the !PRESENT fault, and
+ * then zap the SPTE to unprotect the gfn, and then do it all over
+ * again. Report the error to userspace.
+ */
+ if (emulation_type & EMULTYPE_WRITE_PF_TO_SP)
+ return false;
+
if (!vcpu->arch.mmu->root_role.direct) {
/*
* Write permission should be allowed since only
@@ -8890,16 +8903,13 @@ static bool reexecute_instruction(struct kvm_vcpu *vcpu, gpa_t cr2_or_gpa,
kvm_mmu_unprotect_page(vcpu->kvm, gpa_to_gfn(gpa));
/*
- * If the failed instruction faulted on an access to page tables that
- * are used to translate any part of the instruction, KVM can't resolve
- * the issue by unprotecting the gfn, as zapping the shadow page will
- * result in the instruction taking a !PRESENT page fault and thus put
- * the vCPU into an infinite loop of page faults. E.g. KVM will create
- * a SPTE and write-protect the gfn to resolve the !PRESENT fault, and
- * then zap the SPTE to unprotect the gfn, and then do it all over
- * again. Report the error to userspace.
+ * Retry even if _this_ vCPU didn't unprotect the gfn, as it's possible
+ * all SPTEs were already zapped by a different task. The alternative
+ * is to report the error to userspace and likely terminate the guest,
+ * and the last_retry_{eip,addr} checks will prevent retrying the page
+ * fault indefinitely, i.e. there's nothing to lose by retrying.
*/
- return !(emulation_type & EMULTYPE_WRITE_PF_TO_SP);
+ return true;
}
static int complete_emulated_mmio(struct kvm_vcpu *vcpu);
--
2.46.0.469.g59c65b2a67-goog
^ permalink raw reply [flat|nested] 26+ messages in thread
* [PATCH v2 17/22] KVM: x86: Apply retry protection to "unprotect on failure" path
2024-08-31 0:15 [PATCH v2 00/22] KVM: x86: Fix multiple #PF RO infinite loop bugs Sean Christopherson
` (15 preceding siblings ...)
2024-08-31 0:15 ` [PATCH v2 16/22] KVM: x86: Check EMULTYPE_WRITE_PF_TO_SP before unprotecting gfn Sean Christopherson
@ 2024-08-31 0:15 ` Sean Christopherson
2024-08-31 0:15 ` [PATCH v2 18/22] KVM: x86: Update retry protection fields when forcing retry on emulation failure Sean Christopherson
` (5 subsequent siblings)
22 siblings, 0 replies; 26+ messages in thread
From: Sean Christopherson @ 2024-08-31 0:15 UTC (permalink / raw)
To: Sean Christopherson, Paolo Bonzini; +Cc: kvm, linux-kernel, Yuan Yao, Yuan Yao
Use kvm_mmu_unprotect_gfn_and_retry() in reexecute_instruction() to pick
up protection against infinite loops, e.g. if KVM somehow manages to
encounter an unsupported instruction and unprotecting the gfn doesn't
allow the vCPU to make forward progress. Other than that, the retry-on-
failure logic is a functionally equivalent, open coded version of
kvm_mmu_unprotect_gfn_and_retry().
Note, the emulation failure path still isn't fully protected, as KVM
won't update the retry protection fields if no shadow pages are zapped
(but this change is still a step forward). That flaw will be addressed
in a future patch.
Signed-off-by: Sean Christopherson <seanjc@google.com>
---
arch/x86/kvm/x86.c | 20 +-------------------
1 file changed, 1 insertion(+), 19 deletions(-)
diff --git a/arch/x86/kvm/x86.c b/arch/x86/kvm/x86.c
index ad457487971c..09fc43699b15 100644
--- a/arch/x86/kvm/x86.c
+++ b/arch/x86/kvm/x86.c
@@ -8860,8 +8860,6 @@ static int handle_emulation_failure(struct kvm_vcpu *vcpu, int emulation_type)
static bool reexecute_instruction(struct kvm_vcpu *vcpu, gpa_t cr2_or_gpa,
int emulation_type)
{
- gpa_t gpa = cr2_or_gpa;
-
if (!(emulation_type & EMULTYPE_ALLOW_RETRY_PF))
return false;
@@ -8878,29 +8876,13 @@ static bool reexecute_instruction(struct kvm_vcpu *vcpu, gpa_t cr2_or_gpa,
if (emulation_type & EMULTYPE_WRITE_PF_TO_SP)
return false;
- if (!vcpu->arch.mmu->root_role.direct) {
- /*
- * Write permission should be allowed since only
- * write access need to be emulated.
- */
- gpa = kvm_mmu_gva_to_gpa_write(vcpu, cr2_or_gpa, NULL);
-
- /*
- * If the mapping is invalid in guest, let cpu retry
- * it to generate fault.
- */
- if (gpa == INVALID_GPA)
- return true;
- }
-
/*
* If emulation may have been triggered by a write to a shadowed page
* table, unprotect the gfn (zap any relevant SPTEs) and re-enter the
* guest to let the CPU re-execute the instruction in the hope that the
* CPU can cleanly execute the instruction that KVM failed to emulate.
*/
- if (vcpu->kvm->arch.indirect_shadow_pages)
- kvm_mmu_unprotect_page(vcpu->kvm, gpa_to_gfn(gpa));
+ kvm_mmu_unprotect_gfn_and_retry(vcpu, cr2_or_gpa);
/*
* Retry even if _this_ vCPU didn't unprotect the gfn, as it's possible
--
2.46.0.469.g59c65b2a67-goog
^ permalink raw reply [flat|nested] 26+ messages in thread
* [PATCH v2 18/22] KVM: x86: Update retry protection fields when forcing retry on emulation failure
2024-08-31 0:15 [PATCH v2 00/22] KVM: x86: Fix multiple #PF RO infinite loop bugs Sean Christopherson
` (16 preceding siblings ...)
2024-08-31 0:15 ` [PATCH v2 17/22] KVM: x86: Apply retry protection to "unprotect on failure" path Sean Christopherson
@ 2024-08-31 0:15 ` Sean Christopherson
2024-08-31 0:15 ` [PATCH v2 19/22] KVM: x86: Rename reexecute_instruction()=>kvm_unprotect_and_retry_on_failure() Sean Christopherson
` (4 subsequent siblings)
22 siblings, 0 replies; 26+ messages in thread
From: Sean Christopherson @ 2024-08-31 0:15 UTC (permalink / raw)
To: Sean Christopherson, Paolo Bonzini; +Cc: kvm, linux-kernel, Yuan Yao, Yuan Yao
When retrying the faulting instruction after emulation failure, refresh
the infinite loop protection fields even if no shadow pages were zapped,
i.e. avoid hitting an infinite loop even when retrying the instruction as
a last-ditch effort to avoid terminating the guest.
Signed-off-by: Sean Christopherson <seanjc@google.com>
---
arch/x86/include/asm/kvm_host.h | 10 +++++++++-
arch/x86/kvm/mmu/mmu.c | 12 +++++++-----
arch/x86/kvm/x86.c | 2 +-
3 files changed, 17 insertions(+), 7 deletions(-)
diff --git a/arch/x86/include/asm/kvm_host.h b/arch/x86/include/asm/kvm_host.h
index 2c3f28331118..4aa10db97f6f 100644
--- a/arch/x86/include/asm/kvm_host.h
+++ b/arch/x86/include/asm/kvm_host.h
@@ -2135,7 +2135,15 @@ int kvm_get_nr_pending_nmis(struct kvm_vcpu *vcpu);
void kvm_update_dr7(struct kvm_vcpu *vcpu);
int kvm_mmu_unprotect_page(struct kvm *kvm, gfn_t gfn);
-bool kvm_mmu_unprotect_gfn_and_retry(struct kvm_vcpu *vcpu, gpa_t cr2_or_gpa);
+bool __kvm_mmu_unprotect_gfn_and_retry(struct kvm_vcpu *vcpu, gpa_t cr2_or_gpa,
+ bool always_retry);
+
+static inline bool kvm_mmu_unprotect_gfn_and_retry(struct kvm_vcpu *vcpu,
+ gpa_t cr2_or_gpa)
+{
+ return __kvm_mmu_unprotect_gfn_and_retry(vcpu, cr2_or_gpa, false);
+}
+
void kvm_mmu_free_roots(struct kvm *kvm, struct kvm_mmu *mmu,
ulong roots_to_free);
void kvm_mmu_free_guest_mode_roots(struct kvm *kvm, struct kvm_mmu *mmu);
diff --git a/arch/x86/kvm/mmu/mmu.c b/arch/x86/kvm/mmu/mmu.c
index 4910ac3d7f83..aabed77f35d4 100644
--- a/arch/x86/kvm/mmu/mmu.c
+++ b/arch/x86/kvm/mmu/mmu.c
@@ -2713,10 +2713,11 @@ int kvm_mmu_unprotect_page(struct kvm *kvm, gfn_t gfn)
return r;
}
-bool kvm_mmu_unprotect_gfn_and_retry(struct kvm_vcpu *vcpu, gpa_t cr2_or_gpa)
+bool __kvm_mmu_unprotect_gfn_and_retry(struct kvm_vcpu *vcpu, gpa_t cr2_or_gpa,
+ bool always_retry)
{
gpa_t gpa = cr2_or_gpa;
- bool r;
+ bool r = false;
/*
* Bail early if there aren't any write-protected shadow pages to avoid
@@ -2727,16 +2728,17 @@ bool kvm_mmu_unprotect_gfn_and_retry(struct kvm_vcpu *vcpu, gpa_t cr2_or_gpa)
* skipping the unprotect+retry path, which is also an optimization.
*/
if (!READ_ONCE(vcpu->kvm->arch.indirect_shadow_pages))
- return false;
+ goto out;
if (!vcpu->arch.mmu->root_role.direct) {
gpa = kvm_mmu_gva_to_gpa_write(vcpu, cr2_or_gpa, NULL);
if (gpa == INVALID_GPA)
- return false;
+ goto out;
}
r = kvm_mmu_unprotect_page(vcpu->kvm, gpa_to_gfn(gpa));
- if (r) {
+out:
+ if (r || always_retry) {
vcpu->arch.last_retry_eip = kvm_rip_read(vcpu);
vcpu->arch.last_retry_addr = cr2_or_gpa;
}
diff --git a/arch/x86/kvm/x86.c b/arch/x86/kvm/x86.c
index 09fc43699b15..081ac4069666 100644
--- a/arch/x86/kvm/x86.c
+++ b/arch/x86/kvm/x86.c
@@ -8882,7 +8882,7 @@ static bool reexecute_instruction(struct kvm_vcpu *vcpu, gpa_t cr2_or_gpa,
* guest to let the CPU re-execute the instruction in the hope that the
* CPU can cleanly execute the instruction that KVM failed to emulate.
*/
- kvm_mmu_unprotect_gfn_and_retry(vcpu, cr2_or_gpa);
+ __kvm_mmu_unprotect_gfn_and_retry(vcpu, cr2_or_gpa, true);
/*
* Retry even if _this_ vCPU didn't unprotect the gfn, as it's possible
--
2.46.0.469.g59c65b2a67-goog
^ permalink raw reply [flat|nested] 26+ messages in thread
* [PATCH v2 19/22] KVM: x86: Rename reexecute_instruction()=>kvm_unprotect_and_retry_on_failure()
2024-08-31 0:15 [PATCH v2 00/22] KVM: x86: Fix multiple #PF RO infinite loop bugs Sean Christopherson
` (17 preceding siblings ...)
2024-08-31 0:15 ` [PATCH v2 18/22] KVM: x86: Update retry protection fields when forcing retry on emulation failure Sean Christopherson
@ 2024-08-31 0:15 ` Sean Christopherson
2024-08-31 0:15 ` [PATCH v2 20/22] KVM: x86/mmu: Subsume kvm_mmu_unprotect_page() into the and_retry() version Sean Christopherson
` (3 subsequent siblings)
22 siblings, 0 replies; 26+ messages in thread
From: Sean Christopherson @ 2024-08-31 0:15 UTC (permalink / raw)
To: Sean Christopherson, Paolo Bonzini; +Cc: kvm, linux-kernel, Yuan Yao, Yuan Yao
Rename reexecute_instruction() to kvm_unprotect_and_retry_on_failure() to
make the intent and purpose of the helper much more obvious.
No functional change intended.
Signed-off-by: Sean Christopherson <seanjc@google.com>
---
arch/x86/kvm/x86.c | 12 +++++++-----
1 file changed, 7 insertions(+), 5 deletions(-)
diff --git a/arch/x86/kvm/x86.c b/arch/x86/kvm/x86.c
index 081ac4069666..450db5cec088 100644
--- a/arch/x86/kvm/x86.c
+++ b/arch/x86/kvm/x86.c
@@ -8857,8 +8857,9 @@ static int handle_emulation_failure(struct kvm_vcpu *vcpu, int emulation_type)
return 1;
}
-static bool reexecute_instruction(struct kvm_vcpu *vcpu, gpa_t cr2_or_gpa,
- int emulation_type)
+static bool kvm_unprotect_and_retry_on_failure(struct kvm_vcpu *vcpu,
+ gpa_t cr2_or_gpa,
+ int emulation_type)
{
if (!(emulation_type & EMULTYPE_ALLOW_RETRY_PF))
return false;
@@ -9125,8 +9126,8 @@ int x86_emulate_instruction(struct kvm_vcpu *vcpu, gpa_t cr2_or_gpa,
kvm_queue_exception(vcpu, UD_VECTOR);
return 1;
}
- if (reexecute_instruction(vcpu, cr2_or_gpa,
- emulation_type))
+ if (kvm_unprotect_and_retry_on_failure(vcpu, cr2_or_gpa,
+ emulation_type))
return 1;
if (ctxt->have_exception &&
@@ -9212,7 +9213,8 @@ int x86_emulate_instruction(struct kvm_vcpu *vcpu, gpa_t cr2_or_gpa,
return 1;
if (r == EMULATION_FAILED) {
- if (reexecute_instruction(vcpu, cr2_or_gpa, emulation_type))
+ if (kvm_unprotect_and_retry_on_failure(vcpu, cr2_or_gpa,
+ emulation_type))
return 1;
return handle_emulation_failure(vcpu, emulation_type);
--
2.46.0.469.g59c65b2a67-goog
^ permalink raw reply [flat|nested] 26+ messages in thread
* [PATCH v2 20/22] KVM: x86/mmu: Subsume kvm_mmu_unprotect_page() into the and_retry() version
2024-08-31 0:15 [PATCH v2 00/22] KVM: x86: Fix multiple #PF RO infinite loop bugs Sean Christopherson
` (18 preceding siblings ...)
2024-08-31 0:15 ` [PATCH v2 19/22] KVM: x86: Rename reexecute_instruction()=>kvm_unprotect_and_retry_on_failure() Sean Christopherson
@ 2024-08-31 0:15 ` Sean Christopherson
2024-08-31 0:15 ` [PATCH v2 21/22] KVM: x86/mmu: Detect if unprotect will do anything based on invalid_list Sean Christopherson
` (2 subsequent siblings)
22 siblings, 0 replies; 26+ messages in thread
From: Sean Christopherson @ 2024-08-31 0:15 UTC (permalink / raw)
To: Sean Christopherson, Paolo Bonzini; +Cc: kvm, linux-kernel, Yuan Yao, Yuan Yao
Fold kvm_mmu_unprotect_page() into kvm_mmu_unprotect_gfn_and_retry() now
that all other direct usage is gone.
No functional change intended.
Signed-off-by: Sean Christopherson <seanjc@google.com>
---
arch/x86/include/asm/kvm_host.h | 1 -
arch/x86/kvm/mmu/mmu.c | 33 +++++++++++++--------------------
2 files changed, 13 insertions(+), 21 deletions(-)
diff --git a/arch/x86/include/asm/kvm_host.h b/arch/x86/include/asm/kvm_host.h
index 4aa10db97f6f..0fbde3ca8d1a 100644
--- a/arch/x86/include/asm/kvm_host.h
+++ b/arch/x86/include/asm/kvm_host.h
@@ -2134,7 +2134,6 @@ int kvm_get_nr_pending_nmis(struct kvm_vcpu *vcpu);
void kvm_update_dr7(struct kvm_vcpu *vcpu);
-int kvm_mmu_unprotect_page(struct kvm *kvm, gfn_t gfn);
bool __kvm_mmu_unprotect_gfn_and_retry(struct kvm_vcpu *vcpu, gpa_t cr2_or_gpa,
bool always_retry);
diff --git a/arch/x86/kvm/mmu/mmu.c b/arch/x86/kvm/mmu/mmu.c
index aabed77f35d4..d042874b0a3b 100644
--- a/arch/x86/kvm/mmu/mmu.c
+++ b/arch/x86/kvm/mmu/mmu.c
@@ -2695,27 +2695,12 @@ void kvm_mmu_change_mmu_pages(struct kvm *kvm, unsigned long goal_nr_mmu_pages)
write_unlock(&kvm->mmu_lock);
}
-int kvm_mmu_unprotect_page(struct kvm *kvm, gfn_t gfn)
-{
- struct kvm_mmu_page *sp;
- LIST_HEAD(invalid_list);
- int r;
-
- r = 0;
- write_lock(&kvm->mmu_lock);
- for_each_gfn_valid_sp_with_gptes(kvm, sp, gfn) {
- r = 1;
- kvm_mmu_prepare_zap_page(kvm, sp, &invalid_list);
- }
- kvm_mmu_commit_zap_page(kvm, &invalid_list);
- write_unlock(&kvm->mmu_lock);
-
- return r;
-}
-
bool __kvm_mmu_unprotect_gfn_and_retry(struct kvm_vcpu *vcpu, gpa_t cr2_or_gpa,
bool always_retry)
{
+ struct kvm *kvm = vcpu->kvm;
+ LIST_HEAD(invalid_list);
+ struct kvm_mmu_page *sp;
gpa_t gpa = cr2_or_gpa;
bool r = false;
@@ -2727,7 +2712,7 @@ bool __kvm_mmu_unprotect_gfn_and_retry(struct kvm_vcpu *vcpu, gpa_t cr2_or_gpa,
* positive is benign, and a false negative will simply result in KVM
* skipping the unprotect+retry path, which is also an optimization.
*/
- if (!READ_ONCE(vcpu->kvm->arch.indirect_shadow_pages))
+ if (!READ_ONCE(kvm->arch.indirect_shadow_pages))
goto out;
if (!vcpu->arch.mmu->root_role.direct) {
@@ -2736,7 +2721,15 @@ bool __kvm_mmu_unprotect_gfn_and_retry(struct kvm_vcpu *vcpu, gpa_t cr2_or_gpa,
goto out;
}
- r = kvm_mmu_unprotect_page(vcpu->kvm, gpa_to_gfn(gpa));
+ r = false;
+ write_lock(&kvm->mmu_lock);
+ for_each_gfn_valid_sp_with_gptes(kvm, sp, gpa_to_gfn(gpa)) {
+ r = true;
+ kvm_mmu_prepare_zap_page(kvm, sp, &invalid_list);
+ }
+ kvm_mmu_commit_zap_page(kvm, &invalid_list);
+ write_unlock(&kvm->mmu_lock);
+
out:
if (r || always_retry) {
vcpu->arch.last_retry_eip = kvm_rip_read(vcpu);
--
2.46.0.469.g59c65b2a67-goog
^ permalink raw reply [flat|nested] 26+ messages in thread
* [PATCH v2 21/22] KVM: x86/mmu: Detect if unprotect will do anything based on invalid_list
2024-08-31 0:15 [PATCH v2 00/22] KVM: x86: Fix multiple #PF RO infinite loop bugs Sean Christopherson
` (19 preceding siblings ...)
2024-08-31 0:15 ` [PATCH v2 20/22] KVM: x86/mmu: Subsume kvm_mmu_unprotect_page() into the and_retry() version Sean Christopherson
@ 2024-08-31 0:15 ` Sean Christopherson
2024-08-31 0:15 ` [PATCH v2 22/22] KVM: x86/mmu: WARN on MMIO cache hit when emulating write-protected gfn Sean Christopherson
2024-09-10 4:56 ` [PATCH v2 00/22] KVM: x86: Fix multiple #PF RO infinite loop bugs Sean Christopherson
22 siblings, 0 replies; 26+ messages in thread
From: Sean Christopherson @ 2024-08-31 0:15 UTC (permalink / raw)
To: Sean Christopherson, Paolo Bonzini; +Cc: kvm, linux-kernel, Yuan Yao, Yuan Yao
Explicitly query the list of to-be-zapped shadow pages when checking to
see if unprotecting a gfn for retry has succeeded, i.e. if KVM should
retry the faulting instruction.
Add a comment to explain why the list needs to be checked before zapping,
which is the primary motivation for this change.
No functional change intended.
Signed-off-by: Sean Christopherson <seanjc@google.com>
---
arch/x86/kvm/mmu/mmu.c | 11 +++++++----
1 file changed, 7 insertions(+), 4 deletions(-)
diff --git a/arch/x86/kvm/mmu/mmu.c b/arch/x86/kvm/mmu/mmu.c
index d042874b0a3b..be5c2c33b530 100644
--- a/arch/x86/kvm/mmu/mmu.c
+++ b/arch/x86/kvm/mmu/mmu.c
@@ -2721,12 +2721,15 @@ bool __kvm_mmu_unprotect_gfn_and_retry(struct kvm_vcpu *vcpu, gpa_t cr2_or_gpa,
goto out;
}
- r = false;
write_lock(&kvm->mmu_lock);
- for_each_gfn_valid_sp_with_gptes(kvm, sp, gpa_to_gfn(gpa)) {
- r = true;
+ for_each_gfn_valid_sp_with_gptes(kvm, sp, gpa_to_gfn(gpa))
kvm_mmu_prepare_zap_page(kvm, sp, &invalid_list);
- }
+
+ /*
+ * Snapshot the result before zapping, as zapping will remove all list
+ * entries, i.e. checking the list later would yield a false negative.
+ */
+ r = !list_empty(&invalid_list);
kvm_mmu_commit_zap_page(kvm, &invalid_list);
write_unlock(&kvm->mmu_lock);
--
2.46.0.469.g59c65b2a67-goog
^ permalink raw reply [flat|nested] 26+ messages in thread
* [PATCH v2 22/22] KVM: x86/mmu: WARN on MMIO cache hit when emulating write-protected gfn
2024-08-31 0:15 [PATCH v2 00/22] KVM: x86: Fix multiple #PF RO infinite loop bugs Sean Christopherson
` (20 preceding siblings ...)
2024-08-31 0:15 ` [PATCH v2 21/22] KVM: x86/mmu: Detect if unprotect will do anything based on invalid_list Sean Christopherson
@ 2024-08-31 0:15 ` Sean Christopherson
2024-09-10 4:56 ` [PATCH v2 00/22] KVM: x86: Fix multiple #PF RO infinite loop bugs Sean Christopherson
22 siblings, 0 replies; 26+ messages in thread
From: Sean Christopherson @ 2024-08-31 0:15 UTC (permalink / raw)
To: Sean Christopherson, Paolo Bonzini; +Cc: kvm, linux-kernel, Yuan Yao, Yuan Yao
WARN if KVM gets an MMIO cache hit on a RET_PF_WRITE_PROTECTED fault, as
KVM should return RET_PF_WRITE_PROTECTED if and only if there is a memslot,
and creating a memslot is supposed to invalidate the MMIO cache by virtue
of changing the memslot generation.
Keep the code around mainly to provide a convenient location to document
why emulated MMIO should be impossible.
Suggested-by: Yuan Yao <yuan.yao@linux.intel.com>
Signed-off-by: Sean Christopherson <seanjc@google.com>
---
arch/x86/kvm/mmu/mmu.c | 28 +++++++++++++++++++---------
1 file changed, 19 insertions(+), 9 deletions(-)
diff --git a/arch/x86/kvm/mmu/mmu.c b/arch/x86/kvm/mmu/mmu.c
index be5c2c33b530..c9cea020aad6 100644
--- a/arch/x86/kvm/mmu/mmu.c
+++ b/arch/x86/kvm/mmu/mmu.c
@@ -5990,6 +5990,18 @@ static int kvm_mmu_write_protect_fault(struct kvm_vcpu *vcpu, gpa_t cr2_or_gpa,
vcpu->arch.last_retry_eip = 0;
vcpu->arch.last_retry_addr = 0;
+ /*
+ * It should be impossible to reach this point with an MMIO cache hit,
+ * as RET_PF_WRITE_PROTECTED is returned if and only if there's a valid,
+ * writable memslot, and creating a memslot should invalidate the MMIO
+ * cache by way of changing the memslot generation. WARN and disallow
+ * retry if MMIO is detected, as retrying MMIO emulation is pointless
+ * and could put the vCPU into an infinite loop because the processor
+ * will keep faulting on the non-existent MMIO address.
+ */
+ if (WARN_ON_ONCE(mmio_info_in_cache(vcpu, cr2_or_gpa, direct)))
+ return RET_PF_EMULATE;
+
/*
* Before emulating the instruction, check to see if the access was due
* to a read-only violation while the CPU was walking non-nested NPT
@@ -6031,17 +6043,15 @@ static int kvm_mmu_write_protect_fault(struct kvm_vcpu *vcpu, gpa_t cr2_or_gpa,
return RET_PF_RETRY;
/*
- * The gfn is write-protected, but if emulation fails we can still
- * optimistically try to just unprotect the page and let the processor
+ * The gfn is write-protected, but if KVM detects its emulating an
+ * instruction that is unlikely to be used to modify page tables, or if
+ * emulation fails, KVM can try to unprotect the gfn and let the CPU
* re-execute the instruction that caused the page fault. Do not allow
- * retrying MMIO emulation, as it's not only pointless but could also
- * cause us to enter an infinite loop because the processor will keep
- * faulting on the non-existent MMIO address. Retrying an instruction
- * from a nested guest is also pointless and dangerous as we are only
- * explicitly shadowing L1's page tables, i.e. unprotecting something
- * for L1 isn't going to magically fix whatever issue cause L2 to fail.
+ * retrying an instruction from a nested guest as KVM is only explicitly
+ * shadowing L1's page tables, i.e. unprotecting something for L1 isn't
+ * going to magically fix whatever issue caused L2 to fail.
*/
- if (!mmio_info_in_cache(vcpu, cr2_or_gpa, direct) && !is_guest_mode(vcpu))
+ if (!is_guest_mode(vcpu))
*emulation_type |= EMULTYPE_ALLOW_RETRY_PF;
return RET_PF_EMULATE;
--
2.46.0.469.g59c65b2a67-goog
^ permalink raw reply [flat|nested] 26+ messages in thread
* Re: [PATCH v2 04/22] KVM: x86/mmu: Skip emulation on page fault iff 1+ SPs were unprotected
2024-08-31 0:15 ` [PATCH v2 04/22] KVM: x86/mmu: Skip emulation on page fault iff 1+ SPs were unprotected Sean Christopherson
@ 2024-09-06 6:22 ` Yuan Yao
0 siblings, 0 replies; 26+ messages in thread
From: Yuan Yao @ 2024-09-06 6:22 UTC (permalink / raw)
To: Sean Christopherson; +Cc: Paolo Bonzini, kvm, linux-kernel, Yuan Yao
On Fri, Aug 30, 2024 at 05:15:19PM -0700, Sean Christopherson wrote:
> When doing "fast unprotection" of nested TDP page tables, skip emulation
> if and only if at least one gfn was unprotected, i.e. continue with
> emulation if simply resuming is likely to hit the same fault and risk
> putting the vCPU into an infinite loop.
>
> Note, it's entirely possible to get a false negative, e.g. if a different
> vCPU faults on the same gfn and unprotects the gfn first, but that's a
> relatively rare edge case, and emulating is still functionally ok, i.e.
> saving a few cycles by avoiding emulation isn't worth the risk of putting
> the vCPU into an infinite loop.
>
> Opportunistically rewrite the relevant comment to document in gory detail
> exactly what scenario the "fast unprotect" logic is handling.
>
> Fixes: 147277540bbc ("kvm: svm: Add support for additional SVM NPF error codes")
> Cc: Yuan Yao <yuan.yao@intel.com>
> Signed-off-by: Sean Christopherson <seanjc@google.com>
> ---
> arch/x86/kvm/mmu/mmu.c | 37 +++++++++++++++++++++++++++++--------
> 1 file changed, 29 insertions(+), 8 deletions(-)
>
> diff --git a/arch/x86/kvm/mmu/mmu.c b/arch/x86/kvm/mmu/mmu.c
> index 57692d873f76..6b5f80f38a95 100644
> --- a/arch/x86/kvm/mmu/mmu.c
> +++ b/arch/x86/kvm/mmu/mmu.c
> @@ -5959,16 +5959,37 @@ static int kvm_mmu_write_protect_fault(struct kvm_vcpu *vcpu, gpa_t cr2_or_gpa,
> bool direct = vcpu->arch.mmu->root_role.direct;
>
> /*
> - * Before emulating the instruction, check if the error code
> - * was due to a RO violation while translating the guest page.
> - * This can occur when using nested virtualization with nested
> - * paging in both guests. If true, we simply unprotect the page
> - * and resume the guest.
> + * Before emulating the instruction, check to see if the access was due
> + * to a read-only violation while the CPU was walking non-nested NPT
> + * page tables, i.e. for a direct MMU, for _guest_ page tables in L1.
> + * If L1 is sharing (a subset of) its page tables with L2, e.g. by
> + * having nCR3 share lower level page tables with hCR3, then when KVM
> + * (L0) write-protects the nested NPTs, i.e. npt12 entries, KVM is also
> + * unknowingly write-protecting L1's guest page tables, which KVM isn't
> + * shadowing.
Hi Sean,
Sorry I didn't reply to you in v1 in time.
Now it's very clear to me why we had this code path here.
Thank you for the excellent explaination on this interesting behaivor!
Reviewed-by: Yuan Yao <yuan.yao@intel.com>
> + *
> + * Because the CPU (by default) walks NPT page tables using a write
> + * access (to ensure the CPU can do A/D updates), page walks in L1 can
> + * trigger write faults for the above case even when L1 isn't modifying
> + * PTEs. As a result, KVM will unnecessarily emulate (or at least, try
> + * to emulate) an excessive number of L1 instructions; because L1's MMU
> + * isn't shadowed by KVM, there is no need to write-protect L1's gPTEs
> + * and thus no need to emulate in order to guarantee forward progress.
> + *
> + * Try to unprotect the gfn, i.e. zap any shadow pages, so that L1 can
> + * proceed without triggering emulation. If one or more shadow pages
> + * was zapped, skip emulation and resume L1 to let it natively execute
> + * the instruction. If no shadow pages were zapped, then the write-
> + * fault is due to something else entirely, i.e. KVM needs to emulate,
> + * as resuming the guest will put it into an infinite loop.
> + *
> + * Note, this code also applies to Intel CPUs, even though it is *very*
> + * unlikely that an L1 will share its page tables (IA32/PAE/paging64
> + * format) with L2's page tables (EPT format).
> */
> - if (direct && is_write_to_guest_page_table(error_code)) {
> - kvm_mmu_unprotect_page(vcpu->kvm, gpa_to_gfn(cr2_or_gpa));
> + if (direct && is_write_to_guest_page_table(error_code) &&
> + kvm_mmu_unprotect_page(vcpu->kvm, gpa_to_gfn(cr2_or_gpa)))
> return RET_PF_RETRY;
> - }
>
> /*
> * The gfn is write-protected, but if emulation fails we can still
> --
> 2.46.0.469.g59c65b2a67-goog
>
^ permalink raw reply [flat|nested] 26+ messages in thread
* Re: [PATCH v2 05/22] KVM: x86: Retry to-be-emulated insn in "slow" unprotect path iff sp is zapped
2024-08-31 0:15 ` [PATCH v2 05/22] KVM: x86: Retry to-be-emulated insn in "slow" unprotect path iff sp is zapped Sean Christopherson
@ 2024-09-06 8:17 ` Yuan Yao
0 siblings, 0 replies; 26+ messages in thread
From: Yuan Yao @ 2024-09-06 8:17 UTC (permalink / raw)
To: Sean Christopherson; +Cc: Paolo Bonzini, kvm, linux-kernel, Yuan Yao
On Fri, Aug 30, 2024 at 05:15:20PM -0700, Sean Christopherson wrote:
> Resume the guest and thus skip emulation of a non-PTE-writing instruction
> if and only if unprotecting the gfn actually zapped at least one shadow
> page. If the gfn is write-protected for some reason other than shadow
> paging, attempting to unprotect the gfn will effectively fail, and thus
> retrying the instruction is all but guaranteed to be pointless. This bug
> has existed for a long time, but was effectively fudged around by the
> retry RIP+address anti-loop detection.
>
> Signed-off-by: Sean Christopherson <seanjc@google.com>
> ---
> arch/x86/kvm/x86.c | 12 ++++++------
> 1 file changed, 6 insertions(+), 6 deletions(-)
>
> diff --git a/arch/x86/kvm/x86.c b/arch/x86/kvm/x86.c
> index 966fb301d44b..c4cb6c6d605b 100644
> --- a/arch/x86/kvm/x86.c
> +++ b/arch/x86/kvm/x86.c
> @@ -8961,14 +8961,14 @@ static bool retry_instruction(struct x86_emulate_ctxt *ctxt,
> if (ctxt->eip == last_retry_eip && last_retry_addr == cr2_or_gpa)
> return false;
>
> + if (!vcpu->arch.mmu->root_role.direct)
> + gpa = kvm_mmu_gva_to_gpa_write(vcpu, cr2_or_gpa, NULL);
> +
> + if (!kvm_mmu_unprotect_page(vcpu->kvm, gpa_to_gfn(gpa)))
> + return false;
> +
Reviewed-by: Yuan Yao <yuan.yao@intel.com>
> vcpu->arch.last_retry_eip = ctxt->eip;
> vcpu->arch.last_retry_addr = cr2_or_gpa;
> -
> - if (!vcpu->arch.mmu->root_role.direct)
> - gpa = kvm_mmu_gva_to_gpa_write(vcpu, cr2_or_gpa, NULL);
> -
> - kvm_mmu_unprotect_page(vcpu->kvm, gpa_to_gfn(gpa));
> -
> return true;
> }
>
> --
> 2.46.0.469.g59c65b2a67-goog
>
^ permalink raw reply [flat|nested] 26+ messages in thread
* Re: [PATCH v2 00/22] KVM: x86: Fix multiple #PF RO infinite loop bugs
2024-08-31 0:15 [PATCH v2 00/22] KVM: x86: Fix multiple #PF RO infinite loop bugs Sean Christopherson
` (21 preceding siblings ...)
2024-08-31 0:15 ` [PATCH v2 22/22] KVM: x86/mmu: WARN on MMIO cache hit when emulating write-protected gfn Sean Christopherson
@ 2024-09-10 4:56 ` Sean Christopherson
22 siblings, 0 replies; 26+ messages in thread
From: Sean Christopherson @ 2024-09-10 4:56 UTC (permalink / raw)
To: Sean Christopherson, Paolo Bonzini; +Cc: kvm, linux-kernel, Yuan Yao, Yuan Yao
On Fri, 30 Aug 2024 17:15:15 -0700, Sean Christopherson wrote:
> Fix an amusing number of minor bugs that can lead to KVM putting the guest into
> an infinite "retry #PF" loop, and cleanup and consolidate the unprotect+retry
> paths (there are four-ish).
>
> As a bonus, adding RET_PF_WRITE_PROTECTED obviates the need for
> kvm_lookup_pfn()[*].
>
> [...]
Applied to kvm-x86 mmu, except for patch 1, which I put at the end of "vmx" in
case Paolo wants to take it through his tree for the CoCo stuff.
[01/22] KVM: VMX: Set PFERR_GUEST_{FINAL,PAGE}_MASK if and only if the GVA is valid
https://github.com/kvm-x86/linux/commit/f3009482512e
[02/22] KVM: x86/mmu: Replace PFERR_NESTED_GUEST_PAGE with a more descriptive helper
https://github.com/kvm-x86/linux/commit/4ececec19a09
[03/22] KVM: x86/mmu: Trigger unprotect logic only on write-protection page faults
https://github.com/kvm-x86/linux/commit/989a84c93f59
[04/22] KVM: x86/mmu: Skip emulation on page fault iff 1+ SPs were unprotected
https://github.com/kvm-x86/linux/commit/2fb2b7877b3a
[05/22] KVM: x86: Retry to-be-emulated insn in "slow" unprotect path iff sp is zapped
https://github.com/kvm-x86/linux/commit/c1edcc41c360
[06/22] KVM: x86: Get RIP from vCPU state when storing it to last_retry_eip
https://github.com/kvm-x86/linux/commit/019f3f84a40c
[07/22] KVM: x86: Store gpa as gpa_t, not unsigned long, when unprotecting for retry
https://github.com/kvm-x86/linux/commit/9c19129e535b
[08/22] KVM: x86/mmu: Apply retry protection to "fast nTDP unprotect" path
https://github.com/kvm-x86/linux/commit/01dd4d319207
[09/22] KVM: x86/mmu: Try "unprotect for retry" iff there are indirect SPs
https://github.com/kvm-x86/linux/commit/dfaae8447c53
[10/22] KVM: x86: Move EMULTYPE_ALLOW_RETRY_PF to x86_emulate_instruction()
https://github.com/kvm-x86/linux/commit/41e6e367d576
[11/22] KVM: x86: Fold retry_instruction() into x86_emulate_instruction()
https://github.com/kvm-x86/linux/commit/2df354e37c13
[12/22] KVM: x86/mmu: Don't try to unprotect an INVALID_GPA
https://github.com/kvm-x86/linux/commit/b7e948898e77
[13/22] KVM: x86/mmu: Always walk guest PTEs with WRITE access when unprotecting
https://github.com/kvm-x86/linux/commit/29e495bdf847
[14/22] KVM: x86/mmu: Move event re-injection unprotect+retry into common path
https://github.com/kvm-x86/linux/commit/b299c273c06f
[15/22] KVM: x86: Remove manual pfn lookup when retrying #PF after failed emulation
https://github.com/kvm-x86/linux/commit/620525739521
[16/22] KVM: x86: Check EMULTYPE_WRITE_PF_TO_SP before unprotecting gfn
https://github.com/kvm-x86/linux/commit/19ab2c8be070
[17/22] KVM: x86: Apply retry protection to "unprotect on failure" path
https://github.com/kvm-x86/linux/commit/dabc4ff70c35
[18/22] KVM: x86: Update retry protection fields when forcing retry on emulation failure
https://github.com/kvm-x86/linux/commit/4df685664bed
[19/22] KVM: x86: Rename reexecute_instruction()=>kvm_unprotect_and_retry_on_failure()
https://github.com/kvm-x86/linux/commit/2876624e1adc
[20/22] KVM: x86/mmu: Subsume kvm_mmu_unprotect_page() into the and_retry() version
https://github.com/kvm-x86/linux/commit/6b3dcabc1091
[21/22] KVM: x86/mmu: Detect if unprotect will do anything based on invalid_list
https://github.com/kvm-x86/linux/commit/d859b16161c8
[22/22] KVM: x86/mmu: WARN on MMIO cache hit when emulating write-protected gfn
https://github.com/kvm-x86/linux/commit/98a69b96caca
--
https://github.com/kvm-x86/linux/tree/next
^ permalink raw reply [flat|nested] 26+ messages in thread
end of thread, other threads:[~2024-09-10 4:59 UTC | newest]
Thread overview: 26+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2024-08-31 0:15 [PATCH v2 00/22] KVM: x86: Fix multiple #PF RO infinite loop bugs Sean Christopherson
2024-08-31 0:15 ` [PATCH v2 01/22] KVM: VMX: Set PFERR_GUEST_{FINAL,PAGE}_MASK if and only if the GVA is valid Sean Christopherson
2024-08-31 0:15 ` [PATCH v2 02/22] KVM: x86/mmu: Replace PFERR_NESTED_GUEST_PAGE with a more descriptive helper Sean Christopherson
2024-08-31 0:15 ` [PATCH v2 03/22] KVM: x86/mmu: Trigger unprotect logic only on write-protection page faults Sean Christopherson
2024-08-31 0:15 ` [PATCH v2 04/22] KVM: x86/mmu: Skip emulation on page fault iff 1+ SPs were unprotected Sean Christopherson
2024-09-06 6:22 ` Yuan Yao
2024-08-31 0:15 ` [PATCH v2 05/22] KVM: x86: Retry to-be-emulated insn in "slow" unprotect path iff sp is zapped Sean Christopherson
2024-09-06 8:17 ` Yuan Yao
2024-08-31 0:15 ` [PATCH v2 06/22] KVM: x86: Get RIP from vCPU state when storing it to last_retry_eip Sean Christopherson
2024-08-31 0:15 ` [PATCH v2 07/22] KVM: x86: Store gpa as gpa_t, not unsigned long, when unprotecting for retry Sean Christopherson
2024-08-31 0:15 ` [PATCH v2 08/22] KVM: x86/mmu: Apply retry protection to "fast nTDP unprotect" path Sean Christopherson
2024-08-31 0:15 ` [PATCH v2 09/22] KVM: x86/mmu: Try "unprotect for retry" iff there are indirect SPs Sean Christopherson
2024-08-31 0:15 ` [PATCH v2 10/22] KVM: x86: Move EMULTYPE_ALLOW_RETRY_PF to x86_emulate_instruction() Sean Christopherson
2024-08-31 0:15 ` [PATCH v2 11/22] KVM: x86: Fold retry_instruction() into x86_emulate_instruction() Sean Christopherson
2024-08-31 0:15 ` [PATCH v2 12/22] KVM: x86/mmu: Don't try to unprotect an INVALID_GPA Sean Christopherson
2024-08-31 0:15 ` [PATCH v2 13/22] KVM: x86/mmu: Always walk guest PTEs with WRITE access when unprotecting Sean Christopherson
2024-08-31 0:15 ` [PATCH v2 14/22] KVM: x86/mmu: Move event re-injection unprotect+retry into common path Sean Christopherson
2024-08-31 0:15 ` [PATCH v2 15/22] KVM: x86: Remove manual pfn lookup when retrying #PF after failed emulation Sean Christopherson
2024-08-31 0:15 ` [PATCH v2 16/22] KVM: x86: Check EMULTYPE_WRITE_PF_TO_SP before unprotecting gfn Sean Christopherson
2024-08-31 0:15 ` [PATCH v2 17/22] KVM: x86: Apply retry protection to "unprotect on failure" path Sean Christopherson
2024-08-31 0:15 ` [PATCH v2 18/22] KVM: x86: Update retry protection fields when forcing retry on emulation failure Sean Christopherson
2024-08-31 0:15 ` [PATCH v2 19/22] KVM: x86: Rename reexecute_instruction()=>kvm_unprotect_and_retry_on_failure() Sean Christopherson
2024-08-31 0:15 ` [PATCH v2 20/22] KVM: x86/mmu: Subsume kvm_mmu_unprotect_page() into the and_retry() version Sean Christopherson
2024-08-31 0:15 ` [PATCH v2 21/22] KVM: x86/mmu: Detect if unprotect will do anything based on invalid_list Sean Christopherson
2024-08-31 0:15 ` [PATCH v2 22/22] KVM: x86/mmu: WARN on MMIO cache hit when emulating write-protected gfn Sean Christopherson
2024-09-10 4:56 ` [PATCH v2 00/22] KVM: x86: Fix multiple #PF RO infinite loop bugs Sean Christopherson
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®