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

selftests: mm: make protection_keys test work on arm64

The encoding of the pkey register differs on arm64, than on x86/ppc. On those
platforms, a bit in the register is used to disable permissions, for arm64, a
bit enabled in the register indicates that the permission is allowed.

This drops two asserts of the form:
assert(read_pkey_reg() <= orig_pkey_reg);
Because on arm64 this doesn't hold, due to the encoding.

The pkey must be reset to both access allow and write allow in the signal
handler. pkey_access_allow() works currently for PowerPC as the
PKEY_DISABLE_ACCESS and PKEY_DISABLE_WRITE have overlapping bits set.

Access to the uc_mcontext is abstracted, as arm64 has a different structure.

Signed-off-by: Joey Gouly <joey.gouly@arm.com>
Cc: Catalin Marinas <catalin.marinas@arm.com>
Cc: Will Deacon <will@kernel.org>
Cc: Andrew Morton <akpm@linux-foundation.org>
Cc: Shuah Khan <shuah@kernel.org>
Cc: Dave Hansen <dave.hansen@linux.intel.com>
Cc: Aneesh Kumar K.V <aneesh.kumar@linux.ibm.com>
Acked-by: Dave Hansen <dave.hansen@linux.intel.com>
Link: https://lore.kernel.org/r/20240822151113.1479789-27-joey.gouly@arm.com
Signed-off-by: Will Deacon <will@kernel.org>

authored by

Joey Gouly and committed by
Will Deacon
f5b5ea51 41bbcf7b

+247 -12
+3
tools/testing/selftests/arm64/signal/testcases/testcases.h
··· 26 26 #define HDR_SZ \ 27 27 sizeof(struct _aarch64_ctx) 28 28 29 + #define GET_UC_RESV_HEAD(uc) \ 30 + (struct _aarch64_ctx *)(&(uc->uc_mcontext.__reserved)) 31 + 29 32 #define GET_SF_RESV_HEAD(sf) \ 30 33 (struct _aarch64_ctx *)(&(sf).uc.uc_mcontext.__reserved) 31 34
+1 -1
tools/testing/selftests/mm/Makefile
··· 104 104 endif 105 105 else 106 106 107 - ifneq (,$(findstring $(ARCH),powerpc)) 107 + ifneq (,$(filter $(ARCH),arm64 powerpc)) 108 108 TEST_GEN_FILES += protection_keys 109 109 endif 110 110
+139
tools/testing/selftests/mm/pkey-arm64.h
··· 1 + /* SPDX-License-Identifier: GPL-2.0 */ 2 + /* 3 + * Copyright (C) 2023 Arm Ltd. 4 + */ 5 + 6 + #ifndef _PKEYS_ARM64_H 7 + #define _PKEYS_ARM64_H 8 + 9 + #include "vm_util.h" 10 + /* for signal frame parsing */ 11 + #include "../arm64/signal/testcases/testcases.h" 12 + 13 + #ifndef SYS_mprotect_key 14 + # define SYS_mprotect_key 288 15 + #endif 16 + #ifndef SYS_pkey_alloc 17 + # define SYS_pkey_alloc 289 18 + # define SYS_pkey_free 290 19 + #endif 20 + #define MCONTEXT_IP(mc) mc.pc 21 + #define MCONTEXT_TRAPNO(mc) -1 22 + 23 + #define PKEY_MASK 0xf 24 + 25 + #define POE_NONE 0x0 26 + #define POE_X 0x2 27 + #define POE_RX 0x3 28 + #define POE_RWX 0x7 29 + 30 + #define NR_PKEYS 8 31 + #define NR_RESERVED_PKEYS 1 /* pkey-0 */ 32 + 33 + #define PKEY_ALLOW_ALL 0x77777777 34 + 35 + #define PKEY_BITS_PER_PKEY 4 36 + #define PAGE_SIZE sysconf(_SC_PAGESIZE) 37 + #undef HPAGE_SIZE 38 + #define HPAGE_SIZE default_huge_page_size() 39 + 40 + /* 4-byte instructions * 16384 = 64K page */ 41 + #define __page_o_noops() asm(".rept 16384 ; nop; .endr") 42 + 43 + static inline u64 __read_pkey_reg(void) 44 + { 45 + u64 pkey_reg = 0; 46 + 47 + // POR_EL0 48 + asm volatile("mrs %0, S3_3_c10_c2_4" : "=r" (pkey_reg)); 49 + 50 + return pkey_reg; 51 + } 52 + 53 + static inline void __write_pkey_reg(u64 pkey_reg) 54 + { 55 + u64 por = pkey_reg; 56 + 57 + dprintf4("%s() changing %016llx to %016llx\n", 58 + __func__, __read_pkey_reg(), pkey_reg); 59 + 60 + // POR_EL0 61 + asm volatile("msr S3_3_c10_c2_4, %0\nisb" :: "r" (por) :); 62 + 63 + dprintf4("%s() pkey register after changing %016llx to %016llx\n", 64 + __func__, __read_pkey_reg(), pkey_reg); 65 + } 66 + 67 + static inline int cpu_has_pkeys(void) 68 + { 69 + /* No simple way to determine this */ 70 + return 1; 71 + } 72 + 73 + static inline u32 pkey_bit_position(int pkey) 74 + { 75 + return pkey * PKEY_BITS_PER_PKEY; 76 + } 77 + 78 + static inline int get_arch_reserved_keys(void) 79 + { 80 + return NR_RESERVED_PKEYS; 81 + } 82 + 83 + void expect_fault_on_read_execonly_key(void *p1, int pkey) 84 + { 85 + } 86 + 87 + void *malloc_pkey_with_mprotect_subpage(long size, int prot, u16 pkey) 88 + { 89 + return PTR_ERR_ENOTSUP; 90 + } 91 + 92 + #define set_pkey_bits set_pkey_bits 93 + static inline u64 set_pkey_bits(u64 reg, int pkey, u64 flags) 94 + { 95 + u32 shift = pkey_bit_position(pkey); 96 + u64 new_val = POE_RWX; 97 + 98 + /* mask out bits from pkey in old value */ 99 + reg &= ~((u64)PKEY_MASK << shift); 100 + 101 + if (flags & PKEY_DISABLE_ACCESS) 102 + new_val = POE_X; 103 + else if (flags & PKEY_DISABLE_WRITE) 104 + new_val = POE_RX; 105 + 106 + /* OR in new bits for pkey */ 107 + reg |= new_val << shift; 108 + 109 + return reg; 110 + } 111 + 112 + #define get_pkey_bits get_pkey_bits 113 + static inline u64 get_pkey_bits(u64 reg, int pkey) 114 + { 115 + u32 shift = pkey_bit_position(pkey); 116 + /* 117 + * shift down the relevant bits to the lowest four, then 118 + * mask off all the other higher bits 119 + */ 120 + u32 perm = (reg >> shift) & PKEY_MASK; 121 + 122 + if (perm == POE_X) 123 + return PKEY_DISABLE_ACCESS; 124 + if (perm == POE_RX) 125 + return PKEY_DISABLE_WRITE; 126 + return 0; 127 + } 128 + 129 + static void aarch64_write_signal_pkey(ucontext_t *uctxt, u64 pkey) 130 + { 131 + struct _aarch64_ctx *ctx = GET_UC_RESV_HEAD(uctxt); 132 + struct poe_context *poe_ctx = 133 + (struct poe_context *) get_header(ctx, POE_MAGIC, 134 + sizeof(uctxt->uc_mcontext), NULL); 135 + if (poe_ctx) 136 + poe_ctx->por_el0 = pkey; 137 + } 138 + 139 + #endif /* _PKEYS_ARM64_H */
+8
tools/testing/selftests/mm/pkey-helpers.h
··· 91 91 #include "pkey-x86.h" 92 92 #elif defined(__powerpc64__) /* arch */ 93 93 #include "pkey-powerpc.h" 94 + #elif defined(__aarch64__) /* arch */ 95 + #include "pkey-arm64.h" 94 96 #else /* arch */ 95 97 #error Architecture not supported 96 98 #endif /* arch */ 97 99 100 + #ifndef PKEY_MASK 98 101 #define PKEY_MASK (PKEY_DISABLE_ACCESS | PKEY_DISABLE_WRITE) 102 + #endif 99 103 104 + #ifndef set_pkey_bits 100 105 static inline u64 set_pkey_bits(u64 reg, int pkey, u64 flags) 101 106 { 102 107 u32 shift = pkey_bit_position(pkey); ··· 111 106 reg |= (flags & PKEY_MASK) << shift; 112 107 return reg; 113 108 } 109 + #endif 114 110 111 + #ifndef get_pkey_bits 115 112 static inline u64 get_pkey_bits(u64 reg, int pkey) 116 113 { 117 114 u32 shift = pkey_bit_position(pkey); ··· 123 116 */ 124 117 return ((reg >> shift) & PKEY_MASK); 125 118 } 119 + #endif 126 120 127 121 extern u64 shadow_pkey_reg; 128 122
+2
tools/testing/selftests/mm/pkey-powerpc.h
··· 8 8 # define SYS_pkey_free 385 9 9 #endif 10 10 #define REG_IP_IDX PT_NIP 11 + #define MCONTEXT_IP(mc) mc.gp_regs[REG_IP_IDX] 12 + #define MCONTEXT_TRAPNO(mc) mc.gp_regs[REG_TRAPNO] 11 13 #define REG_TRAPNO PT_TRAP 12 14 #define MCONTEXT_FPREGS 13 15 #define gregs gp_regs
+2
tools/testing/selftests/mm/pkey-x86.h
··· 15 15 16 16 #endif 17 17 18 + #define MCONTEXT_IP(mc) mc.gregs[REG_IP_IDX] 19 + #define MCONTEXT_TRAPNO(mc) mc.gregs[REG_TRAPNO] 18 20 #define MCONTEXT_FPREGS 19 21 20 22 #ifndef PKEY_DISABLE_ACCESS
+92 -11
tools/testing/selftests/mm/protection_keys.c
··· 147 147 * will then fault, which makes sure that the fault code handles 148 148 * execute-only memory properly. 149 149 */ 150 - #ifdef __powerpc64__ 150 + #if defined(__powerpc64__) || defined(__aarch64__) 151 151 /* This way, both 4K and 64K alignment are maintained */ 152 152 __attribute__((__aligned__(65536))) 153 153 #else ··· 212 212 unsigned long syscall_flags = 0; 213 213 int ret; 214 214 int pkey_rights; 215 - u64 orig_pkey_reg = read_pkey_reg(); 216 215 217 216 dprintf1("START->%s(%d, 0x%x)\n", __func__, 218 217 pkey, flags); ··· 241 242 242 243 dprintf1("%s(%d) pkey_reg: 0x%016llx\n", 243 244 __func__, pkey, read_pkey_reg()); 244 - if (flags) 245 - pkey_assert(read_pkey_reg() >= orig_pkey_reg); 246 245 dprintf1("END<---%s(%d, 0x%x)\n", __func__, 247 246 pkey, flags); 248 247 } ··· 250 253 unsigned long syscall_flags = 0; 251 254 int ret; 252 255 int pkey_rights = hw_pkey_get(pkey, syscall_flags); 253 - u64 orig_pkey_reg = read_pkey_reg(); 254 256 255 257 pkey_assert(flags & (PKEY_DISABLE_ACCESS | PKEY_DISABLE_WRITE)); 256 258 ··· 269 273 270 274 dprintf1("%s(%d) pkey_reg: 0x%016llx\n", __func__, 271 275 pkey, read_pkey_reg()); 272 - if (flags) 273 - assert(read_pkey_reg() <= orig_pkey_reg); 274 276 } 275 277 276 278 void pkey_write_allow(int pkey) ··· 324 330 __func__, __LINE__, 325 331 __read_pkey_reg(), shadow_pkey_reg); 326 332 327 - trapno = uctxt->uc_mcontext.gregs[REG_TRAPNO]; 328 - ip = uctxt->uc_mcontext.gregs[REG_IP_IDX]; 333 + trapno = MCONTEXT_TRAPNO(uctxt->uc_mcontext); 334 + ip = MCONTEXT_IP(uctxt->uc_mcontext); 329 335 #ifdef MCONTEXT_FPREGS 330 336 fpregs = (char *) uctxt->uc_mcontext.fpregs; 331 337 #endif ··· 389 395 #elif defined(__powerpc64__) /* arch */ 390 396 /* restore access and let the faulting instruction continue */ 391 397 pkey_access_allow(siginfo_pkey); 398 + #elif defined(__aarch64__) 399 + aarch64_write_signal_pkey(uctxt, PKEY_ALLOW_ALL); 392 400 #endif /* arch */ 393 401 pkey_faults++; 394 402 dprintf1("<<<<==================================================\n"); ··· 904 908 * test program continue. We now have to restore it. 905 909 */ 906 910 if (__read_pkey_reg() != 0) 907 - #else /* arch */ 911 + #elif defined(__aarch64__) 912 + if (__read_pkey_reg() != PKEY_ALLOW_ALL) 913 + #else 908 914 if (__read_pkey_reg() != shadow_pkey_reg) 909 915 #endif /* arch */ 910 916 pkey_assert(0); ··· 1496 1498 lots_o_noops_around_write(&scratch); 1497 1499 do_not_expect_pkey_fault("executing on PROT_EXEC memory"); 1498 1500 expect_fault_on_read_execonly_key(p1, pkey); 1501 + 1502 + // Reset back to PROT_EXEC | PROT_READ for architectures that support 1503 + // non-PKEY execute-only permissions. 1504 + ret = mprotect_pkey(p1, PAGE_SIZE, PROT_EXEC | PROT_READ, (u64)pkey); 1505 + pkey_assert(!ret); 1499 1506 } 1500 1507 1501 1508 void test_implicit_mprotect_exec_only_memory(int *ptr, u16 pkey) ··· 1674 1671 } 1675 1672 #endif 1676 1673 1674 + #if defined(__aarch64__) 1675 + void test_ptrace_modifies_pkru(int *ptr, u16 pkey) 1676 + { 1677 + pid_t child; 1678 + int status, ret; 1679 + struct iovec iov; 1680 + u64 trace_pkey; 1681 + /* Just a random pkey value.. */ 1682 + u64 new_pkey = (POE_X << PKEY_BITS_PER_PKEY * 2) | 1683 + (POE_NONE << PKEY_BITS_PER_PKEY) | 1684 + POE_RWX; 1685 + 1686 + child = fork(); 1687 + pkey_assert(child >= 0); 1688 + dprintf3("[%d] fork() ret: %d\n", getpid(), child); 1689 + if (!child) { 1690 + ptrace(PTRACE_TRACEME, 0, 0, 0); 1691 + 1692 + /* Stop and allow the tracer to modify PKRU directly */ 1693 + raise(SIGSTOP); 1694 + 1695 + /* 1696 + * need __read_pkey_reg() version so we do not do shadow_pkey_reg 1697 + * checking 1698 + */ 1699 + if (__read_pkey_reg() != new_pkey) 1700 + exit(1); 1701 + 1702 + raise(SIGSTOP); 1703 + 1704 + exit(0); 1705 + } 1706 + 1707 + pkey_assert(child == waitpid(child, &status, 0)); 1708 + dprintf3("[%d] waitpid(%d) status: %x\n", getpid(), child, status); 1709 + pkey_assert(WIFSTOPPED(status) && WSTOPSIG(status) == SIGSTOP); 1710 + 1711 + iov.iov_base = &trace_pkey; 1712 + iov.iov_len = 8; 1713 + ret = ptrace(PTRACE_GETREGSET, child, (void *)NT_ARM_POE, &iov); 1714 + pkey_assert(ret == 0); 1715 + pkey_assert(trace_pkey == read_pkey_reg()); 1716 + 1717 + trace_pkey = new_pkey; 1718 + 1719 + ret = ptrace(PTRACE_SETREGSET, child, (void *)NT_ARM_POE, &iov); 1720 + pkey_assert(ret == 0); 1721 + 1722 + /* Test that the modification is visible in ptrace before any execution */ 1723 + memset(&trace_pkey, 0, sizeof(trace_pkey)); 1724 + ret = ptrace(PTRACE_GETREGSET, child, (void *)NT_ARM_POE, &iov); 1725 + pkey_assert(ret == 0); 1726 + pkey_assert(trace_pkey == new_pkey); 1727 + 1728 + /* Execute the tracee */ 1729 + ret = ptrace(PTRACE_CONT, child, 0, 0); 1730 + pkey_assert(ret == 0); 1731 + 1732 + /* Test that the tracee saw the PKRU value change */ 1733 + pkey_assert(child == waitpid(child, &status, 0)); 1734 + dprintf3("[%d] waitpid(%d) status: %x\n", getpid(), child, status); 1735 + pkey_assert(WIFSTOPPED(status) && WSTOPSIG(status) == SIGSTOP); 1736 + 1737 + /* Test that the modification is visible in ptrace after execution */ 1738 + memset(&trace_pkey, 0, sizeof(trace_pkey)); 1739 + ret = ptrace(PTRACE_GETREGSET, child, (void *)NT_ARM_POE, &iov); 1740 + pkey_assert(ret == 0); 1741 + pkey_assert(trace_pkey == new_pkey); 1742 + 1743 + ret = ptrace(PTRACE_CONT, child, 0, 0); 1744 + pkey_assert(ret == 0); 1745 + pkey_assert(child == waitpid(child, &status, 0)); 1746 + dprintf3("[%d] waitpid(%d) status: %x\n", getpid(), child, status); 1747 + pkey_assert(WIFEXITED(status)); 1748 + pkey_assert(WEXITSTATUS(status) == 0); 1749 + } 1750 + #endif 1751 + 1677 1752 void test_mprotect_pkey_on_unsupported_cpu(int *ptr, u16 pkey) 1678 1753 { 1679 1754 int size = PAGE_SIZE; ··· 1787 1706 test_pkey_syscalls_bad_args, 1788 1707 test_pkey_alloc_exhaust, 1789 1708 test_pkey_alloc_free_attach_pkey0, 1790 - #if defined(__i386__) || defined(__x86_64__) 1709 + #if defined(__i386__) || defined(__x86_64__) || defined(__aarch64__) 1791 1710 test_ptrace_modifies_pkru, 1792 1711 #endif 1793 1712 };