Contributors: 6
Author Tokens Token Proportion Commits Commit Proportion
Jiri Olsa 814 97.60% 1 12.50%
Matt Fleming 8 0.96% 1 12.50%
Arnaldo Carvalho de Melo 6 0.72% 2 25.00%
Ingo Molnar 4 0.48% 2 25.00%
Greg Kroah-Hartman 1 0.12% 1 12.50%
Ian Rogers 1 0.12% 1 12.50%
Total 834 8

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214
// 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;
}