ANDROID: KVM: arm64: Allow setting {P,U}XN in stage-2 PTEs

FEAT_XNX allows to specify PXN and UXN attributes on stage-2 entries.
Make this usable from pKVM by exposing two new kvm_pgtable_prot entries
for each of them.

No functional changes intended.

Bug: 264070847
Change-Id: I47d861fa64ba511370b182f4609fe1c27695a949
Signed-off-by: Quentin Perret <qperret@google.com>
This commit is contained in:
Quentin Perret 2023-02-07 13:13:14 +00:00 committed by Carlos Llamas
parent b7aff5c603
commit b489c53001
3 changed files with 53 additions and 15 deletions

View file

@ -72,7 +72,10 @@ typedef u64 kvm_pte_t;
#define KVM_PTE_LEAF_ATTR_HI_S1_XN BIT(54)
#define KVM_PTE_LEAF_ATTR_HI_S2_XN BIT(54)
#define KVM_PTE_LEAF_ATTR_HI_S2_XN_PXN 1
#define KVM_PTE_LEAF_ATTR_HI_S2_XN_UXN 3
#define KVM_PTE_LEAF_ATTR_HI_S2_XN_XN 2
#define KVM_PTE_LEAF_ATTR_HI_S2_XN GENMASK(54, 53)
static inline bool kvm_pte_valid(kvm_pte_t pte)
{
@ -189,7 +192,9 @@ enum kvm_pgtable_stage2_flags {
* @KVM_PGTABLE_PROT_W: Write permission.
* @KVM_PGTABLE_PROT_R: Read permission.
* @KVM_PGTABLE_PROT_DEVICE: Device attributes.
* @KVM_PGTABLE_PROT_NC: Normal non-cacheable attributes.
* @KVM_PGTABLE_PROT_NC: Normal non-cacheable attributes.
* @KVM_PGTABLE_PROT_PXN: Privileged execute-never.
* @KVM_PGTABLE_PROT_UXN: Unprivileged execute-never.
* @KVM_PGTABLE_PROT_SW0: Software bit 0.
* @KVM_PGTABLE_PROT_SW1: Software bit 1.
* @KVM_PGTABLE_PROT_SW2: Software bit 2.
@ -202,6 +207,8 @@ enum kvm_pgtable_prot {
KVM_PGTABLE_PROT_DEVICE = BIT(3),
KVM_PGTABLE_PROT_NC = BIT(4),
KVM_PGTABLE_PROT_PXN = BIT(5),
KVM_PGTABLE_PROT_UXN = BIT(6),
KVM_PGTABLE_PROT_SW0 = BIT(55),
KVM_PGTABLE_PROT_SW1 = BIT(56),

View file

@ -2037,6 +2037,10 @@ static int restrict_host_page_perms(u64 addr, kvm_pte_t pte, u32 level, enum kvm
return ret;
}
#define MODULE_PROT_ALLOWLIST (KVM_PGTABLE_PROT_RWX | \
KVM_PGTABLE_PROT_NC | \
KVM_PGTABLE_PROT_PXN | \
KVM_PGTABLE_PROT_UXN)
int module_change_host_page_prot(u64 pfn, enum kvm_pgtable_prot prot)
{
u64 addr = hyp_pfn_to_phys(pfn);
@ -2045,7 +2049,7 @@ int module_change_host_page_prot(u64 pfn, enum kvm_pgtable_prot prot)
u32 level;
int ret;
if ((prot & KVM_PGTABLE_PROT_RWX) != prot)
if ((prot & MODULE_PROT_ALLOWLIST) != prot)
return -EINVAL;
host_lock_component();

View file

@ -276,7 +276,8 @@ static int hyp_set_prot_attr(enum kvm_pgtable_prot prot, kvm_pte_t *ptep)
kvm_pte_t attr;
u32 mtype;
if (!(prot & KVM_PGTABLE_PROT_R) || (device && nc))
if (!(prot & KVM_PGTABLE_PROT_R) || (device && nc) ||
(prot & (KVM_PGTABLE_PROT_PXN | KVM_PGTABLE_PROT_UXN)))
return -EINVAL;
if (device)
@ -565,16 +566,15 @@ static bool stage2_has_fwb(struct kvm_pgtable *pgt)
#define KVM_S2_MEMATTR(pgt, attr) PAGE_S2_MEMATTR(attr, stage2_has_fwb(pgt))
static int stage2_set_prot_attr(struct kvm_pgtable *pgt, enum kvm_pgtable_prot prot,
kvm_pte_t *ptep)
kvm_pte_t *ptep)
{
u64 exec_type = KVM_PTE_LEAF_ATTR_HI_S2_XN_XN;
bool device = prot & KVM_PGTABLE_PROT_DEVICE;
u32 sh = KVM_PTE_LEAF_ATTR_LO_S2_SH_IS;
bool nc = prot & KVM_PGTABLE_PROT_NC;
enum kvm_pgtable_prot exec_prot;
kvm_pte_t attr;
if (device && nc)
return -EINVAL;
if (device)
attr = KVM_S2_MEMATTR(pgt, DEVICE_nGnRE);
else if (nc)
@ -582,11 +582,23 @@ static int stage2_set_prot_attr(struct kvm_pgtable *pgt, enum kvm_pgtable_prot p
else
attr = KVM_S2_MEMATTR(pgt, NORMAL);
if (!(prot & KVM_PGTABLE_PROT_X))
attr |= KVM_PTE_LEAF_ATTR_HI_S2_XN;
else if (device)
return -EINVAL;
exec_prot = prot & (KVM_PGTABLE_PROT_X | KVM_PGTABLE_PROT_PXN | KVM_PGTABLE_PROT_UXN);
switch(exec_prot) {
case KVM_PGTABLE_PROT_X:
goto set_ap;
case KVM_PGTABLE_PROT_PXN:
exec_type = KVM_PTE_LEAF_ATTR_HI_S2_XN_PXN;
break;
case KVM_PGTABLE_PROT_UXN:
exec_type = KVM_PTE_LEAF_ATTR_HI_S2_XN_UXN;
break;
default:
if (exec_prot)
return -EINVAL;
}
attr |= FIELD_PREP(KVM_PTE_LEAF_ATTR_HI_S2_XN, exec_type);
set_ap:
if (prot & KVM_PGTABLE_PROT_R)
attr |= KVM_PTE_LEAF_ATTR_LO_S2_S2AP_R;
@ -612,8 +624,21 @@ enum kvm_pgtable_prot kvm_pgtable_stage2_pte_prot(kvm_pte_t pte)
prot |= KVM_PGTABLE_PROT_R;
if (pte & KVM_PTE_LEAF_ATTR_LO_S2_S2AP_W)
prot |= KVM_PGTABLE_PROT_W;
if (!(pte & KVM_PTE_LEAF_ATTR_HI_S2_XN))
switch(FIELD_GET(KVM_PTE_LEAF_ATTR_HI_S2_XN, pte)) {
case 0:
prot |= KVM_PGTABLE_PROT_X;
break;
case KVM_PTE_LEAF_ATTR_HI_S2_XN_PXN:
prot |= KVM_PGTABLE_PROT_PXN;
break;
case KVM_PTE_LEAF_ATTR_HI_S2_XN_UXN:
prot |= KVM_PGTABLE_PROT_UXN;
break;
case KVM_PTE_LEAF_ATTR_HI_S2_XN_XN:
break;
default:
WARN_ON(1);
}
return prot;
}
@ -655,7 +680,9 @@ static bool stage2_pte_cacheable(struct kvm_pgtable *pgt, kvm_pte_t pte)
static bool stage2_pte_executable(kvm_pte_t pte)
{
return kvm_pte_valid(pte) && !(pte & KVM_PTE_LEAF_ATTR_HI_S2_XN);
kvm_pte_t xn = FIELD_GET(KVM_PTE_LEAF_ATTR_HI_S2_XN, pte);
return kvm_pte_valid(pte) && xn != KVM_PTE_LEAF_ATTR_HI_S2_XN_XN;
}
static bool stage2_leaf_mapping_allowed(u64 addr, u64 end, u32 level,
@ -1154,7 +1181,7 @@ int kvm_pgtable_stage2_relax_perms(struct kvm_pgtable *pgt, u64 addr,
u32 level;
kvm_pte_t set = 0, clr = 0;
if (prot & KVM_PTE_LEAF_ATTR_HI_SW)
if (prot & !KVM_PGTABLE_PROT_RWX)
return -EINVAL;
if (prot & KVM_PGTABLE_PROT_R)