KVM: x86/mmu: use kvm_page_format to test SPTEs

is_access_allowed(), and is_executable_pte() within it, are effectively a
special version of permission_fault() that only supports a subset of roles.
In particular it does not allow SMEP, SMAP and PKE; while SMAP and PKE are
not a problem (they are not supported by EPT/NPT and is_access_allowed() is
only used for either EPT/NPT or CR0.PG=0), lack of ring-0 execution checks
support means that KVM can only use MBEC and GMET with shadow paging.

Replace is_access_allowed() with a modified version of permission_fault(),
looking at the same bitmasks but using the struct kvm_mmu's fmt member;
the new version supports MBEC/GMET for free, just by virtue of filling
in the ACC_* masks from the SPTE.

This prepares for a possible future where TDP entries could have XS!=XU,
for example as part of implementing Hyper-V VSM natively inside KVM.

Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
This commit is contained in:
Paolo Bonzini 2026-03-03 21:34:46 +01:00
parent 6bb13c2eb7
commit 216b1a47e8
3 changed files with 47 additions and 25 deletions

View File

@ -3694,6 +3694,7 @@ static u64 *fast_pf_get_last_sptep(struct kvm_vcpu *vcpu, gpa_t gpa, u64 *spte)
*/
static int fast_page_fault(struct kvm_vcpu *vcpu, struct kvm_page_fault *fault)
{
struct kvm_mmu *mmu;
struct kvm_mmu_page *sp;
int ret = RET_PF_INVALID;
u64 spte;
@ -3703,6 +3704,7 @@ static int fast_page_fault(struct kvm_vcpu *vcpu, struct kvm_page_fault *fault)
if (!page_fault_can_be_fast(vcpu->kvm, fault))
return ret;
mmu = vcpu->arch.mmu;
walk_shadow_page_lockless_begin(vcpu);
do {
@ -3738,7 +3740,7 @@ static int fast_page_fault(struct kvm_vcpu *vcpu, struct kvm_page_fault *fault)
* Need not check the access of upper level table entries since
* they are always ACC_ALL.
*/
if (is_access_allowed(fault, spte)) {
if (!spte_permission_fault(mmu, spte, fault)) {
ret = RET_PF_SPURIOUS;
break;
}
@ -3761,7 +3763,7 @@ static int fast_page_fault(struct kvm_vcpu *vcpu, struct kvm_page_fault *fault)
* that were write-protected for dirty-logging or access
* tracking are handled here. Don't bother checking if the
* SPTE is writable to prioritize running with A/D bits enabled.
* The is_access_allowed() check above handles the common case
* The spte_permission_fault() check above handles the common case
* of the fault being spurious, and the SPTE is known to be
* shadow-present, i.e. except for access tracking restoration
* making the new SPTE writable, the check is wasteful.
@ -3786,7 +3788,7 @@ static int fast_page_fault(struct kvm_vcpu *vcpu, struct kvm_page_fault *fault)
/* Verify that the fault can be handled in the fast path */
if (new_spte == spte ||
!is_access_allowed(fault, new_spte))
spte_permission_fault(mmu, new_spte, fault))
break;
/*
@ -5764,6 +5766,12 @@ static void update_permission_bitmask(struct kvm_pagewalk *w, bool tdp, bool ept
is_cr0_wp(w), is_efer_nx(w));
}
static void update_spte_permission_bitmask(struct kvm_mmu *mmu, bool tdp, bool ept)
{
__update_permission_bitmask(&mmu->fmt, tdp, ept,
mmu->root_role.cr4_smep, false, true, true);
}
/*
* PKU is an additional mechanism by which the paging controls access to
* user-mode addresses based on the value in the PKRU register. Protection
@ -5973,6 +5981,7 @@ static void init_kvm_tdp_mmu(struct kvm_vcpu *vcpu,
context->page_fault = kvm_tdp_page_fault;
context->sync_spte = NULL;
update_spte_permission_bitmask(context, true, shadow_xs_mask);
reset_tdp_shadow_zero_bits_mask(context);
}
@ -5991,6 +6000,7 @@ static void shadow_mmu_init_context(struct kvm_vcpu *vcpu, struct kvm_mmu *conte
else
paging32_init_context(context);
update_spte_permission_bitmask(context, context == &vcpu->arch.guest_mmu, false);
reset_shadow_zero_bits_mask(vcpu, context);
}
@ -6119,6 +6129,8 @@ void kvm_init_shadow_ept_mmu(struct kvm_vcpu *vcpu, bool execonly,
update_permission_bitmask(ngpa_walk, true, true);
ngpa_walk->fmt.pkru_mask = 0;
reset_rsvds_bits_mask_ept(vcpu, execonly, huge_page_level);
update_spte_permission_bitmask(context, true, true);
reset_ept_shadow_zero_bits_mask(context, execonly);
}

View File

@ -357,17 +357,6 @@ static inline bool is_last_spte(u64 pte, int level)
return (level == PG_LEVEL_4K) || is_large_pte(pte);
}
static inline bool is_executable_pte(u64 spte)
{
/*
* For now, return true if either the XS or XU bit is set
* This function is only used for fast_page_fault,
* which never processes shadow EPT, and regular page
* tables always have XS==XU.
*/
return (spte & (shadow_xs_mask | shadow_xu_mask | shadow_nx_mask)) != shadow_nx_mask;
}
static inline kvm_pfn_t spte_to_pfn(u64 pte)
{
return (pte & SPTE_BASE_ADDR_MASK) >> PAGE_SHIFT;
@ -496,20 +485,40 @@ static inline bool is_mmu_writable_spte(u64 spte)
}
/*
* Returns true if the access indicated by @fault is allowed by the existing
* SPTE protections. Note, the caller is responsible for checking that the
* SPTE is a shadow-present, leaf SPTE (either before or after).
* Returns true if the access indicated by @fault is forbidden by the existing
* SPTE protections.
*/
static inline bool is_access_allowed(struct kvm_page_fault *fault, u64 spte)
static inline bool spte_permission_fault(struct kvm_mmu *mmu, u64 spte,
struct kvm_page_fault *fault)
{
if (fault->exec)
return is_executable_pte(spte);
unsigned pfec, pte_access;
if (fault->write)
return is_writable_pte(spte);
if (!is_shadow_present_pte(spte))
return true;
/* Fault was on Read access */
return spte & PT_PRESENT_MASK;
BUILD_BUG_ON(PT_PRESENT_MASK != ACC_READ_MASK);
BUILD_BUG_ON(PT_WRITABLE_MASK != ACC_WRITE_MASK);
BUILD_BUG_ON(VMX_EPT_READABLE_MASK != ACC_READ_MASK);
BUILD_BUG_ON(VMX_EPT_WRITABLE_MASK != ACC_WRITE_MASK);
/* strip nested paging fault error codes */
pte_access = spte & (PT_PRESENT_MASK | PT_WRITABLE_MASK);
if (shadow_nx_mask) {
pte_access |= spte & shadow_user_mask ? ACC_USER_MASK : 0;
pte_access |= spte & shadow_nx_mask ? 0 : ACC_EXEC_MASK;
} else {
pte_access |= spte & shadow_xs_mask ? ACC_EXEC_MASK : 0;
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;
}
/*

View File

@ -1122,6 +1122,7 @@ static int tdp_mmu_map_handle_target_level(struct kvm_vcpu *vcpu,
struct kvm_page_fault *fault,
struct tdp_iter *iter)
{
struct kvm_mmu *mmu = vcpu->arch.mmu;
struct kvm_mmu_page *sp = sptep_to_sp(rcu_dereference(iter->sptep));
u64 new_spte;
int ret = RET_PF_FIXED;
@ -1131,7 +1132,7 @@ static int tdp_mmu_map_handle_target_level(struct kvm_vcpu *vcpu,
return RET_PF_RETRY;
if (is_shadow_present_pte(iter->old_spte) &&
(fault->prefetch || is_access_allowed(fault, iter->old_spte)) &&
(fault->prefetch || !spte_permission_fault(mmu, iter->old_spte, fault)) &&
is_last_spte(iter->old_spte, iter->level)) {
WARN_ON_ONCE(fault->pfn != spte_to_pfn(iter->old_spte));
return RET_PF_SPURIOUS;