Commit 29edd8b0 authored by Jiri Olsa's avatar Jiri Olsa Committed by Masami Hiramatsu (Google)
Browse files

selftests/x86: Add return uprobe shadow stack test

Adding return uprobe test for shadow stack and making sure it's
working properly. Borrowed some of the code from bpf selftests.

Link: https://lore.kernel.org/all/20240611112158.40795-5-jolsa@kernel.org/



Acked-by: default avatarAndrii Nakryiko <andrii@kernel.org>
Signed-off-by: default avatarJiri Olsa <jolsa@kernel.org>
Signed-off-by: default avatarMasami Hiramatsu (Google) <mhiramat@kernel.org>
parent ff474a78
Loading
Loading
Loading
Loading
+145 −0
Original line number Diff line number Diff line
@@ -34,6 +34,7 @@
#include <sys/ptrace.h>
#include <sys/signal.h>
#include <linux/elf.h>
#include <linux/perf_event.h>

/*
 * Define the ABI defines if needed, so people can run the tests
@@ -734,6 +735,144 @@ int test_32bit(void)
	return !segv_triggered;
}

static int parse_uint_from_file(const char *file, const char *fmt)
{
	int err, ret;
	FILE *f;

	f = fopen(file, "re");
	if (!f) {
		err = -errno;
		printf("failed to open '%s': %d\n", file, err);
		return err;
	}
	err = fscanf(f, fmt, &ret);
	if (err != 1) {
		err = err == EOF ? -EIO : -errno;
		printf("failed to parse '%s': %d\n", file, err);
		fclose(f);
		return err;
	}
	fclose(f);
	return ret;
}

static int determine_uprobe_perf_type(void)
{
	const char *file = "/sys/bus/event_source/devices/uprobe/type";

	return parse_uint_from_file(file, "%d\n");
}

static int determine_uprobe_retprobe_bit(void)
{
	const char *file = "/sys/bus/event_source/devices/uprobe/format/retprobe";

	return parse_uint_from_file(file, "config:%d\n");
}

static ssize_t get_uprobe_offset(const void *addr)
{
	size_t start, end, base;
	char buf[256];
	bool found = false;
	FILE *f;

	f = fopen("/proc/self/maps", "r");
	if (!f)
		return -errno;

	while (fscanf(f, "%zx-%zx %s %zx %*[^\n]\n", &start, &end, buf, &base) == 4) {
		if (buf[2] == 'x' && (uintptr_t)addr >= start && (uintptr_t)addr < end) {
			found = true;
			break;
		}
	}

	fclose(f);

	if (!found)
		return -ESRCH;

	return (uintptr_t)addr - start + base;
}

static __attribute__((noinline)) void uretprobe_trigger(void)
{
	asm volatile ("");
}

/*
 * This test setups return uprobe, which is sensitive to shadow stack
 * (crashes without extra fix). After executing the uretprobe we fail
 * the test if we receive SIGSEGV, no crash means we're good.
 *
 * Helper functions above borrowed from bpf selftests.
 */
static int test_uretprobe(void)
{
	const size_t attr_sz = sizeof(struct perf_event_attr);
	const char *file = "/proc/self/exe";
	int bit, fd = 0, type, err = 1;
	struct perf_event_attr attr;
	struct sigaction sa = {};
	ssize_t offset;

	type = determine_uprobe_perf_type();
	if (type < 0) {
		if (type == -ENOENT)
			printf("[SKIP]\tUretprobe test, uprobes are not available\n");
		return 0;
	}

	offset = get_uprobe_offset(uretprobe_trigger);
	if (offset < 0)
		return 1;

	bit = determine_uprobe_retprobe_bit();
	if (bit < 0)
		return 1;

	sa.sa_sigaction = segv_gp_handler;
	sa.sa_flags = SA_SIGINFO;
	if (sigaction(SIGSEGV, &sa, NULL))
		return 1;

	/* Setup return uprobe through perf event interface. */
	memset(&attr, 0, attr_sz);
	attr.size = attr_sz;
	attr.type = type;
	attr.config = 1 << bit;
	attr.config1 = (__u64) (unsigned long) file;
	attr.config2 = offset;

	fd = syscall(__NR_perf_event_open, &attr, 0 /* pid */, -1 /* cpu */,
		     -1 /* group_fd */, PERF_FLAG_FD_CLOEXEC);
	if (fd < 0)
		goto out;

	if (sigsetjmp(jmp_buffer, 1))
		goto out;

	ARCH_PRCTL(ARCH_SHSTK_ENABLE, ARCH_SHSTK_SHSTK);

	/*
	 * This either segfaults and goes through sigsetjmp above
	 * or succeeds and we're good.
	 */
	uretprobe_trigger();

	printf("[OK]\tUretprobe test\n");
	err = 0;

out:
	ARCH_PRCTL(ARCH_SHSTK_DISABLE, ARCH_SHSTK_SHSTK);
	signal(SIGSEGV, SIG_DFL);
	if (fd)
		close(fd);
	return err;
}

void segv_handler_ptrace(int signum, siginfo_t *si, void *uc)
{
	/* The SSP adjustment caused a segfault. */
@@ -926,6 +1065,12 @@ int main(int argc, char *argv[])
		goto out;
	}

	if (test_uretprobe()) {
		ret = 1;
		printf("[FAIL]\turetprobe test\n");
		goto out;
	}

	return ret;

out: