$RodHat_
Syscall reference · Memory

pkey_alloc / pkey_mprotect(2)

Allocate a memory protection key and use it to gate access to a memory region via a per-thread register, without a syscall per permission switch.

Since
Linux 4.8check your kernel →
glibc
glibc 2.27 (pkey_alloc, pkey_mprotect, pkey_free, pkey_set, pkey_get)
System
Linux
Man page
pkey_alloc / pkey_mprotect(2)

Synopsis

#include <sys/pkey.h>

int pkey_alloc(unsigned int flags, unsigned int access_rights);
int pkey_mprotect(void *addr, size_t len, int prot, int pkey);
int pkey_free(int pkey);

/* permission switches via glibc wrapper (WRPKRU instruction): */
int pkey_set(int pkey, unsigned int access_rights);
int pkey_get(int pkey);

Rod's take

mprotect(2) costs a syscall. For setup code that runs once, fine. For a JIT that toggles W^X on every emit pass, not fine. PKU fixes this: pkey_mprotect stamps a key number onto the VMA, and WRPKRU changes the per-thread PKRU register to enable or disable that key without ever entering the kernel. It's x86-only (Intel Skylake+, AMD Zen 1+), you get 15 usable keys, and the signal handler footgun will catch you: PKRU is inherited at signal delivery, so if your handler touches a disabled domain it faults. Read PKRU at handler entry and unlock what you need. Check for ENOSYS on every architecture you ship to. Everything else is straightforward.

Flags and arguments that matter

NameWhat it does
PKEY_DISABLE_ACCESSSet the Access Disable bit in PKRU for this key: any read or write to tagged memory faults with SIGSEGV.
PKEY_DISABLE_WRITESet the Write Disable bit in PKRU for this key: writes fault, reads are allowed.

Errors you will actually hit

errnoMeaning
ENOSYSKernel older than 4.8, or CPU lacks PKU feature (non-x86, or Intel pre-Skylake / AMD pre-Zen).
EINVALflags is non-zero, or access_rights contains bits other than PKEY_DISABLE_ACCESS and PKEY_DISABLE_WRITE.
ENOSPCAll 15 user-allocatable pkeys (1-15) are in use; pkey 0 is implicit and never allocatable.

Example

#include <sys/mman.h>
#include <sys/pkey.h>
#include <err.h>
#include <string.h>

int pk = pkey_alloc(0, PKEY_DISABLE_ACCESS);
if (pk == -1)
    err(1, "pkey_alloc");  /* ENOSYS on non-x86 or kernel < 4.8 */

void *buf = mmap(NULL, 4096, PROT_READ | PROT_WRITE,
                 MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
if (buf == MAP_FAILED)
    err(1, "mmap");

if (pkey_mprotect(buf, 4096, PROT_READ | PROT_WRITE, pk) == -1)
    err(1, "pkey_mprotect");

/* access here faults (SIGSEGV) */

pkey_set(pk, 0);                     /* unlock: one WRPKRU, no syscall */
memcpy(buf, key_material, key_len);
pkey_set(pk, PKEY_DISABLE_ACCESS);   /* lock again */

pkey_free(pk);

Trimmed from the full walkthrough in pkey_mprotect: in-process memory isolation without a syscall.

BSD equivalent

FreeBSD: no equivalent in base as of 14.2. ARM: MTE (Memory Tagging Extension, ARMv8.5-A) provides a related but incompatible mechanism. OpenBSD: none.

The long version, from the console

Sources

  1. pkey_alloc(2), man7.org
  2. pkey_mprotect(2), man7.org

Version numbers are read off the cited man pages, not memory. Spot one that's wrong? Tell Rod.