Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

selftests/ptrace: add a test case for PTRACE_SET_SYSCALL_INFO

Check whether PTRACE_SET_SYSCALL_INFO semantics implemented in the kernel
matches userspace expectations.

Link: https://lkml.kernel.org/r/20250303112052.GG24170@strace.io
Signed-off-by: Dmitry V. Levin <ldv@strace.io>
Reviewed-by: Oleg Nesterov <oleg@redhat.com>
Cc: Alexander Gordeev <agordeev@linux.ibm.com>
Cc: Alexey Gladkov (Intel) <legion@kernel.org>
Cc: Andreas Larsson <andreas@gaisler.com>
Cc: anton ivanov <anton.ivanov@cambridgegreys.com>
Cc: Arnd Bergmann <arnd@arndb.de>
Cc: Borislav Betkov <bp@alien8.de>
Cc: Brian Cain <bcain@quicinc.com>
Cc: Charlie Jenkins <charlie@rivosinc.com>
Cc: Christian Borntraeger <borntraeger@linux.ibm.com>
Cc: Christian Zankel <chris@zankel.net>
Cc: Christophe Leroy <christophe.leroy@csgroup.eu>
Cc: Dave Hansen <dave.hansen@linux.intel.com>
Cc: Davide Berardi <berardi.dav@gmail.com>
Cc: David S. Miller <davem@davemloft.net>
Cc: Dinh Nguyen <dinguyen@kernel.org>
Cc: Eugene Syromiatnikov <esyr@redhat.com>
Cc: Eugene Syromyatnikov <evgsyr@gmail.com>
Cc: Geert Uytterhoeven <geert@linux-m68k.org>
Cc: Guo Ren <guoren@kernel.org>
Cc: Heiko Carstens <hca@linux.ibm.com>
Cc: Helge Deller <deller@gmx.de>
Cc: "H. Peter Anvin" <hpa@zytor.com>
Cc: Huacai Chen <chenhuacai@kernel.org>
Cc: Ingo Molnar <mingo@redhat.com>
Cc: Johannes Berg <johannes@sipsolutions.net>
Cc: John Paul Adrian Glaubitz <glaubitz@physik.fu-berlin.de>
Cc: Jonas Bonn <jonas@southpole.se>
Cc: Maciej W. Rozycki <macro@orcam.me.uk>
Cc: Madhavan Srinivasan <maddy@linux.ibm.com>
Cc: Max Filippov <jcmvbkbc@gmail.com>
Cc: Michael Ellerman <mpe@ellerman.id.au>
Cc: Michal Simek <monstr@monstr.eu>
Cc: Mike Frysinger <vapier@gentoo.org>
Cc: Naveen N Rao <naveen@kernel.org>
Cc: Nicholas Piggin <npiggin@gmail.com>
Cc: Renzo Davoi <renzo@cs.unibo.it>
Cc: Richard Weinberger <richard@nod.at>
Cc: Rich Felker <dalias@libc.org>
Cc: Russel King <linux@armlinux.org.uk>
Cc: Shuah Khan <shuah@kernel.org>
Cc: Stafford Horne <shorne@gmail.com>
Cc: Stefan Kristiansson <stefan.kristiansson@saunalahti.fi>
Cc: Sven Schnelle <svens@linux.ibm.com>
Cc: Thomas Gleinxer <tglx@linutronix.de>
Cc: Vasily Gorbik <gor@linux.ibm.com>
Cc: Vineet Gupta <vgupta@kernel.org>
Cc: WANG Xuerui <kernel@xen0n.name>
Cc: Will Deacon <will@kernel.org>
Cc: Yoshinori Sato <ysato@users.sourceforge.jp>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>

authored by

Dmitry V. Levin and committed by
Andrew Morton
bc6fa711 26bb3276

+520 -1
+1 -1
tools/testing/selftests/ptrace/Makefile
··· 1 1 # SPDX-License-Identifier: GPL-2.0-only 2 2 CFLAGS += -std=c99 -pthread -Wall $(KHDR_INCLUDES) 3 3 4 - TEST_GEN_PROGS := get_syscall_info peeksiginfo vmaccess get_set_sud 4 + TEST_GEN_PROGS := get_syscall_info set_syscall_info peeksiginfo vmaccess get_set_sud 5 5 6 6 include ../lib.mk
+519
tools/testing/selftests/ptrace/set_syscall_info.c
··· 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