1 | // SPDX-License-Identifier: GPL-2.0+ |
2 | /* |
3 | * Ptrace test for Memory Protection Key registers |
4 | * |
5 | * Copyright (C) 2015 Anshuman Khandual, IBM Corporation. |
6 | * Copyright (C) 2018 IBM Corporation. |
7 | */ |
8 | #include "ptrace.h" |
9 | #include "child.h" |
10 | |
11 | #ifndef __NR_pkey_alloc |
12 | #define __NR_pkey_alloc 384 |
13 | #endif |
14 | |
15 | #ifndef __NR_pkey_free |
16 | #define __NR_pkey_free 385 |
17 | #endif |
18 | |
19 | #ifndef NT_PPC_PKEY |
20 | #define NT_PPC_PKEY 0x110 |
21 | #endif |
22 | |
23 | #ifndef PKEY_DISABLE_EXECUTE |
24 | #define PKEY_DISABLE_EXECUTE 0x4 |
25 | #endif |
26 | |
27 | #define AMR_BITS_PER_PKEY 2 |
28 | #define PKEY_REG_BITS (sizeof(u64) * 8) |
29 | #define pkeyshift(pkey) (PKEY_REG_BITS - ((pkey + 1) * AMR_BITS_PER_PKEY)) |
30 | |
31 | static const char user_read[] = "[User Read (Running)]" ; |
32 | static const char user_write[] = "[User Write (Running)]" ; |
33 | static const char ptrace_read_running[] = "[Ptrace Read (Running)]" ; |
34 | static const char ptrace_write_running[] = "[Ptrace Write (Running)]" ; |
35 | |
36 | /* Information shared between the parent and the child. */ |
37 | struct shared_info { |
38 | struct child_sync child_sync; |
39 | |
40 | /* AMR value the parent expects to read from the child. */ |
41 | unsigned long amr1; |
42 | |
43 | /* AMR value the parent is expected to write to the child. */ |
44 | unsigned long amr2; |
45 | |
46 | /* AMR value that ptrace should refuse to write to the child. */ |
47 | unsigned long invalid_amr; |
48 | |
49 | /* IAMR value the parent expects to read from the child. */ |
50 | unsigned long expected_iamr; |
51 | |
52 | /* UAMOR value the parent expects to read from the child. */ |
53 | unsigned long expected_uamor; |
54 | |
55 | /* |
56 | * IAMR and UAMOR values that ptrace should refuse to write to the child |
57 | * (even though they're valid ones) because userspace doesn't have |
58 | * access to those registers. |
59 | */ |
60 | unsigned long invalid_iamr; |
61 | unsigned long invalid_uamor; |
62 | }; |
63 | |
64 | static int sys_pkey_alloc(unsigned long flags, unsigned long init_access_rights) |
65 | { |
66 | return syscall(__NR_pkey_alloc, flags, init_access_rights); |
67 | } |
68 | |
69 | static int child(struct shared_info *info) |
70 | { |
71 | unsigned long reg; |
72 | bool disable_execute = true; |
73 | int pkey1, pkey2, pkey3; |
74 | int ret; |
75 | |
76 | /* Wait until parent fills out the initial register values. */ |
77 | ret = wait_parent(sync: &info->child_sync); |
78 | if (ret) |
79 | return ret; |
80 | |
81 | /* Get some pkeys so that we can change their bits in the AMR. */ |
82 | pkey1 = sys_pkey_alloc(flags: 0, PKEY_DISABLE_EXECUTE); |
83 | if (pkey1 < 0) { |
84 | pkey1 = sys_pkey_alloc(flags: 0, init_access_rights: 0); |
85 | CHILD_FAIL_IF(pkey1 < 0, &info->child_sync); |
86 | |
87 | disable_execute = false; |
88 | } |
89 | |
90 | pkey2 = sys_pkey_alloc(flags: 0, init_access_rights: 0); |
91 | CHILD_FAIL_IF(pkey2 < 0, &info->child_sync); |
92 | |
93 | pkey3 = sys_pkey_alloc(flags: 0, init_access_rights: 0); |
94 | CHILD_FAIL_IF(pkey3 < 0, &info->child_sync); |
95 | |
96 | info->amr1 |= 3ul << pkeyshift(pkey1); |
97 | info->amr2 |= 3ul << pkeyshift(pkey2); |
98 | /* |
99 | * invalid amr value where we try to force write |
100 | * things which are deined by a uamor setting. |
101 | */ |
102 | info->invalid_amr = info->amr2 | (~0x0UL & ~info->expected_uamor); |
103 | |
104 | /* |
105 | * if PKEY_DISABLE_EXECUTE succeeded we should update the expected_iamr |
106 | */ |
107 | if (disable_execute) |
108 | info->expected_iamr |= 1ul << pkeyshift(pkey1); |
109 | else |
110 | info->expected_iamr &= ~(1ul << pkeyshift(pkey1)); |
111 | |
112 | /* |
113 | * We allocated pkey2 and pkey 3 above. Clear the IAMR bits. |
114 | */ |
115 | info->expected_iamr &= ~(1ul << pkeyshift(pkey2)); |
116 | info->expected_iamr &= ~(1ul << pkeyshift(pkey3)); |
117 | |
118 | /* |
119 | * Create an IAMR value different from expected value. |
120 | * Kernel will reject an IAMR and UAMOR change. |
121 | */ |
122 | info->invalid_iamr = info->expected_iamr | (1ul << pkeyshift(pkey1) | 1ul << pkeyshift(pkey2)); |
123 | info->invalid_uamor = info->expected_uamor & ~(0x3ul << pkeyshift(pkey1)); |
124 | |
125 | printf("%-30s AMR: %016lx pkey1: %d pkey2: %d pkey3: %d\n" , |
126 | user_write, info->amr1, pkey1, pkey2, pkey3); |
127 | |
128 | set_amr(info->amr1); |
129 | |
130 | /* Wait for parent to read our AMR value and write a new one. */ |
131 | ret = prod_parent(sync: &info->child_sync); |
132 | CHILD_FAIL_IF(ret, &info->child_sync); |
133 | |
134 | ret = wait_parent(sync: &info->child_sync); |
135 | if (ret) |
136 | return ret; |
137 | |
138 | reg = mfspr(SPRN_AMR); |
139 | |
140 | printf("%-30s AMR: %016lx\n" , user_read, reg); |
141 | |
142 | CHILD_FAIL_IF(reg != info->amr2, &info->child_sync); |
143 | |
144 | /* |
145 | * Wait for parent to try to write an invalid AMR value. |
146 | */ |
147 | ret = prod_parent(sync: &info->child_sync); |
148 | CHILD_FAIL_IF(ret, &info->child_sync); |
149 | |
150 | ret = wait_parent(sync: &info->child_sync); |
151 | if (ret) |
152 | return ret; |
153 | |
154 | reg = mfspr(SPRN_AMR); |
155 | |
156 | printf("%-30s AMR: %016lx\n" , user_read, reg); |
157 | |
158 | CHILD_FAIL_IF(reg != info->amr2, &info->child_sync); |
159 | |
160 | /* |
161 | * Wait for parent to try to write an IAMR and a UAMOR value. We can't |
162 | * verify them, but we can verify that the AMR didn't change. |
163 | */ |
164 | ret = prod_parent(sync: &info->child_sync); |
165 | CHILD_FAIL_IF(ret, &info->child_sync); |
166 | |
167 | ret = wait_parent(sync: &info->child_sync); |
168 | if (ret) |
169 | return ret; |
170 | |
171 | reg = mfspr(SPRN_AMR); |
172 | |
173 | printf("%-30s AMR: %016lx\n" , user_read, reg); |
174 | |
175 | CHILD_FAIL_IF(reg != info->amr2, &info->child_sync); |
176 | |
177 | /* Now let parent now that we are finished. */ |
178 | |
179 | ret = prod_parent(sync: &info->child_sync); |
180 | CHILD_FAIL_IF(ret, &info->child_sync); |
181 | |
182 | return TEST_PASS; |
183 | } |
184 | |
185 | static int parent(struct shared_info *info, pid_t pid) |
186 | { |
187 | unsigned long regs[3]; |
188 | int ret, status; |
189 | |
190 | /* |
191 | * Get the initial values for AMR, IAMR and UAMOR and communicate them |
192 | * to the child. |
193 | */ |
194 | ret = ptrace_read_regs(child: pid, NT_PPC_PKEY, regs, n: 3); |
195 | PARENT_SKIP_IF_UNSUPPORTED(ret, &info->child_sync, "PKEYs not supported" ); |
196 | PARENT_FAIL_IF(ret, &info->child_sync); |
197 | |
198 | info->amr1 = info->amr2 = regs[0]; |
199 | info->expected_iamr = regs[1]; |
200 | info->expected_uamor = regs[2]; |
201 | |
202 | /* Wake up child so that it can set itself up. */ |
203 | ret = prod_child(sync: &info->child_sync); |
204 | PARENT_FAIL_IF(ret, &info->child_sync); |
205 | |
206 | ret = wait_child(sync: &info->child_sync); |
207 | if (ret) |
208 | return ret; |
209 | |
210 | /* Verify that we can read the pkey registers from the child. */ |
211 | ret = ptrace_read_regs(child: pid, NT_PPC_PKEY, regs, n: 3); |
212 | PARENT_FAIL_IF(ret, &info->child_sync); |
213 | |
214 | printf("%-30s AMR: %016lx IAMR: %016lx UAMOR: %016lx\n" , |
215 | ptrace_read_running, regs[0], regs[1], regs[2]); |
216 | |
217 | PARENT_FAIL_IF(regs[0] != info->amr1, &info->child_sync); |
218 | PARENT_FAIL_IF(regs[1] != info->expected_iamr, &info->child_sync); |
219 | PARENT_FAIL_IF(regs[2] != info->expected_uamor, &info->child_sync); |
220 | |
221 | /* Write valid AMR value in child. */ |
222 | ret = ptrace_write_regs(child: pid, NT_PPC_PKEY, regs: &info->amr2, n: 1); |
223 | PARENT_FAIL_IF(ret, &info->child_sync); |
224 | |
225 | printf("%-30s AMR: %016lx\n" , ptrace_write_running, info->amr2); |
226 | |
227 | /* Wake up child so that it can verify it changed. */ |
228 | ret = prod_child(sync: &info->child_sync); |
229 | PARENT_FAIL_IF(ret, &info->child_sync); |
230 | |
231 | ret = wait_child(sync: &info->child_sync); |
232 | if (ret) |
233 | return ret; |
234 | |
235 | /* Write invalid AMR value in child. */ |
236 | ret = ptrace_write_regs(child: pid, NT_PPC_PKEY, regs: &info->invalid_amr, n: 1); |
237 | PARENT_FAIL_IF(ret, &info->child_sync); |
238 | |
239 | printf("%-30s AMR: %016lx\n" , ptrace_write_running, info->invalid_amr); |
240 | |
241 | /* Wake up child so that it can verify it didn't change. */ |
242 | ret = prod_child(sync: &info->child_sync); |
243 | PARENT_FAIL_IF(ret, &info->child_sync); |
244 | |
245 | ret = wait_child(sync: &info->child_sync); |
246 | if (ret) |
247 | return ret; |
248 | |
249 | /* Try to write to IAMR. */ |
250 | regs[0] = info->amr1; |
251 | regs[1] = info->invalid_iamr; |
252 | ret = ptrace_write_regs(child: pid, NT_PPC_PKEY, regs, n: 2); |
253 | PARENT_FAIL_IF(!ret, &info->child_sync); |
254 | |
255 | printf("%-30s AMR: %016lx IAMR: %016lx\n" , |
256 | ptrace_write_running, regs[0], regs[1]); |
257 | |
258 | /* Try to write to IAMR and UAMOR. */ |
259 | regs[2] = info->invalid_uamor; |
260 | ret = ptrace_write_regs(child: pid, NT_PPC_PKEY, regs, n: 3); |
261 | PARENT_FAIL_IF(!ret, &info->child_sync); |
262 | |
263 | printf("%-30s AMR: %016lx IAMR: %016lx UAMOR: %016lx\n" , |
264 | ptrace_write_running, regs[0], regs[1], regs[2]); |
265 | |
266 | /* Verify that all registers still have their expected values. */ |
267 | ret = ptrace_read_regs(child: pid, NT_PPC_PKEY, regs, n: 3); |
268 | PARENT_FAIL_IF(ret, &info->child_sync); |
269 | |
270 | printf("%-30s AMR: %016lx IAMR: %016lx UAMOR: %016lx\n" , |
271 | ptrace_read_running, regs[0], regs[1], regs[2]); |
272 | |
273 | PARENT_FAIL_IF(regs[0] != info->amr2, &info->child_sync); |
274 | PARENT_FAIL_IF(regs[1] != info->expected_iamr, &info->child_sync); |
275 | PARENT_FAIL_IF(regs[2] != info->expected_uamor, &info->child_sync); |
276 | |
277 | /* Wake up child so that it can verify AMR didn't change and wrap up. */ |
278 | ret = prod_child(sync: &info->child_sync); |
279 | PARENT_FAIL_IF(ret, &info->child_sync); |
280 | |
281 | ret = wait(&status); |
282 | if (ret != pid) { |
283 | printf("Child's exit status not captured\n" ); |
284 | ret = TEST_PASS; |
285 | } else if (!WIFEXITED(status)) { |
286 | printf("Child exited abnormally\n" ); |
287 | ret = TEST_FAIL; |
288 | } else |
289 | ret = WEXITSTATUS(status) ? TEST_FAIL : TEST_PASS; |
290 | |
291 | return ret; |
292 | } |
293 | |
294 | static int ptrace_pkey(void) |
295 | { |
296 | struct shared_info *info; |
297 | int shm_id; |
298 | int ret; |
299 | pid_t pid; |
300 | |
301 | shm_id = shmget(IPC_PRIVATE, sizeof(*info), 0777 | IPC_CREAT); |
302 | info = shmat(shm_id, NULL, 0); |
303 | |
304 | ret = init_child_sync(sync: &info->child_sync); |
305 | if (ret) |
306 | return ret; |
307 | |
308 | pid = fork(); |
309 | if (pid < 0) { |
310 | perror("fork() failed" ); |
311 | ret = TEST_FAIL; |
312 | } else if (pid == 0) |
313 | ret = child(info); |
314 | else |
315 | ret = parent(info, pid); |
316 | |
317 | shmdt(info); |
318 | |
319 | if (pid) { |
320 | destroy_child_sync(sync: &info->child_sync); |
321 | shmctl(shm_id, IPC_RMID, NULL); |
322 | } |
323 | |
324 | return ret; |
325 | } |
326 | |
327 | int main(int argc, char *argv[]) |
328 | { |
329 | return test_harness(ptrace_pkey, "ptrace_pkey" ); |
330 | } |
331 | |