[PATCH 17/24] KVM: x86/mmu: Take memory protection attributes into account during faults
From: Paolo Bonzini
Date: Thu Jul 16 2026 - 14:33:29 EST
From: Nicolas Saenz Julienne <nsaenz@xxxxxxxxxx>
Take memory protection attributes when faulting guest memory. Prohibited
memory accesses will cause a user-space -EFAULT exit just like private
memory accesses. Userspace will either enable the access or bump it to
the guest as some kind of exception (e.g. a VTL return).
Since the struct kvm_page_fault already has the access type in PFERR_*
format, the check is done via the kvm_page_format permissions table.
This means that it supports naturally all page table format variants,
and it can even handle mode-based memory protection when the host
uses MBEC/GMET. The only thing that needs some care is to build the
restricted ACC_* mask with the root page's own access mask as a base
(and not ACC_ALL). Otherwise, supervisor mode execution would be
handled incorrectly on AMD processors with GMET.
To avoid spamming the trace buffer too much, the new trace event only
kicks in if memory protection attributes are present for the faulted gfn.
Signed-off-by: Nicolas Saenz Julienne <nsaenz@xxxxxxxxxx>
Signed-off-by: Paolo Bonzini <pbonzini@xxxxxxxxxx>
---
arch/x86/kvm/mmu/mmu.c | 49 +++++++++++++++++++++++++++++++++
arch/x86/kvm/mmu/mmu_internal.h | 19 +++++++++++++
arch/x86/kvm/mmu/mmutrace.h | 29 +++++++++++++++++++
arch/x86/kvm/mmu/paging_tmpl.h | 2 +-
arch/x86/kvm/mmu/spte.h | 11 ++------
5 files changed, 100 insertions(+), 10 deletions(-)
diff --git a/arch/x86/kvm/mmu/mmu.c b/arch/x86/kvm/mmu/mmu.c
index b6463b0b0b6d..59c2a04f5648 100644
--- a/arch/x86/kvm/mmu/mmu.c
+++ b/arch/x86/kvm/mmu/mmu.c
@@ -4611,6 +4611,50 @@ static int kvm_mmu_faultin_pfn_gmem(struct kvm_vcpu *vcpu,
return RET_PF_CONTINUE;
}
+static inline unsigned kvm_get_gfn_protections(struct kvm_vcpu *vcpu, gfn_t gfn)
+{
+ struct kvm *kvm = vcpu->kvm;
+ unsigned int access = vcpu->arch.mmu->root_role.access;
+ unsigned long attrs = kvm_get_memory_attributes(kvm, gfn);
+ if (!attrs)
+ return access;
+
+ WARN_ON_ONCE(!kvm_mem_attributes_valid(kvm, attrs));
+
+ if (!kvm_mem_attributes_may_read(attrs))
+ access &= ~ACC_READ_MASK;
+ if (!kvm_mem_attributes_may_write(attrs))
+ access &= ~ACC_WRITE_MASK;
+ if (!kvm_mem_attributes_may_exec(attrs)) {
+ access &= ~ACC_EXEC_MASK;
+ if (shadow_xu_mask)
+ access &= ~ACC_USER_EXEC_MASK;
+ }
+
+ return access;
+}
+
+static int kvm_faultin_memory_protections(struct kvm_vcpu *vcpu,
+ struct kvm_page_fault *fault)
+{
+ unsigned access;
+
+ /* Memory attributes don't apply to MMIO regions */
+ if (unlikely(!fault->slot))
+ return RET_PF_CONTINUE;
+
+ access = kvm_get_gfn_protections(vcpu, fault->gfn);
+ if (access == ACC_ALL)
+ return RET_PF_CONTINUE;
+
+ trace_kvm_faultin_memory_protections(vcpu, fault, access);
+ if (__permission_fault(vcpu->arch.mmu, access, fault))
+ return -EFAULT;
+
+ fault->host_access &= access;
+ return RET_PF_CONTINUE;
+}
+
static int __kvm_mmu_faultin_pfn(struct kvm_vcpu *vcpu,
struct kvm_page_fault *fault)
{
@@ -4691,6 +4735,11 @@ static int kvm_mmu_faultin_pfn(struct kvm_vcpu *vcpu,
if (unlikely(!slot))
return kvm_handle_noslot_fault(vcpu, fault, access);
+ if (kvm_faultin_memory_protections(vcpu, fault)) {
+ kvm_mmu_prepare_memory_fault_exit(vcpu, fault);
+ return -EFAULT;
+ }
+
/*
* Retry the page fault if the gfn hit a memslot that is being deleted
* or moved. This ensures any existing SPTEs for the old memslot will
diff --git a/arch/x86/kvm/mmu/mmu_internal.h b/arch/x86/kvm/mmu/mmu_internal.h
index 00215b9f309f..0bad60c5aab6 100644
--- a/arch/x86/kvm/mmu/mmu_internal.h
+++ b/arch/x86/kvm/mmu/mmu_internal.h
@@ -290,6 +290,25 @@ struct kvm_page_fault {
bool write_fault_to_shadow_pgtable;
};
+/*
+ * Returns true if the access indicated by @fault is forbidden by the existing
+ * SPTE protections.
+ */
+static inline bool __permission_fault(struct kvm_mmu *mmu, unsigned access,
+ struct kvm_page_fault *fault)
+{
+ unsigned pfec;
+
+ /*
+ * RSVD is handled elsewhere, and is used for SMAP in the context
+ * of accessing fmt.permissions[]. SPTEs never use PK or SS, as
+ * they are not supported for shadow paging and irrelevant for TDP.
+ */
+ pfec = fault->error_code & (
+ PFERR_WRITE_MASK | PFERR_USER_MASK | PFERR_FETCH_MASK);
+ return (mmu->fmt.permissions[pfec >> 1] >> access) & 1;
+}
+
/*
* Return values of handle_mmio_page_fault(), mmu.page_fault(), fast_page_fault(),
* and of course kvm_mmu_do_page_fault().
diff --git a/arch/x86/kvm/mmu/mmutrace.h b/arch/x86/kvm/mmu/mmutrace.h
index 8354d9f39777..6bbd66a827b6 100644
--- a/arch/x86/kvm/mmu/mmutrace.h
+++ b/arch/x86/kvm/mmu/mmutrace.h
@@ -447,6 +447,35 @@ TRACE_EVENT(
__entry->gfn, __entry->spte, __entry->level, __entry->errno)
);
+TRACE_EVENT(kvm_faultin_memory_protections,
+ TP_PROTO(struct kvm_vcpu *vcpu, struct kvm_page_fault *fault,
+ unsigned access),
+ TP_ARGS(vcpu, fault, access),
+
+ TP_STRUCT__entry(
+ __field(unsigned int, vcpu_id)
+ __field(unsigned long, guest_rip)
+ __field(u64, fault_address)
+ __field(bool, write)
+ __field(bool, exec)
+ __field(unsigned, access)
+ ),
+
+ TP_fast_assign(
+ __entry->vcpu_id = vcpu->vcpu_id;
+ __entry->guest_rip = kvm_rip_read(vcpu);
+ __entry->fault_address = fault->gfn;
+ __entry->write = fault->write;
+ __entry->exec = fault->exec;
+ __entry->access = access;
+ ),
+
+ TP_printk("vcpu %d rip 0x%lx gfn 0x%016llx access %s protections 0x%x",
+ __entry->vcpu_id, __entry->guest_rip, __entry->fault_address,
+ __entry->exec ? "X" : (__entry->write ? "W" : "R"),
+ __entry->access)
+);
+
#endif /* _TRACE_KVMMMU_H */
#undef TRACE_INCLUDE_PATH
diff --git a/arch/x86/kvm/mmu/paging_tmpl.h b/arch/x86/kvm/mmu/paging_tmpl.h
index 1871d334fed7..cdf05cd76d63 100644
--- a/arch/x86/kvm/mmu/paging_tmpl.h
+++ b/arch/x86/kvm/mmu/paging_tmpl.h
@@ -990,7 +990,7 @@ static int FNAME(sync_spte)(struct kvm_vcpu *vcpu, struct kvm_mmu_page *sp, int
sptep = &sp->spt[i];
spte = *sptep;
- host_access = ACC_ALL;
+ host_access = kvm_get_gfn_protections(vcpu, gfn);
if (!(spte & shadow_host_writable_mask))
host_access &= ~ACC_WRITE_MASK;
slot = kvm_vcpu_gfn_to_memslot(vcpu, gfn);
diff --git a/arch/x86/kvm/mmu/spte.h b/arch/x86/kvm/mmu/spte.h
index 589f3954633e..14f322944671 100644
--- a/arch/x86/kvm/mmu/spte.h
+++ b/arch/x86/kvm/mmu/spte.h
@@ -491,7 +491,7 @@ static inline bool is_mmu_writable_spte(u64 spte)
static inline bool spte_permission_fault(struct kvm_mmu *mmu, u64 spte,
struct kvm_page_fault *fault)
{
- unsigned pfec, pte_access;
+ unsigned pte_access;
if (!is_shadow_present_pte(spte))
return true;
@@ -511,14 +511,7 @@ static inline bool spte_permission_fault(struct kvm_mmu *mmu, u64 spte,
pte_access |= spte & shadow_xu_mask ? ACC_USER_EXEC_MASK : 0;
}
- /*
- * RSVD is handled elsewhere, and is used for SMAP in the context
- * of accessing fmt.permissions[]. SPTEs never use PK or SS, as
- * they are not supported for shadow paging and irrelevant for TDP.
- */
- pfec = fault->error_code & (
- PFERR_WRITE_MASK | PFERR_USER_MASK | PFERR_FETCH_MASK);
- return (mmu->fmt.permissions[pfec >> 1] >> pte_access) & 1;
+ return __permission_fault(mmu, pte_access, fault);
}
/*
--
2.52.0