From b489c53001270b0d70152c82a4d1e0dcc8b4ff21 Mon Sep 17 00:00:00 2001 From: Quentin Perret Date: Tue, 7 Feb 2023 13:13:14 +0000 Subject: [PATCH] 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 --- arch/arm64/include/asm/kvm_pgtable.h | 11 ++++-- arch/arm64/kvm/hyp/nvhe/mem_protect.c | 6 +++- arch/arm64/kvm/hyp/pgtable.c | 51 ++++++++++++++++++++------- 3 files changed, 53 insertions(+), 15 deletions(-) diff --git a/arch/arm64/include/asm/kvm_pgtable.h b/arch/arm64/include/asm/kvm_pgtable.h index 55fa5403781a..ce85d55fb981 100644 --- a/arch/arm64/include/asm/kvm_pgtable.h +++ b/arch/arm64/include/asm/kvm_pgtable.h @@ -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), diff --git a/arch/arm64/kvm/hyp/nvhe/mem_protect.c b/arch/arm64/kvm/hyp/nvhe/mem_protect.c index 0242fa1b7c45..2aeb5a8a7baa 100644 --- a/arch/arm64/kvm/hyp/nvhe/mem_protect.c +++ b/arch/arm64/kvm/hyp/nvhe/mem_protect.c @@ -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(); diff --git a/arch/arm64/kvm/hyp/pgtable.c b/arch/arm64/kvm/hyp/pgtable.c index 23df444d8f73..1293a9b5319d 100644 --- a/arch/arm64/kvm/hyp/pgtable.c +++ b/arch/arm64/kvm/hyp/pgtable.c @@ -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)