1 // SPDX-License-Identifier: GPL-2.0+ 2 /* 3 * Copyright (c) 2018-2025 Dmitry V. Levin <ldv@strace.io> 4 * All rights reserved. 5 * 6 * Check whether PTRACE_SET_SYSCALL_INFO semantics implemented in the kernel 7 * matches userspace expectations. 8 */ 9 10 #include "../kselftest_harness.h" 11 #include <err.h> 12 #include <fcntl.h> 13 #include <signal.h> 14 #include <asm/unistd.h> 15 #include <linux/types.h> 16 #include <linux/ptrace.h> 17 18 #if defined(_MIPS_SIM) && _MIPS_SIM == _MIPS_SIM_NABI32 19 /* 20 * MIPS N32 is the only architecture where __kernel_ulong_t 21 * does not match the bitness of syscall arguments. 22 */ 23 typedef unsigned long long kernel_ulong_t; 24 #else 25 typedef __kernel_ulong_t kernel_ulong_t; 26 #endif 27 28 struct si_entry { 29 int nr; 30 kernel_ulong_t args[6]; 31 }; 32 struct si_exit { 33 unsigned int is_error; 34 int rval; 35 }; 36 37 static unsigned int ptrace_stop; 38 static pid_t tracee_pid; 39 40 static int 41 kill_tracee(pid_t pid) 42 { 43 if (!pid) 44 return 0; 45 46 int saved_errno = errno; 47 48 int rc = kill(pid, SIGKILL); 49 50 errno = saved_errno; 51 return rc; 52 } 53 54 static long 55 sys_ptrace(int request, pid_t pid, unsigned long addr, unsigned long data) 56 { 57 return syscall(__NR_ptrace, request, pid, addr, data); 58 } 59 60 #define LOG_KILL_TRACEE(fmt, ...) \ 61 do { \ 62 kill_tracee(tracee_pid); \ 63 TH_LOG("wait #%d: " fmt, \ 64 ptrace_stop, ##__VA_ARGS__); \ 65 } while (0) 66 67 static void 68 check_psi_entry(struct __test_metadata *_metadata, 69 const struct ptrace_syscall_info *info, 70 const struct si_entry *exp_entry, 71 const char *text) 72 { 73 unsigned int i; 74 int exp_nr = exp_entry->nr; 75 #if defined __s390__ || defined __s390x__ 76 /* s390 is the only architecture that has 16-bit syscall numbers */ 77 exp_nr &= 0xffff; 78 #endif 79 80 ASSERT_EQ(PTRACE_SYSCALL_INFO_ENTRY, info->op) { 81 LOG_KILL_TRACEE("%s: entry stop mismatch", text); 82 } 83 ASSERT_TRUE(info->arch) { 84 LOG_KILL_TRACEE("%s: entry stop mismatch", text); 85 } 86 ASSERT_TRUE(info->instruction_pointer) { 87 LOG_KILL_TRACEE("%s: entry stop mismatch", text); 88 } 89 ASSERT_TRUE(info->stack_pointer) { 90 LOG_KILL_TRACEE("%s: entry stop mismatch", text); 91 } 92 ASSERT_EQ(exp_nr, info->entry.nr) { 93 LOG_KILL_TRACEE("%s: syscall nr mismatch", text); 94 } 95 for (i = 0; i < ARRAY_SIZE(exp_entry->args); ++i) { 96 ASSERT_EQ(exp_entry->args[i], info->entry.args[i]) { 97 LOG_KILL_TRACEE("%s: syscall arg #%u mismatch", 98 text, i); 99 } 100 } 101 } 102 103 static void 104 check_psi_exit(struct __test_metadata *_metadata, 105 const struct ptrace_syscall_info *info, 106 const struct si_exit *exp_exit, 107 const char *text) 108 { 109 ASSERT_EQ(PTRACE_SYSCALL_INFO_EXIT, info->op) { 110 LOG_KILL_TRACEE("%s: exit stop mismatch", text); 111 } 112 ASSERT_TRUE(info->arch) { 113 LOG_KILL_TRACEE("%s: exit stop mismatch", text); 114 } 115 ASSERT_TRUE(info->instruction_pointer) { 116 LOG_KILL_TRACEE("%s: exit stop mismatch", text); 117 } 118 ASSERT_TRUE(info->stack_pointer) { 119 LOG_KILL_TRACEE("%s: exit stop mismatch", text); 120 } 121 ASSERT_EQ(exp_exit->is_error, info->exit.is_error) { 122 LOG_KILL_TRACEE("%s: exit stop mismatch", text); 123 } 124 ASSERT_EQ(exp_exit->rval, info->exit.rval) { 125 LOG_KILL_TRACEE("%s: exit stop mismatch", text); 126 } 127 } 128 129 TEST(set_syscall_info) 130 { 131 const pid_t tracer_pid = getpid(); 132 const kernel_ulong_t dummy[] = { 133 (kernel_ulong_t) 0xdad0bef0bad0fed0ULL, 134 (kernel_ulong_t) 0xdad1bef1bad1fed1ULL, 135 (kernel_ulong_t) 0xdad2bef2bad2fed2ULL, 136 (kernel_ulong_t) 0xdad3bef3bad3fed3ULL, 137 (kernel_ulong_t) 0xdad4bef4bad4fed4ULL, 138 (kernel_ulong_t) 0xdad5bef5bad5fed5ULL, 139 }; 140 int splice_in[2], splice_out[2]; 141 142 ASSERT_EQ(0, pipe(splice_in)); 143 ASSERT_EQ(0, pipe(splice_out)); 144 ASSERT_EQ(sizeof(dummy), write(splice_in[1], dummy, sizeof(dummy))); 145 146 const struct { 147 struct si_entry entry[2]; 148 struct si_exit exit[2]; 149 } si[] = { 150 /* change scno, keep non-error rval */ 151 { 152 { 153 { 154 __NR_gettid, 155 { 156 dummy[0], dummy[1], dummy[2], 157 dummy[3], dummy[4], dummy[5] 158 } 159 }, { 160 __NR_getppid, 161 { 162 dummy[0], dummy[1], dummy[2], 163 dummy[3], dummy[4], dummy[5] 164 } 165 } 166 }, { 167 { 0, tracer_pid }, { 0, tracer_pid } 168 } 169 }, 170 171 /* set scno to -1, keep error rval */ 172 { 173 { 174 { 175 __NR_chdir, 176 { 177 (uintptr_t) ".", 178 dummy[1], dummy[2], 179 dummy[3], dummy[4], dummy[5] 180 } 181 }, { 182 -1, 183 { 184 (uintptr_t) ".", 185 dummy[1], dummy[2], 186 dummy[3], dummy[4], dummy[5] 187 } 188 } 189 }, { 190 { 1, -ENOSYS }, { 1, -ENOSYS } 191 } 192 }, 193 194 /* keep scno, change non-error rval */ 195 { 196 { 197 { 198 __NR_getppid, 199 { 200 dummy[0], dummy[1], dummy[2], 201 dummy[3], dummy[4], dummy[5] 202 } 203 }, { 204 __NR_getppid, 205 { 206 dummy[0], dummy[1], dummy[2], 207 dummy[3], dummy[4], dummy[5] 208 } 209 } 210 }, { 211 { 0, tracer_pid }, { 0, tracer_pid + 1 } 212 } 213 }, 214 215 /* change arg1, keep non-error rval */ 216 { 217 { 218 { 219 __NR_chdir, 220 { 221 (uintptr_t) "", 222 dummy[1], dummy[2], 223 dummy[3], dummy[4], dummy[5] 224 } 225 }, { 226 __NR_chdir, 227 { 228 (uintptr_t) ".", 229 dummy[1], dummy[2], 230 dummy[3], dummy[4], dummy[5] 231 } 232 } 233 }, { 234 { 0, 0 }, { 0, 0 } 235 } 236 }, 237 238 /* set scno to -1, change error rval to non-error */ 239 { 240 { 241 { 242 __NR_gettid, 243 { 244 dummy[0], dummy[1], dummy[2], 245 dummy[3], dummy[4], dummy[5] 246 } 247 }, { 248 -1, 249 { 250 dummy[0], dummy[1], dummy[2], 251 dummy[3], dummy[4], dummy[5] 252 } 253 } 254 }, { 255 { 1, -ENOSYS }, { 0, tracer_pid } 256 } 257 }, 258 259 /* change scno, change non-error rval to error */ 260 { 261 { 262 { 263 __NR_chdir, 264 { 265 dummy[0], dummy[1], dummy[2], 266 dummy[3], dummy[4], dummy[5] 267 } 268 }, { 269 __NR_getppid, 270 { 271 dummy[0], dummy[1], dummy[2], 272 dummy[3], dummy[4], dummy[5] 273 } 274 } 275 }, { 276 { 0, tracer_pid }, { 1, -EISDIR } 277 } 278 }, 279 280 /* change scno and all args, change non-error rval */ 281 { 282 { 283 { 284 __NR_gettid, 285 { 286 dummy[0], dummy[1], dummy[2], 287 dummy[3], dummy[4], dummy[5] 288 } 289 }, { 290 __NR_splice, 291 { 292 splice_in[0], 0, splice_out[1], 0, 293 sizeof(dummy), SPLICE_F_NONBLOCK 294 } 295 } 296 }, { 297 { 0, sizeof(dummy) }, { 0, sizeof(dummy) + 1 } 298 } 299 }, 300 301 /* change arg1, no exit stop */ 302 { 303 { 304 { 305 __NR_exit_group, 306 { 307 dummy[0], dummy[1], dummy[2], 308 dummy[3], dummy[4], dummy[5] 309 } 310 }, { 311 __NR_exit_group, 312 { 313 0, dummy[1], dummy[2], 314 dummy[3], dummy[4], dummy[5] 315 } 316 } 317 }, { 318 { 0, 0 }, { 0, 0 } 319 } 320 }, 321 }; 322 323 long rc; 324 unsigned int i; 325 326 tracee_pid = fork(); 327 328 ASSERT_LE(0, tracee_pid) { 329 TH_LOG("fork: %m"); 330 } 331 332 if (tracee_pid == 0) { 333 /* get the pid before PTRACE_TRACEME */ 334 tracee_pid = getpid(); 335 ASSERT_EQ(0, sys_ptrace(PTRACE_TRACEME, 0, 0, 0)) { 336 TH_LOG("PTRACE_TRACEME: %m"); 337 } 338 ASSERT_EQ(0, kill(tracee_pid, SIGSTOP)) { 339 /* cannot happen */ 340 TH_LOG("kill SIGSTOP: %m"); 341 } 342 for (i = 0; i < ARRAY_SIZE(si); ++i) { 343 rc = syscall(si[i].entry[0].nr, 344 si[i].entry[0].args[0], 345 si[i].entry[0].args[1], 346 si[i].entry[0].args[2], 347 si[i].entry[0].args[3], 348 si[i].entry[0].args[4], 349 si[i].entry[0].args[5]); 350 if (si[i].exit[1].is_error) { 351 if (rc != -1 || errno != -si[i].exit[1].rval) 352 break; 353 } else { 354 if (rc != si[i].exit[1].rval) 355 break; 356 } 357 } 358 /* 359 * Something went wrong, but in this state tracee 360 * cannot reliably issue syscalls, so just crash. 361 */ 362 *(volatile unsigned char *) (uintptr_t) i = 42; 363 /* unreachable */ 364 _exit(i + 1); 365 } 366 367 for (ptrace_stop = 0; ; ++ptrace_stop) { 368 struct ptrace_syscall_info info = { 369 .op = 0xff /* invalid PTRACE_SYSCALL_INFO_* op */ 370 }; 371 const size_t size = sizeof(info); 372 const int expected_entry_size = 373 (void *) &info.entry.args[6] - (void *) &info; 374 const int expected_exit_size = 375 (void *) (&info.exit.is_error + 1) - 376 (void *) &info; 377 int status; 378 379 ASSERT_EQ(tracee_pid, wait(&status)) { 380 /* cannot happen */ 381 LOG_KILL_TRACEE("wait: %m"); 382 } 383 if (WIFEXITED(status)) { 384 tracee_pid = 0; /* the tracee is no more */ 385 ASSERT_EQ(0, WEXITSTATUS(status)) { 386 LOG_KILL_TRACEE("unexpected exit status %u", 387 WEXITSTATUS(status)); 388 } 389 break; 390 } 391 ASSERT_FALSE(WIFSIGNALED(status)) { 392 tracee_pid = 0; /* the tracee is no more */ 393 LOG_KILL_TRACEE("unexpected signal %u", 394 WTERMSIG(status)); 395 } 396 ASSERT_TRUE(WIFSTOPPED(status)) { 397 /* cannot happen */ 398 LOG_KILL_TRACEE("unexpected wait status %#x", status); 399 } 400 401 ASSERT_LT(ptrace_stop, ARRAY_SIZE(si) * 2) { 402 LOG_KILL_TRACEE("ptrace stop overflow"); 403 } 404 405 switch (WSTOPSIG(status)) { 406 case SIGSTOP: 407 ASSERT_EQ(0, ptrace_stop) { 408 LOG_KILL_TRACEE("unexpected signal stop"); 409 } 410 ASSERT_EQ(0, sys_ptrace(PTRACE_SETOPTIONS, tracee_pid, 411 0, PTRACE_O_TRACESYSGOOD)) { 412 LOG_KILL_TRACEE("PTRACE_SETOPTIONS: %m"); 413 } 414 break; 415 416 case SIGTRAP | 0x80: 417 ASSERT_LT(0, ptrace_stop) { 418 LOG_KILL_TRACEE("unexpected syscall stop"); 419 } 420 ASSERT_LT(0, (rc = sys_ptrace(PTRACE_GET_SYSCALL_INFO, 421 tracee_pid, size, 422 (uintptr_t) &info))) { 423 LOG_KILL_TRACEE("PTRACE_GET_SYSCALL_INFO #1: %m"); 424 } 425 if (ptrace_stop & 1) { 426 /* entering syscall */ 427 const struct si_entry *exp_entry = 428 &si[ptrace_stop / 2].entry[0]; 429 const struct si_entry *set_entry = 430 &si[ptrace_stop / 2].entry[1]; 431 432 /* check ptrace_syscall_info before the changes */ 433 ASSERT_EQ(expected_entry_size, rc) { 434 LOG_KILL_TRACEE("PTRACE_GET_SYSCALL_INFO #1" 435 ": entry stop mismatch"); 436 } 437 check_psi_entry(_metadata, &info, exp_entry, 438 "PTRACE_GET_SYSCALL_INFO #1"); 439 440 /* apply the changes */ 441 info.entry.nr = set_entry->nr; 442 for (i = 0; i < ARRAY_SIZE(set_entry->args); ++i) 443 info.entry.args[i] = set_entry->args[i]; 444 ASSERT_EQ(0, sys_ptrace(PTRACE_SET_SYSCALL_INFO, 445 tracee_pid, size, 446 (uintptr_t) &info)) { 447 LOG_KILL_TRACEE("PTRACE_SET_SYSCALL_INFO: %m"); 448 } 449 450 /* check ptrace_syscall_info after the changes */ 451 memset(&info, 0, sizeof(info)); 452 info.op = 0xff; 453 ASSERT_LT(0, (rc = sys_ptrace(PTRACE_GET_SYSCALL_INFO, 454 tracee_pid, size, 455 (uintptr_t) &info))) { 456 LOG_KILL_TRACEE("PTRACE_GET_SYSCALL_INFO: %m"); 457 } 458 ASSERT_EQ(expected_entry_size, rc) { 459 LOG_KILL_TRACEE("PTRACE_GET_SYSCALL_INFO #2" 460 ": entry stop mismatch"); 461 } 462 check_psi_entry(_metadata, &info, set_entry, 463 "PTRACE_GET_SYSCALL_INFO #2"); 464 } else { 465 /* exiting syscall */ 466 const struct si_exit *exp_exit = 467 &si[ptrace_stop / 2 - 1].exit[0]; 468 const struct si_exit *set_exit = 469 &si[ptrace_stop / 2 - 1].exit[1]; 470 471 /* check ptrace_syscall_info before the changes */ 472 ASSERT_EQ(expected_exit_size, rc) { 473 LOG_KILL_TRACEE("PTRACE_GET_SYSCALL_INFO #1" 474 ": exit stop mismatch"); 475 } 476 check_psi_exit(_metadata, &info, exp_exit, 477 "PTRACE_GET_SYSCALL_INFO #1"); 478 479 /* apply the changes */ 480 info.exit.is_error = set_exit->is_error; 481 info.exit.rval = set_exit->rval; 482 ASSERT_EQ(0, sys_ptrace(PTRACE_SET_SYSCALL_INFO, 483 tracee_pid, size, 484 (uintptr_t) &info)) { 485 LOG_KILL_TRACEE("PTRACE_SET_SYSCALL_INFO: %m"); 486 } 487 488 /* check ptrace_syscall_info after the changes */ 489 memset(&info, 0, sizeof(info)); 490 info.op = 0xff; 491 ASSERT_LT(0, (rc = sys_ptrace(PTRACE_GET_SYSCALL_INFO, 492 tracee_pid, size, 493 (uintptr_t) &info))) { 494 LOG_KILL_TRACEE("PTRACE_GET_SYSCALL_INFO #2: %m"); 495 } 496 ASSERT_EQ(expected_exit_size, rc) { 497 LOG_KILL_TRACEE("PTRACE_GET_SYSCALL_INFO #2" 498 ": exit stop mismatch"); 499 } 500 check_psi_exit(_metadata, &info, set_exit, 501 "PTRACE_GET_SYSCALL_INFO #2"); 502 } 503 break; 504 505 default: 506 LOG_KILL_TRACEE("unexpected stop signal %u", 507 WSTOPSIG(status)); 508 abort(); 509 } 510 511 ASSERT_EQ(0, sys_ptrace(PTRACE_SYSCALL, tracee_pid, 0, 0)) { 512 LOG_KILL_TRACEE("PTRACE_SYSCALL: %m"); 513 } 514 } 515 516 ASSERT_EQ(ptrace_stop, ARRAY_SIZE(si) * 2); 517 } 518 519 TEST_HARNESS_MAIN 520