diff mbox series

[v3] aarch64: Add support for memory protection keys

Message ID 20240926121315.20096-1-yury.khrustalev@arm.com
State New
Headers show
Series [v3] aarch64: Add support for memory protection keys | expand

Commit Message

Yury Khrustalev Sept. 26, 2024, 12:13 p.m. UTC
From: Yury Khrustalev <yury.khrustalev@arm.com>

This patch adds support for memory protection keys on AArch64 targets
where it can be implemented by hardware with enabled Permission Overlay
Extension introduced in Armv8.9 / Armv9.4 [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 AArch64-specific flags:

 - PKEY_UNRESTRICTED: 0x0 (for completeness)
 - PKEY_DISABLE_ACCESS: 0x1 (existing flag)
 - PKEY_DISABLE_WRITE: 0x2 (existing flag)
 - PKEY_DISABLE_EXECUTE: 0x4 (new flag)
 - PKEY_DISABLE_READ: 0x8 (new flag)

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

However, in this implementation the following combinations considered
to be an error:

 - PKEY_DISABLE_ACCESS | PKEY_DISABLE_READ
 - PKEY_DISABLE_ACCESS | PKEY_DISABLE_WRITE

But this is acceptable (although 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 documented as flags.

It should be noted that PKEY_DISABLE_ACCESS does not prevent execution.

Co-authored-by: Szabolcs Nagy <szabolcs.nagy@arm.com>

[1] DDI 0487K.a Arm Architecture Reference Manual for A-profile architecture
---

This patch is intended to work with and has been tested on a model using Linux
kernel from linux-next [2] (any next/<date> branch starting with next/20240924).

Since I've added changes to the generic test "misc/tst-pkey" that runs on several
targets, I've tested this change on aarch64-linux-gnu and x86_64-linux-gnu using
native build and on the following targets using cross-build (build-many-glibcs.py):
 - aarch64_be-linux-gnu
 - arm-linux-gnueabi
 - armeb-linux-gnueabi
 - arm-linux-gnueabihf
 - armeb-linux-gnueabihf
 - powerpc-linux-gnu
 - powerpc64-linux-gnu
and no regressions have been found.

Any feedback welcome! Note, this would only be committed after linux-next is
accepted into mainline.

Applies to d14c977c65 in master.

[2] https://git.kernel.org/pub/scm/linux/kernel/git/next/linux-next.git
---

 sysdeps/unix/sysv/linux/aarch64/Makefile      |   3 +
 sysdeps/unix/sysv/linux/aarch64/arch-pkey.h   |  51 +++++++
 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    |  71 ++++++++++
 sysdeps/unix/sysv/linux/aarch64/pkey_set.c    | 108 +++++++++++++++
 .../sysv/linux/aarch64/tst-aarch64-pkey.c     | 127 ++++++++++++++++++
 sysdeps/unix/sysv/linux/tst-pkey.c            |  51 +++++--
 8 files changed, 409 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

Comments

Adhemerval Zanella Oct. 1, 2024, 7:45 p.m. UTC | #1
On 26/09/24 09:13, yury.khrustalev@arm.com wrote:
> From: Yury Khrustalev <yury.khrustalev@arm.com>
> 
> This patch adds support for memory protection keys on AArch64 targets
> where it can be implemented by hardware with enabled Permission Overlay
> Extension introduced in Armv8.9 / Armv9.4 [1].

I think it would be worth to add the kernel commit and Linux version that
it is supported or expected to be support.  It seems that it was enabled
by bf83dae90fbc01d66477a3440eaad07da6657fdc on Linus tree, which is 
scheduled for 6.12.  So I think we will need 6.12 release before installing
this patch.

> 
>  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 AArch64-specific flags:
> 
>  - PKEY_UNRESTRICTED: 0x0 (for completeness)
>  - PKEY_DISABLE_ACCESS: 0x1 (existing flag)
>  - PKEY_DISABLE_WRITE: 0x2 (existing flag)
>  - PKEY_DISABLE_EXECUTE: 0x4 (new flag)
>  - PKEY_DISABLE_READ: 0x8 (new flag)

These new flags need to be documented on pkey_set section at
manual/memory.texi, as an aarch64 extension.  The pkey already have 
some arch-specific semantic that make portability not straightforward 
(like the signal handling), so I am wondering if we should just make
the new flags not aarch64 specific. Both x86 and powerpc already returns 
EINVAL for larger rights.

> 
> 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
> 
> However, in this implementation the following combinations considered
> to be an error:
> 
>  - PKEY_DISABLE_ACCESS | PKEY_DISABLE_READ
>  - PKEY_DISABLE_ACCESS | PKEY_DISABLE_WRITE
> 
> But this is acceptable (although 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 documented as flags.

Right, this another possible pitfall that it would be good to have
it documented on manual.

> 
> It should be noted that PKEY_DISABLE_ACCESS does not prevent execution.
> 
> Co-authored-by: Szabolcs Nagy <szabolcs.nagy@arm.com>
> 
> [1] DDI 0487K.a Arm Architecture Reference Manual for A-profile architecture

I am still configuring an enviroment where I can actually tests it, but
besides the points above and some style issues below patch looks ok.

> ---
> 
> This patch is intended to work with and has been tested on a model using Linux
> kernel from linux-next [2] (any next/<date> branch starting with next/20240924).
> 
> Since I've added changes to the generic test "misc/tst-pkey" that runs on several
> targets, I've tested this change on aarch64-linux-gnu and x86_64-linux-gnu using
> native build and on the following targets using cross-build (build-many-glibcs.py):
>  - aarch64_be-linux-gnu
>  - arm-linux-gnueabi
>  - armeb-linux-gnueabi
>  - arm-linux-gnueabihf
>  - armeb-linux-gnueabihf
>  - powerpc-linux-gnu
>  - powerpc64-linux-gnu
> and no regressions have been found.
> 
> Any feedback welcome! Note, this would only be committed after linux-next is
> accepted into mainline.
> 
> Applies to d14c977c65 in master.
> 
> [2] https://git.kernel.org/pub/scm/linux/kernel/git/next/linux-next.git
> ---
> 
>  sysdeps/unix/sysv/linux/aarch64/Makefile      |   3 +
>  sysdeps/unix/sysv/linux/aarch64/arch-pkey.h   |  51 +++++++
>  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    |  71 ++++++++++
>  sysdeps/unix/sysv/linux/aarch64/pkey_set.c    | 108 +++++++++++++++
>  .../sysv/linux/aarch64/tst-aarch64-pkey.c     | 127 ++++++++++++++++++
>  sysdeps/unix/sysv/linux/tst-pkey.c            |  51 +++++--
>  8 files changed, 409 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)
> 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..cbb87c2a64
> --- /dev/null
> +++ b/	
> @@ -0,0 +1,51 @@
> +/* 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
> +
> +#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 inline unsigned long

Usually we use __always_inline for static function in headers.

> +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 inline void
> +pkey_write (unsigned long value)
> +{
> +  __asm__ volatile ("msr s3_3_c10_c2_4, %0" : : "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)

Usually we sync such headers after the Linux release, however it should
be ok to tie with the pkey support.

> 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..5290ac2e3c
> --- /dev/null
> +++ b/sysdeps/unix/sysv/linux/aarch64/pkey_get.c
> @@ -0,0 +1,71 @@
> +/* 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 por_el0 = pkey_read ();

The glibc code guideline do not omit the type, so 'unsigned long int' here.

> +  unsigned long perm = (por_el0 >> (S1POE_BITS_PER_POI * key)) & S1POE_PERM_MASK;

Ditto and line too long:

  unsigned long 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 redundat as it implies

Style issue, double space after period.

> +     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;

Line too long:

  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; // undefined behavior
> +}
> 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..a4057fbe13
> --- /dev/null
> +++ b/sysdeps/unix/sysv/linux/aarch64/pkey_set.c
> @@ -0,0 +1,108 @@
> +/* 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 rights)
> +{
> +  if (key < 0 || key > 15 || rights > 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 (rights)
> +    {
> +      case PKEY_DISABLE_ACCESS | PKEY_DISABLE_READ | PKEY_DISABLE_WRITE | PKEY_DISABLE_EXECUTE:

Line too long.

> +      case PKEY_DISABLE_ACCESS | 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 | 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..1623ccffd0
> --- /dev/null
> +++ b/sysdeps/unix/sysv/linux/aarch64/tst-aarch64-pkey.c
> @@ -0,0 +1,127 @@
> +/* 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 volatile sig_atomic_t sigusr1_handler_ran;

Afaik there is no need to volatile here.

> +static volatile 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)
> +{
> +

Spurious new line.

> +  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 pagesize = xsysconf (_SC_PAGESIZE);

s/long/long int/

> +
> +  int *page = xmmap (NULL, pagesize, PROT_NONE, MAP_ANONYMOUS | MAP_PRIVATE, -1);

Line too long.

> +  pkey = pkey_alloc (0, PKEY_UNRESTRICTED);
> +
> +  /* On AArch64 pkey == 0 is reserved and should never be accocated.  */

s/accocated/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);

Line too long.

> +  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.  */
> +  TEST_COMPARE (pkey_set(pkey, PKEY_UNRESTRICTED), 0);
> +  TEST_COMPARE (pkey_get(pkey), PKEY_UNRESTRICTED);
> +  TEST_COMPARE (pkey_set(pkey, PKEY_DISABLE_ACCESS), 0);
> +  TEST_COMPARE (pkey_get(pkey), PKEY_DISABLE_ACCESS | PKEY_DISABLE_READ | PKEY_DISABLE_WRITE);

Line too long and space after function name.

> +  TEST_COMPARE (pkey_set(pkey, PKEY_DISABLE_READ | PKEY_DISABLE_WRITE), 0);
> +  TEST_COMPARE (pkey_get(pkey), PKEY_DISABLE_ACCESS | PKEY_DISABLE_READ | PKEY_DISABLE_WRITE);
> +  TEST_COMPARE (pkey_set(pkey, PKEY_DISABLE_READ), 0);
> +  TEST_COMPARE (pkey_get(pkey), PKEY_DISABLE_READ);
> +  TEST_COMPARE (pkey_set(pkey, PKEY_DISABLE_WRITE), 0);
> +  TEST_COMPARE (pkey_get(pkey), PKEY_DISABLE_WRITE);
> +  TEST_COMPARE (pkey_set(pkey, PKEY_DISABLE_EXECUTE), 0);
> +  TEST_COMPARE (pkey_get(pkey), PKEY_DISABLE_EXECUTE);
> +  TEST_COMPARE (pkey_set(pkey, PKEY_DISABLE_WRITE | PKEY_DISABLE_EXECUTE), 0);
> +  TEST_COMPARE (pkey_get(pkey), PKEY_DISABLE_WRITE | PKEY_DISABLE_EXECUTE);
> +  TEST_COMPARE (pkey_set(pkey, PKEY_DISABLE_READ | PKEY_DISABLE_EXECUTE), 0);
> +  TEST_COMPARE (pkey_get(pkey), PKEY_DISABLE_READ | PKEY_DISABLE_EXECUTE);
> +
> +  /* Check EINVAL cases for pkey_set: use of
> +      - PKEY_DISABLE_ACCESS with PKEY_DISABLE_READ only
> +      - PKEY_DISABLE_ACCESS with PKEY_DISABLE_WRITE only
> +     is likely an error.  */
> +  TEST_COMPARE (pkey_set(pkey, PKEY_DISABLE_ACCESS | PKEY_DISABLE_READ), -1);
> +  TEST_COMPARE (errno, EINVAL);
> +  TEST_COMPARE (pkey_set(pkey, PKEY_DISABLE_ACCESS | PKEY_DISABLE_READ | PKEY_DISABLE_EXECUTE), -1);
> +  TEST_COMPARE (errno, EINVAL);
> +  TEST_COMPARE (pkey_set(pkey, PKEY_DISABLE_ACCESS | PKEY_DISABLE_WRITE), -1);
> +  TEST_COMPARE (errno, EINVAL);
> +  TEST_COMPARE (pkey_set(pkey, PKEY_DISABLE_ACCESS | PKEY_DISABLE_WRITE | PKEY_DISABLE_EXECUTE), -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.
diff mbox series

Patch

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..cbb87c2a64
--- /dev/null
+++ b/sysdeps/unix/sysv/linux/aarch64/arch-pkey.h
@@ -0,0 +1,51 @@ 
+/* 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
+
+#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 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 inline void
+pkey_write (unsigned long value)
+{
+  __asm__ volatile ("msr s3_3_c10_c2_4, %0" : : "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..5290ac2e3c
--- /dev/null
+++ b/sysdeps/unix/sysv/linux/aarch64/pkey_get.c
@@ -0,0 +1,71 @@ 
+/* 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 por_el0 = pkey_read ();
+  unsigned long 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 redundat 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; // undefined behavior
+}
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..a4057fbe13
--- /dev/null
+++ b/sysdeps/unix/sysv/linux/aarch64/pkey_set.c
@@ -0,0 +1,108 @@ 
+/* 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 rights)
+{
+  if (key < 0 || key > 15 || rights > 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 (rights)
+    {
+      case PKEY_DISABLE_ACCESS | PKEY_DISABLE_READ | PKEY_DISABLE_WRITE | PKEY_DISABLE_EXECUTE:
+      case PKEY_DISABLE_ACCESS | 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 | 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..1623ccffd0
--- /dev/null
+++ b/sysdeps/unix/sysv/linux/aarch64/tst-aarch64-pkey.c
@@ -0,0 +1,127 @@ 
+/* 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 volatile sig_atomic_t sigusr1_handler_ran;
+static volatile 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 pagesize = xsysconf (_SC_PAGESIZE);
+
+  int *page = xmmap (NULL, pagesize, PROT_NONE, MAP_ANONYMOUS | MAP_PRIVATE, -1);
+  pkey = pkey_alloc (0, PKEY_UNRESTRICTED);
+
+  /* On AArch64 pkey == 0 is reserved and should never be accocated.  */
+  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.  */
+  TEST_COMPARE (pkey_set(pkey, PKEY_UNRESTRICTED), 0);
+  TEST_COMPARE (pkey_get(pkey), PKEY_UNRESTRICTED);
+  TEST_COMPARE (pkey_set(pkey, PKEY_DISABLE_ACCESS), 0);
+  TEST_COMPARE (pkey_get(pkey), PKEY_DISABLE_ACCESS | PKEY_DISABLE_READ | PKEY_DISABLE_WRITE);
+  TEST_COMPARE (pkey_set(pkey, PKEY_DISABLE_READ | PKEY_DISABLE_WRITE), 0);
+  TEST_COMPARE (pkey_get(pkey), PKEY_DISABLE_ACCESS | PKEY_DISABLE_READ | PKEY_DISABLE_WRITE);
+  TEST_COMPARE (pkey_set(pkey, PKEY_DISABLE_READ), 0);
+  TEST_COMPARE (pkey_get(pkey), PKEY_DISABLE_READ);
+  TEST_COMPARE (pkey_set(pkey, PKEY_DISABLE_WRITE), 0);
+  TEST_COMPARE (pkey_get(pkey), PKEY_DISABLE_WRITE);
+  TEST_COMPARE (pkey_set(pkey, PKEY_DISABLE_EXECUTE), 0);
+  TEST_COMPARE (pkey_get(pkey), PKEY_DISABLE_EXECUTE);
+  TEST_COMPARE (pkey_set(pkey, PKEY_DISABLE_WRITE | PKEY_DISABLE_EXECUTE), 0);
+  TEST_COMPARE (pkey_get(pkey), PKEY_DISABLE_WRITE | PKEY_DISABLE_EXECUTE);
+  TEST_COMPARE (pkey_set(pkey, PKEY_DISABLE_READ | PKEY_DISABLE_EXECUTE), 0);
+  TEST_COMPARE (pkey_get(pkey), PKEY_DISABLE_READ | PKEY_DISABLE_EXECUTE);
+
+  /* Check EINVAL cases for pkey_set: use of
+      - PKEY_DISABLE_ACCESS with PKEY_DISABLE_READ only
+      - PKEY_DISABLE_ACCESS with PKEY_DISABLE_WRITE only
+     is likely an error.  */
+  TEST_COMPARE (pkey_set(pkey, PKEY_DISABLE_ACCESS | PKEY_DISABLE_READ), -1);
+  TEST_COMPARE (errno, EINVAL);
+  TEST_COMPARE (pkey_set(pkey, PKEY_DISABLE_ACCESS | PKEY_DISABLE_READ | PKEY_DISABLE_EXECUTE), -1);
+  TEST_COMPARE (errno, EINVAL);
+  TEST_COMPARE (pkey_set(pkey, PKEY_DISABLE_ACCESS | PKEY_DISABLE_WRITE), -1);
+  TEST_COMPARE (errno, EINVAL);
+  TEST_COMPARE (pkey_set(pkey, PKEY_DISABLE_ACCESS | PKEY_DISABLE_WRITE | PKEY_DISABLE_EXECUTE), -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)