本文将从motivation、usage demo和硬件实现来介绍PKU(Memory Protection Keys for Userspace)。

motivation

Memory Protection Keys (pkeys) are an extension to existing page-based memory permissions. Normal page permissions using page tables require expensive system calls and TLB invalidations when changing permissions. Memory Protection Keys provide a mechanism for changing protections without requiring modification of the page tables on every permission change.

usage demo

To use pkeys, software must first “tag” a page in the page tables with a pkey. After this tag is in place, an application only has to change the contents of a register in order to remove write access, or all access to a tagged page.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
#define _GNU_SOURCE
#include <unistd.h>
#include <sys/syscall.h>
#include <stdio.h>
#include <sys/mman.h>

static inline void
wrpkru(unsigned int pkru)
{
unsigned int eax = pkru;
unsigned int ecx = 0;
unsigned int edx = 0;

asm volatile(".byte 0x0f,0x01,0xef\n\t"
: : "a" (eax), "c" (ecx), "d" (edx));
}

int
pkey_set(int pkey, unsigned long rights, unsigned long flags)
{
unsigned int pkru = (rights << (2 * pkey));
return wrpkru(pkru);
}

int
pkey_mprotect(void *ptr, size_t size, unsigned long orig_prot,
unsigned long pkey)
{
return syscall(SYS_pkey_mprotect, ptr, size, orig_prot, pkey);
}

int
pkey_alloc(void)
{
return syscall(SYS_pkey_alloc, 0, 0);
}

int
pkey_free(unsigned long pkey)
{
return syscall(SYS_pkey_free, pkey);
}

#define errExit(msg) do { perror(msg); exit(EXIT_FAILURE); \
} while (0)

int
main(void)
{
int status;
int pkey;
int *buffer;

/*
*Allocate one page of memory
*/
buffer = mmap(NULL, getpagesize(), PROT_READ | PROT_WRITE,
MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
if (buffer == MAP_FAILED)
errExit("mmap");

/*
* Put some random data into the page (still OK to touch)
*/
*buffer = __LINE__;
printf("buffer contains: %d\n", *buffer);

/*
* Allocate a protection key:
*/
pkey = pkey_alloc();
if (pkey == -1)
errExit("pkey_alloc");

/*
* Disable access to any memory with "pkey" set,
* even though there is none right now
*/
status = pkey_set(pkey, PKEY_DISABLE_ACCESS, 0);
if (status)
errExit("pkey_set");

/*
* Set the protection key on "buffer".
* Note that it is still read/write as far as mprotect() is
* concerned and the previous pkey_set() overrides it.
*/
status = pkey_mprotect(buffer, getpagesize(),
PROT_READ | PROT_WRITE, pkey);
if (status == -1)
errExit("pkey_mprotect");

printf("about to read buffer again...\n");

/*
* This will crash, because we have disallowed access
*/
printf("buffer contains: %d\n", *buffer);

status = pkey_free(pkey);
if (status == -1)
errExit("pkey_free");

exit(EXIT_SUCCESS);
}

The program below allocates a page of memory with read and write permissions. It then writes some data to the memory and successfully reads it back. After that, it attempts to allocate a protection key and disallows access to the page by using the WRPKRU instruction. It then tries to access the page, which we now expect to cause a fatal signal to the application.

1
2
3
4
$ ./a.out
buffer contains: 73
about to read buffer again...
Segmentation fault (core dumped)

硬件实现

PKU的底层实现依赖于protection key rights register for user pages (PKRU) 。


参考资料:

  1. pkeys - overview of Memory Protection Keys
  2. protection-keys.txt
  3. x86: Memory Protection Keys
  4. Intel SDM