Skip to content

Commit 97d64b7

Browse files
committed
KVM: MMU: Optimize pte permission checks
walk_addr_generic() permission checks are a maze of branchy code, which is performed four times per lookup. It depends on the type of access, efer.nxe, cr0.wp, cr4.smep, and in the near future, cr4.smap. Optimize this away by precalculating all variants and storing them in a bitmap. The bitmap is recalculated when rarely-changing variables change (cr0, cr4) and is indexed by the often-changing variables (page fault error code, pte access permissions). The permission check is moved to the end of the loop, otherwise an SMEP fault could be reported as a false positive, when PDE.U=1 but PTE.U=0. Noted by Xiao Guangrong. The result is short, branch-free code. Reviewed-by: Xiao Guangrong <[email protected]> Signed-off-by: Avi Kivity <[email protected]>
1 parent 8cbc706 commit 97d64b7

File tree

5 files changed

+61
-36
lines changed

5 files changed

+61
-36
lines changed

arch/x86/include/asm/kvm_host.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -287,6 +287,13 @@ struct kvm_mmu {
287287
union kvm_mmu_page_role base_role;
288288
bool direct_map;
289289

290+
/*
291+
* Bitmap; bit set = permission fault
292+
* Byte index: page fault error code [4:1]
293+
* Bit index: pte permissions in ACC_* format
294+
*/
295+
u8 permissions[16];
296+
290297
u64 *pae_root;
291298
u64 *lm_root;
292299
u64 rsvd_bits_mask[2][4];

arch/x86/kvm/mmu.c

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3516,6 +3516,38 @@ static void reset_rsvds_bits_mask(struct kvm_vcpu *vcpu,
35163516
}
35173517
}
35183518

3519+
static void update_permission_bitmask(struct kvm_vcpu *vcpu, struct kvm_mmu *mmu)
3520+
{
3521+
unsigned bit, byte, pfec;
3522+
u8 map;
3523+
bool fault, x, w, u, wf, uf, ff, smep;
3524+
3525+
smep = kvm_read_cr4_bits(vcpu, X86_CR4_SMEP);
3526+
for (byte = 0; byte < ARRAY_SIZE(mmu->permissions); ++byte) {
3527+
pfec = byte << 1;
3528+
map = 0;
3529+
wf = pfec & PFERR_WRITE_MASK;
3530+
uf = pfec & PFERR_USER_MASK;
3531+
ff = pfec & PFERR_FETCH_MASK;
3532+
for (bit = 0; bit < 8; ++bit) {
3533+
x = bit & ACC_EXEC_MASK;
3534+
w = bit & ACC_WRITE_MASK;
3535+
u = bit & ACC_USER_MASK;
3536+
3537+
/* Not really needed: !nx will cause pte.nx to fault */
3538+
x |= !mmu->nx;
3539+
/* Allow supervisor writes if !cr0.wp */
3540+
w |= !is_write_protection(vcpu) && !uf;
3541+
/* Disallow supervisor fetches of user code if cr4.smep */
3542+
x &= !(smep && u && !uf);
3543+
3544+
fault = (ff && !x) || (uf && !u) || (wf && !w);
3545+
map |= fault << bit;
3546+
}
3547+
mmu->permissions[byte] = map;
3548+
}
3549+
}
3550+
35193551
static int paging64_init_context_common(struct kvm_vcpu *vcpu,
35203552
struct kvm_mmu *context,
35213553
int level)
@@ -3524,6 +3556,7 @@ static int paging64_init_context_common(struct kvm_vcpu *vcpu,
35243556
context->root_level = level;
35253557

35263558
reset_rsvds_bits_mask(vcpu, context);
3559+
update_permission_bitmask(vcpu, context);
35273560

35283561
ASSERT(is_pae(vcpu));
35293562
context->new_cr3 = paging_new_cr3;
@@ -3552,6 +3585,7 @@ static int paging32_init_context(struct kvm_vcpu *vcpu,
35523585
context->root_level = PT32_ROOT_LEVEL;
35533586

35543587
reset_rsvds_bits_mask(vcpu, context);
3588+
update_permission_bitmask(vcpu, context);
35553589

35563590
context->new_cr3 = paging_new_cr3;
35573591
context->page_fault = paging32_page_fault;
@@ -3612,6 +3646,8 @@ static int init_kvm_tdp_mmu(struct kvm_vcpu *vcpu)
36123646
context->gva_to_gpa = paging32_gva_to_gpa;
36133647
}
36143648

3649+
update_permission_bitmask(vcpu, context);
3650+
36153651
return 0;
36163652
}
36173653

@@ -3687,6 +3723,8 @@ static int init_kvm_nested_mmu(struct kvm_vcpu *vcpu)
36873723
g_context->gva_to_gpa = paging32_gva_to_gpa_nested;
36883724
}
36893725

3726+
update_permission_bitmask(vcpu, g_context);
3727+
36903728
return 0;
36913729
}
36923730

arch/x86/kvm/mmu.h

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -89,17 +89,14 @@ static inline bool is_write_protection(struct kvm_vcpu *vcpu)
8989
return kvm_read_cr0_bits(vcpu, X86_CR0_WP);
9090
}
9191

92-
static inline bool check_write_user_access(struct kvm_vcpu *vcpu,
93-
bool write_fault, bool user_fault,
94-
unsigned long pte)
92+
/*
93+
* Will a fault with a given page-fault error code (pfec) cause a permission
94+
* fault with the given access (in ACC_* format)?
95+
*/
96+
static inline bool permission_fault(struct kvm_mmu *mmu, unsigned pte_access,
97+
unsigned pfec)
9598
{
96-
if (unlikely(write_fault && !is_writable_pte(pte)
97-
&& (user_fault || is_write_protection(vcpu))))
98-
return false;
99-
100-
if (unlikely(user_fault && !(pte & PT_USER_MASK)))
101-
return false;
102-
103-
return true;
99+
return (mmu->permissions[pfec >> 1] >> pte_access) & 1;
104100
}
101+
105102
#endif

arch/x86/kvm/paging_tmpl.h

Lines changed: 4 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,7 @@ static int FNAME(walk_addr_generic)(struct guest_walker *walker,
169169
pt_element_t pte;
170170
pt_element_t __user *uninitialized_var(ptep_user);
171171
gfn_t table_gfn;
172-
unsigned index, pt_access, uninitialized_var(pte_access);
172+
unsigned index, pt_access, pte_access;
173173
gpa_t pte_gpa;
174174
bool eperm, last_gpte;
175175
int offset;
@@ -237,24 +237,9 @@ static int FNAME(walk_addr_generic)(struct guest_walker *walker,
237237
goto error;
238238
}
239239

240-
if (!check_write_user_access(vcpu, write_fault, user_fault,
241-
pte))
242-
eperm = true;
243-
244-
#if PTTYPE == 64
245-
if (unlikely(fetch_fault && (pte & PT64_NX_MASK)))
246-
eperm = true;
247-
#endif
240+
pte_access = pt_access & gpte_access(vcpu, pte);
248241

249242
last_gpte = FNAME(is_last_gpte)(walker, vcpu, mmu, pte);
250-
if (last_gpte) {
251-
pte_access = pt_access & gpte_access(vcpu, pte);
252-
/* check if the kernel is fetching from user page */
253-
if (unlikely(pte_access & PT_USER_MASK) &&
254-
kvm_read_cr4_bits(vcpu, X86_CR4_SMEP))
255-
if (fetch_fault && !user_fault)
256-
eperm = true;
257-
}
258243

259244
walker->ptes[walker->level - 1] = pte;
260245

@@ -284,10 +269,11 @@ static int FNAME(walk_addr_generic)(struct guest_walker *walker,
284269
break;
285270
}
286271

287-
pt_access &= gpte_access(vcpu, pte);
272+
pt_access &= pte_access;
288273
--walker->level;
289274
}
290275

276+
eperm |= permission_fault(mmu, pte_access, access);
291277
if (unlikely(eperm)) {
292278
errcode |= PFERR_PRESENT_MASK;
293279
goto error;

arch/x86/kvm/x86.c

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3672,20 +3672,17 @@ static int vcpu_mmio_gva_to_gpa(struct kvm_vcpu *vcpu, unsigned long gva,
36723672
gpa_t *gpa, struct x86_exception *exception,
36733673
bool write)
36743674
{
3675-
u32 access = (kvm_x86_ops->get_cpl(vcpu) == 3) ? PFERR_USER_MASK : 0;
3675+
u32 access = ((kvm_x86_ops->get_cpl(vcpu) == 3) ? PFERR_USER_MASK : 0)
3676+
| (write ? PFERR_WRITE_MASK : 0);
36763677

3677-
if (vcpu_match_mmio_gva(vcpu, gva) &&
3678-
check_write_user_access(vcpu, write, access,
3679-
vcpu->arch.access)) {
3678+
if (vcpu_match_mmio_gva(vcpu, gva)
3679+
&& !permission_fault(vcpu->arch.walk_mmu, vcpu->arch.access, access)) {
36803680
*gpa = vcpu->arch.mmio_gfn << PAGE_SHIFT |
36813681
(gva & (PAGE_SIZE - 1));
36823682
trace_vcpu_match_mmio(gva, *gpa, write, false);
36833683
return 1;
36843684
}
36853685

3686-
if (write)
3687-
access |= PFERR_WRITE_MASK;
3688-
36893686
*gpa = vcpu->arch.walk_mmu->gva_to_gpa(vcpu, gva, access, exception);
36903687

36913688
if (*gpa == UNMAPPED_GVA)

0 commit comments

Comments
 (0)