KVM: arm64: nv: Forward FEAT_XNX permissions to the shadow stage-2
Add support for FEAT_XNX to shadow stage-2 MMUs, being careful to only evaluate XN[0] when the feature is actually exposed to the VM. Restructure the layering of permissions in the fault handler to assume pX and uX then restricting based on the guest's stage-2 afterwards. Reviewed-by: Marc Zyngier <maz@kernel.org> Tested-by: Marc Zyngier <maz@kernel.org> Link: https://msgid.link/20251124190158.177318-4-oupton@kernel.org Signed-off-by: Oliver Upton <oupton@kernel.org>pull/1354/merge
parent
2608563b46
commit
d93febe2ed
|
|
@ -120,9 +120,42 @@ static inline bool kvm_s2_trans_writable(struct kvm_s2_trans *trans)
|
||||||
return trans->writable;
|
return trans->writable;
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline bool kvm_s2_trans_executable(struct kvm_s2_trans *trans)
|
static inline bool kvm_has_xnx(struct kvm *kvm)
|
||||||
{
|
{
|
||||||
return !(trans->desc & BIT(54));
|
return cpus_have_final_cap(ARM64_HAS_XNX) &&
|
||||||
|
kvm_has_feat(kvm, ID_AA64MMFR1_EL1, XNX, IMP);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline bool kvm_s2_trans_exec_el0(struct kvm *kvm, struct kvm_s2_trans *trans)
|
||||||
|
{
|
||||||
|
u8 xn = FIELD_GET(KVM_PTE_LEAF_ATTR_HI_S2_XN, trans->desc);
|
||||||
|
|
||||||
|
if (!kvm_has_xnx(kvm))
|
||||||
|
xn &= FIELD_PREP(KVM_PTE_LEAF_ATTR_HI_S2_XN, 0b10);
|
||||||
|
|
||||||
|
switch (xn) {
|
||||||
|
case 0b00:
|
||||||
|
case 0b01:
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline bool kvm_s2_trans_exec_el1(struct kvm *kvm, struct kvm_s2_trans *trans)
|
||||||
|
{
|
||||||
|
u8 xn = FIELD_GET(KVM_PTE_LEAF_ATTR_HI_S2_XN, trans->desc);
|
||||||
|
|
||||||
|
if (!kvm_has_xnx(kvm))
|
||||||
|
xn &= FIELD_PREP(KVM_PTE_LEAF_ATTR_HI_S2_XN, 0b10);
|
||||||
|
|
||||||
|
switch (xn) {
|
||||||
|
case 0b00:
|
||||||
|
case 0b11:
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extern int kvm_walk_nested_s2(struct kvm_vcpu *vcpu, phys_addr_t gipa,
|
extern int kvm_walk_nested_s2(struct kvm_vcpu *vcpu, phys_addr_t gipa,
|
||||||
|
|
|
||||||
|
|
@ -1521,6 +1521,16 @@ static void adjust_nested_fault_perms(struct kvm_s2_trans *nested,
|
||||||
*prot |= kvm_encode_nested_level(nested);
|
*prot |= kvm_encode_nested_level(nested);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void adjust_nested_exec_perms(struct kvm *kvm,
|
||||||
|
struct kvm_s2_trans *nested,
|
||||||
|
enum kvm_pgtable_prot *prot)
|
||||||
|
{
|
||||||
|
if (!kvm_s2_trans_exec_el0(kvm, nested))
|
||||||
|
*prot &= ~KVM_PGTABLE_PROT_UX;
|
||||||
|
if (!kvm_s2_trans_exec_el1(kvm, nested))
|
||||||
|
*prot &= ~KVM_PGTABLE_PROT_PX;
|
||||||
|
}
|
||||||
|
|
||||||
#define KVM_PGTABLE_WALK_MEMABORT_FLAGS (KVM_PGTABLE_WALK_HANDLE_FAULT | KVM_PGTABLE_WALK_SHARED)
|
#define KVM_PGTABLE_WALK_MEMABORT_FLAGS (KVM_PGTABLE_WALK_HANDLE_FAULT | KVM_PGTABLE_WALK_SHARED)
|
||||||
|
|
||||||
static int gmem_abort(struct kvm_vcpu *vcpu, phys_addr_t fault_ipa,
|
static int gmem_abort(struct kvm_vcpu *vcpu, phys_addr_t fault_ipa,
|
||||||
|
|
@ -1572,11 +1582,12 @@ static int gmem_abort(struct kvm_vcpu *vcpu, phys_addr_t fault_ipa,
|
||||||
if (writable)
|
if (writable)
|
||||||
prot |= KVM_PGTABLE_PROT_W;
|
prot |= KVM_PGTABLE_PROT_W;
|
||||||
|
|
||||||
if (exec_fault ||
|
if (exec_fault || cpus_have_final_cap(ARM64_HAS_CACHE_DIC))
|
||||||
(cpus_have_final_cap(ARM64_HAS_CACHE_DIC) &&
|
|
||||||
(!nested || kvm_s2_trans_executable(nested))))
|
|
||||||
prot |= KVM_PGTABLE_PROT_X;
|
prot |= KVM_PGTABLE_PROT_X;
|
||||||
|
|
||||||
|
if (nested)
|
||||||
|
adjust_nested_exec_perms(kvm, nested, &prot);
|
||||||
|
|
||||||
kvm_fault_lock(kvm);
|
kvm_fault_lock(kvm);
|
||||||
if (mmu_invalidate_retry(kvm, mmu_seq)) {
|
if (mmu_invalidate_retry(kvm, mmu_seq)) {
|
||||||
ret = -EAGAIN;
|
ret = -EAGAIN;
|
||||||
|
|
@ -1851,11 +1862,13 @@ static int user_mem_abort(struct kvm_vcpu *vcpu, phys_addr_t fault_ipa,
|
||||||
prot |= KVM_PGTABLE_PROT_NORMAL_NC;
|
prot |= KVM_PGTABLE_PROT_NORMAL_NC;
|
||||||
else
|
else
|
||||||
prot |= KVM_PGTABLE_PROT_DEVICE;
|
prot |= KVM_PGTABLE_PROT_DEVICE;
|
||||||
} else if (cpus_have_final_cap(ARM64_HAS_CACHE_DIC) &&
|
} else if (cpus_have_final_cap(ARM64_HAS_CACHE_DIC)) {
|
||||||
(!nested || kvm_s2_trans_executable(nested))) {
|
|
||||||
prot |= KVM_PGTABLE_PROT_X;
|
prot |= KVM_PGTABLE_PROT_X;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (nested)
|
||||||
|
adjust_nested_exec_perms(kvm, nested, &prot);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Under the premise of getting a FSC_PERM fault, we just need to relax
|
* Under the premise of getting a FSC_PERM fault, we just need to relax
|
||||||
* permissions only if vma_pagesize equals fault_granule. Otherwise,
|
* permissions only if vma_pagesize equals fault_granule. Otherwise,
|
||||||
|
|
|
||||||
|
|
@ -788,7 +788,10 @@ int kvm_s2_handle_perm_fault(struct kvm_vcpu *vcpu, struct kvm_s2_trans *trans)
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
if (kvm_vcpu_trap_is_iabt(vcpu)) {
|
if (kvm_vcpu_trap_is_iabt(vcpu)) {
|
||||||
forward_fault = !kvm_s2_trans_executable(trans);
|
if (vcpu_mode_priv(vcpu))
|
||||||
|
forward_fault = !kvm_s2_trans_exec_el1(vcpu->kvm, trans);
|
||||||
|
else
|
||||||
|
forward_fault = !kvm_s2_trans_exec_el0(vcpu->kvm, trans);
|
||||||
} else {
|
} else {
|
||||||
bool write_fault = kvm_is_write_fault(vcpu);
|
bool write_fault = kvm_is_write_fault(vcpu);
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue