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
| Name | What it does |
|---|---|
| PKEY_DISABLE_ACCESS | Set the Access Disable bit in PKRU for this key: any read or write to tagged memory faults with SIGSEGV. |
| PKEY_DISABLE_WRITE | Set the Write Disable bit in PKRU for this key: writes fault, reads are allowed. |
Errors you will actually hit
| errno | Meaning |
|---|---|
| ENOSYS | Kernel older than 4.8, or CPU lacks PKU feature (non-x86, or Intel pre-Skylake / AMD pre-Zen). |
| EINVAL | flags is non-zero, or access_rights contains bits other than PKEY_DISABLE_ACCESS and PKEY_DISABLE_WRITE. |
| ENOSPC | All 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
Version numbers are read off the cited man pages, not memory. Spot one that's wrong? Tell Rod.
