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: Andrii Nakryiko <andrii@kernel.org> Signed-off-by: Jiri Olsa <jolsa@kernel.org> Signed-off-by: Masami Hiramatsu (Google) <mhiramat@kernel.org>
This commit is contained in:
parent
ff474a78ce
commit
29edd8b003
@ -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:
|
||||
|
Loading…
x
Reference in New Issue
Block a user