// SPDX-License-Identifier: GPL-2.0 #include <linux/compiler.h> #include <sys/types.h> #include <sys/wait.h> #include <sys/user.h> #include <syscall.h> #include <unistd.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/ptrace.h> #include <asm/ptrace.h> #include <errno.h> #include "debug.h" #include "tests/tests.h" #include "arch-tests.h" static noinline int bp_1(void) { pr_debug("in %s\n", __func__); return 0; } static noinline int bp_2(void) { pr_debug("in %s\n", __func__); return 0; } static int spawn_child(void) { int child = fork(); if (child == 0) { /* * The child sets itself for as tracee and * waits in signal for parent to trace it, * then it calls bp_1 and quits. */ int err = ptrace(PTRACE_TRACEME, 0, NULL, NULL); if (err) { pr_debug("failed to PTRACE_TRACEME\n"); exit(1); } raise(SIGCONT); bp_1(); exit(0); } return child; } /* * This tests creates HW breakpoint, tries to * change it and checks it was properly changed. */ static int bp_modify1(void) { pid_t child; int status; unsigned long rip = 0, dr7 = 1; child = spawn_child(); waitpid(child, &status, 0); if (WIFEXITED(status)) { pr_debug("tracee exited prematurely 1\n"); return TEST_FAIL; } /* * The parent does following steps: * - creates a new breakpoint (id 0) for bp_2 function * - changes that breakpoint to bp_1 function * - waits for the breakpoint to hit and checks * it has proper rip of bp_1 function * - detaches the child */ if (ptrace(PTRACE_POKEUSER, child, offsetof(struct user, u_debugreg[0]), bp_2)) { pr_debug("failed to set breakpoint, 1st time: %s\n", strerror(errno)); goto out; } if (ptrace(PTRACE_POKEUSER, child, offsetof(struct user, u_debugreg[0]), bp_1)) { pr_debug("failed to set breakpoint, 2nd time: %s\n", strerror(errno)); goto out; } if (ptrace(PTRACE_POKEUSER, child, offsetof(struct user, u_debugreg[7]), dr7)) { pr_debug("failed to set dr7: %s\n", strerror(errno)); goto out; } if (ptrace(PTRACE_CONT, child, NULL, NULL)) { pr_debug("failed to PTRACE_CONT: %s\n", strerror(errno)); goto out; } waitpid(child, &status, 0); if (WIFEXITED(status)) { pr_debug("tracee exited prematurely 2\n"); return TEST_FAIL; } rip = ptrace(PTRACE_PEEKUSER, child, offsetof(struct user_regs_struct, rip), NULL); if (rip == (unsigned long) -1) { pr_debug("failed to PTRACE_PEEKUSER: %s\n", strerror(errno)); goto out; } pr_debug("rip %lx, bp_1 %p\n", rip, bp_1); out: if (ptrace(PTRACE_DETACH, child, NULL, NULL)) { pr_debug("failed to PTRACE_DETACH: %s", strerror(errno)); return TEST_FAIL; } return rip == (unsigned long) bp_1 ? TEST_OK : TEST_FAIL; } /* * This tests creates HW breakpoint, tries to * change it to bogus value and checks the original * breakpoint is hit. */ static int bp_modify2(void) { pid_t child; int status; unsigned long rip = 0, dr7 = 1; child = spawn_child(); waitpid(child, &status, 0); if (WIFEXITED(status)) { pr_debug("tracee exited prematurely 1\n"); return TEST_FAIL; } /* * The parent does following steps: * - creates a new breakpoint (id 0) for bp_1 function * - tries to change that breakpoint to (-1) address * - waits for the breakpoint to hit and checks * it has proper rip of bp_1 function * - detaches the child */ if (ptrace(PTRACE_POKEUSER, child, offsetof(struct user, u_debugreg[0]), bp_1)) { pr_debug("failed to set breakpoint: %s\n", strerror(errno)); goto out; } if (ptrace(PTRACE_POKEUSER, child, offsetof(struct user, u_debugreg[7]), dr7)) { pr_debug("failed to set dr7: %s\n", strerror(errno)); goto out; } if (!ptrace(PTRACE_POKEUSER, child, offsetof(struct user, u_debugreg[0]), (unsigned long) (-1))) { pr_debug("failed, breakpoint set to bogus address\n"); goto out; } if (ptrace(PTRACE_CONT, child, NULL, NULL)) { pr_debug("failed to PTRACE_CONT: %s\n", strerror(errno)); goto out; } waitpid(child, &status, 0); if (WIFEXITED(status)) { pr_debug("tracee exited prematurely 2\n"); return TEST_FAIL; } rip = ptrace(PTRACE_PEEKUSER, child, offsetof(struct user_regs_struct, rip), NULL); if (rip == (unsigned long) -1) { pr_debug("failed to PTRACE_PEEKUSER: %s\n", strerror(errno)); goto out; } pr_debug("rip %lx, bp_1 %p\n", rip, bp_1); out: if (ptrace(PTRACE_DETACH, child, NULL, NULL)) { pr_debug("failed to PTRACE_DETACH: %s", strerror(errno)); return TEST_FAIL; } return rip == (unsigned long) bp_1 ? TEST_OK : TEST_FAIL; } int test__bp_modify(struct test_suite *test __maybe_unused, int subtest __maybe_unused) { TEST_ASSERT_VAL("modify test 1 failed\n", !bp_modify1()); TEST_ASSERT_VAL("modify test 2 failed\n", !bp_modify2()); return 0; }