Commit d93febe2 authored by Oliver Upton's avatar Oliver Upton
Browse files

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: default avatarMarc Zyngier <maz@kernel.org>
Tested-by: default avatarMarc Zyngier <maz@kernel.org>
Link: https://msgid.link/20251124190158.177318-4-oupton@kernel.org


Signed-off-by: default avatarOliver Upton <oupton@kernel.org>
parent 2608563b
Loading
Loading
Loading
Loading
+35 −2
Original line number Diff line number Diff line
@@ -120,9 +120,42 @@ static inline bool kvm_s2_trans_writable(struct kvm_s2_trans *trans)
	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,
+18 −5
Original line number Diff line number Diff line
@@ -1521,6 +1521,16 @@ static void adjust_nested_fault_perms(struct kvm_s2_trans *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)

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)
		prot |= KVM_PGTABLE_PROT_W;

	if (exec_fault ||
	    (cpus_have_final_cap(ARM64_HAS_CACHE_DIC) &&
	     (!nested || kvm_s2_trans_executable(nested))))
	if (exec_fault || cpus_have_final_cap(ARM64_HAS_CACHE_DIC))
		prot |= KVM_PGTABLE_PROT_X;

	if (nested)
		adjust_nested_exec_perms(kvm, nested, &prot);

	kvm_fault_lock(kvm);
	if (mmu_invalidate_retry(kvm, mmu_seq)) {
		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;
		else
			prot |= KVM_PGTABLE_PROT_DEVICE;
	} else if (cpus_have_final_cap(ARM64_HAS_CACHE_DIC) &&
		   (!nested || kvm_s2_trans_executable(nested))) {
	} else if (cpus_have_final_cap(ARM64_HAS_CACHE_DIC)) {
		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
	 * permissions only if vma_pagesize equals fault_granule. Otherwise,
+4 −1
Original line number Diff line number Diff line
@@ -788,7 +788,10 @@ int kvm_s2_handle_perm_fault(struct kvm_vcpu *vcpu, struct kvm_s2_trans *trans)
		return 0;

	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 {
		bool write_fault = kvm_is_write_fault(vcpu);