Message ID | 20241101121709.2227463-2-yury.khrustalev@arm.com |
---|---|
State | New |
Headers | show |
Series | aarch64: Add support for memory protection keys | expand |
On 01/11/24 09:17, Yury Khrustalev wrote: > This patch adds support for memory protection keys on AArch64 systems with > enabled Stage 1 permission overlays feature introduced in Armv8.9 / 9.4 > (FEAT_S1POE) [1]. > > 1. Internal functions "pkey_read" and "pkey_write" to access data > associated with memory protection keys. > 2. Implementation of API functions "pkey_get" and "pkey_set" for > the AArch64 target. > 3. AArch64-specific PKEY flags for READ and EXECUTE (see below). > 4. New target-specific test that checks behaviour of pkeys on > AArch64 targets. > 5. This patch also extends existing generic test for pkeys. > 6. HWCAP constant for Permission Overlay Extension feature. > > To support more accurate mapping of underlying permissions to the > PKEY flags, we introduce additional AArch64-specific flags. The full > list of flags is: > > - PKEY_UNRESTRICTED: 0x0 (for completeness) > - PKEY_DISABLE_ACCESS: 0x1 (existing flag) > - PKEY_DISABLE_WRITE: 0x2 (existing flag) > - PKEY_DISABLE_EXECUTE: 0x4 (new flag, AArch64 specific) > - PKEY_DISABLE_READ: 0x8 (new flag, AArch64 specific) > > The problem here is that PKEY_DISABLE_ACCESS has unusual semantics as > it overlaps with existing PKEY_DISABLE_WRITE and new PKEY_DISABLE_READ. > For this reason mapping between permission bits RWX and "restrictions" > bits awxr (a for disable access, etc) becomes complicated: > > - PKEY_DISABLE_ACCESS disables both R and W > - PKEY_DISABLE_{WRITE,READ} disables W and R respectively > - PKEY_DISABLE_EXECUTE disables X > > Combinations like the one below are accepted although they are redundant: > > - PKEY_DISABLE_ACCESS | PKEY_DISABLE_READ | PKEY_DISABLE_WRITE > > Reverse mapping tries to retain backward compatibility and ORs > PKEY_DISABLE_ACCESS whenever both flags PKEY_DISABLE_READ and > PKEY_DISABLE_WRITE would be present. > > This will break code that compares pkey_get output with == instead > of using bitwise operations. The latter is more correct since PKEY_* > constants are essentially bit flags. > > It should be noted that PKEY_DISABLE_ACCESS does not prevent execution. > > Co-authored-by: Szabolcs Nagy <szabolcs.nagy@arm.com> > > [1] https://developer.arm.com/documentation/ddi0487/ka/ section D8.4.1.4 LGTM, with just a minor nit below. Reviewed-by: Adhemerval Zanella <adhemerval.zanella@linaro.org> > --- > sysdeps/unix/sysv/linux/aarch64/Makefile | 3 + > sysdeps/unix/sysv/linux/aarch64/arch-pkey.h | 53 +++++ > sysdeps/unix/sysv/linux/aarch64/bits/hwcap.h | 1 + > sysdeps/unix/sysv/linux/aarch64/bits/mman.h | 8 + > sysdeps/unix/sysv/linux/aarch64/pkey_get.c | 73 +++++++ > sysdeps/unix/sysv/linux/aarch64/pkey_set.c | 113 +++++++++++ > .../sysv/linux/aarch64/tst-aarch64-pkey.c | 185 ++++++++++++++++++ > sysdeps/unix/sysv/linux/tst-pkey.c | 51 +++-- > 8 files changed, 476 insertions(+), 11 deletions(-) > create mode 100644 sysdeps/unix/sysv/linux/aarch64/arch-pkey.h > create mode 100644 sysdeps/unix/sysv/linux/aarch64/pkey_get.c > create mode 100644 sysdeps/unix/sysv/linux/aarch64/pkey_set.c > create mode 100644 sysdeps/unix/sysv/linux/aarch64/tst-aarch64-pkey.c > > diff --git a/sysdeps/unix/sysv/linux/aarch64/Makefile b/sysdeps/unix/sysv/linux/aarch64/Makefile > index 40b9a2e5de..1fdad67fae 100644 > --- a/sysdeps/unix/sysv/linux/aarch64/Makefile > +++ b/sysdeps/unix/sysv/linux/aarch64/Makefile > @@ -1,5 +1,8 @@ > ifeq ($(subdir),misc) > sysdep_headers += sys/elf.h > +tests += \ > + tst-aarch64-pkey \ > + # tests > endif > > ifeq ($(subdir),stdlib) Ok. > diff --git a/sysdeps/unix/sysv/linux/aarch64/arch-pkey.h b/sysdeps/unix/sysv/linux/aarch64/arch-pkey.h > new file mode 100644 > index 0000000000..6149b5da7b > --- /dev/null > +++ b/sysdeps/unix/sysv/linux/aarch64/arch-pkey.h > @@ -0,0 +1,53 @@ > +/* Helper functions for manipulating memory protection keys. > + Copyright (C) 2024 Free Software Foundation, Inc. > + This file is part of the GNU C Library. > + > + The GNU C Library is free software; you can redistribute it and/or > + modify it under the terms of the GNU Lesser General Public > + License as published by the Free Software Foundation; either > + version 2.1 of the License, or (at your option) any later version. > + > + The GNU C Library is distributed in the hope that it will be useful, > + but WITHOUT ANY WARRANTY; without even the implied warranty of > + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU > + Lesser General Public License for more details. > + > + You should have received a copy of the GNU Lesser General Public > + License along with the GNU C Library; if not, see > + <https://www.gnu.org/licenses/>. */ > + > +#ifndef _ARCH_PKEY_H > +#define _ARCH_PKEY_H > + > +#include <sys/cdefs.h> > + > +#define S1POE_PERM_NO_ACCESS 0b0000UL > +#define S1POE_PERM_R 0b0001UL > +#define S1POE_PERM_X 0b0010UL > +#define S1POE_PERM_RX 0b0011UL > +#define S1POE_PERM_W 0b0100UL > +#define S1POE_PERM_RW 0b0101UL > +#define S1POE_PERM_WX 0b0110UL > +#define S1POE_PERM_RWX 0b0111UL > + > +#define S1POE_PERM_MASK 0b1111UL > + > +#define S1POE_BITS_PER_POI 4UL > + > +/* Return the value of the POR_EL0 register. */ > +static __always_inline unsigned long > +pkey_read (void) > +{ > + unsigned long r; > + __asm__ volatile ("mrs %0, s3_3_c10_c2_4" : "=r" (r)); > + return r; > +} > + > +/* Overwrite the POR_EL0 register with VALUE. */ > +static __always_inline void > +pkey_write (unsigned long value) > +{ > + __asm__ volatile ("msr s3_3_c10_c2_4, %0; isb" : : "r" (value)); > +} > + > +#endif /* _ARCH_PKEY_H */ Ok. > diff --git a/sysdeps/unix/sysv/linux/aarch64/bits/hwcap.h b/sysdeps/unix/sysv/linux/aarch64/bits/hwcap.h > index 8dceaa1a52..2fa158fcc0 100644 > --- a/sysdeps/unix/sysv/linux/aarch64/bits/hwcap.h > +++ b/sysdeps/unix/sysv/linux/aarch64/bits/hwcap.h > @@ -118,3 +118,4 @@ > #define HWCAP2_SME_SF8FMA (1UL << 60) > #define HWCAP2_SME_SF8DP4 (1UL << 61) > #define HWCAP2_SME_SF8DP2 (1UL << 62) > +#define HWCAP2_POE (1UL << 63) Ok. > diff --git a/sysdeps/unix/sysv/linux/aarch64/bits/mman.h b/sysdeps/unix/sysv/linux/aarch64/bits/mman.h > index c5b6c69d43..288dff064b 100644 > --- a/sysdeps/unix/sysv/linux/aarch64/bits/mman.h > +++ b/sysdeps/unix/sysv/linux/aarch64/bits/mman.h > @@ -26,6 +26,14 @@ > #define PROT_BTI 0x10 > #define PROT_MTE 0x20 > > +#ifdef __USE_GNU > +# define PKEY_UNRESTRICTED 0x0 > +# define PKEY_DISABLE_ACCESS 0x1 > +# define PKEY_DISABLE_WRITE 0x2 > +# define PKEY_DISABLE_EXECUTE 0x4 > +# define PKEY_DISABLE_READ 0x8 > +#endif > + > #include <bits/mman-map-flags-generic.h> > > /* Include generic Linux declarations. */ Ok. > diff --git a/sysdeps/unix/sysv/linux/aarch64/pkey_get.c b/sysdeps/unix/sysv/linux/aarch64/pkey_get.c > new file mode 100644 > index 0000000000..66a47f5ce6 > --- /dev/null > +++ b/sysdeps/unix/sysv/linux/aarch64/pkey_get.c > @@ -0,0 +1,73 @@ > +/* Reading the per-thread memory protection key, AArch64 version. > + Copyright (C) 2024 Free Software Foundation, Inc. > + This file is part of the GNU C Library. > + > + The GNU C Library is free software; you can redistribute it and/or > + modify it under the terms of the GNU Lesser General Public > + License as published by the Free Software Foundation; either > + version 2.1 of the License, or (at your option) any later version. > + > + The GNU C Library is distributed in the hope that it will be useful, > + but WITHOUT ANY WARRANTY; without even the implied warranty of > + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU > + Lesser General Public License for more details. > + > + You should have received a copy of the GNU Lesser General Public > + License along with the GNU C Library; if not, see > + <https://www.gnu.org/licenses/>. */ > + > +#include <arch-pkey.h> > +#include <errno.h> > +#include <sys/mman.h> > + > +int > +pkey_get (int key) > +{ > + if (key < 0 || key > 15) > + { > + __set_errno (EINVAL); > + return -1; > + } > + unsigned long int por_el0 = pkey_read (); > + unsigned long int perm = (por_el0 >> (S1POE_BITS_PER_POI * key)) > + & S1POE_PERM_MASK; > + > + /* The following mapping between POR permission bits (4 bits) > + and PKEY flags is supported: > + > + -WXR POR to PKEY_ mapping > + 0000 => DISABLE_ACCESS | DISABLE_READ | DISABLE_WRITE | DISABLE_EXECUTE > + 0001 => DISABLE_WRITE | DISABLE_EXECUTE (read-only) > + 0010 => DISABLE_ACCESS | DISABLE_READ | DISABLE_WRITE (execute-only) > + 0011 => DISABLE_WRITE (read-execute) > + 0100 => DISABLE_READ | DISABLE_EXECUTE (write-only) > + 0101 => DISABLE_EXECUTE (read-write) > + 0110 => DISABLE_READ (execute-write) > + 0111 => UNRESTRICTED (no restrictions, read-write-execute) > + else => undefined behavior > + > + Note that pkey_set and pkey_alloc would only set these specific > + values. The PKEY_DISABLE_ACCESS flag is redundant as it implies > + PKEY_DISABLE_READ | PKEY_DISABLE_WRITE but is kept for backward > + compatibility. */ > + > + if (perm == S1POE_PERM_NO_ACCESS) > + return PKEY_DISABLE_ACCESS | PKEY_DISABLE_WRITE | PKEY_DISABLE_EXECUTE > + | PKEY_DISABLE_READ; > + if (perm == S1POE_PERM_R) > + return PKEY_DISABLE_WRITE | PKEY_DISABLE_EXECUTE; > + if (perm == S1POE_PERM_X) > + return PKEY_DISABLE_ACCESS | PKEY_DISABLE_READ | PKEY_DISABLE_WRITE; > + if (perm == S1POE_PERM_RX) > + return PKEY_DISABLE_WRITE; > + if (perm == S1POE_PERM_W) > + return PKEY_DISABLE_READ | PKEY_DISABLE_EXECUTE; > + if (perm == S1POE_PERM_RW) > + return PKEY_DISABLE_EXECUTE; > + if (perm == S1POE_PERM_WX) > + return PKEY_DISABLE_READ; > + if (perm == S1POE_PERM_RWX) > + return PKEY_UNRESTRICTED; > + > + return PKEY_DISABLE_ACCESS; > +} Ok. > diff --git a/sysdeps/unix/sysv/linux/aarch64/pkey_set.c b/sysdeps/unix/sysv/linux/aarch64/pkey_set.c > new file mode 100644 > index 0000000000..12161127a4 > --- /dev/null > +++ b/sysdeps/unix/sysv/linux/aarch64/pkey_set.c > @@ -0,0 +1,113 @@ > +/* Changing the per-thread memory protection key, AArch64 version. > + Copyright (C) 2024 Free Software Foundation, Inc. > + This file is part of the GNU C Library. > + > + The GNU C Library is free software; you can redistribute it and/or > + modify it under the terms of the GNU Lesser General Public > + License as published by the Free Software Foundation; either > + version 2.1 of the License, or (at your option) any later version. > + > + The GNU C Library is distributed in the hope that it will be useful, > + but WITHOUT ANY WARRANTY; without even the implied warranty of > + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU > + Lesser General Public License for more details. > + > + You should have received a copy of the GNU Lesser General Public > + License along with the GNU C Library; if not, see > + <https://www.gnu.org/licenses/>. */ > + > +#include <arch-pkey.h> > +#include <errno.h> > +#include <sys/mman.h> > + > +#define MAX_PKEY_RIGHTS (PKEY_DISABLE_ACCESS | \ > + PKEY_DISABLE_WRITE | PKEY_DISABLE_EXECUTE | PKEY_DISABLE_READ) > + > +int > +pkey_set (int key, unsigned int restrictions) > +{ > + if (key < 0 || key > 15 || restrictions > MAX_PKEY_RIGHTS) > + { > + __set_errno (EINVAL); > + return -1; > + } > + unsigned long mask = S1POE_PERM_MASK << (S1POE_BITS_PER_POI * key); > + unsigned long por_el0 = pkey_read (); > + unsigned long perm; > + > + /* POR ot PKEY mapping: -WXR > + PKEY_UNRESTRICTED => 0111 (read-write-execute) > + PKEY_DISABLE_ACCESS => removes R and W access > + PKEY_DISABLE_READ => removes R access > + PKEY_DISABLE_WRITE => removes W access > + PKEY_DISABLE_EXECUTE => removes X access > + > + Either of PKEY_DISABLE_ACCESS or PKEY_DISABLE_READ removes R access. > + Either of PKEY_DISABLE_ACCESS or PKEY_DISABLE_WRITE removes W access. > + Using PKEY_DISABLE_ACCESS along with only one of PKEY_DISABLE_READ or > + PKEY_DISABLE_WRITE is considered to be in error. > + > + Furthermore, for avoidance of doubt: > + > + PKEY flags Permissions > + rxwa -WXR > + 1111 => 0000 S1POE_PERM_NO_ACCESS > + 1110 => 0000 S1POE_PERM_NO_ACCESS > + 1101 => EINVAL > + 1100 => 0100 S1POE_PERM_W > + 1011 => 0010 S1POE_PERM_X > + 1010 => 0010 S1POE_PERM_X > + 1001 => EINVAL > + 1000 => 0110 S1POE_PERM_WX > + 0111 => EINVAL > + 0110 => 0001 S1POE_PERM_R > + 0101 => 0000 S1POE_PERM_NO_ACCESS > + 0100 => 0101 S1POE_PERM_RW > + 0011 => EINVAL > + 0010 => 0011 S1POE_PERM_RX > + 0001 => 0010 S1POE_PERM_X > + 0000 => 0111 S1POE_PERM_RWX */ > + switch (restrictions) > + { > + case PKEY_DISABLE_ACCESS | PKEY_DISABLE_READ | PKEY_DISABLE_WRITE > + | PKEY_DISABLE_EXECUTE: > + case PKEY_DISABLE_ACCESS | PKEY_DISABLE_EXECUTE: > + case PKEY_DISABLE_ACCESS | PKEY_DISABLE_READ | PKEY_DISABLE_EXECUTE: > + case PKEY_DISABLE_ACCESS | PKEY_DISABLE_WRITE | PKEY_DISABLE_EXECUTE: > + case PKEY_DISABLE_READ | PKEY_DISABLE_WRITE | PKEY_DISABLE_EXECUTE: > + perm = S1POE_PERM_NO_ACCESS; > + break; > + case PKEY_DISABLE_READ | PKEY_DISABLE_EXECUTE: > + perm = S1POE_PERM_W; > + break; > + case PKEY_DISABLE_ACCESS | PKEY_DISABLE_READ: > + case PKEY_DISABLE_ACCESS | PKEY_DISABLE_WRITE: > + case PKEY_DISABLE_ACCESS | PKEY_DISABLE_READ | PKEY_DISABLE_WRITE: > + case PKEY_DISABLE_READ | PKEY_DISABLE_WRITE: > + case PKEY_DISABLE_ACCESS: > + perm = S1POE_PERM_X; > + break; > + case PKEY_DISABLE_READ: > + perm = S1POE_PERM_WX; > + break; > + case PKEY_DISABLE_WRITE | PKEY_DISABLE_EXECUTE: > + perm = S1POE_PERM_R; > + break; > + case PKEY_DISABLE_EXECUTE: > + perm = S1POE_PERM_RW; > + break; > + case PKEY_DISABLE_WRITE: > + perm = S1POE_PERM_RX; > + break; > + case PKEY_UNRESTRICTED: > + perm = S1POE_PERM_RWX; > + break; > + default: > + __set_errno (EINVAL); > + return -1; > + } > + > + por_el0 = (por_el0 & ~mask) | (perm << (S1POE_BITS_PER_POI * key)); > + pkey_write (por_el0); > + return 0; > +} Ok. > diff --git a/sysdeps/unix/sysv/linux/aarch64/tst-aarch64-pkey.c b/sysdeps/unix/sysv/linux/aarch64/tst-aarch64-pkey.c > new file mode 100644 > index 0000000000..8e5d06d6bb > --- /dev/null > +++ b/sysdeps/unix/sysv/linux/aarch64/tst-aarch64-pkey.c > @@ -0,0 +1,185 @@ > +/* AArch64 tests for memory protection keys. > + Copyright (C) 2024 Free Software Foundation, Inc. > + This file is part of the GNU C Library. > + > + The GNU C Library is free software; you can redistribute it and/or > + modify it under the terms of the GNU Lesser General Public > + License as published by the Free Software Foundation; either > + version 2.1 of the License, or (at your option) any later version. > + > + The GNU C Library is distributed in the hope that it will be useful, > + but WITHOUT ANY WARRANTY; without even the implied warranty of > + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU > + Lesser General Public License for more details. > + > + You should have received a copy of the GNU Lesser General Public > + License along with the GNU C Library; if not, see > + <https://www.gnu.org/licenses/>. */ > + > +#include <errno.h> > +#include <inttypes.h> > +#include <stdbool.h> > +#include <stdio.h> > +#include <stdlib.h> > +#include <string.h> > +#include <support/check.h> > +#include <support/support.h> > +#include <support/test-driver.h> > +#include <support/xsignal.h> > +#include <support/xunistd.h> > +#include <sys/mman.h> > + > +static sig_atomic_t sigusr1_handler_ran; > +static int pkey; > + > +/* On AArch64 access is revoked during signal handling for > + pkey > 0 because POR is reset to the default value 0x7. */ > +static void > +sigusr1_handler (int signum) > +{ > + TEST_COMPARE (signum, SIGUSR1); > + TEST_COMPARE (pkey_get (pkey) & PKEY_DISABLE_ACCESS, PKEY_DISABLE_ACCESS); > + TEST_COMPARE (pkey_get (pkey) & PKEY_DISABLE_READ, PKEY_DISABLE_READ); > + TEST_COMPARE (pkey_get (pkey) & PKEY_DISABLE_WRITE, PKEY_DISABLE_WRITE); > + TEST_COMPARE (pkey_get (pkey) & PKEY_DISABLE_EXECUTE, PKEY_DISABLE_EXECUTE); > + sigusr1_handler_ran += 1; > +} > + > +static int > +do_test (void) > +{ > + pkey = pkey_alloc (0, PKEY_UNRESTRICTED); > + if (pkey < 0) > + { > + if (errno == ENOSYS || errno == EINVAL) > + FAIL_UNSUPPORTED > + ("kernel or CPU does not support memory protection keys"); > + FAIL_EXIT1 ("pkey_alloc: %m"); > + } > + > + long int pagesize = xsysconf (_SC_PAGESIZE); > + > + int *page = xmmap (NULL, pagesize, PROT_NONE, MAP_ANONYMOUS | MAP_PRIVATE, > + -1); > + > + /* On AArch64 pkey == 0 is reserved and should never be allocated. */ > + TEST_VERIFY (pkey > 0); > + TEST_COMPARE (pkey_get(pkey), PKEY_UNRESTRICTED); > + > + /* Check that access is revoked during signal handling > + with initial rights being set to no restrictions. */ > + TEST_COMPARE (pkey_mprotect ((void *) page, pagesize, PROT_READ > + | PROT_WRITE, pkey), 0); > + xsignal (SIGUSR1, &sigusr1_handler); > + xraise (SIGUSR1); > + xsignal (SIGUSR1, SIG_DFL); > + TEST_COMPARE (sigusr1_handler_ran, 1); > + > + /* Check that access is revoked during signal handling > + with initial rights being set to PKEY_DISABLE_WRITE. */ > + TEST_COMPARE (pkey_set (pkey, PKEY_DISABLE_WRITE), 0); > + xsignal (SIGUSR1, &sigusr1_handler); > + xraise (SIGUSR1); > + xsignal (SIGUSR1, SIG_DFL); > + TEST_COMPARE (sigusr1_handler_ran, 2); > + > + /* Check that all combinations of PKEY flags used in pkey_set > + result in consistent values obtained via pkey_get. > + Note that whenever flags PKEY_DISABLE_READ and PKEY_DISABLE_WRITE > + are set, the PKEY_DISABLE_ACCESS is also set. */ > + struct > + { > + unsigned int set; > + unsigned int expected; > + } rrs[] = > + { > + { > + PKEY_UNRESTRICTED, > + PKEY_UNRESTRICTED > + }, > + { > + PKEY_DISABLE_ACCESS, > + PKEY_DISABLE_ACCESS | PKEY_DISABLE_READ | PKEY_DISABLE_WRITE > + }, > + { > + PKEY_DISABLE_WRITE, > + PKEY_DISABLE_WRITE > + }, > + { > + PKEY_DISABLE_ACCESS | PKEY_DISABLE_WRITE, > + PKEY_DISABLE_ACCESS | PKEY_DISABLE_READ | PKEY_DISABLE_WRITE > + }, > + { > + PKEY_DISABLE_EXECUTE, > + PKEY_DISABLE_EXECUTE > + }, > + { > + PKEY_DISABLE_ACCESS | PKEY_DISABLE_EXECUTE, > + PKEY_DISABLE_ACCESS | PKEY_DISABLE_READ | PKEY_DISABLE_WRITE > + | PKEY_DISABLE_EXECUTE > + }, > + { > + PKEY_DISABLE_WRITE | PKEY_DISABLE_EXECUTE, > + PKEY_DISABLE_WRITE | PKEY_DISABLE_EXECUTE > + }, > + { > + PKEY_DISABLE_ACCESS | PKEY_DISABLE_WRITE | PKEY_DISABLE_EXECUTE, > + PKEY_DISABLE_ACCESS | PKEY_DISABLE_READ | PKEY_DISABLE_WRITE > + | PKEY_DISABLE_EXECUTE > + }, > + { > + PKEY_DISABLE_READ, > + PKEY_DISABLE_READ > + }, > + { > + PKEY_DISABLE_ACCESS | PKEY_DISABLE_READ, > + PKEY_DISABLE_ACCESS | PKEY_DISABLE_READ | PKEY_DISABLE_WRITE > + }, > + { > + PKEY_DISABLE_WRITE | PKEY_DISABLE_READ, > + PKEY_DISABLE_ACCESS | PKEY_DISABLE_READ | PKEY_DISABLE_WRITE > + }, > + { > + PKEY_DISABLE_ACCESS | PKEY_DISABLE_WRITE | PKEY_DISABLE_READ, > + PKEY_DISABLE_ACCESS | PKEY_DISABLE_WRITE | PKEY_DISABLE_READ > + }, > + { > + PKEY_DISABLE_EXECUTE | PKEY_DISABLE_READ, > + PKEY_DISABLE_EXECUTE | PKEY_DISABLE_READ > + }, > + { > + PKEY_DISABLE_ACCESS | PKEY_DISABLE_EXECUTE | PKEY_DISABLE_READ, > + PKEY_DISABLE_ACCESS | PKEY_DISABLE_READ | PKEY_DISABLE_WRITE > + | PKEY_DISABLE_EXECUTE > + }, > + { > + PKEY_DISABLE_WRITE | PKEY_DISABLE_EXECUTE | PKEY_DISABLE_READ, > + PKEY_DISABLE_ACCESS | PKEY_DISABLE_READ | PKEY_DISABLE_WRITE > + | PKEY_DISABLE_EXECUTE > + }, > + { > + PKEY_DISABLE_ACCESS | PKEY_DISABLE_WRITE | PKEY_DISABLE_EXECUTE > + | PKEY_DISABLE_READ, > + PKEY_DISABLE_ACCESS | PKEY_DISABLE_WRITE | PKEY_DISABLE_EXECUTE > + | PKEY_DISABLE_READ > + }, > + }; > + > + for (int k = 0; k < (sizeof(rrs) / 2 / sizeof(unsigned int)); k++) { We have array_length for this. > + TEST_COMPARE (k, rrs[k].set); > + TEST_COMPARE (pkey_set (pkey, rrs[k].set), 0); > + TEST_COMPARE (pkey_get (pkey), rrs[k].expected); > + } > + > + /* Check that restrictions above maximum allowed value are rejected. */ > + TEST_COMPARE (pkey_set (pkey, 16), -1); > + TEST_COMPARE (errno, EINVAL); > + > + TEST_COMPARE (pkey_free (pkey), 0); > + > + xmunmap ((void *) page, pagesize); > + > + return 0; > +} > + > +#include <support/test-driver.c> > diff --git a/sysdeps/unix/sysv/linux/tst-pkey.c b/sysdeps/unix/sysv/linux/tst-pkey.c > index 297a7fbc02..fb04b60889 100644 > --- a/sysdeps/unix/sysv/linux/tst-pkey.c > +++ b/sysdeps/unix/sysv/linux/tst-pkey.c > @@ -120,7 +120,7 @@ sigusr1_handler (int signum) > TEST_COMPARE (signum, SIGUSR1); > for (int i = 0; i < key_count; ++i) > TEST_VERIFY (pkey_get (keys[i]) == PKEY_DISABLE_ACCESS > - || pkey_get (keys[i]) == i); > + || (pkey_get (keys[i]) & i) == i); > sigusr1_handler_ran = 1; > } > > @@ -185,6 +185,7 @@ do_test (void) > xmunmap (page, pagesize); > } > > + /* Create thread before setting up key in the current thread. */ > xpthread_barrier_init (&barrier, NULL, 2); > bool delayed_thread_check_access = true; > pthread_t delayed_thread = xpthread_create > @@ -212,19 +213,47 @@ do_test (void) > ("glibc does not support memory protection keys"); > FAIL_EXIT1 ("pkey_get: %m"); > } > + > + /* Check that initial rights that are set via pkey_alloc > + can be accessed via pkey_get. */ > + { > + int pkey = -1; > + pkey = pkey_alloc (0, PKEY_DISABLE_ACCESS); > + TEST_COMPARE (pkey_get (pkey) & PKEY_DISABLE_ACCESS, PKEY_DISABLE_ACCESS); > + pkey_free (pkey); > + pkey = pkey_alloc (0, PKEY_DISABLE_WRITE); > + TEST_COMPARE (pkey_get (pkey) & PKEY_DISABLE_WRITE, PKEY_DISABLE_WRITE); > + pkey_free (pkey); > + } > + > + /* Check that unallocated pkey is not accepted by the > + pkey_mprotect function. */ > + { > + int pkey = -1; > + pkey = pkey_alloc (0, PKEY_DISABLE_WRITE); > + pkey_free (pkey); > + int *page = xmmap (NULL, pagesize, PROT_NONE, > + MAP_ANONYMOUS | MAP_PRIVATE, -1); > + TEST_COMPARE (pkey_mprotect (page, pagesize, PROT_READ, pkey), -1); > + TEST_COMPARE (errno, EINVAL); > + xmunmap (page, pagesize); > + } > + > for (int i = 1; i < key_count; ++i) > { > + /* i == 1 corresponds to PKEY_DISABLE_ACCESS > + i == 2 corresponds to PKEY_DISABLE_WRITE */ > keys[i] = pkey_alloc (0, i); > if (keys[i] < 0) > FAIL_EXIT1 ("pkey_alloc (0, %d): %m", i); > /* pkey_alloc is supposed to change the current thread's access > rights for the new key. */ > - TEST_COMPARE (pkey_get (keys[i]), i); > + TEST_COMPARE (pkey_get (keys[i]) & i, i); > } > /* Check that all the keys have the expected access rights for the > current thread. */ > for (int i = 0; i < key_count; ++i) > - TEST_COMPARE (pkey_get (keys[i]), i); > + TEST_COMPARE (pkey_get (keys[i]) & i, i); > > /* Allocate a test page for each key. */ > for (int i = 0; i < key_count; ++i) > @@ -241,12 +270,12 @@ do_test (void) > pthread_barrier_wait (&barrier); > struct thread_result *result = xpthread_join (delayed_thread); > for (int i = 0; i < key_count; ++i) > - TEST_COMPARE (result->access_rights[i], > - PKEY_DISABLE_ACCESS); > + TEST_COMPARE (result->access_rights[i] & > + PKEY_DISABLE_ACCESS, PKEY_DISABLE_ACCESS); > struct thread_result *result2 = xpthread_join (result->next_thread); > for (int i = 0; i < key_count; ++i) > - TEST_COMPARE (result->access_rights[i], > - PKEY_DISABLE_ACCESS); > + TEST_COMPARE (result->access_rights[i] & > + PKEY_DISABLE_ACCESS, PKEY_DISABLE_ACCESS); > free (result); > free (result2); > } > @@ -257,12 +286,12 @@ do_test (void) > pthread_t get_thread = xpthread_create (NULL, get_thread_func, NULL); > struct thread_result *result = xpthread_join (get_thread); > for (int i = 0; i < key_count; ++i) > - TEST_COMPARE (result->access_rights[i], i); > + TEST_COMPARE (result->access_rights[i] & i, i); > free (result); > } > > for (int i = 0; i < key_count; ++i) > - TEST_COMPARE (pkey_get (keys[i]), i); > + TEST_COMPARE (pkey_get (keys[i]) & i, i); > > /* Check that in a signal handler, there is no access. */ > xsignal (SIGUSR1, &sigusr1_handler); > @@ -281,7 +310,7 @@ do_test (void) > printf ("info: checking access for key %d, bits 0x%x\n", > i, pkey_get (keys[i])); > for (int j = 0; j < key_count; ++j) > - TEST_COMPARE (pkey_get (keys[j]), j); > + TEST_COMPARE (pkey_get (keys[j]) & j, j); > if (i & PKEY_DISABLE_ACCESS) > { > TEST_VERIFY (!check_page_access (i, false)); > @@ -355,7 +384,7 @@ do_test (void) > not what happens in practice. */ > { > /* The limit is in place to avoid running indefinitely in case > - there many keys available. */ > + there are many keys available. */ > int *keys_array = xcalloc (100000, sizeof (*keys_array)); > int keys_allocated = 0; > while (keys_allocated < 100000) Ok.
On Mon, Nov 18, 2024 at 05:39:26PM -0300, Adhemerval Zanella Netto wrote: > > > LGTM, with just a minor nit below. > > Reviewed-by: Adhemerval Zanella <adhemerval.zanella@linaro.org> > > ... > > > + > > + for (int k = 0; k < (sizeof(rrs) / 2 / sizeof(unsigned int)); k++) { > > We have array_length for this. > Thank you. Fixed in v7 [1] [1] https://inbox.sourceware.org/libc-alpha/20241119145207.1218319-1-yury.khrustalev@arm.com/ Kind regards, Yury
diff --git a/sysdeps/unix/sysv/linux/aarch64/Makefile b/sysdeps/unix/sysv/linux/aarch64/Makefile index 40b9a2e5de..1fdad67fae 100644 --- a/sysdeps/unix/sysv/linux/aarch64/Makefile +++ b/sysdeps/unix/sysv/linux/aarch64/Makefile @@ -1,5 +1,8 @@ ifeq ($(subdir),misc) sysdep_headers += sys/elf.h +tests += \ + tst-aarch64-pkey \ + # tests endif ifeq ($(subdir),stdlib) diff --git a/sysdeps/unix/sysv/linux/aarch64/arch-pkey.h b/sysdeps/unix/sysv/linux/aarch64/arch-pkey.h new file mode 100644 index 0000000000..6149b5da7b --- /dev/null +++ b/sysdeps/unix/sysv/linux/aarch64/arch-pkey.h @@ -0,0 +1,53 @@ +/* Helper functions for manipulating memory protection keys. + Copyright (C) 2024 Free Software Foundation, Inc. + This file is part of the GNU C Library. + + The GNU C Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + The GNU C Library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with the GNU C Library; if not, see + <https://www.gnu.org/licenses/>. */ + +#ifndef _ARCH_PKEY_H +#define _ARCH_PKEY_H + +#include <sys/cdefs.h> + +#define S1POE_PERM_NO_ACCESS 0b0000UL +#define S1POE_PERM_R 0b0001UL +#define S1POE_PERM_X 0b0010UL +#define S1POE_PERM_RX 0b0011UL +#define S1POE_PERM_W 0b0100UL +#define S1POE_PERM_RW 0b0101UL +#define S1POE_PERM_WX 0b0110UL +#define S1POE_PERM_RWX 0b0111UL + +#define S1POE_PERM_MASK 0b1111UL + +#define S1POE_BITS_PER_POI 4UL + +/* Return the value of the POR_EL0 register. */ +static __always_inline unsigned long +pkey_read (void) +{ + unsigned long r; + __asm__ volatile ("mrs %0, s3_3_c10_c2_4" : "=r" (r)); + return r; +} + +/* Overwrite the POR_EL0 register with VALUE. */ +static __always_inline void +pkey_write (unsigned long value) +{ + __asm__ volatile ("msr s3_3_c10_c2_4, %0; isb" : : "r" (value)); +} + +#endif /* _ARCH_PKEY_H */ diff --git a/sysdeps/unix/sysv/linux/aarch64/bits/hwcap.h b/sysdeps/unix/sysv/linux/aarch64/bits/hwcap.h index 8dceaa1a52..2fa158fcc0 100644 --- a/sysdeps/unix/sysv/linux/aarch64/bits/hwcap.h +++ b/sysdeps/unix/sysv/linux/aarch64/bits/hwcap.h @@ -118,3 +118,4 @@ #define HWCAP2_SME_SF8FMA (1UL << 60) #define HWCAP2_SME_SF8DP4 (1UL << 61) #define HWCAP2_SME_SF8DP2 (1UL << 62) +#define HWCAP2_POE (1UL << 63) diff --git a/sysdeps/unix/sysv/linux/aarch64/bits/mman.h b/sysdeps/unix/sysv/linux/aarch64/bits/mman.h index c5b6c69d43..288dff064b 100644 --- a/sysdeps/unix/sysv/linux/aarch64/bits/mman.h +++ b/sysdeps/unix/sysv/linux/aarch64/bits/mman.h @@ -26,6 +26,14 @@ #define PROT_BTI 0x10 #define PROT_MTE 0x20 +#ifdef __USE_GNU +# define PKEY_UNRESTRICTED 0x0 +# define PKEY_DISABLE_ACCESS 0x1 +# define PKEY_DISABLE_WRITE 0x2 +# define PKEY_DISABLE_EXECUTE 0x4 +# define PKEY_DISABLE_READ 0x8 +#endif + #include <bits/mman-map-flags-generic.h> /* Include generic Linux declarations. */ diff --git a/sysdeps/unix/sysv/linux/aarch64/pkey_get.c b/sysdeps/unix/sysv/linux/aarch64/pkey_get.c new file mode 100644 index 0000000000..66a47f5ce6 --- /dev/null +++ b/sysdeps/unix/sysv/linux/aarch64/pkey_get.c @@ -0,0 +1,73 @@ +/* Reading the per-thread memory protection key, AArch64 version. + Copyright (C) 2024 Free Software Foundation, Inc. + This file is part of the GNU C Library. + + The GNU C Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + The GNU C Library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with the GNU C Library; if not, see + <https://www.gnu.org/licenses/>. */ + +#include <arch-pkey.h> +#include <errno.h> +#include <sys/mman.h> + +int +pkey_get (int key) +{ + if (key < 0 || key > 15) + { + __set_errno (EINVAL); + return -1; + } + unsigned long int por_el0 = pkey_read (); + unsigned long int perm = (por_el0 >> (S1POE_BITS_PER_POI * key)) + & S1POE_PERM_MASK; + + /* The following mapping between POR permission bits (4 bits) + and PKEY flags is supported: + + -WXR POR to PKEY_ mapping + 0000 => DISABLE_ACCESS | DISABLE_READ | DISABLE_WRITE | DISABLE_EXECUTE + 0001 => DISABLE_WRITE | DISABLE_EXECUTE (read-only) + 0010 => DISABLE_ACCESS | DISABLE_READ | DISABLE_WRITE (execute-only) + 0011 => DISABLE_WRITE (read-execute) + 0100 => DISABLE_READ | DISABLE_EXECUTE (write-only) + 0101 => DISABLE_EXECUTE (read-write) + 0110 => DISABLE_READ (execute-write) + 0111 => UNRESTRICTED (no restrictions, read-write-execute) + else => undefined behavior + + Note that pkey_set and pkey_alloc would only set these specific + values. The PKEY_DISABLE_ACCESS flag is redundant as it implies + PKEY_DISABLE_READ | PKEY_DISABLE_WRITE but is kept for backward + compatibility. */ + + if (perm == S1POE_PERM_NO_ACCESS) + return PKEY_DISABLE_ACCESS | PKEY_DISABLE_WRITE | PKEY_DISABLE_EXECUTE + | PKEY_DISABLE_READ; + if (perm == S1POE_PERM_R) + return PKEY_DISABLE_WRITE | PKEY_DISABLE_EXECUTE; + if (perm == S1POE_PERM_X) + return PKEY_DISABLE_ACCESS | PKEY_DISABLE_READ | PKEY_DISABLE_WRITE; + if (perm == S1POE_PERM_RX) + return PKEY_DISABLE_WRITE; + if (perm == S1POE_PERM_W) + return PKEY_DISABLE_READ | PKEY_DISABLE_EXECUTE; + if (perm == S1POE_PERM_RW) + return PKEY_DISABLE_EXECUTE; + if (perm == S1POE_PERM_WX) + return PKEY_DISABLE_READ; + if (perm == S1POE_PERM_RWX) + return PKEY_UNRESTRICTED; + + return PKEY_DISABLE_ACCESS; +} diff --git a/sysdeps/unix/sysv/linux/aarch64/pkey_set.c b/sysdeps/unix/sysv/linux/aarch64/pkey_set.c new file mode 100644 index 0000000000..12161127a4 --- /dev/null +++ b/sysdeps/unix/sysv/linux/aarch64/pkey_set.c @@ -0,0 +1,113 @@ +/* Changing the per-thread memory protection key, AArch64 version. + Copyright (C) 2024 Free Software Foundation, Inc. + This file is part of the GNU C Library. + + The GNU C Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + The GNU C Library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with the GNU C Library; if not, see + <https://www.gnu.org/licenses/>. */ + +#include <arch-pkey.h> +#include <errno.h> +#include <sys/mman.h> + +#define MAX_PKEY_RIGHTS (PKEY_DISABLE_ACCESS | \ + PKEY_DISABLE_WRITE | PKEY_DISABLE_EXECUTE | PKEY_DISABLE_READ) + +int +pkey_set (int key, unsigned int restrictions) +{ + if (key < 0 || key > 15 || restrictions > MAX_PKEY_RIGHTS) + { + __set_errno (EINVAL); + return -1; + } + unsigned long mask = S1POE_PERM_MASK << (S1POE_BITS_PER_POI * key); + unsigned long por_el0 = pkey_read (); + unsigned long perm; + + /* POR ot PKEY mapping: -WXR + PKEY_UNRESTRICTED => 0111 (read-write-execute) + PKEY_DISABLE_ACCESS => removes R and W access + PKEY_DISABLE_READ => removes R access + PKEY_DISABLE_WRITE => removes W access + PKEY_DISABLE_EXECUTE => removes X access + + Either of PKEY_DISABLE_ACCESS or PKEY_DISABLE_READ removes R access. + Either of PKEY_DISABLE_ACCESS or PKEY_DISABLE_WRITE removes W access. + Using PKEY_DISABLE_ACCESS along with only one of PKEY_DISABLE_READ or + PKEY_DISABLE_WRITE is considered to be in error. + + Furthermore, for avoidance of doubt: + + PKEY flags Permissions + rxwa -WXR + 1111 => 0000 S1POE_PERM_NO_ACCESS + 1110 => 0000 S1POE_PERM_NO_ACCESS + 1101 => EINVAL + 1100 => 0100 S1POE_PERM_W + 1011 => 0010 S1POE_PERM_X + 1010 => 0010 S1POE_PERM_X + 1001 => EINVAL + 1000 => 0110 S1POE_PERM_WX + 0111 => EINVAL + 0110 => 0001 S1POE_PERM_R + 0101 => 0000 S1POE_PERM_NO_ACCESS + 0100 => 0101 S1POE_PERM_RW + 0011 => EINVAL + 0010 => 0011 S1POE_PERM_RX + 0001 => 0010 S1POE_PERM_X + 0000 => 0111 S1POE_PERM_RWX */ + switch (restrictions) + { + case PKEY_DISABLE_ACCESS | PKEY_DISABLE_READ | PKEY_DISABLE_WRITE + | PKEY_DISABLE_EXECUTE: + case PKEY_DISABLE_ACCESS | PKEY_DISABLE_EXECUTE: + case PKEY_DISABLE_ACCESS | PKEY_DISABLE_READ | PKEY_DISABLE_EXECUTE: + case PKEY_DISABLE_ACCESS | PKEY_DISABLE_WRITE | PKEY_DISABLE_EXECUTE: + case PKEY_DISABLE_READ | PKEY_DISABLE_WRITE | PKEY_DISABLE_EXECUTE: + perm = S1POE_PERM_NO_ACCESS; + break; + case PKEY_DISABLE_READ | PKEY_DISABLE_EXECUTE: + perm = S1POE_PERM_W; + break; + case PKEY_DISABLE_ACCESS | PKEY_DISABLE_READ: + case PKEY_DISABLE_ACCESS | PKEY_DISABLE_WRITE: + case PKEY_DISABLE_ACCESS | PKEY_DISABLE_READ | PKEY_DISABLE_WRITE: + case PKEY_DISABLE_READ | PKEY_DISABLE_WRITE: + case PKEY_DISABLE_ACCESS: + perm = S1POE_PERM_X; + break; + case PKEY_DISABLE_READ: + perm = S1POE_PERM_WX; + break; + case PKEY_DISABLE_WRITE | PKEY_DISABLE_EXECUTE: + perm = S1POE_PERM_R; + break; + case PKEY_DISABLE_EXECUTE: + perm = S1POE_PERM_RW; + break; + case PKEY_DISABLE_WRITE: + perm = S1POE_PERM_RX; + break; + case PKEY_UNRESTRICTED: + perm = S1POE_PERM_RWX; + break; + default: + __set_errno (EINVAL); + return -1; + } + + por_el0 = (por_el0 & ~mask) | (perm << (S1POE_BITS_PER_POI * key)); + pkey_write (por_el0); + return 0; +} diff --git a/sysdeps/unix/sysv/linux/aarch64/tst-aarch64-pkey.c b/sysdeps/unix/sysv/linux/aarch64/tst-aarch64-pkey.c new file mode 100644 index 0000000000..8e5d06d6bb --- /dev/null +++ b/sysdeps/unix/sysv/linux/aarch64/tst-aarch64-pkey.c @@ -0,0 +1,185 @@ +/* AArch64 tests for memory protection keys. + Copyright (C) 2024 Free Software Foundation, Inc. + This file is part of the GNU C Library. + + The GNU C Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + The GNU C Library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with the GNU C Library; if not, see + <https://www.gnu.org/licenses/>. */ + +#include <errno.h> +#include <inttypes.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <support/check.h> +#include <support/support.h> +#include <support/test-driver.h> +#include <support/xsignal.h> +#include <support/xunistd.h> +#include <sys/mman.h> + +static sig_atomic_t sigusr1_handler_ran; +static int pkey; + +/* On AArch64 access is revoked during signal handling for + pkey > 0 because POR is reset to the default value 0x7. */ +static void +sigusr1_handler (int signum) +{ + TEST_COMPARE (signum, SIGUSR1); + TEST_COMPARE (pkey_get (pkey) & PKEY_DISABLE_ACCESS, PKEY_DISABLE_ACCESS); + TEST_COMPARE (pkey_get (pkey) & PKEY_DISABLE_READ, PKEY_DISABLE_READ); + TEST_COMPARE (pkey_get (pkey) & PKEY_DISABLE_WRITE, PKEY_DISABLE_WRITE); + TEST_COMPARE (pkey_get (pkey) & PKEY_DISABLE_EXECUTE, PKEY_DISABLE_EXECUTE); + sigusr1_handler_ran += 1; +} + +static int +do_test (void) +{ + pkey = pkey_alloc (0, PKEY_UNRESTRICTED); + if (pkey < 0) + { + if (errno == ENOSYS || errno == EINVAL) + FAIL_UNSUPPORTED + ("kernel or CPU does not support memory protection keys"); + FAIL_EXIT1 ("pkey_alloc: %m"); + } + + long int pagesize = xsysconf (_SC_PAGESIZE); + + int *page = xmmap (NULL, pagesize, PROT_NONE, MAP_ANONYMOUS | MAP_PRIVATE, + -1); + + /* On AArch64 pkey == 0 is reserved and should never be allocated. */ + TEST_VERIFY (pkey > 0); + TEST_COMPARE (pkey_get(pkey), PKEY_UNRESTRICTED); + + /* Check that access is revoked during signal handling + with initial rights being set to no restrictions. */ + TEST_COMPARE (pkey_mprotect ((void *) page, pagesize, PROT_READ + | PROT_WRITE, pkey), 0); + xsignal (SIGUSR1, &sigusr1_handler); + xraise (SIGUSR1); + xsignal (SIGUSR1, SIG_DFL); + TEST_COMPARE (sigusr1_handler_ran, 1); + + /* Check that access is revoked during signal handling + with initial rights being set to PKEY_DISABLE_WRITE. */ + TEST_COMPARE (pkey_set (pkey, PKEY_DISABLE_WRITE), 0); + xsignal (SIGUSR1, &sigusr1_handler); + xraise (SIGUSR1); + xsignal (SIGUSR1, SIG_DFL); + TEST_COMPARE (sigusr1_handler_ran, 2); + + /* Check that all combinations of PKEY flags used in pkey_set + result in consistent values obtained via pkey_get. + Note that whenever flags PKEY_DISABLE_READ and PKEY_DISABLE_WRITE + are set, the PKEY_DISABLE_ACCESS is also set. */ + struct + { + unsigned int set; + unsigned int expected; + } rrs[] = + { + { + PKEY_UNRESTRICTED, + PKEY_UNRESTRICTED + }, + { + PKEY_DISABLE_ACCESS, + PKEY_DISABLE_ACCESS | PKEY_DISABLE_READ | PKEY_DISABLE_WRITE + }, + { + PKEY_DISABLE_WRITE, + PKEY_DISABLE_WRITE + }, + { + PKEY_DISABLE_ACCESS | PKEY_DISABLE_WRITE, + PKEY_DISABLE_ACCESS | PKEY_DISABLE_READ | PKEY_DISABLE_WRITE + }, + { + PKEY_DISABLE_EXECUTE, + PKEY_DISABLE_EXECUTE + }, + { + PKEY_DISABLE_ACCESS | PKEY_DISABLE_EXECUTE, + PKEY_DISABLE_ACCESS | PKEY_DISABLE_READ | PKEY_DISABLE_WRITE + | PKEY_DISABLE_EXECUTE + }, + { + PKEY_DISABLE_WRITE | PKEY_DISABLE_EXECUTE, + PKEY_DISABLE_WRITE | PKEY_DISABLE_EXECUTE + }, + { + PKEY_DISABLE_ACCESS | PKEY_DISABLE_WRITE | PKEY_DISABLE_EXECUTE, + PKEY_DISABLE_ACCESS | PKEY_DISABLE_READ | PKEY_DISABLE_WRITE + | PKEY_DISABLE_EXECUTE + }, + { + PKEY_DISABLE_READ, + PKEY_DISABLE_READ + }, + { + PKEY_DISABLE_ACCESS | PKEY_DISABLE_READ, + PKEY_DISABLE_ACCESS | PKEY_DISABLE_READ | PKEY_DISABLE_WRITE + }, + { + PKEY_DISABLE_WRITE | PKEY_DISABLE_READ, + PKEY_DISABLE_ACCESS | PKEY_DISABLE_READ | PKEY_DISABLE_WRITE + }, + { + PKEY_DISABLE_ACCESS | PKEY_DISABLE_WRITE | PKEY_DISABLE_READ, + PKEY_DISABLE_ACCESS | PKEY_DISABLE_WRITE | PKEY_DISABLE_READ + }, + { + PKEY_DISABLE_EXECUTE | PKEY_DISABLE_READ, + PKEY_DISABLE_EXECUTE | PKEY_DISABLE_READ + }, + { + PKEY_DISABLE_ACCESS | PKEY_DISABLE_EXECUTE | PKEY_DISABLE_READ, + PKEY_DISABLE_ACCESS | PKEY_DISABLE_READ | PKEY_DISABLE_WRITE + | PKEY_DISABLE_EXECUTE + }, + { + PKEY_DISABLE_WRITE | PKEY_DISABLE_EXECUTE | PKEY_DISABLE_READ, + PKEY_DISABLE_ACCESS | PKEY_DISABLE_READ | PKEY_DISABLE_WRITE + | PKEY_DISABLE_EXECUTE + }, + { + PKEY_DISABLE_ACCESS | PKEY_DISABLE_WRITE | PKEY_DISABLE_EXECUTE + | PKEY_DISABLE_READ, + PKEY_DISABLE_ACCESS | PKEY_DISABLE_WRITE | PKEY_DISABLE_EXECUTE + | PKEY_DISABLE_READ + }, + }; + + for (int k = 0; k < (sizeof(rrs) / 2 / sizeof(unsigned int)); k++) { + TEST_COMPARE (k, rrs[k].set); + TEST_COMPARE (pkey_set (pkey, rrs[k].set), 0); + TEST_COMPARE (pkey_get (pkey), rrs[k].expected); + } + + /* Check that restrictions above maximum allowed value are rejected. */ + TEST_COMPARE (pkey_set (pkey, 16), -1); + TEST_COMPARE (errno, EINVAL); + + TEST_COMPARE (pkey_free (pkey), 0); + + xmunmap ((void *) page, pagesize); + + return 0; +} + +#include <support/test-driver.c> diff --git a/sysdeps/unix/sysv/linux/tst-pkey.c b/sysdeps/unix/sysv/linux/tst-pkey.c index 297a7fbc02..fb04b60889 100644 --- a/sysdeps/unix/sysv/linux/tst-pkey.c +++ b/sysdeps/unix/sysv/linux/tst-pkey.c @@ -120,7 +120,7 @@ sigusr1_handler (int signum) TEST_COMPARE (signum, SIGUSR1); for (int i = 0; i < key_count; ++i) TEST_VERIFY (pkey_get (keys[i]) == PKEY_DISABLE_ACCESS - || pkey_get (keys[i]) == i); + || (pkey_get (keys[i]) & i) == i); sigusr1_handler_ran = 1; } @@ -185,6 +185,7 @@ do_test (void) xmunmap (page, pagesize); } + /* Create thread before setting up key in the current thread. */ xpthread_barrier_init (&barrier, NULL, 2); bool delayed_thread_check_access = true; pthread_t delayed_thread = xpthread_create @@ -212,19 +213,47 @@ do_test (void) ("glibc does not support memory protection keys"); FAIL_EXIT1 ("pkey_get: %m"); } + + /* Check that initial rights that are set via pkey_alloc + can be accessed via pkey_get. */ + { + int pkey = -1; + pkey = pkey_alloc (0, PKEY_DISABLE_ACCESS); + TEST_COMPARE (pkey_get (pkey) & PKEY_DISABLE_ACCESS, PKEY_DISABLE_ACCESS); + pkey_free (pkey); + pkey = pkey_alloc (0, PKEY_DISABLE_WRITE); + TEST_COMPARE (pkey_get (pkey) & PKEY_DISABLE_WRITE, PKEY_DISABLE_WRITE); + pkey_free (pkey); + } + + /* Check that unallocated pkey is not accepted by the + pkey_mprotect function. */ + { + int pkey = -1; + pkey = pkey_alloc (0, PKEY_DISABLE_WRITE); + pkey_free (pkey); + int *page = xmmap (NULL, pagesize, PROT_NONE, + MAP_ANONYMOUS | MAP_PRIVATE, -1); + TEST_COMPARE (pkey_mprotect (page, pagesize, PROT_READ, pkey), -1); + TEST_COMPARE (errno, EINVAL); + xmunmap (page, pagesize); + } + for (int i = 1; i < key_count; ++i) { + /* i == 1 corresponds to PKEY_DISABLE_ACCESS + i == 2 corresponds to PKEY_DISABLE_WRITE */ keys[i] = pkey_alloc (0, i); if (keys[i] < 0) FAIL_EXIT1 ("pkey_alloc (0, %d): %m", i); /* pkey_alloc is supposed to change the current thread's access rights for the new key. */ - TEST_COMPARE (pkey_get (keys[i]), i); + TEST_COMPARE (pkey_get (keys[i]) & i, i); } /* Check that all the keys have the expected access rights for the current thread. */ for (int i = 0; i < key_count; ++i) - TEST_COMPARE (pkey_get (keys[i]), i); + TEST_COMPARE (pkey_get (keys[i]) & i, i); /* Allocate a test page for each key. */ for (int i = 0; i < key_count; ++i) @@ -241,12 +270,12 @@ do_test (void) pthread_barrier_wait (&barrier); struct thread_result *result = xpthread_join (delayed_thread); for (int i = 0; i < key_count; ++i) - TEST_COMPARE (result->access_rights[i], - PKEY_DISABLE_ACCESS); + TEST_COMPARE (result->access_rights[i] & + PKEY_DISABLE_ACCESS, PKEY_DISABLE_ACCESS); struct thread_result *result2 = xpthread_join (result->next_thread); for (int i = 0; i < key_count; ++i) - TEST_COMPARE (result->access_rights[i], - PKEY_DISABLE_ACCESS); + TEST_COMPARE (result->access_rights[i] & + PKEY_DISABLE_ACCESS, PKEY_DISABLE_ACCESS); free (result); free (result2); } @@ -257,12 +286,12 @@ do_test (void) pthread_t get_thread = xpthread_create (NULL, get_thread_func, NULL); struct thread_result *result = xpthread_join (get_thread); for (int i = 0; i < key_count; ++i) - TEST_COMPARE (result->access_rights[i], i); + TEST_COMPARE (result->access_rights[i] & i, i); free (result); } for (int i = 0; i < key_count; ++i) - TEST_COMPARE (pkey_get (keys[i]), i); + TEST_COMPARE (pkey_get (keys[i]) & i, i); /* Check that in a signal handler, there is no access. */ xsignal (SIGUSR1, &sigusr1_handler); @@ -281,7 +310,7 @@ do_test (void) printf ("info: checking access for key %d, bits 0x%x\n", i, pkey_get (keys[i])); for (int j = 0; j < key_count; ++j) - TEST_COMPARE (pkey_get (keys[j]), j); + TEST_COMPARE (pkey_get (keys[j]) & j, j); if (i & PKEY_DISABLE_ACCESS) { TEST_VERIFY (!check_page_access (i, false)); @@ -355,7 +384,7 @@ do_test (void) not what happens in practice. */ { /* The limit is in place to avoid running indefinitely in case - there many keys available. */ + there are many keys available. */ int *keys_array = xcalloc (100000, sizeof (*keys_array)); int keys_allocated = 0; while (keys_allocated < 100000)