• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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