b.liu | e958203 | 2025-04-17 19:18:16 +0800 | [diff] [blame^] | 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(&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(0, PKEY_DISABLE_EXECUTE); |
| 83 | if (pkey1 < 0) { |
| 84 | pkey1 = sys_pkey_alloc(0, 0); |
| 85 | CHILD_FAIL_IF(pkey1 < 0, &info->child_sync); |
| 86 | |
| 87 | disable_execute = false; |
| 88 | } |
| 89 | |
| 90 | pkey2 = sys_pkey_alloc(0, 0); |
| 91 | CHILD_FAIL_IF(pkey2 < 0, &info->child_sync); |
| 92 | |
| 93 | pkey3 = sys_pkey_alloc(0, 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 | mtspr(SPRN_AMR, info->amr1); |
| 129 | |
| 130 | /* Wait for parent to read our AMR value and write a new one. */ |
| 131 | ret = prod_parent(&info->child_sync); |
| 132 | CHILD_FAIL_IF(ret, &info->child_sync); |
| 133 | |
| 134 | ret = wait_parent(&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(&info->child_sync); |
| 148 | CHILD_FAIL_IF(ret, &info->child_sync); |
| 149 | |
| 150 | ret = wait_parent(&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(&info->child_sync); |
| 165 | CHILD_FAIL_IF(ret, &info->child_sync); |
| 166 | |
| 167 | ret = wait_parent(&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(&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(pid, NT_PPC_PKEY, regs, 3); |
| 195 | PARENT_SKIP_IF_UNSUPPORTED(ret, &info->child_sync); |
| 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(&info->child_sync); |
| 204 | PARENT_FAIL_IF(ret, &info->child_sync); |
| 205 | |
| 206 | ret = wait_child(&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(pid, NT_PPC_PKEY, regs, 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(pid, NT_PPC_PKEY, &info->amr2, 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(&info->child_sync); |
| 229 | PARENT_FAIL_IF(ret, &info->child_sync); |
| 230 | |
| 231 | ret = wait_child(&info->child_sync); |
| 232 | if (ret) |
| 233 | return ret; |
| 234 | |
| 235 | /* Write invalid AMR value in child. */ |
| 236 | ret = ptrace_write_regs(pid, NT_PPC_PKEY, &info->invalid_amr, 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(&info->child_sync); |
| 243 | PARENT_FAIL_IF(ret, &info->child_sync); |
| 244 | |
| 245 | ret = wait_child(&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(pid, NT_PPC_PKEY, regs, 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(pid, NT_PPC_PKEY, regs, 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(pid, NT_PPC_PKEY, regs, 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(&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(&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(&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 | } |