1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /*
3 * Copyright (c) 2019 Red Hat, Inc.
4 *
5 * Memory Protection Keys for Userspace (PKU aka PKEYs) is a Skylake-SP
6 * server feature that provides a mechanism for enforcing page-based
7 * protections, but without requiring modification of the page tables
8 * when an application changes protection domains. It works by dedicating
9 * 4 previously ignored bits in each page table entry to a "protection key",
10 * giving 16 possible keys.
11 *
12 * Basic method for PKEYs testing:
13 * 1. test allocates a pkey(e.g. PKEY_DISABLE_ACCESS) via pkey_alloc()
14 * 2. pkey_mprotect() apply this pkey to a piece of memory(buffer)
15 * 3. check if access right of the buffer has been changed and take effect
16 * 4. remove the access right(pkey) from this buffer via pkey_mprotect()
17 * 5. check if buffer area can be read or write after removing pkey
18 * 6. pkey_free() releases the pkey after using it
19 *
20 * Looping around this basic test on diffenrent types of memory.
21 */
22
23 #define _GNU_SOURCE
24 #include <stdio.h>
25 #include <unistd.h>
26 #include <errno.h>
27 #include <stdlib.h>
28 #include <sys/syscall.h>
29 #include <sys/mman.h>
30 #include <sys/wait.h>
31
32 #include "pkey.h"
33
34 #define TEST_FILE "pkey_testfile"
35 #define STR "abcdefghijklmnopqrstuvwxyz12345\n"
36 #define PATH_VM_NRHPS "/proc/sys/vm/nr_hugepages"
37
38 static int size;
39 static int no_hugepage;
40
41 static const char * const save_restore[] = {
42 "?/proc/sys/vm/nr_hugepages",
43 NULL,
44 };
45
46 static struct tcase {
47 unsigned long flags;
48 unsigned long access_rights;
49 char *name;
50 } tcases[] = {
51 {0, PKEY_DISABLE_ACCESS, "PKEY_DISABLE_ACCESS"},
52 {0, PKEY_DISABLE_WRITE, "PKEY_DISABLE_WRITE"},
53 };
54
setup(void)55 static void setup(void)
56 {
57 int i, fd;
58
59 if (access("/sys/kernel/mm/hugepages/", F_OK)) {
60 tst_res(TINFO, "Huge page is not supported");
61 size = getpagesize();
62 no_hugepage = 1;
63 } else {
64 int val;
65 SAFE_FILE_PRINTF(PATH_VM_NRHPS, "%d", 1);
66 SAFE_FILE_SCANF(PATH_VM_NRHPS, "%d", &val);
67 if (val != 1)
68 tst_brk(TBROK, "nr_hugepages = %d, but expect %d",
69 val, 1);
70 size = SAFE_READ_MEMINFO("Hugepagesize:") * 1024;
71 }
72
73 check_pkey_support();
74
75 fd = SAFE_OPEN(TEST_FILE, O_RDWR | O_CREAT, 0664);
76 for (i = 0; i < 128; i++)
77 SAFE_WRITE(1, fd, STR, strlen(STR));
78
79 SAFE_CLOSE(fd);
80 }
81
82 static struct mmap_param {
83 int prot;
84 int flags;
85 int fd;
86 } mmap_params[] = {
87 {PROT_READ, MAP_ANONYMOUS | MAP_PRIVATE, -1},
88 {PROT_READ, MAP_ANONYMOUS | MAP_SHARED, -1},
89 {PROT_READ, MAP_ANONYMOUS | MAP_PRIVATE | MAP_HUGETLB, -1},
90 {PROT_READ, MAP_ANONYMOUS | MAP_SHARED | MAP_HUGETLB, -1},
91 {PROT_READ, MAP_PRIVATE, 0},
92 {PROT_READ, MAP_SHARED, 0},
93
94 {PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, -1},
95 {PROT_WRITE, MAP_ANONYMOUS | MAP_SHARED, -1},
96 {PROT_WRITE, MAP_PRIVATE, 0},
97 {PROT_WRITE, MAP_SHARED, 0},
98 {PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE | MAP_HUGETLB, -1},
99 {PROT_WRITE, MAP_ANONYMOUS | MAP_SHARED | MAP_HUGETLB, -1},
100
101 {PROT_EXEC, MAP_ANONYMOUS | MAP_PRIVATE, -1},
102 {PROT_EXEC, MAP_ANONYMOUS | MAP_SHARED, -1},
103 {PROT_EXEC, MAP_ANONYMOUS | MAP_PRIVATE | MAP_HUGETLB, -1},
104 {PROT_EXEC, MAP_ANONYMOUS | MAP_SHARED | MAP_HUGETLB, -1},
105 {PROT_EXEC, MAP_PRIVATE, 0},
106 {PROT_EXEC, MAP_SHARED, 0},
107
108 {PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, -1},
109 {PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_SHARED, -1},
110 {PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE | MAP_HUGETLB, -1},
111 {PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_SHARED | MAP_HUGETLB, -1},
112 {PROT_READ | PROT_WRITE, MAP_PRIVATE, 0},
113 {PROT_READ | PROT_WRITE, MAP_SHARED, 0},
114
115 {PROT_READ | PROT_WRITE | PROT_EXEC, MAP_ANONYMOUS | MAP_PRIVATE, -1},
116 {PROT_READ | PROT_WRITE | PROT_EXEC, MAP_ANONYMOUS | MAP_SHARED, -1},
117 {PROT_READ | PROT_WRITE | PROT_EXEC, MAP_ANONYMOUS | MAP_PRIVATE | MAP_HUGETLB, -1},
118 {PROT_READ | PROT_WRITE | PROT_EXEC, MAP_ANONYMOUS | MAP_SHARED | MAP_HUGETLB, -1},
119 {PROT_READ | PROT_WRITE | PROT_EXEC, MAP_PRIVATE, 0},
120 {PROT_READ | PROT_WRITE | PROT_EXEC, MAP_SHARED, 0},
121 };
122
flag_to_str(int flags)123 static char *flag_to_str(int flags)
124 {
125 switch (flags) {
126 case MAP_PRIVATE:
127 return "MAP_PRIVATE";
128 case MAP_SHARED:
129 return "MAP_SHARED";
130 case MAP_ANONYMOUS | MAP_PRIVATE:
131 return "MAP_ANONYMOUS|MAP_PRIVATE";
132 case MAP_ANONYMOUS | MAP_SHARED:
133 return "MAP_ANONYMOUS|MAP_SHARED";
134 case MAP_ANONYMOUS | MAP_PRIVATE | MAP_HUGETLB:
135 return "MAP_ANONYMOUS|MAP_PRIVATE|MAP_HUGETLB";
136 case MAP_ANONYMOUS | MAP_SHARED | MAP_HUGETLB:
137 return "MAP_ANONYMOUS|MAP_SHARED|MAP_HUGETLB";
138 default:
139 return "UNKNOWN FLAGS";
140 }
141 }
142
pkey_test(struct tcase * tc,struct mmap_param * mpa)143 static void pkey_test(struct tcase *tc, struct mmap_param *mpa)
144 {
145 pid_t pid;
146 char *buffer;
147 int pkey, status;
148 int fd = mpa->fd;
149
150 if (no_hugepage && (mpa->flags & MAP_HUGETLB)) {
151 tst_res(TINFO, "Skip test on (%s) buffer", flag_to_str(mpa->flags));
152 return;
153 }
154
155 if (fd == 0)
156 fd = SAFE_OPEN(TEST_FILE, O_RDWR | O_CREAT, 0664);
157
158 buffer = SAFE_MMAP(NULL, size, mpa->prot, mpa->flags, fd, 0);
159
160 pkey = ltp_pkey_alloc(tc->flags, tc->access_rights);
161 if (pkey == -1)
162 tst_brk(TBROK | TERRNO, "pkey_alloc failed");
163
164 tst_res(TINFO, "Set %s on (%s) buffer", tc->name, flag_to_str(mpa->flags));
165 if (ltp_pkey_mprotect(buffer, size, mpa->prot, pkey) == -1)
166 tst_brk(TBROK | TERRNO, "pkey_mprotect failed");
167
168 pid = SAFE_FORK();
169 if (pid == 0) {
170 tst_no_corefile(0);
171
172 switch (tc->access_rights) {
173 case PKEY_DISABLE_ACCESS:
174 tst_res(TFAIL | TERRNO,
175 "Read buffer success, buffer[0] = %d", *buffer);
176 break;
177 case PKEY_DISABLE_WRITE:
178 *buffer = 'a';
179 break;
180 }
181 exit(0);
182 }
183
184 SAFE_WAITPID(pid, &status, 0);
185
186 if (WIFSIGNALED(status) && WTERMSIG(status) == SIGSEGV)
187 tst_res(TPASS, "Child ended by %s as expected", tst_strsig(SIGSEGV));
188 else
189 tst_res(TFAIL, "Child: %s", tst_strstatus(status));
190
191 tst_res(TINFO, "Remove %s from the buffer", tc->name);
192 if (ltp_pkey_mprotect(buffer, size, mpa->prot, 0x0) == -1)
193 tst_brk(TBROK | TERRNO, "pkey_mprotect failed");
194
195 switch (mpa->prot) {
196 case PROT_READ:
197 tst_res(TPASS, "Read buffer success, buffer[0] = %d", *buffer);
198 break;
199 case PROT_WRITE:
200 *buffer = 'a';
201 break;
202 case PROT_READ | PROT_WRITE:
203 case PROT_READ | PROT_WRITE | PROT_EXEC:
204 *buffer = 'a';
205 tst_res(TPASS, "Read & Write buffer success, buffer[0] = %d", *buffer);
206 break;
207 }
208
209 if (fd >= 0)
210 SAFE_CLOSE(fd);
211
212 SAFE_MUNMAP(buffer, size);
213
214 if (ltp_pkey_free(pkey) == -1)
215 tst_brk(TBROK | TERRNO, "pkey_free failed");
216 }
217
verify_pkey(unsigned int i)218 static void verify_pkey(unsigned int i)
219 {
220 long unsigned int j;
221 struct mmap_param *mpa;
222
223 struct tcase *tc = &tcases[i];
224
225 for (j = 0; j < ARRAY_SIZE(mmap_params); j++) {
226 mpa = &mmap_params[j];
227
228 pkey_test(tc, mpa);
229 }
230 }
231
232 static struct tst_test test = {
233 .tcnt = ARRAY_SIZE(tcases),
234 .needs_root = 1,
235 .needs_tmpdir = 1,
236 .forks_child = 1,
237 .test = verify_pkey,
238 .setup = setup,
239 .save_restore = save_restore,
240 };
241