Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

arm64: implement PKEYS support

Implement the PKEYS interface, using the Permission Overlay Extension.

Signed-off-by: Joey Gouly <joey.gouly@arm.com>
Cc: Catalin Marinas <catalin.marinas@arm.com>
Cc: Will Deacon <will@kernel.org>
Reviewed-by: Catalin Marinas <catalin.marinas@arm.com>
Link: https://lore.kernel.org/r/20240822151113.1479789-19-joey.gouly@arm.com
Signed-off-by: Will Deacon <will@kernel.org>

authored by

Joey Gouly and committed by
Will Deacon
7f955be9 fc2d9cd3

+262 -2
+1
arch/arm64/include/asm/mmu.h
··· 25 25 refcount_t pinned; 26 26 void *vdso; 27 27 unsigned long flags; 28 + u8 pkey_allocation_map; 28 29 } mm_context_t; 29 30 30 31 /*
+45 -1
arch/arm64/include/asm/mmu_context.h
··· 15 15 #include <linux/sched/hotplug.h> 16 16 #include <linux/mm_types.h> 17 17 #include <linux/pgtable.h> 18 + #include <linux/pkeys.h> 18 19 19 20 #include <asm/cacheflush.h> 20 21 #include <asm/cpufeature.h> 21 22 #include <asm/daifflags.h> 22 23 #include <asm/proc-fns.h> 23 - #include <asm-generic/mm_hooks.h> 24 24 #include <asm/cputype.h> 25 25 #include <asm/sysreg.h> 26 26 #include <asm/tlbflush.h> ··· 175 175 { 176 176 atomic64_set(&mm->context.id, 0); 177 177 refcount_set(&mm->context.pinned, 0); 178 + 179 + /* pkey 0 is the default, so always reserve it. */ 180 + mm->context.pkey_allocation_map = BIT(0); 181 + 178 182 return 0; 183 + } 184 + 185 + static inline void arch_dup_pkeys(struct mm_struct *oldmm, 186 + struct mm_struct *mm) 187 + { 188 + /* Duplicate the oldmm pkey state in mm: */ 189 + mm->context.pkey_allocation_map = oldmm->context.pkey_allocation_map; 190 + } 191 + 192 + static inline int arch_dup_mmap(struct mm_struct *oldmm, struct mm_struct *mm) 193 + { 194 + arch_dup_pkeys(oldmm, mm); 195 + 196 + return 0; 197 + } 198 + 199 + static inline void arch_exit_mmap(struct mm_struct *mm) 200 + { 201 + } 202 + 203 + static inline void arch_unmap(struct mm_struct *mm, 204 + unsigned long start, unsigned long end) 205 + { 179 206 } 180 207 181 208 #ifdef CONFIG_ARM64_SW_TTBR0_PAN ··· 292 265 static inline unsigned long mm_untag_mask(struct mm_struct *mm) 293 266 { 294 267 return -1UL >> 8; 268 + } 269 + 270 + /* 271 + * Only enforce protection keys on the current process, because there is no 272 + * user context to access POR_EL0 for another address space. 273 + */ 274 + static inline bool arch_vma_access_permitted(struct vm_area_struct *vma, 275 + bool write, bool execute, bool foreign) 276 + { 277 + if (!system_supports_poe()) 278 + return true; 279 + 280 + /* allow access if the VMA is not one from this process */ 281 + if (foreign || vma_is_foreign(vma)) 282 + return true; 283 + 284 + return por_el0_allows_pkey(vma_pkey(vma), write, execute); 295 285 } 296 286 297 287 #include <asm-generic/mmu_context.h>
+21 -1
arch/arm64/include/asm/pgtable.h
··· 34 34 35 35 #include <asm/cmpxchg.h> 36 36 #include <asm/fixmap.h> 37 + #include <asm/por.h> 37 38 #include <linux/mmdebug.h> 38 39 #include <linux/mm_types.h> 39 40 #include <linux/sched.h> ··· 150 149 #define pte_accessible(mm, pte) \ 151 150 (mm_tlb_flush_pending(mm) ? pte_present(pte) : pte_valid(pte)) 152 151 152 + static inline bool por_el0_allows_pkey(u8 pkey, bool write, bool execute) 153 + { 154 + u64 por; 155 + 156 + if (!system_supports_poe()) 157 + return true; 158 + 159 + por = read_sysreg_s(SYS_POR_EL0); 160 + 161 + if (write) 162 + return por_elx_allows_write(por, pkey); 163 + 164 + if (execute) 165 + return por_elx_allows_exec(por, pkey); 166 + 167 + return por_elx_allows_read(por, pkey); 168 + } 169 + 153 170 /* 154 171 * p??_access_permitted() is true for valid user mappings (PTE_USER 155 172 * bit set, subject to the write permission check). For execute-only ··· 178 159 #define pte_access_permitted_no_overlay(pte, write) \ 179 160 (((pte_val(pte) & (PTE_VALID | PTE_USER)) == (PTE_VALID | PTE_USER)) && (!(write) || pte_write(pte))) 180 161 #define pte_access_permitted(pte, write) \ 181 - pte_access_permitted_no_overlay(pte, write) 162 + (pte_access_permitted_no_overlay(pte, write) && \ 163 + por_el0_allows_pkey(FIELD_GET(PTE_PO_IDX_MASK, pte_val(pte)), write, false)) 182 164 #define pmd_access_permitted(pmd, write) \ 183 165 (pte_access_permitted(pmd_pte(pmd), (write))) 184 166 #define pud_access_permitted(pud, write) \
+108
arch/arm64/include/asm/pkeys.h
··· 1 + /* SPDX-License-Identifier: GPL-2.0 */ 2 + /* 3 + * Copyright (C) 2023 Arm Ltd. 4 + * 5 + * Based on arch/x86/include/asm/pkeys.h 6 + */ 7 + 8 + #ifndef _ASM_ARM64_PKEYS_H 9 + #define _ASM_ARM64_PKEYS_H 10 + 11 + #define ARCH_VM_PKEY_FLAGS (VM_PKEY_BIT0 | VM_PKEY_BIT1 | VM_PKEY_BIT2) 12 + 13 + #define arch_max_pkey() 8 14 + 15 + int arch_set_user_pkey_access(struct task_struct *tsk, int pkey, 16 + unsigned long init_val); 17 + 18 + static inline bool arch_pkeys_enabled(void) 19 + { 20 + return false; 21 + } 22 + 23 + static inline int vma_pkey(struct vm_area_struct *vma) 24 + { 25 + return (vma->vm_flags & ARCH_VM_PKEY_FLAGS) >> VM_PKEY_SHIFT; 26 + } 27 + 28 + static inline int arch_override_mprotect_pkey(struct vm_area_struct *vma, 29 + int prot, int pkey) 30 + { 31 + if (pkey != -1) 32 + return pkey; 33 + 34 + return vma_pkey(vma); 35 + } 36 + 37 + static inline int execute_only_pkey(struct mm_struct *mm) 38 + { 39 + // Execute-only mappings are handled by EPAN/FEAT_PAN3. 40 + WARN_ON_ONCE(!cpus_have_final_cap(ARM64_HAS_EPAN)); 41 + 42 + return -1; 43 + } 44 + 45 + #define mm_pkey_allocation_map(mm) (mm)->context.pkey_allocation_map 46 + #define mm_set_pkey_allocated(mm, pkey) do { \ 47 + mm_pkey_allocation_map(mm) |= (1U << pkey); \ 48 + } while (0) 49 + #define mm_set_pkey_free(mm, pkey) do { \ 50 + mm_pkey_allocation_map(mm) &= ~(1U << pkey); \ 51 + } while (0) 52 + 53 + static inline bool mm_pkey_is_allocated(struct mm_struct *mm, int pkey) 54 + { 55 + /* 56 + * "Allocated" pkeys are those that have been returned 57 + * from pkey_alloc() or pkey 0 which is allocated 58 + * implicitly when the mm is created. 59 + */ 60 + if (pkey < 0 || pkey >= arch_max_pkey()) 61 + return false; 62 + 63 + return mm_pkey_allocation_map(mm) & (1U << pkey); 64 + } 65 + 66 + /* 67 + * Returns a positive, 3-bit key on success, or -1 on failure. 68 + */ 69 + static inline int mm_pkey_alloc(struct mm_struct *mm) 70 + { 71 + /* 72 + * Note: this is the one and only place we make sure 73 + * that the pkey is valid as far as the hardware is 74 + * concerned. The rest of the kernel trusts that 75 + * only good, valid pkeys come out of here. 76 + */ 77 + u8 all_pkeys_mask = GENMASK(arch_max_pkey() - 1, 0); 78 + int ret; 79 + 80 + if (!arch_pkeys_enabled()) 81 + return -1; 82 + 83 + /* 84 + * Are we out of pkeys? We must handle this specially 85 + * because ffz() behavior is undefined if there are no 86 + * zeros. 87 + */ 88 + if (mm_pkey_allocation_map(mm) == all_pkeys_mask) 89 + return -1; 90 + 91 + ret = ffz(mm_pkey_allocation_map(mm)); 92 + 93 + mm_set_pkey_allocated(mm, ret); 94 + 95 + return ret; 96 + } 97 + 98 + static inline int mm_pkey_free(struct mm_struct *mm, int pkey) 99 + { 100 + if (!mm_pkey_is_allocated(mm, pkey)) 101 + return -EINVAL; 102 + 103 + mm_set_pkey_free(mm, pkey); 104 + 105 + return 0; 106 + } 107 + 108 + #endif /* _ASM_ARM64_PKEYS_H */
+33
arch/arm64/include/asm/por.h
··· 1 + /* SPDX-License-Identifier: GPL-2.0 */ 2 + /* 3 + * Copyright (C) 2023 Arm Ltd. 4 + */ 5 + 6 + #ifndef _ASM_ARM64_POR_H 7 + #define _ASM_ARM64_POR_H 8 + 9 + #define POR_BITS_PER_PKEY 4 10 + #define POR_ELx_IDX(por_elx, idx) (((por_elx) >> ((idx) * POR_BITS_PER_PKEY)) & 0xf) 11 + 12 + static inline bool por_elx_allows_read(u64 por, u8 pkey) 13 + { 14 + u8 perm = POR_ELx_IDX(por, pkey); 15 + 16 + return perm & POE_R; 17 + } 18 + 19 + static inline bool por_elx_allows_write(u64 por, u8 pkey) 20 + { 21 + u8 perm = POR_ELx_IDX(por, pkey); 22 + 23 + return perm & POE_W; 24 + } 25 + 26 + static inline bool por_elx_allows_exec(u64 por, u8 pkey) 27 + { 28 + u8 perm = POR_ELx_IDX(por, pkey); 29 + 30 + return perm & POE_X; 31 + } 32 + 33 + #endif /* _ASM_ARM64_POR_H */
+9
arch/arm64/include/uapi/asm/mman.h
··· 7 7 #define PROT_BTI 0x10 /* BTI guarded page */ 8 8 #define PROT_MTE 0x20 /* Normal Tagged mapping */ 9 9 10 + /* Override any generic PKEY permission defines */ 11 + #define PKEY_DISABLE_EXECUTE 0x4 12 + #define PKEY_DISABLE_READ 0x8 13 + #undef PKEY_ACCESS_MASK 14 + #define PKEY_ACCESS_MASK (PKEY_DISABLE_ACCESS |\ 15 + PKEY_DISABLE_WRITE |\ 16 + PKEY_DISABLE_READ |\ 17 + PKEY_DISABLE_EXECUTE) 18 + 10 19 #endif /* ! _UAPI__ASM_MMAN_H */
+45
arch/arm64/mm/mmu.c
··· 25 25 #include <linux/vmalloc.h> 26 26 #include <linux/set_memory.h> 27 27 #include <linux/kfence.h> 28 + #include <linux/pkeys.h> 28 29 29 30 #include <asm/barrier.h> 30 31 #include <asm/cputype.h> ··· 1550 1549 1551 1550 cpu_uninstall_idmap(); 1552 1551 } 1552 + 1553 + #ifdef CONFIG_ARCH_HAS_PKEYS 1554 + int arch_set_user_pkey_access(struct task_struct *tsk, int pkey, unsigned long init_val) 1555 + { 1556 + u64 new_por = POE_RXW; 1557 + u64 old_por; 1558 + u64 pkey_shift; 1559 + 1560 + if (!system_supports_poe()) 1561 + return -ENOSPC; 1562 + 1563 + /* 1564 + * This code should only be called with valid 'pkey' 1565 + * values originating from in-kernel users. Complain 1566 + * if a bad value is observed. 1567 + */ 1568 + if (WARN_ON_ONCE(pkey >= arch_max_pkey())) 1569 + return -EINVAL; 1570 + 1571 + /* Set the bits we need in POR: */ 1572 + new_por = POE_RXW; 1573 + if (init_val & PKEY_DISABLE_WRITE) 1574 + new_por &= ~POE_W; 1575 + if (init_val & PKEY_DISABLE_ACCESS) 1576 + new_por &= ~POE_RW; 1577 + if (init_val & PKEY_DISABLE_READ) 1578 + new_por &= ~POE_R; 1579 + if (init_val & PKEY_DISABLE_EXECUTE) 1580 + new_por &= ~POE_X; 1581 + 1582 + /* Shift the bits in to the correct place in POR for pkey: */ 1583 + pkey_shift = pkey * POR_BITS_PER_PKEY; 1584 + new_por <<= pkey_shift; 1585 + 1586 + /* Get old POR and mask off any old bits in place: */ 1587 + old_por = read_sysreg_s(SYS_POR_EL0); 1588 + old_por &= ~(POE_MASK << pkey_shift); 1589 + 1590 + /* Write old part along with new part: */ 1591 + write_sysreg_s(old_por | new_por, SYS_POR_EL0); 1592 + 1593 + return 0; 1594 + } 1595 + #endif