Skip to content

Commit 29edd8b

Browse files
olsajirimhiramat
authored andcommitted
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/[email protected]/ Acked-by: Andrii Nakryiko <[email protected]> Signed-off-by: Jiri Olsa <[email protected]> Signed-off-by: Masami Hiramatsu (Google) <[email protected]>
1 parent ff474a7 commit 29edd8b

File tree

1 file changed

+145
-0
lines changed

1 file changed

+145
-0
lines changed

tools/testing/selftests/x86/test_shadow_stack.c

Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
#include <sys/ptrace.h>
3535
#include <sys/signal.h>
3636
#include <linux/elf.h>
37+
#include <linux/perf_event.h>
3738

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

738+
static int parse_uint_from_file(const char *file, const char *fmt)
739+
{
740+
int err, ret;
741+
FILE *f;
742+
743+
f = fopen(file, "re");
744+
if (!f) {
745+
err = -errno;
746+
printf("failed to open '%s': %d\n", file, err);
747+
return err;
748+
}
749+
err = fscanf(f, fmt, &ret);
750+
if (err != 1) {
751+
err = err == EOF ? -EIO : -errno;
752+
printf("failed to parse '%s': %d\n", file, err);
753+
fclose(f);
754+
return err;
755+
}
756+
fclose(f);
757+
return ret;
758+
}
759+
760+
static int determine_uprobe_perf_type(void)
761+
{
762+
const char *file = "/sys/bus/event_source/devices/uprobe/type";
763+
764+
return parse_uint_from_file(file, "%d\n");
765+
}
766+
767+
static int determine_uprobe_retprobe_bit(void)
768+
{
769+
const char *file = "/sys/bus/event_source/devices/uprobe/format/retprobe";
770+
771+
return parse_uint_from_file(file, "config:%d\n");
772+
}
773+
774+
static ssize_t get_uprobe_offset(const void *addr)
775+
{
776+
size_t start, end, base;
777+
char buf[256];
778+
bool found = false;
779+
FILE *f;
780+
781+
f = fopen("/proc/self/maps", "r");
782+
if (!f)
783+
return -errno;
784+
785+
while (fscanf(f, "%zx-%zx %s %zx %*[^\n]\n", &start, &end, buf, &base) == 4) {
786+
if (buf[2] == 'x' && (uintptr_t)addr >= start && (uintptr_t)addr < end) {
787+
found = true;
788+
break;
789+
}
790+
}
791+
792+
fclose(f);
793+
794+
if (!found)
795+
return -ESRCH;
796+
797+
return (uintptr_t)addr - start + base;
798+
}
799+
800+
static __attribute__((noinline)) void uretprobe_trigger(void)
801+
{
802+
asm volatile ("");
803+
}
804+
805+
/*
806+
* This test setups return uprobe, which is sensitive to shadow stack
807+
* (crashes without extra fix). After executing the uretprobe we fail
808+
* the test if we receive SIGSEGV, no crash means we're good.
809+
*
810+
* Helper functions above borrowed from bpf selftests.
811+
*/
812+
static int test_uretprobe(void)
813+
{
814+
const size_t attr_sz = sizeof(struct perf_event_attr);
815+
const char *file = "/proc/self/exe";
816+
int bit, fd = 0, type, err = 1;
817+
struct perf_event_attr attr;
818+
struct sigaction sa = {};
819+
ssize_t offset;
820+
821+
type = determine_uprobe_perf_type();
822+
if (type < 0) {
823+
if (type == -ENOENT)
824+
printf("[SKIP]\tUretprobe test, uprobes are not available\n");
825+
return 0;
826+
}
827+
828+
offset = get_uprobe_offset(uretprobe_trigger);
829+
if (offset < 0)
830+
return 1;
831+
832+
bit = determine_uprobe_retprobe_bit();
833+
if (bit < 0)
834+
return 1;
835+
836+
sa.sa_sigaction = segv_gp_handler;
837+
sa.sa_flags = SA_SIGINFO;
838+
if (sigaction(SIGSEGV, &sa, NULL))
839+
return 1;
840+
841+
/* Setup return uprobe through perf event interface. */
842+
memset(&attr, 0, attr_sz);
843+
attr.size = attr_sz;
844+
attr.type = type;
845+
attr.config = 1 << bit;
846+
attr.config1 = (__u64) (unsigned long) file;
847+
attr.config2 = offset;
848+
849+
fd = syscall(__NR_perf_event_open, &attr, 0 /* pid */, -1 /* cpu */,
850+
-1 /* group_fd */, PERF_FLAG_FD_CLOEXEC);
851+
if (fd < 0)
852+
goto out;
853+
854+
if (sigsetjmp(jmp_buffer, 1))
855+
goto out;
856+
857+
ARCH_PRCTL(ARCH_SHSTK_ENABLE, ARCH_SHSTK_SHSTK);
858+
859+
/*
860+
* This either segfaults and goes through sigsetjmp above
861+
* or succeeds and we're good.
862+
*/
863+
uretprobe_trigger();
864+
865+
printf("[OK]\tUretprobe test\n");
866+
err = 0;
867+
868+
out:
869+
ARCH_PRCTL(ARCH_SHSTK_DISABLE, ARCH_SHSTK_SHSTK);
870+
signal(SIGSEGV, SIG_DFL);
871+
if (fd)
872+
close(fd);
873+
return err;
874+
}
875+
737876
void segv_handler_ptrace(int signum, siginfo_t *si, void *uc)
738877
{
739878
/* The SSP adjustment caused a segfault. */
@@ -926,6 +1065,12 @@ int main(int argc, char *argv[])
9261065
goto out;
9271066
}
9281067

1068+
if (test_uretprobe()) {
1069+
ret = 1;
1070+
printf("[FAIL]\turetprobe test\n");
1071+
goto out;
1072+
}
1073+
9291074
return ret;
9301075

9311076
out:

0 commit comments

Comments
 (0)