| Seq | Timestamp | Type | Name | Duration |
|---|---|---|---|---|
| 0/0 | 2026/08/12 19:51 | flow | patch-triage |
2mArgs:null Results: {
"EnableConfigs": [
"X86_USER_SHADOW_STACK"
],
"FocusSymbols": [
"arch_set_shadow_stack_status",
"arch_lock_shadow_stack_status",
"arch_get_shadow_stack_status"
],
"KMSANReasoning": "The patch introduces generic `prctl` handlers for shadow stack on x86 (`arch_set_shadow_stack_status`, `arch_lock_shadow_stack_status`, `arch_get_shadow_stack_status`) and adds corresponding selftests. The only data copied to user space is a scalar `unsigned long` (`task-\u003ethread.features`) via `put_user()` in `shstk_prctl()`. This does not involve any struct padding, uninitialized memory, or complex allocations. While there is a potential operator precedence bug (`!status \u0026 PR_SHADOW_STACK_ENABLE`), it is a functional logic bug and does not expose uninitialized memory. Thus, a dedicated KMSAN fuzzing session is not required.",
"NeedsKMSAN": false,
"Reasoning": "The patch adds generic prctl support for x86 shadow stack. The functions `arch_set_shadow_stack_status`, `arch_lock_shadow_stack_status`, and `arch_get_shadow_stack_status` are reachable via the `prctl` syscall and should be fuzzed to ensure correct handling of the new prctl options.",
"WorthFuzzing": true
} |
| 1/1 | 2026/08/12 19:51 | action | read-patch-diff |
0mArgs:null Results: {
"PatchDiff": "commit 079ec6fe340458147e0fc94e583f8c31b1ddb2e8\nAuthor: syz-cluster \u003ctriage@syzkaller.com\u003e\nDate: Wed Aug 12 19:51:31 2026 +0000\n\n syz-cluster: applied patch under review\n\ndiff --git a/arch/x86/kernel/shstk.c b/arch/x86/kernel/shstk.c\nindex 0ca64900192f8..c34ba3701dd06 100644\n--- a/arch/x86/kernel/shstk.c\n+++ b/arch/x86/kernel/shstk.c\n@@ -18,6 +18,7 @@\n #include \u003clinux/sizes.h\u003e\n #include \u003clinux/user.h\u003e\n #include \u003clinux/syscalls.h\u003e\n+#include \u003clinux/prctl.h\u003e\n #include \u003casm/msr.h\u003e\n #include \u003casm/fpu/xstate.h\u003e\n #include \u003casm/fpu/types.h\u003e\n@@ -630,3 +631,39 @@ bool shstk_is_enabled(void)\n {\n \treturn features_enabled(ARCH_SHSTK_SHSTK);\n }\n+\n+#define PR_SHADOW_STACK_SUPPORTED_STATUS_MASK \\\n+\t\t(PR_SHADOW_STACK_ENABLE | PR_SHADOW_STACK_WRITE | PR_SHADOW_STACK_PUSH)\n+\n+/* Handles the generic prctl interface for PR_SET_SHADOW_STACK_STATUS and its feature bits */\n+int arch_set_shadow_stack_status(struct task_struct *t, unsigned long status)\n+{\n+\tint rc;\n+\n+\tif (status \u0026 ~PR_SHADOW_STACK_SUPPORTED_STATUS_MASK)\n+\t\treturn -EINVAL;\n+\n+\t/* x86 arch_prctl is single bit at a time, so handle these one at time */\n+\tif (!status \u0026 PR_SHADOW_STACK_ENABLE)\n+\t\treturn shstk_prctl(t, ARCH_SHSTK_DISABLE, ARCH_SHSTK_SHSTK);\n+\n+\trc = shstk_prctl(t, ARCH_SHSTK_ENABLE, ARCH_SHSTK_SHSTK);\n+\tif (rc)\n+\t\treturn rc;\n+\n+\tif (status \u0026 PR_SHADOW_STACK_WRITE)\n+\t\treturn shstk_prctl(t, ARCH_SHSTK_ENABLE, ARCH_SHSTK_WRSS);\n+\n+\treturn shstk_prctl(t, ARCH_SHSTK_DISABLE, ARCH_SHSTK_WRSS);\n+}\n+\n+/* Handles the generic prctl interface for PR_LOCK_SHADOW_STACK_STATUS and its feature bits */\n+int arch_lock_shadow_stack_status(struct task_struct *t, unsigned long status)\n+{\n+\treturn shstk_prctl(t, ARCH_SHSTK_LOCK, status);\n+}\n+\n+int arch_get_shadow_stack_status(struct task_struct *t, unsigned long __user *status)\n+{\n+\treturn shstk_prctl(t, ARCH_SHSTK_STATUS, (unsigned long)status);\n+}\ndiff --git a/tools/testing/selftests/x86/Makefile b/tools/testing/selftests/x86/Makefile\nindex 434065215d127..8748e2f4a915d 100644\n--- a/tools/testing/selftests/x86/Makefile\n+++ b/tools/testing/selftests/x86/Makefile\n@@ -19,7 +19,8 @@ TARGETS_C_32BIT_ONLY := entry_from_vm86 test_syscall_vdso unwind_vdso \\\n \t\t\ttest_FCMOV test_FCOMI test_FISTTP \\\n \t\t\tvdso_restorer\n TARGETS_C_64BIT_ONLY := fsgsbase sysret_rip syscall_numbering \\\n-\t\t\tcorrupt_xstate_header amx lam test_shadow_stack avx apx\n+\t\t\tcorrupt_xstate_header amx lam test_shadow_stack avx apx \\\n+\t\t\ttest_shadow_stack_prctl\n # Some selftests require 32bit support enabled also on 64bit systems\n TARGETS_C_32BIT_NEEDED := ldt_gdt ptrace_syscall\n \n@@ -138,3 +139,5 @@ $(OUTPUT)/avx_64: CFLAGS += -mno-avx -mno-avx512f\n $(OUTPUT)/amx_64: EXTRA_FILES += xstate.c\n $(OUTPUT)/avx_64: EXTRA_FILES += xstate.c\n $(OUTPUT)/apx_64: EXTRA_FILES += xstate.c\n+$(OUTPUT)/test_shadow_stack_64: EXTRA_FILES += shadow_stack.c\n+$(OUTPUT)/test_shadow_stack_prctl_64: EXTRA_FILES += shadow_stack.c\ndiff --git a/tools/testing/selftests/x86/shadow_stack.c b/tools/testing/selftests/x86/shadow_stack.c\nnew file mode 100644\nindex 0000000000000..a375c4ff7a6ac\n--- /dev/null\n+++ b/tools/testing/selftests/x86/shadow_stack.c\n@@ -0,0 +1,1205 @@\n+// SPDX-License-Identifier: GPL-2.0\n+\n+#define _GNU_SOURCE\n+\n+#include \u003csys/syscall.h\u003e\n+#include \u003casm/mman.h\u003e\n+#include \u003csys/mman.h\u003e\n+#include \u003csys/stat.h\u003e\n+#include \u003csys/wait.h\u003e\n+#include \u003cstdio.h\u003e\n+#include \u003cstdlib.h\u003e\n+#include \u003cfcntl.h\u003e\n+#include \u003cunistd.h\u003e\n+#include \u003cstring.h\u003e\n+#include \u003cerrno.h\u003e\n+#include \u003cstdbool.h\u003e\n+#include \u003cx86intrin.h\u003e\n+#include \u003casm/prctl.h\u003e\n+#include \u003csys/prctl.h\u003e\n+#include \u003cstdint.h\u003e\n+#include \u003csignal.h\u003e\n+#include \u003cpthread.h\u003e\n+#include \u003csys/ioctl.h\u003e\n+#include \u003clinux/userfaultfd.h\u003e\n+#include \u003csetjmp.h\u003e\n+#include \u003csys/ptrace.h\u003e\n+#include \u003csys/signal.h\u003e\n+#include \u003clinux/elf.h\u003e\n+#include \u003clinux/perf_event.h\u003e\n+\n+#include \"shadow_stack.h\"\n+\n+/*\n+ * Define the ABI defines if needed, so people can run the tests\n+ * without building the headers.\n+ */\n+#ifndef __NR_map_shadow_stack\n+#define __NR_map_shadow_stack\t453\n+\n+#define SHADOW_STACK_SET_TOKEN\t(1ULL \u003c\u003c 0)\n+\n+#define ARCH_SHSTK_ENABLE\t0x5001\n+#define ARCH_SHSTK_DISABLE\t0x5002\n+#define ARCH_SHSTK_LOCK\t\t0x5003\n+#define ARCH_SHSTK_UNLOCK\t0x5004\n+#define ARCH_SHSTK_STATUS\t0x5005\n+\n+#define ARCH_SHSTK_SHSTK\t(1ULL \u003c\u003c 0)\n+#define ARCH_SHSTK_WRSS\t\t(1ULL \u003c\u003c 1)\n+\n+#define NT_X86_SHSTK\t0x204\n+#endif\n+\n+#define SS_SIZE 0x200000\n+#define PAGE_SIZE 0x1000\n+\n+void write_shstk(unsigned long *addr, unsigned long val)\n+{\n+\tasm volatile(\"wrssq %[val], (%[addr])\\n\"\n+\t\t : \"=m\" (addr)\n+\t\t : [addr] \"r\" (addr), [val] \"r\" (val));\n+}\n+\n+static inline unsigned long __attribute__((always_inline)) get_ssp(void)\n+{\n+\tunsigned long ret = 0;\n+\n+\tasm volatile(\"xor %0, %0; rdsspq %0\" : \"=r\" (ret));\n+\treturn ret;\n+}\n+\n+/*\n+ * For use in inline enablement of shadow stack.\n+ *\n+ * The program can't return from the point where shadow stack gets enabled\n+ * because there will be no address on the shadow stack. So it can't use\n+ * syscall() for enablement, since it is a function.\n+ *\n+ * Based on code from nolibc.h. Keep a copy here because this can't pull in all\n+ * of nolibc.h.\n+ */\n+#define ARCH_PRCTL(arg1, arg2)\t\t\t\t\t\\\n+({\t\t\t\t\t\t\t\t\\\n+\tlong _ret;\t\t\t\t\t\t\\\n+\tregister long _num asm(\"eax\") = __NR_arch_prctl;\t\\\n+\tregister long _arg1 asm(\"rdi\") = (long)(arg1);\t\t\\\n+\tregister long _arg2 asm(\"rsi\") = (long)(arg2);\t\t\\\n+\t\t\t\t\t\t\t\t\\\n+\tasm volatile (\t\t\t\t\t\t\\\n+\t\t\"syscall\\n\"\t\t\t\t\t\\\n+\t\t: \"=a\"(_ret)\t\t\t\t\t\\\n+\t\t: \"r\"(_arg1), \"r\"(_arg2),\t\t\t\\\n+\t\t \"0\"(_num)\t\t\t\t\t\\\n+\t\t: \"rcx\", \"r11\", \"memory\", \"cc\"\t\t\t\\\n+\t);\t\t\t\t\t\t\t\\\n+\t_ret;\t\t\t\t\t\t\t\\\n+})\n+\n+#define PRCTL(option, arg2, arg3, arg4, arg5) \\\n+({ \\\n+\tlong _ret; \\\n+\tregister long _num asm(\"rax\") = __NR_prctl; \\\n+\tregister long _arg1 asm(\"rdi\") = (long)(option); \\\n+\tregister long _arg2 asm(\"rsi\") = (long)(arg2); \\\n+\tregister long _arg3 asm(\"rdx\") = (long)(arg3); \\\n+\tregister long _arg4 asm(\"r10\") = (long)(arg4); \\\n+\tregister long _arg5 asm(\"r8\") = (long)(arg5); \\\n+\t\t\t\t\t\t\t \\\n+\tasm volatile ( \\\n+\t\t\"syscall\" \\\n+\t\t: \"=a\"(_ret) \\\n+\t\t: \"0\"(_num), \\\n+\t\t \"r\"(_arg1), \"r\"(_arg2), \"r\"(_arg3), \\\n+\t\t \"r\"(_arg4), \"r\"(_arg5) \\\n+\t\t: \"rcx\", \"r11\", \"memory\", \"cc\" \\\n+\t); \\\n+\t_ret; \\\n+})\n+\n+#define SHADOW_STACK_DISABLE(which_test) \\\n+\t((which_test) == SHADOW_STACK_TEST_ARCH_PRCTL ? \\\n+\t\tARCH_PRCTL(ARCH_SHSTK_DISABLE, ARCH_SHSTK_SHSTK) : \\\n+\t\tPRCTL(PR_SET_SHADOW_STACK_STATUS, 0, 0, 0, 0))\n+\n+#define SHADOW_STACK_ENABLE(which_test) \\\n+\t((which_test) == SHADOW_STACK_TEST_ARCH_PRCTL ? \\\n+\t\tARCH_PRCTL(ARCH_SHSTK_ENABLE, ARCH_SHSTK_SHSTK) : \\\n+\t\tPRCTL(PR_SET_SHADOW_STACK_STATUS, PR_SHADOW_STACK_ENABLE, 0, 0, 0))\n+\n+#define SHADOW_STACK_ENABLE_WRITE(which_test) \\\n+\t((which_test) == SHADOW_STACK_TEST_ARCH_PRCTL ? \\\n+\t\tARCH_PRCTL(ARCH_SHSTK_ENABLE, ARCH_SHSTK_WRSS) : \\\n+\t\tPRCTL(PR_SET_SHADOW_STACK_STATUS, \\\n+\t\t\tPR_SHADOW_STACK_ENABLE|PR_SHADOW_STACK_WRITE, 0, 0, 0))\n+\n+void *create_shstk(void *addr)\n+{\n+\treturn (void *)syscall(__NR_map_shadow_stack, addr, SS_SIZE, SHADOW_STACK_SET_TOKEN);\n+}\n+\n+void *create_normal_mem(void *addr)\n+{\n+\treturn mmap(addr, SS_SIZE, PROT_READ | PROT_WRITE,\n+\t\t MAP_PRIVATE | MAP_ANONYMOUS, 0, 0);\n+}\n+\n+void free_shstk(void *shstk)\n+{\n+\tmunmap(shstk, SS_SIZE);\n+}\n+\n+int reset_shstk(void *shstk)\n+{\n+\treturn madvise(shstk, SS_SIZE, MADV_DONTNEED);\n+}\n+\n+void try_shstk(unsigned long new_ssp)\n+{\n+\tunsigned long ssp;\n+\n+\tprintf(\"[INFO]\\tnew_ssp = %lx, *new_ssp = %lx\\n\",\n+\t new_ssp, *((unsigned long *)new_ssp));\n+\n+\tssp = get_ssp();\n+\tprintf(\"[INFO]\\tchanging ssp from %lx to %lx\\n\", ssp, new_ssp);\n+\n+\tasm volatile(\"rstorssp (%0)\\n\":: \"r\" (new_ssp));\n+\tasm volatile(\"saveprevssp\");\n+\tprintf(\"[INFO]\\tssp is now %lx\\n\", get_ssp());\n+\n+\t/* Switch back to original shadow stack */\n+\tssp -= 8;\n+\tasm volatile(\"rstorssp (%0)\\n\":: \"r\" (ssp));\n+\tasm volatile(\"saveprevssp\");\n+}\n+\n+int test_shstk_pivot(void)\n+{\n+\tvoid *shstk = create_shstk(0);\n+\n+\tif (shstk == MAP_FAILED) {\n+\t\tprintf(\"[FAIL]\\tError creating shadow stack: %d\\n\", errno);\n+\t\treturn 1;\n+\t}\n+\ttry_shstk((unsigned long)shstk + SS_SIZE - 8);\n+\tfree_shstk(shstk);\n+\n+\tprintf(\"[OK]\\tShadow stack pivot\\n\");\n+\treturn 0;\n+}\n+\n+int test_shstk_faults(void)\n+{\n+\tunsigned long *shstk = create_shstk(0);\n+\n+\t/* Read shadow stack, test if it's zero to not get read optimized out */\n+\tif (*shstk != 0)\n+\t\tgoto err;\n+\n+\t/* Wrss memory that was already read. */\n+\twrite_shstk(shstk, 1);\n+\tif (*shstk != 1)\n+\t\tgoto err;\n+\n+\t/* Page out memory, so we can wrss it again. */\n+\tif (reset_shstk((void *)shstk))\n+\t\tgoto err;\n+\n+\twrite_shstk(shstk, 1);\n+\tif (*shstk != 1)\n+\t\tgoto err;\n+\n+\tprintf(\"[OK]\\tShadow stack faults\\n\");\n+\treturn 0;\n+\n+err:\n+\treturn 1;\n+}\n+\n+unsigned long saved_ssp;\n+unsigned long saved_ssp_val;\n+volatile bool segv_triggered;\n+\n+void __attribute__((noinline)) violate_ss(void)\n+{\n+\tsaved_ssp = get_ssp();\n+\tsaved_ssp_val = *(unsigned long *)saved_ssp;\n+\n+\t/* Corrupt shadow stack */\n+\tprintf(\"[INFO]\\tCorrupting shadow stack\\n\");\n+\twrite_shstk((void *)saved_ssp, 0);\n+}\n+\n+void segv_handler(int signum, siginfo_t *si, void *uc)\n+{\n+\tprintf(\"[INFO]\\tGenerated shadow stack violation successfully\\n\");\n+\n+\tsegv_triggered = true;\n+\n+\t/* Fix shadow stack */\n+\twrite_shstk((void *)saved_ssp, saved_ssp_val);\n+}\n+\n+int test_shstk_violation(void)\n+{\n+\tstruct sigaction sa = {};\n+\n+\tsa.sa_sigaction = segv_handler;\n+\tsa.sa_flags = SA_SIGINFO;\n+\tif (sigaction(SIGSEGV, \u0026sa, NULL))\n+\t\treturn 1;\n+\n+\tsegv_triggered = false;\n+\n+\t/* Make sure segv_triggered is set before violate_ss() */\n+\tasm volatile(\"\" : : : \"memory\");\n+\n+\tviolate_ss();\n+\n+\tsignal(SIGSEGV, SIG_DFL);\n+\n+\tprintf(\"[OK]\\tShadow stack violation test\\n\");\n+\n+\treturn !segv_triggered;\n+}\n+\n+/* Gup test state */\n+#define MAGIC_VAL 0x12345678\n+bool is_shstk_access;\n+void *shstk_ptr;\n+int fd;\n+\n+void reset_test_shstk(void *addr)\n+{\n+\tif (shstk_ptr)\n+\t\tfree_shstk(shstk_ptr);\n+\tshstk_ptr = create_shstk(addr);\n+}\n+\n+void test_access_fix_handler(int signum, siginfo_t *si, void *uc)\n+{\n+\tprintf(\"[INFO]\\tViolation from %s\\n\", is_shstk_access ? \"shstk access\" : \"normal write\");\n+\n+\tsegv_triggered = true;\n+\n+\t/* Fix shadow stack */\n+\tif (is_shstk_access) {\n+\t\treset_test_shstk(shstk_ptr);\n+\t\treturn;\n+\t}\n+\n+\tfree_shstk(shstk_ptr);\n+\tcreate_normal_mem(shstk_ptr);\n+}\n+\n+bool test_shstk_access(void *ptr)\n+{\n+\tis_shstk_access = true;\n+\tsegv_triggered = false;\n+\twrite_shstk(ptr, MAGIC_VAL);\n+\n+\tasm volatile(\"\" : : : \"memory\");\n+\n+\treturn segv_triggered;\n+}\n+\n+bool test_write_access(void *ptr)\n+{\n+\tis_shstk_access = false;\n+\tsegv_triggered = false;\n+\t*(unsigned long *)ptr = MAGIC_VAL;\n+\n+\tasm volatile(\"\" : : : \"memory\");\n+\n+\treturn segv_triggered;\n+}\n+\n+bool gup_write(void *ptr)\n+{\n+\tunsigned long val;\n+\n+\tlseek(fd, (unsigned long)ptr, SEEK_SET);\n+\tif (write(fd, \u0026val, sizeof(val)) \u003c 0)\n+\t\treturn 1;\n+\n+\treturn 0;\n+}\n+\n+bool gup_read(void *ptr)\n+{\n+\tunsigned long val;\n+\n+\tlseek(fd, (unsigned long)ptr, SEEK_SET);\n+\tif (read(fd, \u0026val, sizeof(val)) \u003c 0)\n+\t\treturn 1;\n+\n+\treturn 0;\n+}\n+\n+int test_gup(void)\n+{\n+\tstruct sigaction sa = {};\n+\tint status;\n+\tpid_t pid;\n+\n+\tsa.sa_sigaction = test_access_fix_handler;\n+\tsa.sa_flags = SA_SIGINFO;\n+\tif (sigaction(SIGSEGV, \u0026sa, NULL))\n+\t\treturn 1;\n+\n+\tsegv_triggered = false;\n+\n+\tfd = open(\"/proc/self/mem\", O_RDWR);\n+\tif (fd == -1)\n+\t\treturn 1;\n+\n+\treset_test_shstk(0);\n+\tif (gup_read(shstk_ptr))\n+\t\treturn 1;\n+\tif (test_shstk_access(shstk_ptr))\n+\t\treturn 1;\n+\tprintf(\"[INFO]\\tGup read -\u003e shstk access success\\n\");\n+\n+\treset_test_shstk(0);\n+\tif (gup_write(shstk_ptr))\n+\t\treturn 1;\n+\tif (test_shstk_access(shstk_ptr))\n+\t\treturn 1;\n+\tprintf(\"[INFO]\\tGup write -\u003e shstk access success\\n\");\n+\n+\treset_test_shstk(0);\n+\tif (gup_read(shstk_ptr))\n+\t\treturn 1;\n+\tif (!test_write_access(shstk_ptr))\n+\t\treturn 1;\n+\tprintf(\"[INFO]\\tGup read -\u003e write access success\\n\");\n+\n+\treset_test_shstk(0);\n+\tif (gup_write(shstk_ptr))\n+\t\treturn 1;\n+\tif (!test_write_access(shstk_ptr))\n+\t\treturn 1;\n+\tprintf(\"[INFO]\\tGup write -\u003e write access success\\n\");\n+\n+\tclose(fd);\n+\n+\t/* COW/gup test */\n+\treset_test_shstk(0);\n+\tpid = fork();\n+\tif (!pid) {\n+\t\tfd = open(\"/proc/self/mem\", O_RDWR);\n+\t\tif (fd == -1)\n+\t\t\texit(1);\n+\n+\t\tif (gup_write(shstk_ptr)) {\n+\t\t\tclose(fd);\n+\t\t\texit(1);\n+\t\t}\n+\t\tclose(fd);\n+\t\texit(0);\n+\t}\n+\twaitpid(pid, \u0026status, 0);\n+\tif (WEXITSTATUS(status)) {\n+\t\tprintf(\"[FAIL]\\tWrite in child failed\\n\");\n+\t\treturn 1;\n+\t}\n+\tif (*(unsigned long *)shstk_ptr == MAGIC_VAL) {\n+\t\tprintf(\"[FAIL]\\tWrite in child wrote through to shared memory\\n\");\n+\t\treturn 1;\n+\t}\n+\n+\tprintf(\"[INFO]\\tCow gup write -\u003e write access success\\n\");\n+\n+\tfree_shstk(shstk_ptr);\n+\n+\tsignal(SIGSEGV, SIG_DFL);\n+\n+\tprintf(\"[OK]\\tShadow gup test\\n\");\n+\n+\treturn 0;\n+}\n+\n+int test_mprotect(void)\n+{\n+\tstruct sigaction sa = {};\n+\n+\tsa.sa_sigaction = test_access_fix_handler;\n+\tsa.sa_flags = SA_SIGINFO;\n+\tif (sigaction(SIGSEGV, \u0026sa, NULL))\n+\t\treturn 1;\n+\n+\tsegv_triggered = false;\n+\n+\t/* mprotect a shadow stack as read only */\n+\treset_test_shstk(0);\n+\tif (mprotect(shstk_ptr, SS_SIZE, PROT_READ) \u003c 0) {\n+\t\tprintf(\"[FAIL]\\tmprotect(PROT_READ) failed\\n\");\n+\t\treturn 1;\n+\t}\n+\n+\t/* try to wrss it and fail */\n+\tif (!test_shstk_access(shstk_ptr)) {\n+\t\tprintf(\"[FAIL]\\tShadow stack access to read-only memory succeeded\\n\");\n+\t\treturn 1;\n+\t}\n+\n+\t/*\n+\t * The shadow stack was reset above to resolve the fault, make the new one\n+\t * read-only.\n+\t */\n+\tif (mprotect(shstk_ptr, SS_SIZE, PROT_READ) \u003c 0) {\n+\t\tprintf(\"[FAIL]\\tmprotect(PROT_READ) failed\\n\");\n+\t\treturn 1;\n+\t}\n+\n+\t/* then back to writable */\n+\tif (mprotect(shstk_ptr, SS_SIZE, PROT_WRITE | PROT_READ) \u003c 0) {\n+\t\tprintf(\"[FAIL]\\tmprotect(PROT_WRITE) failed\\n\");\n+\t\treturn 1;\n+\t}\n+\n+\t/* then wrss to it and succeed */\n+\tif (test_shstk_access(shstk_ptr)) {\n+\t\tprintf(\"[FAIL]\\tShadow stack access to mprotect() writable memory failed\\n\");\n+\t\treturn 1;\n+\t}\n+\n+\tfree_shstk(shstk_ptr);\n+\n+\tsignal(SIGSEGV, SIG_DFL);\n+\n+\tprintf(\"[OK]\\tmprotect() test\\n\");\n+\n+\treturn 0;\n+}\n+\n+char zero[4096];\n+\n+static void *uffd_thread(void *arg)\n+{\n+\tstruct uffdio_copy req;\n+\tint uffd = *(int *)arg;\n+\tstruct uffd_msg msg;\n+\tint ret;\n+\n+\twhile (1) {\n+\t\tret = read(uffd, \u0026msg, sizeof(msg));\n+\t\tif (ret \u003e 0)\n+\t\t\tbreak;\n+\t\telse if (errno == EAGAIN)\n+\t\t\tcontinue;\n+\t\treturn (void *)1;\n+\t}\n+\n+\treq.dst = msg.arg.pagefault.address;\n+\treq.src = (__u64)zero;\n+\treq.len = 4096;\n+\treq.mode = 0;\n+\n+\tif (ioctl(uffd, UFFDIO_COPY, \u0026req))\n+\t\treturn (void *)1;\n+\n+\treturn (void *)0;\n+}\n+\n+int test_userfaultfd(void)\n+{\n+\tstruct uffdio_register uffdio_register;\n+\tstruct uffdio_api uffdio_api;\n+\tstruct sigaction sa = {};\n+\tpthread_t thread;\n+\tvoid *res;\n+\tint uffd;\n+\n+\tsa.sa_sigaction = test_access_fix_handler;\n+\tsa.sa_flags = SA_SIGINFO;\n+\tif (sigaction(SIGSEGV, \u0026sa, NULL))\n+\t\treturn 1;\n+\n+\tuffd = syscall(__NR_userfaultfd, O_CLOEXEC | O_NONBLOCK);\n+\tif (uffd \u003c 0) {\n+\t\tprintf(\"[SKIP]\\tUserfaultfd unavailable.\\n\");\n+\t\treturn 0;\n+\t}\n+\n+\treset_test_shstk(0);\n+\n+\tuffdio_api.api = UFFD_API;\n+\tuffdio_api.features = 0;\n+\tif (ioctl(uffd, UFFDIO_API, \u0026uffdio_api))\n+\t\tgoto err;\n+\n+\tuffdio_register.range.start = (__u64)shstk_ptr;\n+\tuffdio_register.range.len = 4096;\n+\tuffdio_register.mode = UFFDIO_REGISTER_MODE_MISSING;\n+\tif (ioctl(uffd, UFFDIO_REGISTER, \u0026uffdio_register))\n+\t\tgoto err;\n+\n+\tif (pthread_create(\u0026thread, NULL, \u0026uffd_thread, \u0026uffd))\n+\t\tgoto err;\n+\n+\treset_shstk(shstk_ptr);\n+\ttest_shstk_access(shstk_ptr);\n+\n+\tif (pthread_join(thread, \u0026res))\n+\t\tgoto err;\n+\n+\tif (test_shstk_access(shstk_ptr))\n+\t\tgoto err;\n+\n+\tfree_shstk(shstk_ptr);\n+\n+\tsignal(SIGSEGV, SIG_DFL);\n+\n+\tif (!res)\n+\t\tprintf(\"[OK]\\tUserfaultfd test\\n\");\n+\treturn !!res;\n+err:\n+\tfree_shstk(shstk_ptr);\n+\tclose(uffd);\n+\tsignal(SIGSEGV, SIG_DFL);\n+\treturn 1;\n+}\n+\n+/* Simple linked list for keeping track of mappings in test_guard_gap() */\n+struct node {\n+\tstruct node *next;\n+\tvoid *mapping;\n+};\n+\n+/*\n+ * This tests whether mmap will place other mappings in a shadow stack's guard\n+ * gap. The steps are:\n+ * 1. Finds an empty place by mapping and unmapping something.\n+ * 2. Map a shadow stack in the middle of the known empty area.\n+ * 3. Map a bunch of PAGE_SIZE mappings. These will use the search down\n+ * direction, filling any gaps until it encounters the shadow stack's\n+ * guard gap.\n+ * 4. When a mapping lands below the shadow stack from step 2, then all\n+ * of the above gaps are filled. The search down algorithm will have\n+ * looked at the shadow stack gaps.\n+ * 5. See if it landed in the gap.\n+ */\n+int test_guard_gap_other_gaps(void)\n+{\n+\tvoid *free_area, *shstk, *test_map = (void *)0xFFFFFFFFFFFFFFFF;\n+\tstruct node *head = NULL, *cur;\n+\n+\tfree_area = mmap(0, SS_SIZE * 3, PROT_READ | PROT_WRITE,\n+\t\t\t MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);\n+\tmunmap(free_area, SS_SIZE * 3);\n+\n+\tshstk = create_shstk(free_area + SS_SIZE);\n+\tif (shstk == MAP_FAILED)\n+\t\treturn 1;\n+\n+\twhile (test_map \u003e shstk) {\n+\t\ttest_map = mmap(0, PAGE_SIZE, PROT_READ | PROT_WRITE,\n+\t\t\t\tMAP_PRIVATE | MAP_ANONYMOUS, -1, 0);\n+\t\tif (test_map == MAP_FAILED)\n+\t\t\treturn 1;\n+\t\tcur = malloc(sizeof(*cur));\n+\t\tcur-\u003emapping = test_map;\n+\n+\t\tcur-\u003enext = head;\n+\t\thead = cur;\n+\t}\n+\n+\twhile (head) {\n+\t\tcur = head;\n+\t\thead = cur-\u003enext;\n+\t\tmunmap(cur-\u003emapping, PAGE_SIZE);\n+\t\tfree(cur);\n+\t}\n+\n+\tfree_shstk(shstk);\n+\n+\tif (shstk - test_map - PAGE_SIZE != PAGE_SIZE)\n+\t\treturn 1;\n+\n+\tprintf(\"[OK]\\tGuard gap test, other mapping's gaps\\n\");\n+\n+\treturn 0;\n+}\n+\n+/* Tests respecting the guard gap of the mapping getting placed */\n+int test_guard_gap_new_mappings_gaps(void)\n+{\n+\tvoid *free_area, *shstk_start, *test_map = (void *)0xFFFFFFFFFFFFFFFF;\n+\tstruct node *head = NULL, *cur;\n+\tint ret = 0;\n+\n+\tfree_area = mmap(0, PAGE_SIZE * 4, PROT_READ | PROT_WRITE,\n+\t\t\t MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);\n+\tmunmap(free_area, PAGE_SIZE * 4);\n+\n+\t/* Test letting map_shadow_stack find a free space */\n+\tshstk_start = mmap(free_area, PAGE_SIZE, PROT_READ | PROT_WRITE,\n+\t\t\t MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);\n+\tif (shstk_start == MAP_FAILED || shstk_start != free_area)\n+\t\treturn 1;\n+\n+\twhile (test_map \u003e shstk_start) {\n+\t\ttest_map = (void *)syscall(__NR_map_shadow_stack, 0, PAGE_SIZE, 0);\n+\t\tif (test_map == MAP_FAILED) {\n+\t\t\tprintf(\"[INFO]\\tmap_shadow_stack MAP_FAILED\\n\");\n+\t\t\tret = 1;\n+\t\t\tbreak;\n+\t\t}\n+\n+\t\tcur = malloc(sizeof(*cur));\n+\t\tcur-\u003emapping = test_map;\n+\n+\t\tcur-\u003enext = head;\n+\t\thead = cur;\n+\n+\t\tif (test_map == free_area + PAGE_SIZE) {\n+\t\t\tprintf(\"[INFO]\\tNew mapping has other mapping in guard gap!\\n\");\n+\t\t\tret = 1;\n+\t\t\tbreak;\n+\t\t}\n+\t}\n+\n+\twhile (head) {\n+\t\tcur = head;\n+\t\thead = cur-\u003enext;\n+\t\tmunmap(cur-\u003emapping, PAGE_SIZE);\n+\t\tfree(cur);\n+\t}\n+\n+\tmunmap(shstk_start, PAGE_SIZE);\n+\n+\tif (!ret)\n+\t\tprintf(\"[OK]\\tGuard gap test, placement mapping's gaps\\n\");\n+\n+\treturn ret;\n+}\n+\n+/*\n+ * Too complicated to pull it out of the 32 bit header, but also get the\n+ * 64 bit one needed above. Just define a copy here.\n+ */\n+#define __NR_compat_sigaction 67\n+\n+/*\n+ * Call 32 bit signal handler to get 32 bit signals ABI. Make sure\n+ * to push the registers that will get clobbered.\n+ */\n+int sigaction32(int signum, const struct sigaction *restrict act,\n+\t\tstruct sigaction *restrict oldact)\n+{\n+\tregister long syscall_reg asm(\"eax\") = __NR_compat_sigaction;\n+\tregister long signum_reg asm(\"ebx\") = signum;\n+\tregister long act_reg asm(\"ecx\") = (long)act;\n+\tregister long oldact_reg asm(\"edx\") = (long)oldact;\n+\tint ret = 0;\n+\n+\tasm volatile (\"int $0x80;\"\n+\t\t : \"=a\"(ret), \"=m\"(oldact)\n+\t\t : \"r\"(syscall_reg), \"r\"(signum_reg), \"r\"(act_reg),\n+\t\t\t\"r\"(oldact_reg)\n+\t\t : \"r8\", \"r9\", \"r10\", \"r11\"\n+\t\t );\n+\n+\treturn ret;\n+}\n+\n+sigjmp_buf jmp_buffer;\n+enum shadow_stack_test _which_test;\n+void segv_gp_handler(int signum, siginfo_t *si, void *uc)\n+{\n+\tsegv_triggered = true;\n+\n+\t/*\n+\t * To work with old glibc, this can't rely on siglongjmp working with\n+\t * shadow stack enabled, so disable shadow stack before siglongjmp().\n+\t */\n+\tSHADOW_STACK_DISABLE(_which_test);\n+\tsiglongjmp(jmp_buffer, -1);\n+}\n+\n+/*\n+ * Transition to 32 bit mode and check that a #GP triggers a segfault.\n+ */\n+int test_32bit(void)\n+{\n+\tstruct sigaction sa = {};\n+\tstruct sigaction *sa32;\n+\n+\t/* Create sigaction in 32 bit address range */\n+\tsa32 = mmap(0, 4096, PROT_READ | PROT_WRITE,\n+\t\t MAP_32BIT | MAP_PRIVATE | MAP_ANONYMOUS, 0, 0);\n+\tsa32-\u003esa_flags = SA_SIGINFO;\n+\n+\tsa.sa_sigaction = segv_gp_handler;\n+\tsa.sa_flags = SA_SIGINFO;\n+\tif (sigaction(SIGSEGV, \u0026sa, NULL))\n+\t\treturn 1;\n+\n+\n+\tsegv_triggered = false;\n+\n+\t/* Make sure segv_triggered is set before triggering the #GP */\n+\tasm volatile(\"\" : : : \"memory\");\n+\n+\t/*\n+\t * Set handler to somewhere in 32 bit address space\n+\t */\n+\tsa32-\u003esa_handler = (void *)sa32;\n+\tif (sigaction32(SIGUSR1, sa32, NULL))\n+\t\treturn 1;\n+\n+\tif (!sigsetjmp(jmp_buffer, 1))\n+\t\traise(SIGUSR1);\n+\n+\tif (segv_triggered)\n+\t\tprintf(\"[OK]\\t32 bit test\\n\");\n+\n+\treturn !segv_triggered;\n+}\n+\n+static int parse_uint_from_file(const char *file, const char *fmt)\n+{\n+\tint err, ret;\n+\tFILE *f;\n+\n+\tf = fopen(file, \"re\");\n+\tif (!f) {\n+\t\terr = -errno;\n+\t\tprintf(\"failed to open '%s': %d\\n\", file, err);\n+\t\treturn err;\n+\t}\n+\terr = fscanf(f, fmt, \u0026ret);\n+\tif (err != 1) {\n+\t\terr = err == EOF ? -EIO : -errno;\n+\t\tprintf(\"failed to parse '%s': %d\\n\", file, err);\n+\t\tfclose(f);\n+\t\treturn err;\n+\t}\n+\tfclose(f);\n+\treturn ret;\n+}\n+\n+static int determine_uprobe_perf_type(void)\n+{\n+\tconst char *file = \"/sys/bus/event_source/devices/uprobe/type\";\n+\n+\treturn parse_uint_from_file(file, \"%d\\n\");\n+}\n+\n+static int determine_uprobe_retprobe_bit(void)\n+{\n+\tconst char *file = \"/sys/bus/event_source/devices/uprobe/format/retprobe\";\n+\n+\treturn parse_uint_from_file(file, \"config:%d\\n\");\n+}\n+\n+static ssize_t get_uprobe_offset(const void *addr)\n+{\n+\tsize_t start, end, base;\n+\tchar buf[256];\n+\tbool found = false;\n+\tFILE *f;\n+\n+\tf = fopen(\"/proc/self/maps\", \"r\");\n+\tif (!f)\n+\t\treturn -errno;\n+\n+\twhile (fscanf(f, \"%zx-%zx %s %zx %*[^\\n]\\n\", \u0026start, \u0026end, buf, \u0026base) == 4) {\n+\t\tif (buf[2] == 'x' \u0026\u0026 (uintptr_t)addr \u003e= start \u0026\u0026 (uintptr_t)addr \u003c end) {\n+\t\t\tfound = true;\n+\t\t\tbreak;\n+\t\t}\n+\t}\n+\n+\tfclose(f);\n+\n+\tif (!found)\n+\t\treturn -ESRCH;\n+\n+\treturn (uintptr_t)addr - start + base;\n+}\n+\n+static __attribute__((noinline)) void uretprobe_trigger(void)\n+{\n+\tasm volatile (\"\");\n+}\n+\n+/*\n+ * This test setups return uprobe, which is sensitive to shadow stack\n+ * (crashes without extra fix). After executing the uretprobe we fail\n+ * the test if we receive SIGSEGV, no crash means we're good.\n+ *\n+ * Helper functions above borrowed from bpf selftests.\n+ */\n+static int test_uretprobe(enum shadow_stack_test which_test)\n+{\n+\tconst size_t attr_sz = sizeof(struct perf_event_attr);\n+\tconst char *file = \"/proc/self/exe\";\n+\tint bit, fd = 0, type, err = 1;\n+\tstruct perf_event_attr attr;\n+\tstruct sigaction sa = {};\n+\tssize_t offset;\n+\n+\ttype = determine_uprobe_perf_type();\n+\tif (type \u003c 0) {\n+\t\tif (type == -ENOENT)\n+\t\t\tprintf(\"[SKIP]\\tUretprobe test, uprobes are not available\\n\");\n+\t\treturn 0;\n+\t}\n+\n+\toffset = get_uprobe_offset(uretprobe_trigger);\n+\tif (offset \u003c 0)\n+\t\treturn 1;\n+\n+\tbit = determine_uprobe_retprobe_bit();\n+\tif (bit \u003c 0)\n+\t\treturn 1;\n+\n+\tsa.sa_sigaction = segv_gp_handler;\n+\tsa.sa_flags = SA_SIGINFO;\n+\tif (sigaction(SIGSEGV, \u0026sa, NULL))\n+\t\treturn 1;\n+\n+\t/* Setup return uprobe through perf event interface. */\n+\tmemset(\u0026attr, 0, attr_sz);\n+\tattr.size = attr_sz;\n+\tattr.type = type;\n+\tattr.config = 1 \u003c\u003c bit;\n+\tattr.config1 = (__u64) (unsigned long) file;\n+\tattr.config2 = offset;\n+\n+\tfd = syscall(__NR_perf_event_open, \u0026attr, 0 /* pid */, -1 /* cpu */,\n+\t\t -1 /* group_fd */, PERF_FLAG_FD_CLOEXEC);\n+\tif (fd \u003c 0)\n+\t\tgoto out;\n+\n+\tif (sigsetjmp(jmp_buffer, 1))\n+\t\tgoto out;\n+\n+\tSHADOW_STACK_ENABLE(which_test);\n+\n+\t/*\n+\t * This either segfaults and goes through sigsetjmp above\n+\t * or succeeds and we're good.\n+\t */\n+\turetprobe_trigger();\n+\n+\tprintf(\"[OK]\\tUretprobe test\\n\");\n+\terr = 0;\n+\n+out:\n+\tSHADOW_STACK_DISABLE(which_test);\n+\tsignal(SIGSEGV, SIG_DFL);\n+\tif (fd)\n+\t\tclose(fd);\n+\treturn err;\n+}\n+\n+/* Keep the CALL first so the function address is exactly the probed CALL. */\n+extern void uprobe_call_trigger(void);\n+asm (\".pushsection .text\\n\"\n+\t\".global uprobe_call_target\\n\"\n+\t\".type uprobe_call_target, @function\\n\"\n+\t\"uprobe_call_target:\\n\"\n+\t\"\tret\\n\"\n+\t\".size uprobe_call_target, .-uprobe_call_target\\n\"\n+\n+\t\".global uprobe_call_trigger\\n\"\n+\t\".type uprobe_call_trigger, @function\\n\"\n+\t\"uprobe_call_trigger:\\n\"\n+\t\"\tcall uprobe_call_target\\n\"\n+\t\"\tret\\n\"\n+\t\".size uprobe_call_trigger, .-uprobe_call_trigger\\n\"\n+\t\".popsection\\n\"\n+);\n+\n+/* If CALL emulation misses the shadow stack update, this exits via SIGSEGV. */\n+static int test_uprobe_call(enum shadow_stack_test which_test)\n+{\n+\tconst size_t attr_sz = sizeof(struct perf_event_attr);\n+\tconst char *file = \"/proc/self/exe\";\n+\tint fd = -1, type, err = 1;\n+\tstruct perf_event_attr attr;\n+\tstruct sigaction sa = {};\n+\tssize_t offset;\n+\n+\ttype = determine_uprobe_perf_type();\n+\tif (type \u003c 0) {\n+\t\tif (type == -ENOENT)\n+\t\t\tprintf(\"[SKIP]\\tUprobe on CALL test, uprobes are not available\\n\");\n+\t\treturn 0;\n+\t}\n+\n+\toffset = get_uprobe_offset(uprobe_call_trigger);\n+\tif (offset \u003c 0)\n+\t\treturn 1;\n+\n+\tsa.sa_sigaction = segv_gp_handler;\n+\tsa.sa_flags = SA_SIGINFO;\n+\tif (sigaction(SIGSEGV, \u0026sa, NULL))\n+\t\treturn 1;\n+\n+\t/* Setup entry uprobe through perf event interface. */\n+\tmemset(\u0026attr, 0, attr_sz);\n+\tattr.size = attr_sz;\n+\tattr.type = type;\n+\tattr.config = 0;\n+\tattr.config1 = (__u64)(unsigned long)file;\n+\tattr.config2 = offset;\n+\n+\tfd = syscall(__NR_perf_event_open, \u0026attr, 0 /* pid */, -1 /* cpu */,\n+\t\t -1 /* group_fd */, PERF_FLAG_FD_CLOEXEC);\n+\tif (fd \u003c 0)\n+\t\tgoto out;\n+\n+\tif (sigsetjmp(jmp_buffer, 1))\n+\t\tgoto out;\n+\n+\tif (SHADOW_STACK_ENABLE(which_test))\n+\t\tgoto out;\n+\n+\t/*\n+\t * This either segfaults and goes through sigsetjmp above\n+\t * or succeeds and we're good.\n+\t */\n+\tuprobe_call_trigger();\n+\n+\tprintf(\"[OK]\\tUprobe on CALL test\\n\");\n+\terr = 0;\n+\n+out:\n+\tSHADOW_STACK_DISABLE(which_test);\n+\tsignal(SIGSEGV, SIG_DFL);\n+\tif (fd \u003e= 0)\n+\t\tclose(fd);\n+\treturn err;\n+}\n+\n+void segv_handler_ptrace(int signum, siginfo_t *si, void *uc)\n+{\n+\t/* The SSP adjustment caused a segfault. */\n+\texit(0);\n+}\n+\n+int test_ptrace(void)\n+{\n+\tunsigned long saved_ssp, ssp = 0;\n+\tstruct sigaction sa = {};\n+\tstruct iovec iov;\n+\tint status;\n+\tint pid;\n+\n+\tiov.iov_base = \u0026ssp;\n+\tiov.iov_len = sizeof(ssp);\n+\n+\tpid = fork();\n+\tif (!pid) {\n+\t\tssp = get_ssp();\n+\n+\t\tsa.sa_sigaction = segv_handler_ptrace;\n+\t\tsa.sa_flags = SA_SIGINFO;\n+\t\tif (sigaction(SIGSEGV, \u0026sa, NULL))\n+\t\t\treturn 1;\n+\n+\t\tptrace(PTRACE_TRACEME, NULL, NULL, NULL);\n+\t\t/*\n+\t\t * The parent will tweak the SSP and return from this function\n+\t\t * will #CP.\n+\t\t */\n+\t\traise(SIGTRAP);\n+\n+\t\texit(1);\n+\t}\n+\n+\twhile (waitpid(pid, \u0026status, 0) != -1 \u0026\u0026 WSTOPSIG(status) != SIGTRAP);\n+\n+\tif (ptrace(PTRACE_GETREGSET, pid, NT_X86_SHSTK, \u0026iov)) {\n+\t\tprintf(\"[INFO]\\tFailed to PTRACE_GETREGS\\n\");\n+\t\tgoto out_kill;\n+\t}\n+\n+\tif (!ssp) {\n+\t\tprintf(\"[INFO]\\tPtrace child SSP was 0\\n\");\n+\t\tgoto out_kill;\n+\t}\n+\n+\tsaved_ssp = ssp;\n+\n+\tiov.iov_len = 0;\n+\tif (!ptrace(PTRACE_SETREGSET, pid, NT_X86_SHSTK, \u0026iov)) {\n+\t\tprintf(\"[INFO]\\tToo small size accepted via PTRACE_SETREGS\\n\");\n+\t\tgoto out_kill;\n+\t}\n+\n+\tiov.iov_len = sizeof(ssp) + 1;\n+\tif (!ptrace(PTRACE_SETREGSET, pid, NT_X86_SHSTK, \u0026iov)) {\n+\t\tprintf(\"[INFO]\\tToo large size accepted via PTRACE_SETREGS\\n\");\n+\t\tgoto out_kill;\n+\t}\n+\n+\tssp += 1;\n+\tif (!ptrace(PTRACE_SETREGSET, pid, NT_X86_SHSTK, \u0026iov)) {\n+\t\tprintf(\"[INFO]\\tUnaligned SSP written via PTRACE_SETREGS\\n\");\n+\t\tgoto out_kill;\n+\t}\n+\n+\tssp = 0xFFFFFFFFFFFF0000;\n+\tif (!ptrace(PTRACE_SETREGSET, pid, NT_X86_SHSTK, \u0026iov)) {\n+\t\tprintf(\"[INFO]\\tKernel range SSP written via PTRACE_SETREGS\\n\");\n+\t\tgoto out_kill;\n+\t}\n+\n+\t/*\n+\t * Tweak the SSP so the child with #CP when it resumes and returns\n+\t * from raise()\n+\t */\n+\tssp = saved_ssp + 8;\n+\tiov.iov_len = sizeof(ssp);\n+\tif (ptrace(PTRACE_SETREGSET, pid, NT_X86_SHSTK, \u0026iov)) {\n+\t\tprintf(\"[INFO]\\tFailed to PTRACE_SETREGS\\n\");\n+\t\tgoto out_kill;\n+\t}\n+\n+\tif (ptrace(PTRACE_DETACH, pid, NULL, NULL)) {\n+\t\tprintf(\"[INFO]\\tFailed to PTRACE_DETACH\\n\");\n+\t\tgoto out_kill;\n+\t}\n+\n+\twaitpid(pid, \u0026status, 0);\n+\tif (WEXITSTATUS(status))\n+\t\treturn 1;\n+\n+\tprintf(\"[OK]\\tPtrace test\\n\");\n+\treturn 0;\n+\n+out_kill:\n+\tkill(pid, SIGKILL);\n+\treturn 1;\n+}\n+\n+#if (__GNUC__ \u003c 8) || (__GNUC__ == 8 \u0026\u0026 __GNUC_MINOR__ \u003c 5)\n+int shadow_stack_run_tests(enum shadow_stack_test unused)\n+{\n+\tprintf(\"[SKIP]\\tCompiler does not support CET.\\n\");\n+\treturn 0;\n+}\n+#else\n+int shadow_stack_run_tests(enum shadow_stack_test which_test)\n+{\n+\tint ret = 0;\n+\n+\tif (SHADOW_STACK_ENABLE(which_test)) {\n+\t\tprintf(\"[SKIP]\\tCould not enable Shadow stack\\n\");\n+\t\treturn 1;\n+\t}\n+\n+\tif (SHADOW_STACK_DISABLE(which_test)) {\n+\t\tret = 1;\n+\t\tprintf(\"[FAIL]\\tDisabling shadow stack failed\\n\");\n+\t}\n+\n+\tif (SHADOW_STACK_ENABLE(which_test)) {\n+\t\tprintf(\"[SKIP]\\tCould not re-enable Shadow stack\\n\");\n+\t\treturn 1;\n+\t}\n+\n+\tif (SHADOW_STACK_ENABLE_WRITE(which_test)) {\n+\t\tprintf(\"[SKIP]\\tCould not enable WRSS\\n\");\n+\t\tret = 1;\n+\t\tgoto out;\n+\t}\n+\n+\t/* Should have succeeded if here, but this is a test, so double check. */\n+\tif (!get_ssp()) {\n+\t\tprintf(\"[FAIL]\\tShadow stack disabled\\n\");\n+\t\treturn 1;\n+\t}\n+\n+\tif (test_shstk_pivot()) {\n+\t\tret = 1;\n+\t\tprintf(\"[FAIL]\\tShadow stack pivot\\n\");\n+\t\tgoto out;\n+\t}\n+\n+\tif (test_shstk_faults()) {\n+\t\tret = 1;\n+\t\tprintf(\"[FAIL]\\tShadow stack fault test\\n\");\n+\t\tgoto out;\n+\t}\n+\n+\tif (test_shstk_violation()) {\n+\t\tret = 1;\n+\t\tprintf(\"[FAIL]\\tShadow stack violation test\\n\");\n+\t\tgoto out;\n+\t}\n+\n+\tif (test_gup()) {\n+\t\tret = 1;\n+\t\tprintf(\"[FAIL]\\tShadow shadow stack gup\\n\");\n+\t\tgoto out;\n+\t}\n+\n+\tif (test_mprotect()) {\n+\t\tret = 1;\n+\t\tprintf(\"[FAIL]\\tShadow shadow mprotect test\\n\");\n+\t\tgoto out;\n+\t}\n+\n+\tif (test_userfaultfd()) {\n+\t\tret = 1;\n+\t\tprintf(\"[FAIL]\\tUserfaultfd test\\n\");\n+\t\tgoto out;\n+\t}\n+\n+\tif (test_guard_gap_other_gaps()) {\n+\t\tret = 1;\n+\t\tprintf(\"[FAIL]\\tGuard gap test, other mappings' gaps\\n\");\n+\t\tgoto out;\n+\t}\n+\n+\tif (test_guard_gap_new_mappings_gaps()) {\n+\t\tret = 1;\n+\t\tprintf(\"[FAIL]\\tGuard gap test, placement mapping's gaps\\n\");\n+\t\tgoto out;\n+\t}\n+\n+\tif (test_ptrace()) {\n+\t\tret = 1;\n+\t\tprintf(\"[FAIL]\\tptrace test\\n\");\n+\t}\n+\n+\tif (test_32bit()) {\n+\t\tret = 1;\n+\t\tprintf(\"[FAIL]\\t32 bit test\\n\");\n+\t\tgoto out;\n+\t}\n+\n+\tif (test_uretprobe(which_test)) {\n+\t\tret = 1;\n+\t\tprintf(\"[FAIL]\\turetprobe test\\n\");\n+\t\tgoto out;\n+\t}\n+\n+\tif (test_uprobe_call(which_test)) {\n+\t\tret = 1;\n+\t\tprintf(\"[FAIL]\\tuprobe on CALL test\\n\");\n+\t\tgoto out;\n+\t}\n+\n+\treturn ret;\n+\n+out:\n+\t/*\n+\t * Disable shadow stack before the function returns, or there will be a\n+\t * shadow stack violation.\n+\t */\n+\tif (SHADOW_STACK_DISABLE(which_test)) {\n+\t\tret = 1;\n+\t\tprintf(\"[FAIL]\\tDisabling shadow stack failed\\n\");\n+\t}\n+\n+\treturn ret;\n+}\n+#endif\ndiff --git a/tools/testing/selftests/x86/shadow_stack.h b/tools/testing/selftests/x86/shadow_stack.h\nnew file mode 100644\nindex 0000000000000..d36a44df73874\n--- /dev/null\n+++ b/tools/testing/selftests/x86/shadow_stack.h\n@@ -0,0 +1,14 @@\n+// SPDX-License-Identifier: GPL-2.0\n+\n+#ifndef TOOLS_TESTING_SELFTESTS_X86_SHADOW_STACK_H_\n+#define TOOLS_TESTING_SELFTESTS_X86_SHADOW_STACK_H_\n+\n+enum shadow_stack_test {\n+\tSHADOW_STACK_TEST_ARCH_PRCTL,\n+\tSHADOW_STACK_TEST_PRCTL,\n+\tSHADOW_STACK_TEST_COUNT,\n+};\n+\n+int shadow_stack_run_tests(enum shadow_stack_test which_test);\n+\n+#endif /* TOOLS_TESTING_SELFTESTS_X86_SHADOW_STACK_H_ */\ndiff --git a/tools/testing/selftests/x86/test_shadow_stack.c b/tools/testing/selftests/x86/test_shadow_stack.c\nindex 3d6ca33edba42..d89c04623001c 100644\n--- a/tools/testing/selftests/x86/test_shadow_stack.c\n+++ b/tools/testing/selftests/x86/test_shadow_stack.c\n@@ -1,1174 +1,21 @@\n // SPDX-License-Identifier: GPL-2.0\n+\n /*\n * This program test's basic kernel shadow stack support. It enables shadow\n * stack manual via the arch_prctl(), instead of relying on glibc. It's\n * Makefile doesn't compile with shadow stack support, so it doesn't rely on\n * any particular glibc. As a result it can't do any operations that require\n * special glibc shadow stack support (longjmp(), swapcontext(), etc). Just\n- * stick to the basics and hope the compiler doesn't do anything strange.\n+ * stick to the basics and hope the compiler doesn't do anything strange. It\n+ * uses the x86 specific interface for ARCH_PCTL, whereas\n+ * test_shadow_stack_prctl.c uses the generic PRCTL interface mixed with x86\n+ * specific code.\n */\n-\n #define _GNU_SOURCE\n \n-#include \u003csys/syscall.h\u003e\n-#include \u003casm/mman.h\u003e\n-#include \u003csys/mman.h\u003e\n-#include \u003csys/stat.h\u003e\n-#include \u003csys/wait.h\u003e\n-#include \u003cstdio.h\u003e\n-#include \u003cstdlib.h\u003e\n-#include \u003cfcntl.h\u003e\n-#include \u003cunistd.h\u003e\n-#include \u003cstring.h\u003e\n-#include \u003cerrno.h\u003e\n-#include \u003cstdbool.h\u003e\n-#include \u003cx86intrin.h\u003e\n-#include \u003casm/prctl.h\u003e\n-#include \u003csys/prctl.h\u003e\n-#include \u003cstdint.h\u003e\n-#include \u003csignal.h\u003e\n-#include \u003cpthread.h\u003e\n-#include \u003csys/ioctl.h\u003e\n-#include \u003clinux/userfaultfd.h\u003e\n-#include \u003csetjmp.h\u003e\n-#include \u003csys/ptrace.h\u003e\n-#include \u003csys/signal.h\u003e\n-#include \u003clinux/elf.h\u003e\n-#include \u003clinux/perf_event.h\u003e\n-\n-/*\n- * Define the ABI defines if needed, so people can run the tests\n- * without building the headers.\n- */\n-#ifndef __NR_map_shadow_stack\n-#define __NR_map_shadow_stack\t453\n-\n-#define SHADOW_STACK_SET_TOKEN\t(1ULL \u003c\u003c 0)\n-\n-#define ARCH_SHSTK_ENABLE\t0x5001\n-#define ARCH_SHSTK_DISABLE\t0x5002\n-#define ARCH_SHSTK_LOCK\t\t0x5003\n-#define ARCH_SHSTK_UNLOCK\t0x5004\n-#define ARCH_SHSTK_STATUS\t0x5005\n-\n-#define ARCH_SHSTK_SHSTK\t(1ULL \u003c\u003c 0)\n-#define ARCH_SHSTK_WRSS\t\t(1ULL \u003c\u003c 1)\n-\n-#define NT_X86_SHSTK\t0x204\n-#endif\n-\n-#define SS_SIZE 0x200000\n-#define PAGE_SIZE 0x1000\n-\n-#if (__GNUC__ \u003c 8) || (__GNUC__ == 8 \u0026\u0026 __GNUC_MINOR__ \u003c 5)\n-int main(int argc, char *argv[])\n-{\n-\tprintf(\"[SKIP]\\tCompiler does not support CET.\\n\");\n-\treturn 0;\n-}\n-#else\n-void write_shstk(unsigned long *addr, unsigned long val)\n-{\n-\tasm volatile(\"wrssq %[val], (%[addr])\\n\"\n-\t\t : \"=m\" (addr)\n-\t\t : [addr] \"r\" (addr), [val] \"r\" (val));\n-}\n-\n-static inline unsigned long __attribute__((always_inline)) get_ssp(void)\n-{\n-\tunsigned long ret = 0;\n-\n-\tasm volatile(\"xor %0, %0; rdsspq %0\" : \"=r\" (ret));\n-\treturn ret;\n-}\n-\n-/*\n- * For use in inline enablement of shadow stack.\n- *\n- * The program can't return from the point where shadow stack gets enabled\n- * because there will be no address on the shadow stack. So it can't use\n- * syscall() for enablement, since it is a function.\n- *\n- * Based on code from nolibc.h. Keep a copy here because this can't pull in all\n- * of nolibc.h.\n- */\n-#define ARCH_PRCTL(arg1, arg2)\t\t\t\t\t\\\n-({\t\t\t\t\t\t\t\t\\\n-\tlong _ret;\t\t\t\t\t\t\\\n-\tregister long _num asm(\"eax\") = __NR_arch_prctl;\t\\\n-\tregister long _arg1 asm(\"rdi\") = (long)(arg1);\t\t\\\n-\tregister long _arg2 asm(\"rsi\") = (long)(arg2);\t\t\\\n-\t\t\t\t\t\t\t\t\\\n-\tasm volatile (\t\t\t\t\t\t\\\n-\t\t\"syscall\\n\"\t\t\t\t\t\\\n-\t\t: \"=a\"(_ret)\t\t\t\t\t\\\n-\t\t: \"r\"(_arg1), \"r\"(_arg2),\t\t\t\\\n-\t\t \"0\"(_num)\t\t\t\t\t\\\n-\t\t: \"rcx\", \"r11\", \"memory\", \"cc\"\t\t\t\\\n-\t);\t\t\t\t\t\t\t\\\n-\t_ret;\t\t\t\t\t\t\t\\\n-})\n-\n-void *create_shstk(void *addr)\n-{\n-\treturn (void *)syscall(__NR_map_shadow_stack, addr, SS_SIZE, SHADOW_STACK_SET_TOKEN);\n-}\n-\n-void *create_normal_mem(void *addr)\n-{\n-\treturn mmap(addr, SS_SIZE, PROT_READ | PROT_WRITE,\n-\t\t MAP_PRIVATE | MAP_ANONYMOUS, 0, 0);\n-}\n-\n-void free_shstk(void *shstk)\n-{\n-\tmunmap(shstk, SS_SIZE);\n-}\n-\n-int reset_shstk(void *shstk)\n-{\n-\treturn madvise(shstk, SS_SIZE, MADV_DONTNEED);\n-}\n-\n-void try_shstk(unsigned long new_ssp)\n-{\n-\tunsigned long ssp;\n-\n-\tprintf(\"[INFO]\\tnew_ssp = %lx, *new_ssp = %lx\\n\",\n-\t new_ssp, *((unsigned long *)new_ssp));\n-\n-\tssp = get_ssp();\n-\tprintf(\"[INFO]\\tchanging ssp from %lx to %lx\\n\", ssp, new_ssp);\n-\n-\tasm volatile(\"rstorssp (%0)\\n\":: \"r\" (new_ssp));\n-\tasm volatile(\"saveprevssp\");\n-\tprintf(\"[INFO]\\tssp is now %lx\\n\", get_ssp());\n-\n-\t/* Switch back to original shadow stack */\n-\tssp -= 8;\n-\tasm volatile(\"rstorssp (%0)\\n\":: \"r\" (ssp));\n-\tasm volatile(\"saveprevssp\");\n-}\n-\n-int test_shstk_pivot(void)\n-{\n-\tvoid *shstk = create_shstk(0);\n-\n-\tif (shstk == MAP_FAILED) {\n-\t\tprintf(\"[FAIL]\\tError creating shadow stack: %d\\n\", errno);\n-\t\treturn 1;\n-\t}\n-\ttry_shstk((unsigned long)shstk + SS_SIZE - 8);\n-\tfree_shstk(shstk);\n-\n-\tprintf(\"[OK]\\tShadow stack pivot\\n\");\n-\treturn 0;\n-}\n-\n-int test_shstk_faults(void)\n-{\n-\tunsigned long *shstk = create_shstk(0);\n-\n-\t/* Read shadow stack, test if it's zero to not get read optimized out */\n-\tif (*shstk != 0)\n-\t\tgoto err;\n-\n-\t/* Wrss memory that was already read. */\n-\twrite_shstk(shstk, 1);\n-\tif (*shstk != 1)\n-\t\tgoto err;\n-\n-\t/* Page out memory, so we can wrss it again. */\n-\tif (reset_shstk((void *)shstk))\n-\t\tgoto err;\n-\n-\twrite_shstk(shstk, 1);\n-\tif (*shstk != 1)\n-\t\tgoto err;\n-\n-\tprintf(\"[OK]\\tShadow stack faults\\n\");\n-\treturn 0;\n-\n-err:\n-\treturn 1;\n-}\n-\n-unsigned long saved_ssp;\n-unsigned long saved_ssp_val;\n-volatile bool segv_triggered;\n-\n-void __attribute__((noinline)) violate_ss(void)\n-{\n-\tsaved_ssp = get_ssp();\n-\tsaved_ssp_val = *(unsigned long *)saved_ssp;\n-\n-\t/* Corrupt shadow stack */\n-\tprintf(\"[INFO]\\tCorrupting shadow stack\\n\");\n-\twrite_shstk((void *)saved_ssp, 0);\n-}\n-\n-void segv_handler(int signum, siginfo_t *si, void *uc)\n-{\n-\tprintf(\"[INFO]\\tGenerated shadow stack violation successfully\\n\");\n-\n-\tsegv_triggered = true;\n-\n-\t/* Fix shadow stack */\n-\twrite_shstk((void *)saved_ssp, saved_ssp_val);\n-}\n-\n-int test_shstk_violation(void)\n-{\n-\tstruct sigaction sa = {};\n-\n-\tsa.sa_sigaction = segv_handler;\n-\tsa.sa_flags = SA_SIGINFO;\n-\tif (sigaction(SIGSEGV, \u0026sa, NULL))\n-\t\treturn 1;\n-\n-\tsegv_triggered = false;\n-\n-\t/* Make sure segv_triggered is set before violate_ss() */\n-\tasm volatile(\"\" : : : \"memory\");\n-\n-\tviolate_ss();\n-\n-\tsignal(SIGSEGV, SIG_DFL);\n-\n-\tprintf(\"[OK]\\tShadow stack violation test\\n\");\n-\n-\treturn !segv_triggered;\n-}\n-\n-/* Gup test state */\n-#define MAGIC_VAL 0x12345678\n-bool is_shstk_access;\n-void *shstk_ptr;\n-int fd;\n-\n-void reset_test_shstk(void *addr)\n-{\n-\tif (shstk_ptr)\n-\t\tfree_shstk(shstk_ptr);\n-\tshstk_ptr = create_shstk(addr);\n-}\n-\n-void test_access_fix_handler(int signum, siginfo_t *si, void *uc)\n-{\n-\tprintf(\"[INFO]\\tViolation from %s\\n\", is_shstk_access ? \"shstk access\" : \"normal write\");\n-\n-\tsegv_triggered = true;\n-\n-\t/* Fix shadow stack */\n-\tif (is_shstk_access) {\n-\t\treset_test_shstk(shstk_ptr);\n-\t\treturn;\n-\t}\n-\n-\tfree_shstk(shstk_ptr);\n-\tcreate_normal_mem(shstk_ptr);\n-}\n-\n-bool test_shstk_access(void *ptr)\n-{\n-\tis_shstk_access = true;\n-\tsegv_triggered = false;\n-\twrite_shstk(ptr, MAGIC_VAL);\n-\n-\tasm volatile(\"\" : : : \"memory\");\n-\n-\treturn segv_triggered;\n-}\n-\n-bool test_write_access(void *ptr)\n-{\n-\tis_shstk_access = false;\n-\tsegv_triggered = false;\n-\t*(unsigned long *)ptr = MAGIC_VAL;\n-\n-\tasm volatile(\"\" : : : \"memory\");\n-\n-\treturn segv_triggered;\n-}\n-\n-bool gup_write(void *ptr)\n-{\n-\tunsigned long val;\n-\n-\tlseek(fd, (unsigned long)ptr, SEEK_SET);\n-\tif (write(fd, \u0026val, sizeof(val)) \u003c 0)\n-\t\treturn 1;\n-\n-\treturn 0;\n-}\n-\n-bool gup_read(void *ptr)\n-{\n-\tunsigned long val;\n-\n-\tlseek(fd, (unsigned long)ptr, SEEK_SET);\n-\tif (read(fd, \u0026val, sizeof(val)) \u003c 0)\n-\t\treturn 1;\n-\n-\treturn 0;\n-}\n-\n-int test_gup(void)\n-{\n-\tstruct sigaction sa = {};\n-\tint status;\n-\tpid_t pid;\n-\n-\tsa.sa_sigaction = test_access_fix_handler;\n-\tsa.sa_flags = SA_SIGINFO;\n-\tif (sigaction(SIGSEGV, \u0026sa, NULL))\n-\t\treturn 1;\n-\n-\tsegv_triggered = false;\n-\n-\tfd = open(\"/proc/self/mem\", O_RDWR);\n-\tif (fd == -1)\n-\t\treturn 1;\n-\n-\treset_test_shstk(0);\n-\tif (gup_read(shstk_ptr))\n-\t\treturn 1;\n-\tif (test_shstk_access(shstk_ptr))\n-\t\treturn 1;\n-\tprintf(\"[INFO]\\tGup read -\u003e shstk access success\\n\");\n-\n-\treset_test_shstk(0);\n-\tif (gup_write(shstk_ptr))\n-\t\treturn 1;\n-\tif (test_shstk_access(shstk_ptr))\n-\t\treturn 1;\n-\tprintf(\"[INFO]\\tGup write -\u003e shstk access success\\n\");\n-\n-\treset_test_shstk(0);\n-\tif (gup_read(shstk_ptr))\n-\t\treturn 1;\n-\tif (!test_write_access(shstk_ptr))\n-\t\treturn 1;\n-\tprintf(\"[INFO]\\tGup read -\u003e write access success\\n\");\n-\n-\treset_test_shstk(0);\n-\tif (gup_write(shstk_ptr))\n-\t\treturn 1;\n-\tif (!test_write_access(shstk_ptr))\n-\t\treturn 1;\n-\tprintf(\"[INFO]\\tGup write -\u003e write access success\\n\");\n-\n-\tclose(fd);\n-\n-\t/* COW/gup test */\n-\treset_test_shstk(0);\n-\tpid = fork();\n-\tif (!pid) {\n-\t\tfd = open(\"/proc/self/mem\", O_RDWR);\n-\t\tif (fd == -1)\n-\t\t\texit(1);\n-\n-\t\tif (gup_write(shstk_ptr)) {\n-\t\t\tclose(fd);\n-\t\t\texit(1);\n-\t\t}\n-\t\tclose(fd);\n-\t\texit(0);\n-\t}\n-\twaitpid(pid, \u0026status, 0);\n-\tif (WEXITSTATUS(status)) {\n-\t\tprintf(\"[FAIL]\\tWrite in child failed\\n\");\n-\t\treturn 1;\n-\t}\n-\tif (*(unsigned long *)shstk_ptr == MAGIC_VAL) {\n-\t\tprintf(\"[FAIL]\\tWrite in child wrote through to shared memory\\n\");\n-\t\treturn 1;\n-\t}\n-\n-\tprintf(\"[INFO]\\tCow gup write -\u003e write access success\\n\");\n-\n-\tfree_shstk(shstk_ptr);\n-\n-\tsignal(SIGSEGV, SIG_DFL);\n-\n-\tprintf(\"[OK]\\tShadow gup test\\n\");\n-\n-\treturn 0;\n-}\n-\n-int test_mprotect(void)\n-{\n-\tstruct sigaction sa = {};\n-\n-\tsa.sa_sigaction = test_access_fix_handler;\n-\tsa.sa_flags = SA_SIGINFO;\n-\tif (sigaction(SIGSEGV, \u0026sa, NULL))\n-\t\treturn 1;\n-\n-\tsegv_triggered = false;\n-\n-\t/* mprotect a shadow stack as read only */\n-\treset_test_shstk(0);\n-\tif (mprotect(shstk_ptr, SS_SIZE, PROT_READ) \u003c 0) {\n-\t\tprintf(\"[FAIL]\\tmprotect(PROT_READ) failed\\n\");\n-\t\treturn 1;\n-\t}\n-\n-\t/* try to wrss it and fail */\n-\tif (!test_shstk_access(shstk_ptr)) {\n-\t\tprintf(\"[FAIL]\\tShadow stack access to read-only memory succeeded\\n\");\n-\t\treturn 1;\n-\t}\n-\n-\t/*\n-\t * The shadow stack was reset above to resolve the fault, make the new one\n-\t * read-only.\n-\t */\n-\tif (mprotect(shstk_ptr, SS_SIZE, PROT_READ) \u003c 0) {\n-\t\tprintf(\"[FAIL]\\tmprotect(PROT_READ) failed\\n\");\n-\t\treturn 1;\n-\t}\n-\n-\t/* then back to writable */\n-\tif (mprotect(shstk_ptr, SS_SIZE, PROT_WRITE | PROT_READ) \u003c 0) {\n-\t\tprintf(\"[FAIL]\\tmprotect(PROT_WRITE) failed\\n\");\n-\t\treturn 1;\n-\t}\n-\n-\t/* then wrss to it and succeed */\n-\tif (test_shstk_access(shstk_ptr)) {\n-\t\tprintf(\"[FAIL]\\tShadow stack access to mprotect() writable memory failed\\n\");\n-\t\treturn 1;\n-\t}\n-\n-\tfree_shstk(shstk_ptr);\n-\n-\tsignal(SIGSEGV, SIG_DFL);\n-\n-\tprintf(\"[OK]\\tmprotect() test\\n\");\n-\n-\treturn 0;\n-}\n-\n-char zero[4096];\n-\n-static void *uffd_thread(void *arg)\n-{\n-\tstruct uffdio_copy req;\n-\tint uffd = *(int *)arg;\n-\tstruct uffd_msg msg;\n-\tint ret;\n-\n-\twhile (1) {\n-\t\tret = read(uffd, \u0026msg, sizeof(msg));\n-\t\tif (ret \u003e 0)\n-\t\t\tbreak;\n-\t\telse if (errno == EAGAIN)\n-\t\t\tcontinue;\n-\t\treturn (void *)1;\n-\t}\n-\n-\treq.dst = msg.arg.pagefault.address;\n-\treq.src = (__u64)zero;\n-\treq.len = 4096;\n-\treq.mode = 0;\n-\n-\tif (ioctl(uffd, UFFDIO_COPY, \u0026req))\n-\t\treturn (void *)1;\n-\n-\treturn (void *)0;\n-}\n-\n-int test_userfaultfd(void)\n-{\n-\tstruct uffdio_register uffdio_register;\n-\tstruct uffdio_api uffdio_api;\n-\tstruct sigaction sa = {};\n-\tpthread_t thread;\n-\tvoid *res;\n-\tint uffd;\n-\n-\tsa.sa_sigaction = test_access_fix_handler;\n-\tsa.sa_flags = SA_SIGINFO;\n-\tif (sigaction(SIGSEGV, \u0026sa, NULL))\n-\t\treturn 1;\n-\n-\tuffd = syscall(__NR_userfaultfd, O_CLOEXEC | O_NONBLOCK);\n-\tif (uffd \u003c 0) {\n-\t\tprintf(\"[SKIP]\\tUserfaultfd unavailable.\\n\");\n-\t\treturn 0;\n-\t}\n-\n-\treset_test_shstk(0);\n-\n-\tuffdio_api.api = UFFD_API;\n-\tuffdio_api.features = 0;\n-\tif (ioctl(uffd, UFFDIO_API, \u0026uffdio_api))\n-\t\tgoto err;\n-\n-\tuffdio_register.range.start = (__u64)shstk_ptr;\n-\tuffdio_register.range.len = 4096;\n-\tuffdio_register.mode = UFFDIO_REGISTER_MODE_MISSING;\n-\tif (ioctl(uffd, UFFDIO_REGISTER, \u0026uffdio_register))\n-\t\tgoto err;\n-\n-\tif (pthread_create(\u0026thread, NULL, \u0026uffd_thread, \u0026uffd))\n-\t\tgoto err;\n-\n-\treset_shstk(shstk_ptr);\n-\ttest_shstk_access(shstk_ptr);\n-\n-\tif (pthread_join(thread, \u0026res))\n-\t\tgoto err;\n-\n-\tif (test_shstk_access(shstk_ptr))\n-\t\tgoto err;\n-\n-\tfree_shstk(shstk_ptr);\n-\n-\tsignal(SIGSEGV, SIG_DFL);\n-\n-\tif (!res)\n-\t\tprintf(\"[OK]\\tUserfaultfd test\\n\");\n-\treturn !!res;\n-err:\n-\tfree_shstk(shstk_ptr);\n-\tclose(uffd);\n-\tsignal(SIGSEGV, SIG_DFL);\n-\treturn 1;\n-}\n-\n-/* Simple linked list for keeping track of mappings in test_guard_gap() */\n-struct node {\n-\tstruct node *next;\n-\tvoid *mapping;\n-};\n-\n-/*\n- * This tests whether mmap will place other mappings in a shadow stack's guard\n- * gap. The steps are:\n- * 1. Finds an empty place by mapping and unmapping something.\n- * 2. Map a shadow stack in the middle of the known empty area.\n- * 3. Map a bunch of PAGE_SIZE mappings. These will use the search down\n- * direction, filling any gaps until it encounters the shadow stack's\n- * guard gap.\n- * 4. When a mapping lands below the shadow stack from step 2, then all\n- * of the above gaps are filled. The search down algorithm will have\n- * looked at the shadow stack gaps.\n- * 5. See if it landed in the gap.\n- */\n-int test_guard_gap_other_gaps(void)\n-{\n-\tvoid *free_area, *shstk, *test_map = (void *)0xFFFFFFFFFFFFFFFF;\n-\tstruct node *head = NULL, *cur;\n-\n-\tfree_area = mmap(0, SS_SIZE * 3, PROT_READ | PROT_WRITE,\n-\t\t\t MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);\n-\tmunmap(free_area, SS_SIZE * 3);\n-\n-\tshstk = create_shstk(free_area + SS_SIZE);\n-\tif (shstk == MAP_FAILED)\n-\t\treturn 1;\n-\n-\twhile (test_map \u003e shstk) {\n-\t\ttest_map = mmap(0, PAGE_SIZE, PROT_READ | PROT_WRITE,\n-\t\t\t\tMAP_PRIVATE | MAP_ANONYMOUS, -1, 0);\n-\t\tif (test_map == MAP_FAILED)\n-\t\t\treturn 1;\n-\t\tcur = malloc(sizeof(*cur));\n-\t\tcur-\u003emapping = test_map;\n-\n-\t\tcur-\u003enext = head;\n-\t\thead = cur;\n-\t}\n-\n-\twhile (head) {\n-\t\tcur = head;\n-\t\thead = cur-\u003enext;\n-\t\tmunmap(cur-\u003emapping, PAGE_SIZE);\n-\t\tfree(cur);\n-\t}\n-\n-\tfree_shstk(shstk);\n-\n-\tif (shstk - test_map - PAGE_SIZE != PAGE_SIZE)\n-\t\treturn 1;\n-\n-\tprintf(\"[OK]\\tGuard gap test, other mapping's gaps\\n\");\n-\n-\treturn 0;\n-}\n-\n-/* Tests respecting the guard gap of the mapping getting placed */\n-int test_guard_gap_new_mappings_gaps(void)\n-{\n-\tvoid *free_area, *shstk_start, *test_map = (void *)0xFFFFFFFFFFFFFFFF;\n-\tstruct node *head = NULL, *cur;\n-\tint ret = 0;\n-\n-\tfree_area = mmap(0, PAGE_SIZE * 4, PROT_READ | PROT_WRITE,\n-\t\t\t MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);\n-\tmunmap(free_area, PAGE_SIZE * 4);\n-\n-\t/* Test letting map_shadow_stack find a free space */\n-\tshstk_start = mmap(free_area, PAGE_SIZE, PROT_READ | PROT_WRITE,\n-\t\t\t MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);\n-\tif (shstk_start == MAP_FAILED || shstk_start != free_area)\n-\t\treturn 1;\n-\n-\twhile (test_map \u003e shstk_start) {\n-\t\ttest_map = (void *)syscall(__NR_map_shadow_stack, 0, PAGE_SIZE, 0);\n-\t\tif (test_map == MAP_FAILED) {\n-\t\t\tprintf(\"[INFO]\\tmap_shadow_stack MAP_FAILED\\n\");\n-\t\t\tret = 1;\n-\t\t\tbreak;\n-\t\t}\n-\n-\t\tcur = malloc(sizeof(*cur));\n-\t\tcur-\u003emapping = test_map;\n-\n-\t\tcur-\u003enext = head;\n-\t\thead = cur;\n-\n-\t\tif (test_map == free_area + PAGE_SIZE) {\n-\t\t\tprintf(\"[INFO]\\tNew mapping has other mapping in guard gap!\\n\");\n-\t\t\tret = 1;\n-\t\t\tbreak;\n-\t\t}\n-\t}\n-\n-\twhile (head) {\n-\t\tcur = head;\n-\t\thead = cur-\u003enext;\n-\t\tmunmap(cur-\u003emapping, PAGE_SIZE);\n-\t\tfree(cur);\n-\t}\n-\n-\tmunmap(shstk_start, PAGE_SIZE);\n-\n-\tif (!ret)\n-\t\tprintf(\"[OK]\\tGuard gap test, placement mapping's gaps\\n\");\n-\n-\treturn ret;\n-}\n-\n-/*\n- * Too complicated to pull it out of the 32 bit header, but also get the\n- * 64 bit one needed above. Just define a copy here.\n- */\n-#define __NR_compat_sigaction 67\n-\n-/*\n- * Call 32 bit signal handler to get 32 bit signals ABI. Make sure\n- * to push the registers that will get clobbered.\n- */\n-int sigaction32(int signum, const struct sigaction *restrict act,\n-\t\tstruct sigaction *restrict oldact)\n-{\n-\tregister long syscall_reg asm(\"eax\") = __NR_compat_sigaction;\n-\tregister long signum_reg asm(\"ebx\") = signum;\n-\tregister long act_reg asm(\"ecx\") = (long)act;\n-\tregister long oldact_reg asm(\"edx\") = (long)oldact;\n-\tint ret = 0;\n-\n-\tasm volatile (\"int $0x80;\"\n-\t\t : \"=a\"(ret), \"=m\"(oldact)\n-\t\t : \"r\"(syscall_reg), \"r\"(signum_reg), \"r\"(act_reg),\n-\t\t\t\"r\"(oldact_reg)\n-\t\t : \"r8\", \"r9\", \"r10\", \"r11\"\n-\t\t );\n-\n-\treturn ret;\n-}\n-\n-sigjmp_buf jmp_buffer;\n-\n-void segv_gp_handler(int signum, siginfo_t *si, void *uc)\n-{\n-\tsegv_triggered = true;\n-\n-\t/*\n-\t * To work with old glibc, this can't rely on siglongjmp working with\n-\t * shadow stack enabled, so disable shadow stack before siglongjmp().\n-\t */\n-\tARCH_PRCTL(ARCH_SHSTK_DISABLE, ARCH_SHSTK_SHSTK);\n-\tsiglongjmp(jmp_buffer, -1);\n-}\n-\n-/*\n- * Transition to 32 bit mode and check that a #GP triggers a segfault.\n- */\n-int test_32bit(void)\n-{\n-\tstruct sigaction sa = {};\n-\tstruct sigaction *sa32;\n-\n-\t/* Create sigaction in 32 bit address range */\n-\tsa32 = mmap(0, 4096, PROT_READ | PROT_WRITE,\n-\t\t MAP_32BIT | MAP_PRIVATE | MAP_ANONYMOUS, 0, 0);\n-\tsa32-\u003esa_flags = SA_SIGINFO;\n-\n-\tsa.sa_sigaction = segv_gp_handler;\n-\tsa.sa_flags = SA_SIGINFO;\n-\tif (sigaction(SIGSEGV, \u0026sa, NULL))\n-\t\treturn 1;\n-\n-\n-\tsegv_triggered = false;\n-\n-\t/* Make sure segv_triggered is set before triggering the #GP */\n-\tasm volatile(\"\" : : : \"memory\");\n-\n-\t/*\n-\t * Set handler to somewhere in 32 bit address space\n-\t */\n-\tsa32-\u003esa_handler = (void *)sa32;\n-\tif (sigaction32(SIGUSR1, sa32, NULL))\n-\t\treturn 1;\n-\n-\tif (!sigsetjmp(jmp_buffer, 1))\n-\t\traise(SIGUSR1);\n-\n-\tif (segv_triggered)\n-\t\tprintf(\"[OK]\\t32 bit test\\n\");\n-\n-\treturn !segv_triggered;\n-}\n-\n-static int parse_uint_from_file(const char *file, const char *fmt)\n-{\n-\tint err, ret;\n-\tFILE *f;\n-\n-\tf = fopen(file, \"re\");\n-\tif (!f) {\n-\t\terr = -errno;\n-\t\tprintf(\"failed to open '%s': %d\\n\", file, err);\n-\t\treturn err;\n-\t}\n-\terr = fscanf(f, fmt, \u0026ret);\n-\tif (err != 1) {\n-\t\terr = err == EOF ? -EIO : -errno;\n-\t\tprintf(\"failed to parse '%s': %d\\n\", file, err);\n-\t\tfclose(f);\n-\t\treturn err;\n-\t}\n-\tfclose(f);\n-\treturn ret;\n-}\n-\n-static int determine_uprobe_perf_type(void)\n-{\n-\tconst char *file = \"/sys/bus/event_source/devices/uprobe/type\";\n-\n-\treturn parse_uint_from_file(file, \"%d\\n\");\n-}\n-\n-static int determine_uprobe_retprobe_bit(void)\n-{\n-\tconst char *file = \"/sys/bus/event_source/devices/uprobe/format/retprobe\";\n-\n-\treturn parse_uint_from_file(file, \"config:%d\\n\");\n-}\n-\n-static ssize_t get_uprobe_offset(const void *addr)\n-{\n-\tsize_t start, end, base;\n-\tchar buf[256];\n-\tbool found = false;\n-\tFILE *f;\n-\n-\tf = fopen(\"/proc/self/maps\", \"r\");\n-\tif (!f)\n-\t\treturn -errno;\n-\n-\twhile (fscanf(f, \"%zx-%zx %s %zx %*[^\\n]\\n\", \u0026start, \u0026end, buf, \u0026base) == 4) {\n-\t\tif (buf[2] == 'x' \u0026\u0026 (uintptr_t)addr \u003e= start \u0026\u0026 (uintptr_t)addr \u003c end) {\n-\t\t\tfound = true;\n-\t\t\tbreak;\n-\t\t}\n-\t}\n-\n-\tfclose(f);\n-\n-\tif (!found)\n-\t\treturn -ESRCH;\n-\n-\treturn (uintptr_t)addr - start + base;\n-}\n-\n-static __attribute__((noinline)) void uretprobe_trigger(void)\n-{\n-\tasm volatile (\"\");\n-}\n-\n-/*\n- * This test setups return uprobe, which is sensitive to shadow stack\n- * (crashes without extra fix). After executing the uretprobe we fail\n- * the test if we receive SIGSEGV, no crash means we're good.\n- *\n- * Helper functions above borrowed from bpf selftests.\n- */\n-static int test_uretprobe(void)\n-{\n-\tconst size_t attr_sz = sizeof(struct perf_event_attr);\n-\tconst char *file = \"/proc/self/exe\";\n-\tint bit, fd = 0, type, err = 1;\n-\tstruct perf_event_attr attr;\n-\tstruct sigaction sa = {};\n-\tssize_t offset;\n-\n-\ttype = determine_uprobe_perf_type();\n-\tif (type \u003c 0) {\n-\t\tif (type == -ENOENT)\n-\t\t\tprintf(\"[SKIP]\\tUretprobe test, uprobes are not available\\n\");\n-\t\treturn 0;\n-\t}\n-\n-\toffset = get_uprobe_offset(uretprobe_trigger);\n-\tif (offset \u003c 0)\n-\t\treturn 1;\n-\n-\tbit = determine_uprobe_retprobe_bit();\n-\tif (bit \u003c 0)\n-\t\treturn 1;\n-\n-\tsa.sa_sigaction = segv_gp_handler;\n-\tsa.sa_flags = SA_SIGINFO;\n-\tif (sigaction(SIGSEGV, \u0026sa, NULL))\n-\t\treturn 1;\n-\n-\t/* Setup return uprobe through perf event interface. */\n-\tmemset(\u0026attr, 0, attr_sz);\n-\tattr.size = attr_sz;\n-\tattr.type = type;\n-\tattr.config = 1 \u003c\u003c bit;\n-\tattr.config1 = (__u64) (unsigned long) file;\n-\tattr.config2 = offset;\n-\n-\tfd = syscall(__NR_perf_event_open, \u0026attr, 0 /* pid */, -1 /* cpu */,\n-\t\t -1 /* group_fd */, PERF_FLAG_FD_CLOEXEC);\n-\tif (fd \u003c 0)\n-\t\tgoto out;\n-\n-\tif (sigsetjmp(jmp_buffer, 1))\n-\t\tgoto out;\n-\n-\tARCH_PRCTL(ARCH_SHSTK_ENABLE, ARCH_SHSTK_SHSTK);\n-\n-\t/*\n-\t * This either segfaults and goes through sigsetjmp above\n-\t * or succeeds and we're good.\n-\t */\n-\turetprobe_trigger();\n-\n-\tprintf(\"[OK]\\tUretprobe test\\n\");\n-\terr = 0;\n-\n-out:\n-\tARCH_PRCTL(ARCH_SHSTK_DISABLE, ARCH_SHSTK_SHSTK);\n-\tsignal(SIGSEGV, SIG_DFL);\n-\tif (fd)\n-\t\tclose(fd);\n-\treturn err;\n-}\n-\n-/* Keep the CALL first so the function address is exactly the probed CALL. */\n-extern void uprobe_call_trigger(void);\n-asm (\".pushsection .text\\n\"\n-\t\".global uprobe_call_target\\n\"\n-\t\".type uprobe_call_target, @function\\n\"\n-\t\"uprobe_call_target:\\n\"\n-\t\"\tret\\n\"\n-\t\".size uprobe_call_target, .-uprobe_call_target\\n\"\n-\n-\t\".global uprobe_call_trigger\\n\"\n-\t\".type uprobe_call_trigger, @function\\n\"\n-\t\"uprobe_call_trigger:\\n\"\n-\t\"\tcall uprobe_call_target\\n\"\n-\t\"\tret\\n\"\n-\t\".size uprobe_call_trigger, .-uprobe_call_trigger\\n\"\n-\t\".popsection\\n\"\n-);\n-\n-/* If CALL emulation misses the shadow stack update, this exits via SIGSEGV. */\n-static int test_uprobe_call(void)\n-{\n-\tconst size_t attr_sz = sizeof(struct perf_event_attr);\n-\tconst char *file = \"/proc/self/exe\";\n-\tint fd = -1, type, err = 1;\n-\tstruct perf_event_attr attr;\n-\tstruct sigaction sa = {};\n-\tssize_t offset;\n-\n-\ttype = determine_uprobe_perf_type();\n-\tif (type \u003c 0) {\n-\t\tif (type == -ENOENT)\n-\t\t\tprintf(\"[SKIP]\\tUprobe on CALL test, uprobes are not available\\n\");\n-\t\treturn 0;\n-\t}\n-\n-\toffset = get_uprobe_offset(uprobe_call_trigger);\n-\tif (offset \u003c 0)\n-\t\treturn 1;\n-\n-\tsa.sa_sigaction = segv_gp_handler;\n-\tsa.sa_flags = SA_SIGINFO;\n-\tif (sigaction(SIGSEGV, \u0026sa, NULL))\n-\t\treturn 1;\n-\n-\t/* Setup entry uprobe through perf event interface. */\n-\tmemset(\u0026attr, 0, attr_sz);\n-\tattr.size = attr_sz;\n-\tattr.type = type;\n-\tattr.config = 0;\n-\tattr.config1 = (__u64)(unsigned long)file;\n-\tattr.config2 = offset;\n-\n-\tfd = syscall(__NR_perf_event_open, \u0026attr, 0 /* pid */, -1 /* cpu */,\n-\t\t -1 /* group_fd */, PERF_FLAG_FD_CLOEXEC);\n-\tif (fd \u003c 0)\n-\t\tgoto out;\n-\n-\tif (sigsetjmp(jmp_buffer, 1))\n-\t\tgoto out;\n-\n-\tif (ARCH_PRCTL(ARCH_SHSTK_ENABLE, ARCH_SHSTK_SHSTK))\n-\t\tgoto out;\n-\n-\t/*\n-\t * This either segfaults and goes through sigsetjmp above\n-\t * or succeeds and we're good.\n-\t */\n-\tuprobe_call_trigger();\n-\n-\tprintf(\"[OK]\\tUprobe on CALL test\\n\");\n-\terr = 0;\n-\n-out:\n-\tARCH_PRCTL(ARCH_SHSTK_DISABLE, ARCH_SHSTK_SHSTK);\n-\tsignal(SIGSEGV, SIG_DFL);\n-\tif (fd \u003e= 0)\n-\t\tclose(fd);\n-\treturn err;\n-}\n-\n-void segv_handler_ptrace(int signum, siginfo_t *si, void *uc)\n-{\n-\t/* The SSP adjustment caused a segfault. */\n-\texit(0);\n-}\n-\n-int test_ptrace(void)\n-{\n-\tunsigned long saved_ssp, ssp = 0;\n-\tstruct sigaction sa= {};\n-\tstruct iovec iov;\n-\tint status;\n-\tint pid;\n-\n-\tiov.iov_base = \u0026ssp;\n-\tiov.iov_len = sizeof(ssp);\n-\n-\tpid = fork();\n-\tif (!pid) {\n-\t\tssp = get_ssp();\n-\n-\t\tsa.sa_sigaction = segv_handler_ptrace;\n-\t\tsa.sa_flags = SA_SIGINFO;\n-\t\tif (sigaction(SIGSEGV, \u0026sa, NULL))\n-\t\t\treturn 1;\n-\n-\t\tptrace(PTRACE_TRACEME, NULL, NULL, NULL);\n-\t\t/*\n-\t\t * The parent will tweak the SSP and return from this function\n-\t\t * will #CP.\n-\t\t */\n-\t\traise(SIGTRAP);\n-\n-\t\texit(1);\n-\t}\n-\n-\twhile (waitpid(pid, \u0026status, 0) != -1 \u0026\u0026 WSTOPSIG(status) != SIGTRAP);\n-\n-\tif (ptrace(PTRACE_GETREGSET, pid, NT_X86_SHSTK, \u0026iov)) {\n-\t\tprintf(\"[INFO]\\tFailed to PTRACE_GETREGS\\n\");\n-\t\tgoto out_kill;\n-\t}\n-\n-\tif (!ssp) {\n-\t\tprintf(\"[INFO]\\tPtrace child SSP was 0\\n\");\n-\t\tgoto out_kill;\n-\t}\n-\n-\tsaved_ssp = ssp;\n-\n-\tiov.iov_len = 0;\n-\tif (!ptrace(PTRACE_SETREGSET, pid, NT_X86_SHSTK, \u0026iov)) {\n-\t\tprintf(\"[INFO]\\tToo small size accepted via PTRACE_SETREGS\\n\");\n-\t\tgoto out_kill;\n-\t}\n-\n-\tiov.iov_len = sizeof(ssp) + 1;\n-\tif (!ptrace(PTRACE_SETREGSET, pid, NT_X86_SHSTK, \u0026iov)) {\n-\t\tprintf(\"[INFO]\\tToo large size accepted via PTRACE_SETREGS\\n\");\n-\t\tgoto out_kill;\n-\t}\n-\n-\tssp += 1;\n-\tif (!ptrace(PTRACE_SETREGSET, pid, NT_X86_SHSTK, \u0026iov)) {\n-\t\tprintf(\"[INFO]\\tUnaligned SSP written via PTRACE_SETREGS\\n\");\n-\t\tgoto out_kill;\n-\t}\n-\n-\tssp = 0xFFFFFFFFFFFF0000;\n-\tif (!ptrace(PTRACE_SETREGSET, pid, NT_X86_SHSTK, \u0026iov)) {\n-\t\tprintf(\"[INFO]\\tKernel range SSP written via PTRACE_SETREGS\\n\");\n-\t\tgoto out_kill;\n-\t}\n-\n-\t/*\n-\t * Tweak the SSP so the child with #CP when it resumes and returns\n-\t * from raise()\n-\t */\n-\tssp = saved_ssp + 8;\n-\tiov.iov_len = sizeof(ssp);\n-\tif (ptrace(PTRACE_SETREGSET, pid, NT_X86_SHSTK, \u0026iov)) {\n-\t\tprintf(\"[INFO]\\tFailed to PTRACE_SETREGS\\n\");\n-\t\tgoto out_kill;\n-\t}\n-\n-\tif (ptrace(PTRACE_DETACH, pid, NULL, NULL)) {\n-\t\tprintf(\"[INFO]\\tFailed to PTRACE_DETACH\\n\");\n-\t\tgoto out_kill;\n-\t}\n-\n-\twaitpid(pid, \u0026status, 0);\n-\tif (WEXITSTATUS(status))\n-\t\treturn 1;\n-\n-\tprintf(\"[OK]\\tPtrace test\\n\");\n-\treturn 0;\n-\n-out_kill:\n-\tkill(pid, SIGKILL);\n-\treturn 1;\n-}\n+#include \"shadow_stack.h\"\n \n int main(int argc, char *argv[])\n {\n-\tint ret = 0;\n-\n-\tif (ARCH_PRCTL(ARCH_SHSTK_ENABLE, ARCH_SHSTK_SHSTK)) {\n-\t\tprintf(\"[SKIP]\\tCould not enable Shadow stack\\n\");\n-\t\treturn 1;\n-\t}\n-\n-\tif (ARCH_PRCTL(ARCH_SHSTK_DISABLE, ARCH_SHSTK_SHSTK)) {\n-\t\tret = 1;\n-\t\tprintf(\"[FAIL]\\tDisabling shadow stack failed\\n\");\n-\t}\n-\n-\tif (ARCH_PRCTL(ARCH_SHSTK_ENABLE, ARCH_SHSTK_SHSTK)) {\n-\t\tprintf(\"[SKIP]\\tCould not re-enable Shadow stack\\n\");\n-\t\treturn 1;\n-\t}\n-\n-\tif (ARCH_PRCTL(ARCH_SHSTK_ENABLE, ARCH_SHSTK_WRSS)) {\n-\t\tprintf(\"[SKIP]\\tCould not enable WRSS\\n\");\n-\t\tret = 1;\n-\t\tgoto out;\n-\t}\n-\n-\t/* Should have succeeded if here, but this is a test, so double check. */\n-\tif (!get_ssp()) {\n-\t\tprintf(\"[FAIL]\\tShadow stack disabled\\n\");\n-\t\treturn 1;\n-\t}\n-\n-\tif (test_shstk_pivot()) {\n-\t\tret = 1;\n-\t\tprintf(\"[FAIL]\\tShadow stack pivot\\n\");\n-\t\tgoto out;\n-\t}\n-\n-\tif (test_shstk_faults()) {\n-\t\tret = 1;\n-\t\tprintf(\"[FAIL]\\tShadow stack fault test\\n\");\n-\t\tgoto out;\n-\t}\n-\n-\tif (test_shstk_violation()) {\n-\t\tret = 1;\n-\t\tprintf(\"[FAIL]\\tShadow stack violation test\\n\");\n-\t\tgoto out;\n-\t}\n-\n-\tif (test_gup()) {\n-\t\tret = 1;\n-\t\tprintf(\"[FAIL]\\tShadow shadow stack gup\\n\");\n-\t\tgoto out;\n-\t}\n-\n-\tif (test_mprotect()) {\n-\t\tret = 1;\n-\t\tprintf(\"[FAIL]\\tShadow shadow mprotect test\\n\");\n-\t\tgoto out;\n-\t}\n-\n-\tif (test_userfaultfd()) {\n-\t\tret = 1;\n-\t\tprintf(\"[FAIL]\\tUserfaultfd test\\n\");\n-\t\tgoto out;\n-\t}\n-\n-\tif (test_guard_gap_other_gaps()) {\n-\t\tret = 1;\n-\t\tprintf(\"[FAIL]\\tGuard gap test, other mappings' gaps\\n\");\n-\t\tgoto out;\n-\t}\n-\n-\tif (test_guard_gap_new_mappings_gaps()) {\n-\t\tret = 1;\n-\t\tprintf(\"[FAIL]\\tGuard gap test, placement mapping's gaps\\n\");\n-\t\tgoto out;\n-\t}\n-\n-\tif (test_ptrace()) {\n-\t\tret = 1;\n-\t\tprintf(\"[FAIL]\\tptrace test\\n\");\n-\t}\n-\n-\tif (test_32bit()) {\n-\t\tret = 1;\n-\t\tprintf(\"[FAIL]\\t32 bit test\\n\");\n-\t\tgoto out;\n-\t}\n-\n-\tif (test_uretprobe()) {\n-\t\tret = 1;\n-\t\tprintf(\"[FAIL]\\turetprobe test\\n\");\n-\t\tgoto out;\n-\t}\n-\n-\tif (test_uprobe_call()) {\n-\t\tret = 1;\n-\t\tprintf(\"[FAIL]\\tuprobe on CALL test\\n\");\n-\t\tgoto out;\n-\t}\n-\n-\treturn ret;\n-\n-out:\n-\t/*\n-\t * Disable shadow stack before the function returns, or there will be a\n-\t * shadow stack violation.\n-\t */\n-\tif (ARCH_PRCTL(ARCH_SHSTK_DISABLE, ARCH_SHSTK_SHSTK)) {\n-\t\tret = 1;\n-\t\tprintf(\"[FAIL]\\tDisabling shadow stack failed\\n\");\n-\t}\n-\n-\treturn ret;\n+\treturn shadow_stack_run_tests(SHADOW_STACK_TEST_ARCH_PRCTL);\n }\n-#endif\ndiff --git a/tools/testing/selftests/x86/test_shadow_stack_prctl.c b/tools/testing/selftests/x86/test_shadow_stack_prctl.c\nnew file mode 100644\nindex 0000000000000..4641a3b1ca036\n--- /dev/null\n+++ b/tools/testing/selftests/x86/test_shadow_stack_prctl.c\n@@ -0,0 +1,12 @@\n+// SPDX-License-Identifier: GPL-2.0\n+\n+/*\n+ * Same as test_shadow_stack.c but uses the PRCTL interface\n+ * instead of ARCH_PRCTL.\n+ */\n+#include \"shadow_stack.h\"\n+\n+int main(int argc, char *argv[])\n+{\n+\treturn shadow_stack_run_tests(SHADOW_STACK_TEST_PRCTL);\n+}\n"
} |
| 2/1 | 2026/08/12 19:51 | agent | patch-evaluator |
0mModel:best-expensive Args: null Results: {
"EnableConfigs": [
"X86_USER_SHADOW_STACK"
],
"FocusSymbols": [
"arch_set_shadow_stack_status",
"arch_lock_shadow_stack_status",
"arch_get_shadow_stack_status"
],
"Reasoning": "The patch adds generic prctl support for x86 shadow stack. The functions `arch_set_shadow_stack_status`, `arch_lock_shadow_stack_status`, and `arch_get_shadow_stack_status` are reachable via the `prctl` syscall and should be fuzzed to ensure correct handling of the new prctl options.",
"WorthFuzzing": true
}Instruction: You are an expert Linux kernel maintainer. Your job is to review a provided patch series and determine if it makes functional changes to the kernel that should be fuzzed. IMPORTANT: The changes have ALREADY been applied and committed as the HEAD commit in your workspace. Do NOT rely on your internal knowledge of the kernel. You must actively use your code access tools to examine the actual source code and confirm any assumptions. Return WorthFuzzing=false if the patch only contains: - Modifications to Documentation/, Kconfig files, or code comments. - Purely decorative changes, such as logging (e.g., pr_err, printk) or tracepoints. - Changes to numeric constants or macros that do not functionally alter execution flow. - Code paths that are impossible to reach in virtualized environments like GCE or QEMU, even when utilizing software-emulated hardware (e.g., usb gadget, mac80211_hwsim). - Code in vendor-specific PCIe switch, SmartNIC, or GPU drivers (e.g., mlxsw, pds_core, qed, ionic, amdgpu) that require physical PCIe hardware cards not emulated in standard QEMU. - Driver .remove, .shutdown, or pci_unregister_driver teardown callbacks (e.g., igb_remove) that are executed only during PCI hot-unplug or sysfs driver unbind operations. If it modifies reachable core kernel logic, drivers, or architectures, use your code search tools to verify the code can be executed, then return WorthFuzzing=true. When returning WorthFuzzing=true, you MUST ALSO: 1. Extract any specific kernel functions that should be heavily fuzzed into FocusSymbols. Avoid listing generic hot-path functions to prevent skewed test distributions. Prefer non-static, non-inlined API entrypoint functions over internal static helper functions (which are inlined by the compiler and do not have distinct symbol addresses). 2. Identify any specific CONFIG_ options required to properly test this new/modified feature. Go and look into the Kconfig files and check for ifdefs around the code, do not make assumptions. Also check "depends on" lines in Kconfig to include any non-standard parent subsystem configs needed for Kbuild to compile the code statically into vmlinux. List them in the EnableConfigs output array, and DO NOT add a 'CONFIG_' prefix (e.g., return "NET_IPV4" instead of "CONFIG_NET_IPV4"). Prefer calling several tools at the same time to save round-trips. Use set-results tool to provide results of the analysis. It must be called exactly once before the final reply. Ignore results of this tool. Prompt: For your convenience, here is the diff of the changes:
commit 079ec6fe340458147e0fc94e583f8c31b1ddb2e8
Author: syz-cluster <triage@syzkaller.com>
Date: Wed Aug 12 19:51:31 2026 +0000
syz-cluster: applied patch under review
diff --git a/arch/x86/kernel/shstk.c b/arch/x86/kernel/shstk.c
index 0ca64900192f8..c34ba3701dd06 100644
--- a/arch/x86/kernel/shstk.c
+++ b/arch/x86/kernel/shstk.c
@@ -18,6 +18,7 @@
#include <linux/sizes.h>
#include <linux/user.h>
#include <linux/syscalls.h>
+#include <linux/prctl.h>
#include <asm/msr.h>
#include <asm/fpu/xstate.h>
#include <asm/fpu/types.h>
@@ -630,3 +631,39 @@ bool shstk_is_enabled(void)
{
return features_enabled(ARCH_SHSTK_SHSTK);
}
+
+#define PR_SHADOW_STACK_SUPPORTED_STATUS_MASK \
+ (PR_SHADOW_STACK_ENABLE | PR_SHADOW_STACK_WRITE | PR_SHADOW_STACK_PUSH)
+
+/* Handles the generic prctl interface for PR_SET_SHADOW_STACK_STATUS and its feature bits */
+int arch_set_shadow_stack_status(struct task_struct *t, unsigned long status)
+{
+ int rc;
+
+ if (status & ~PR_SHADOW_STACK_SUPPORTED_STATUS_MASK)
+ return -EINVAL;
+
+ /* x86 arch_prctl is single bit at a time, so handle these one at time */
+ if (!status & PR_SHADOW_STACK_ENABLE)
+ return shstk_prctl(t, ARCH_SHSTK_DISABLE, ARCH_SHSTK_SHSTK);
+
+ rc = shstk_prctl(t, ARCH_SHSTK_ENABLE, ARCH_SHSTK_SHSTK);
+ if (rc)
+ return rc;
+
+ if (status & PR_SHADOW_STACK_WRITE)
+ return shstk_prctl(t, ARCH_SHSTK_ENABLE, ARCH_SHSTK_WRSS);
+
+ return shstk_prctl(t, ARCH_SHSTK_DISABLE, ARCH_SHSTK_WRSS);
+}
+
+/* Handles the generic prctl interface for PR_LOCK_SHADOW_STACK_STATUS and its feature bits */
+int arch_lock_shadow_stack_status(struct task_struct *t, unsigned long status)
+{
+ return shstk_prctl(t, ARCH_SHSTK_LOCK, status);
+}
+
+int arch_get_shadow_stack_status(struct task_struct *t, unsigned long __user *status)
+{
+ return shstk_prctl(t, ARCH_SHSTK_STATUS, (unsigned long)status);
+}
diff --git a/tools/testing/selftests/x86/Makefile b/tools/testing/selftests/x86/Makefile
index 434065215d127..8748e2f4a915d 100644
--- a/tools/testing/selftests/x86/Makefile
+++ b/tools/testing/selftests/x86/Makefile
@@ -19,7 +19,8 @@ TARGETS_C_32BIT_ONLY := entry_from_vm86 test_syscall_vdso unwind_vdso \
test_FCMOV test_FCOMI test_FISTTP \
vdso_restorer
TARGETS_C_64BIT_ONLY := fsgsbase sysret_rip syscall_numbering \
- corrupt_xstate_header amx lam test_shadow_stack avx apx
+ corrupt_xstate_header amx lam test_shadow_stack avx apx \
+ test_shadow_stack_prctl
# Some selftests require 32bit support enabled also on 64bit systems
TARGETS_C_32BIT_NEEDED := ldt_gdt ptrace_syscall
@@ -138,3 +139,5 @@ $(OUTPUT)/avx_64: CFLAGS += -mno-avx -mno-avx512f
$(OUTPUT)/amx_64: EXTRA_FILES += xstate.c
$(OUTPUT)/avx_64: EXTRA_FILES += xstate.c
$(OUTPUT)/apx_64: EXTRA_FILES += xstate.c
+$(OUTPUT)/test_shadow_stack_64: EXTRA_FILES += shadow_stack.c
+$(OUTPUT)/test_shadow_stack_prctl_64: EXTRA_FILES += shadow_stack.c
diff --git a/tools/testing/selftests/x86/shadow_stack.c b/tools/testing/selftests/x86/shadow_stack.c
new file mode 100644
index 0000000000000..a375c4ff7a6ac
--- /dev/null
+++ b/tools/testing/selftests/x86/shadow_stack.c
@@ -0,0 +1,1205 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#define _GNU_SOURCE
+
+#include <sys/syscall.h>
+#include <asm/mman.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+#include <stdbool.h>
+#include <x86intrin.h>
+#include <asm/prctl.h>
+#include <sys/prctl.h>
+#include <stdint.h>
+#include <signal.h>
+#include <pthread.h>
+#include <sys/ioctl.h>
+#include <linux/userfaultfd.h>
+#include <setjmp.h>
+#include <sys/ptrace.h>
+#include <sys/signal.h>
+#include <linux/elf.h>
+#include <linux/perf_event.h>
+
+#include "shadow_stack.h"
+
+/*
+ * Define the ABI defines if needed, so people can run the tests
+ * without building the headers.
+ */
+#ifndef __NR_map_shadow_stack
+#define __NR_map_shadow_stack 453
+
+#define SHADOW_STACK_SET_TOKEN (1ULL << 0)
+
+#define ARCH_SHSTK_ENABLE 0x5001
+#define ARCH_SHSTK_DISABLE 0x5002
+#define ARCH_SHSTK_LOCK 0x5003
+#define ARCH_SHSTK_UNLOCK 0x5004
+#define ARCH_SHSTK_STATUS 0x5005
+
+#define ARCH_SHSTK_SHSTK (1ULL << 0)
+#define ARCH_SHSTK_WRSS (1ULL << 1)
+
+#define NT_X86_SHSTK 0x204
+#endif
+
+#define SS_SIZE 0x200000
+#define PAGE_SIZE 0x1000
+
+void write_shstk(unsigned long *addr, unsigned long val)
+{
+ asm volatile("wrssq %[val], (%[addr])\n"
+ : "=m" (addr)
+ : [addr] "r" (addr), [val] "r" (val));
+}
+
+static inline unsigned long __attribute__((always_inline)) get_ssp(void)
+{
+ unsigned long ret = 0;
+
+ asm volatile("xor %0, %0; rdsspq %0" : "=r" (ret));
+ return ret;
+}
+
+/*
+ * For use in inline enablement of shadow stack.
+ *
+ * The program can't return from the point where shadow stack gets enabled
+ * because there will be no address on the shadow stack. So it can't use
+ * syscall() for enablement, since it is a function.
+ *
+ * Based on code from nolibc.h. Keep a copy here because this can't pull in all
+ * of nolibc.h.
+ */
+#define ARCH_PRCTL(arg1, arg2) \
+({ \
+ long _ret; \
+ register long _num asm("eax") = __NR_arch_prctl; \
+ register long _arg1 asm("rdi") = (long)(arg1); \
+ register long _arg2 asm("rsi") = (long)(arg2); \
+ \
+ asm volatile ( \
+ "syscall\n" \
+ : "=a"(_ret) \
+ : "r"(_arg1), "r"(_arg2), \
+ "0"(_num) \
+ : "rcx", "r11", "memory", "cc" \
+ ); \
+ _ret; \
+})
+
+#define PRCTL(option, arg2, arg3, arg4, arg5) \
+({ \
+ long _ret; \
+ register long _num asm("rax") = __NR_prctl; \
+ register long _arg1 asm("rdi") = (long)(option); \
+ register long _arg2 asm("rsi") = (long)(arg2); \
+ register long _arg3 asm("rdx") = (long)(arg3); \
+ register long _arg4 asm("r10") = (long)(arg4); \
+ register long _arg5 asm("r8") = (long)(arg5); \
+ \
+ asm volatile ( \
+ "syscall" \
+ : "=a"(_ret) \
+ : "0"(_num), \
+ "r"(_arg1), "r"(_arg2), "r"(_arg3), \
+ "r"(_arg4), "r"(_arg5) \
+ : "rcx", "r11", "memory", "cc" \
+ ); \
+ _ret; \
+})
+
+#define SHADOW_STACK_DISABLE(which_test) \
+ ((which_test) == SHADOW_STACK_TEST_ARCH_PRCTL ? \
+ ARCH_PRCTL(ARCH_SHSTK_DISABLE, ARCH_SHSTK_SHSTK) : \
+ PRCTL(PR_SET_SHADOW_STACK_STATUS, 0, 0, 0, 0))
+
+#define SHADOW_STACK_ENABLE(which_test) \
+ ((which_test) == SHADOW_STACK_TEST_ARCH_PRCTL ? \
+ ARCH_PRCTL(ARCH_SHSTK_ENABLE, ARCH_SHSTK_SHSTK) : \
+ PRCTL(PR_SET_SHADOW_STACK_STATUS, PR_SHADOW_STACK_ENABLE, 0, 0, 0))
+
+#define SHADOW_STACK_ENABLE_WRITE(which_test) \
+ ((which_test) == SHADOW_STACK_TEST_ARCH_PRCTL ? \
+ ARCH_PRCTL(ARCH_SHSTK_ENABLE, ARCH_SHSTK_WRSS) : \
+ PRCTL(PR_SET_SHADOW_STACK_STATUS, \
+ PR_SHADOW_STACK_ENABLE|PR_SHADOW_STACK_WRITE, 0, 0, 0))
+
+void *create_shstk(void *addr)
+{
+ return (void *)syscall(__NR_map_shadow_stack, addr, SS_SIZE, SHADOW_STACK_SET_TOKEN);
+}
+
+void *create_normal_mem(void *addr)
+{
+ return mmap(addr, SS_SIZE, PROT_READ | PROT_WRITE,
+ MAP_PRIVATE | MAP_ANONYMOUS, 0, 0);
+}
+
+void free_shstk(void *shstk)
+{
+ munmap(shstk, SS_SIZE);
+}
+
+int reset_shstk(void *shstk)
+{
+ return madvise(shstk, SS_SIZE, MADV_DONTNEED);
+}
+
+void try_shstk(unsigned long new_ssp)
+{
+ unsigned long ssp;
+
+ printf("[INFO]\tnew_ssp = %lx, *new_ssp = %lx\n",
+ new_ssp, *((unsigned long *)new_ssp));
+
+ ssp = get_ssp();
+ printf("[INFO]\tchanging ssp from %lx to %lx\n", ssp, new_ssp);
+
+ asm volatile("rstorssp (%0)\n":: "r" (new_ssp));
+ asm volatile("saveprevssp");
+ printf("[INFO]\tssp is now %lx\n", get_ssp());
+
+ /* Switch back to original shadow stack */
+ ssp -= 8;
+ asm volatile("rstorssp (%0)\n":: "r" (ssp));
+ asm volatile("saveprevssp");
+}
+
+int test_shstk_pivot(void)
+{
+ void *shstk = create_shstk(0);
+
+ if (shstk == MAP_FAILED) {
+ printf("[FAIL]\tError creating shadow stack: %d\n", errno);
+ return 1;
+ }
+ try_shstk((unsigned long)shstk + SS_SIZE - 8);
+ free_shstk(shstk);
+
+ printf("[OK]\tShadow stack pivot\n");
+ return 0;
+}
+
+int test_shstk_faults(void)
+{
+ unsigned long *shstk = create_shstk(0);
+
+ /* Read shadow stack, test if it's zero to not get read optimized out */
+ if (*shstk != 0)
+ goto err;
+
+ /* Wrss memory that was already read. */
+ write_shstk(shstk, 1);
+ if (*shstk != 1)
+ goto err;
+
+ /* Page out memory, so we can wrss it again. */
+ if (reset_shstk((void *)shstk))
+ goto err;
+
+ write_shstk(shstk, 1);
+ if (*shstk != 1)
+ goto err;
+
+ printf("[OK]\tShadow stack faults\n");
+ return 0;
+
+err:
+ return 1;
+}
+
+unsigned long saved_ssp;
+unsigned long saved_ssp_val;
+volatile bool segv_triggered;
+
+void __attribute__((noinline)) violate_ss(void)
+{
+ saved_ssp = get_ssp();
+ saved_ssp_val = *(unsigned long *)saved_ssp;
+
+ /* Corrupt shadow stack */
+ printf("[INFO]\tCorrupting shadow stack\n");
+ write_shstk((void *)saved_ssp, 0);
+}
+
+void segv_handler(int signum, siginfo_t *si, void *uc)
+{
+ printf("[INFO]\tGenerated shadow stack violation successfully\n");
+
+ segv_triggered = true;
+
+ /* Fix shadow stack */
+ write_shstk((void *)saved_ssp, saved_ssp_val);
+}
+
+int test_shstk_violation(void)
+{
+ struct sigaction sa = {};
+
+ sa.sa_sigaction = segv_handler;
+ sa.sa_flags = SA_SIGINFO;
+ if (sigaction(SIGSEGV, &sa, NULL))
+ return 1;
+
+ segv_triggered = false;
+
+ /* Make sure segv_triggered is set before violate_ss() */
+ asm volatile("" : : : "memory");
+
+ violate_ss();
+
+ signal(SIGSEGV, SIG_DFL);
+
+ printf("[OK]\tShadow stack violation test\n");
+
+ return !segv_triggered;
+}
+
+/* Gup test state */
+#define MAGIC_VAL 0x12345678
+bool is_shstk_access;
+void *shstk_ptr;
+int fd;
+
+void reset_test_shstk(void *addr)
+{
+ if (shstk_ptr)
+ free_shstk(shstk_ptr);
+ shstk_ptr = create_shstk(addr);
+}
+
+void test_access_fix_handler(int signum, siginfo_t *si, void *uc)
+{
+ printf("[INFO]\tViolation from %s\n", is_shstk_access ? "shstk access" : "normal write");
+
+ segv_triggered = true;
+
+ /* Fix shadow stack */
+ if (is_shstk_access) {
+ reset_test_shstk(shstk_ptr);
+ return;
+ }
+
+ free_shstk(shstk_ptr);
+ create_normal_mem(shstk_ptr);
+}
+
+bool test_shstk_access(void *ptr)
+{
+ is_shstk_access = true;
+ segv_triggered = false;
+ write_shstk(ptr, MAGIC_VAL);
+
+ asm volatile("" : : : "memory");
+
+ return segv_triggered;
+}
+
+bool test_write_access(void *ptr)
+{
+ is_shstk_access = false;
+ segv_triggered = false;
+ *(unsigned long *)ptr = MAGIC_VAL;
+
+ asm volatile("" : : : "memory");
+
+ return segv_triggered;
+}
+
+bool gup_write(void *ptr)
+{
+ unsigned long val;
+
+ lseek(fd, (unsigned long)ptr, SEEK_SET);
+ if (write(fd, &val, sizeof(val)) < 0)
+ return 1;
+
+ return 0;
+}
+
+bool gup_read(void *ptr)
+{
+ unsigned long val;
+
+ lseek(fd, (unsigned long)ptr, SEEK_SET);
+ if (read(fd, &val, sizeof(val)) < 0)
+ return 1;
+
+ return 0;
+}
+
+int test_gup(void)
+{
+ struct sigaction sa = {};
+ int status;
+ pid_t pid;
+
+ sa.sa_sigaction = test_access_fix_handler;
+ sa.sa_flags = SA_SIGINFO;
+ if (sigaction(SIGSEGV, &sa, NULL))
+ return 1;
+
+ segv_triggered = false;
+
+ fd = open("/proc/self/mem", O_RDWR);
+ if (fd == -1)
+ return 1;
+
+ reset_test_shstk(0);
+ if (gup_read(shstk_ptr))
+ return 1;
+ if (test_shstk_access(shstk_ptr))
+ return 1;
+ printf("[INFO]\tGup read -> shstk access success\n");
+
+ reset_test_shstk(0);
+ if (gup_write(shstk_ptr))
+ return 1;
+ if (test_shstk_access(shstk_ptr))
+ return 1;
+ printf("[INFO]\tGup write -> shstk access success\n");
+
+ reset_test_shstk(0);
+ if (gup_read(shstk_ptr))
+ return 1;
+ if (!test_write_access(shstk_ptr))
+ return 1;
+ printf("[INFO]\tGup read -> write access success\n");
+
+ reset_test_shstk(0);
+ if (gup_write(shstk_ptr))
+ return 1;
+ if (!test_write_access(shstk_ptr))
+ return 1;
+ printf("[INFO]\tGup write -> write access success\n");
+
+ close(fd);
+
+ /* COW/gup test */
+ reset_test_shstk(0);
+ pid = fork();
+ if (!pid) {
+ fd = open("/proc/self/mem", O_RDWR);
+ if (fd == -1)
+ exit(1);
+
+ if (gup_write(shstk_ptr)) {
+ close(fd);
+ exit(1);
+ }
+ close(fd);
+ exit(0);
+ }
+ waitpid(pid, &status, 0);
+ if (WEXITSTATUS(status)) {
+ printf("[FAIL]\tWrite in child failed\n");
+ return 1;
+ }
+ if (*(unsigned long *)shstk_ptr == MAGIC_VAL) {
+ printf("[FAIL]\tWrite in child wrote through to shared memory\n");
+ return 1;
+ }
+
+ printf("[INFO]\tCow gup write -> write access success\n");
+
+ free_shstk(shstk_ptr);
+
+ signal(SIGSEGV, SIG_DFL);
+
+ printf("[OK]\tShadow gup test\n");
+
+ return 0;
+}
+
+int test_mprotect(void)
+{
+ struct sigaction sa = {};
+
+ sa.sa_sigaction = test_access_fix_handler;
+ sa.sa_flags = SA_SIGINFO;
+ if (sigaction(SIGSEGV, &sa, NULL))
+ return 1;
+
+ segv_triggered = false;
+
+ /* mprotect a shadow stack as read only */
+ reset_test_shstk(0);
+ if (mprotect(shstk_ptr, SS_SIZE, PROT_READ) < 0) {
+ printf("[FAIL]\tmprotect(PROT_READ) failed\n");
+ return 1;
+ }
+
+ /* try to wrss it and fail */
+ if (!test_shstk_access(shstk_ptr)) {
+ printf("[FAIL]\tShadow stack access to read-only memory succeeded\n");
+ return 1;
+ }
+
+ /*
+ * The shadow stack was reset above to resolve the fault, make the new one
+ * read-only.
+ */
+ if (mprotect(shstk_ptr, SS_SIZE, PROT_READ) < 0) {
+ printf("[FAIL]\tmprotect(PROT_READ) failed\n");
+ return 1;
+ }
+
+ /* then back to writable */
+ if (mprotect(shstk_ptr, SS_SIZE, PROT_WRITE | PROT_READ) < 0) {
+ printf("[FAIL]\tmprotect(PROT_WRITE) failed\n");
+ return 1;
+ }
+
+ /* then wrss to it and succeed */
+ if (test_shstk_access(shstk_ptr)) {
+ printf("[FAIL]\tShadow stack access to mprotect() writable memory failed\n");
+ return 1;
+ }
+
+ free_shstk(shstk_ptr);
+
+ signal(SIGSEGV, SIG_DFL);
+
+ printf("[OK]\tmprotect() test\n");
+
+ return 0;
+}
+
+char zero[4096];
+
+static void *uffd_thread(void *arg)
+{
+ struct uffdio_copy req;
+ int uffd = *(int *)arg;
+ struct uffd_msg msg;
+ int ret;
+
+ while (1) {
+ ret = read(uffd, &msg, sizeof(msg));
+ if (ret > 0)
+ break;
+ else if (errno == EAGAIN)
+ continue;
+ return (void *)1;
+ }
+
+ req.dst = msg.arg.pagefault.address;
+ req.src = (__u64)zero;
+ req.len = 4096;
+ req.mode = 0;
+
+ if (ioctl(uffd, UFFDIO_COPY, &req))
+ return (void *)1;
+
+ return (void *)0;
+}
+
+int test_userfaultfd(void)
+{
+ struct uffdio_register uffdio_register;
+ struct uffdio_api uffdio_api;
+ struct sigaction sa = {};
+ pthread_t thread;
+ void *res;
+ int uffd;
+
+ sa.sa_sigaction = test_access_fix_handler;
+ sa.sa_flags = SA_SIGINFO;
+ if (sigaction(SIGSEGV, &sa, NULL))
+ return 1;
+
+ uffd = syscall(__NR_userfaultfd, O_CLOEXEC | O_NONBLOCK);
+ if (uffd < 0) {
+ printf("[SKIP]\tUserfaultfd unavailable.\n");
+ return 0;
+ }
+
+ reset_test_shstk(0);
+
+ uffdio_api.api = UFFD_API;
+ uffdio_api.features = 0;
+ if (ioctl(uffd, UFFDIO_API, &uffdio_api))
+ goto err;
+
+ uffdio_register.range.start = (__u64)shstk_ptr;
+ uffdio_register.range.len = 4096;
+ uffdio_register.mode = UFFDIO_REGISTER_MODE_MISSING;
+ if (ioctl(uffd, UFFDIO_REGISTER, &uffdio_register))
+ goto err;
+
+ if (pthread_create(&thread, NULL, &uffd_thread, &uffd))
+ goto err;
+
+ reset_shstk(shstk_ptr);
+ test_shstk_access(shstk_ptr);
+
+ if (pthread_join(thread, &res))
+ goto err;
+
+ if (test_shstk_access(shstk_ptr))
+ goto err;
+
+ free_shstk(shstk_ptr);
+
+ signal(SIGSEGV, SIG_DFL);
+
+ if (!res)
+ printf("[OK]\tUserfaultfd test\n");
+ return !!res;
+err:
+ free_shstk(shstk_ptr);
+ close(uffd);
+ signal(SIGSEGV, SIG_DFL);
+ return 1;
+}
+
+/* Simple linked list for keeping track of mappings in test_guard_gap() */
+struct node {
+ struct node *next;
+ void *mapping;
+};
+
+/*
+ * This tests whether mmap will place other mappings in a shadow stack's guard
+ * gap. The steps are:
+ * 1. Finds an empty place by mapping and unmapping something.
+ * 2. Map a shadow stack in the middle of the known empty area.
+ * 3. Map a bunch of PAGE_SIZE mappings. These will use the search down
+ * direction, filling any gaps until it encounters the shadow stack's
+ * guard gap.
+ * 4. When a mapping lands below the shadow stack from step 2, then all
+ * of the above gaps are filled. The search down algorithm will have
+ * looked at the shadow stack gaps.
+ * 5. See if it landed in the gap.
+ */
+int test_guard_gap_other_gaps(void)
+{
+ void *free_area, *shstk, *test_map = (void *)0xFFFFFFFFFFFFFFFF;
+ struct node *head = NULL, *cur;
+
+ free_area = mmap(0, SS_SIZE * 3, PROT_READ | PROT_WRITE,
+ MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
+ munmap(free_area, SS_SIZE * 3);
+
+ shstk = create_shstk(free_area + SS_SIZE);
+ if (shstk == MAP_FAILED)
+ return 1;
+
+ while (test_map > shstk) {
+ test_map = mmap(0, PAGE_SIZE, PROT_READ | PROT_WRITE,
+ MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
+ if (test_map == MAP_FAILED)
+ return 1;
+ cur = malloc(sizeof(*cur));
+ cur->mapping = test_map;
+
+ cur->next = head;
+ head = cur;
+ }
+
+ while (head) {
+ cur = head;
+ head = cur->next;
+ munmap(cur->mapping, PAGE_SIZE);
+ free(cur);
+ }
+
+ free_shstk(shstk);
+
+ if (shstk - test_map - PAGE_SIZE != PAGE_SIZE)
+ return 1;
+
+ printf("[OK]\tGuard gap test, other mapping's gaps\n");
+
+ return 0;
+}
+
+/* Tests respecting the guard gap of the mapping getting placed */
+int test_guard_gap_new_mappings_gaps(void)
+{
+ void *free_area, *shstk_start, *test_map = (void *)0xFFFFFFFFFFFFFFFF;
+ struct node *head = NULL, *cur;
+ int ret = 0;
+
+ free_area = mmap(0, PAGE_SIZE * 4, PROT_READ | PROT_WRITE,
+ MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
+ munmap(free_area, PAGE_SIZE * 4);
+
+ /* Test letting map_shadow_stack find a free space */
+ shstk_start = mmap(free_area, PAGE_SIZE, PROT_READ | PROT_WRITE,
+ MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
+ if (shstk_start == MAP_FAILED || shstk_start != free_area)
+ return 1;
+
+ while (test_map > shstk_start) {
+ test_map = (void *)syscall(__NR_map_shadow_stack, 0, PAGE_SIZE, 0);
+ if (test_map == MAP_FAILED) {
+ printf("[INFO]\tmap_shadow_stack MAP_FAILED\n");
+ ret = 1;
+ break;
+ }
+
+ cur = malloc(sizeof(*cur));
+ cur->mapping = test_map;
+
+ cur->next = head;
+ head = cur;
+
+ if (test_map == free_area + PAGE_SIZE) {
+ printf("[INFO]\tNew mapping has other mapping in guard gap!\n");
+ ret = 1;
+ break;
+ }
+ }
+
+ while (head) {
+ cur = head;
+ head = cur->next;
+ munmap(cur->mapping, PAGE_SIZE);
+ free(cur);
+ }
+
+ munmap(shstk_start, PAGE_SIZE);
+
+ if (!ret)
+ printf("[OK]\tGuard gap test, placement mapping's gaps\n");
+
+ return ret;
+}
+
+/*
+ * Too complicated to pull it out of the 32 bit header, but also get the
+ * 64 bit one needed above. Just define a copy here.
+ */
+#define __NR_compat_sigaction 67
+
+/*
+ * Call 32 bit signal handler to get 32 bit signals ABI. Make sure
+ * to push the registers that will get clobbered.
+ */
+int sigaction32(int signum, const struct sigaction *restrict act,
+ struct sigaction *restrict oldact)
+{
+ register long syscall_reg asm("eax") = __NR_compat_sigaction;
+ register long signum_reg asm("ebx") = signum;
+ register long act_reg asm("ecx") = (long)act;
+ register long oldact_reg asm("edx") = (long)oldact;
+ int ret = 0;
+
+ asm volatile ("int $0x80;"
+ : "=a"(ret), "=m"(oldact)
+ : "r"(syscall_reg), "r"(signum_reg), "r"(act_reg),
+ "r"(oldact_reg)
+ : "r8", "r9", "r10", "r11"
+ );
+
+ return ret;
+}
+
+sigjmp_buf jmp_buffer;
+enum shadow_stack_test _which_test;
+void segv_gp_handler(int signum, siginfo_t *si, void *uc)
+{
+ segv_triggered = true;
+
+ /*
+ * To work with old glibc, this can't rely on siglongjmp working with
+ * shadow stack enabled, so disable shadow stack before siglongjmp().
+ */
+ SHADOW_STACK_DISABLE(_which_test);
+ siglongjmp(jmp_buffer, -1);
+}
+
+/*
+ * Transition to 32 bit mode and check that a #GP triggers a segfault.
+ */
+int test_32bit(void)
+{
+ struct sigaction sa = {};
+ struct sigaction *sa32;
+
+ /* Create sigaction in 32 bit address range */
+ sa32 = mmap(0, 4096, PROT_READ | PROT_WRITE,
+ MAP_32BIT | MAP_PRIVATE | MAP_ANONYMOUS, 0, 0);
+ sa32->sa_flags = SA_SIGINFO;
+
+ sa.sa_sigaction = segv_gp_handler;
+ sa.sa_flags = SA_SIGINFO;
+ if (sigaction(SIGSEGV, &sa, NULL))
+ return 1;
+
+
+ segv_triggered = false;
+
+ /* Make sure segv_triggered is set before triggering the #GP */
+ asm volatile("" : : : "memory");
+
+ /*
+ * Set handler to somewhere in 32 bit address space
+ */
+ sa32->sa_handler = (void *)sa32;
+ if (sigaction32(SIGUSR1, sa32, NULL))
+ return 1;
+
+ if (!sigsetjmp(jmp_buffer, 1))
+ raise(SIGUSR1);
+
+ if (segv_triggered)
+ printf("[OK]\t32 bit test\n");
+
+ 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(enum shadow_stack_test which_test)
+{
+ 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;
+
+ SHADOW_STACK_ENABLE(which_test);
+
+ /*
+ * This either segfaults and goes through sigsetjmp above
+ * or succeeds and we're good.
+ */
+ uretprobe_trigger();
+
+ printf("[OK]\tUretprobe test\n");
+ err = 0;
+
+out:
+ SHADOW_STACK_DISABLE(which_test);
+ signal(SIGSEGV, SIG_DFL);
+ if (fd)
+ close(fd);
+ return err;
+}
+
+/* Keep the CALL first so the function address is exactly the probed CALL. */
+extern void uprobe_call_trigger(void);
+asm (".pushsection .text\n"
+ ".global uprobe_call_target\n"
+ ".type uprobe_call_target, @function\n"
+ "uprobe_call_target:\n"
+ " ret\n"
+ ".size uprobe_call_target, .-uprobe_call_target\n"
+
+ ".global uprobe_call_trigger\n"
+ ".type uprobe_call_trigger, @function\n"
+ "uprobe_call_trigger:\n"
+ " call uprobe_call_target\n"
+ " ret\n"
+ ".size uprobe_call_trigger, .-uprobe_call_trigger\n"
+ ".popsection\n"
+);
+
+/* If CALL emulation misses the shadow stack update, this exits via SIGSEGV. */
+static int test_uprobe_call(enum shadow_stack_test which_test)
+{
+ const size_t attr_sz = sizeof(struct perf_event_attr);
+ const char *file = "/proc/self/exe";
+ int fd = -1, 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]\tUprobe on CALL test, uprobes are not available\n");
+ return 0;
+ }
+
+ offset = get_uprobe_offset(uprobe_call_trigger);
+ if (offset < 0)
+ return 1;
+
+ sa.sa_sigaction = segv_gp_handler;
+ sa.sa_flags = SA_SIGINFO;
+ if (sigaction(SIGSEGV, &sa, NULL))
+ return 1;
+
+ /* Setup entry uprobe through perf event interface. */
+ memset(&attr, 0, attr_sz);
+ attr.size = attr_sz;
+ attr.type = type;
+ attr.config = 0;
+ 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;
+
+ if (SHADOW_STACK_ENABLE(which_test))
+ goto out;
+
+ /*
+ * This either segfaults and goes through sigsetjmp above
+ * or succeeds and we're good.
+ */
+ uprobe_call_trigger();
+
+ printf("[OK]\tUprobe on CALL test\n");
+ err = 0;
+
+out:
+ SHADOW_STACK_DISABLE(which_test);
+ signal(SIGSEGV, SIG_DFL);
+ if (fd >= 0)
+ close(fd);
+ return err;
+}
+
+void segv_handler_ptrace(int signum, siginfo_t *si, void *uc)
+{
+ /* The SSP adjustment caused a segfault. */
+ exit(0);
+}
+
+int test_ptrace(void)
+{
+ unsigned long saved_ssp, ssp = 0;
+ struct sigaction sa = {};
+ struct iovec iov;
+ int status;
+ int pid;
+
+ iov.iov_base = &ssp;
+ iov.iov_len = sizeof(ssp);
+
+ pid = fork();
+ if (!pid) {
+ ssp = get_ssp();
+
+ sa.sa_sigaction = segv_handler_ptrace;
+ sa.sa_flags = SA_SIGINFO;
+ if (sigaction(SIGSEGV, &sa, NULL))
+ return 1;
+
+ ptrace(PTRACE_TRACEME, NULL, NULL, NULL);
+ /*
+ * The parent will tweak the SSP and return from this function
+ * will #CP.
+ */
+ raise(SIGTRAP);
+
+ exit(1);
+ }
+
+ while (waitpid(pid, &status, 0) != -1 && WSTOPSIG(status) != SIGTRAP);
+
+ if (ptrace(PTRACE_GETREGSET, pid, NT_X86_SHSTK, &iov)) {
+ printf("[INFO]\tFailed to PTRACE_GETREGS\n");
+ goto out_kill;
+ }
+
+ if (!ssp) {
+ printf("[INFO]\tPtrace child SSP was 0\n");
+ goto out_kill;
+ }
+
+ saved_ssp = ssp;
+
+ iov.iov_len = 0;
+ if (!ptrace(PTRACE_SETREGSET, pid, NT_X86_SHSTK, &iov)) {
+ printf("[INFO]\tToo small size accepted via PTRACE_SETREGS\n");
+ goto out_kill;
+ }
+
+ iov.iov_len = sizeof(ssp) + 1;
+ if (!ptrace(PTRACE_SETREGSET, pid, NT_X86_SHSTK, &iov)) {
+ printf("[INFO]\tToo large size accepted via PTRACE_SETREGS\n");
+ goto out_kill;
+ }
+
+ ssp += 1;
+ if (!ptrace(PTRACE_SETREGSET, pid, NT_X86_SHSTK, &iov)) {
+ printf("[INFO]\tUnaligned SSP written via PTRACE_SETREGS\n");
+ goto out_kill;
+ }
+
+ ssp = 0xFFFFFFFFFFFF0000;
+ if (!ptrace(PTRACE_SETREGSET, pid, NT_X86_SHSTK, &iov)) {
+ printf("[INFO]\tKernel range SSP written via PTRACE_SETREGS\n");
+ goto out_kill;
+ }
+
+ /*
+ * Tweak the SSP so the child with #CP when it resumes and returns
+ * from raise()
+ */
+ ssp = saved_ssp + 8;
+ iov.iov_len = sizeof(ssp);
+ if (ptrace(PTRACE_SETREGSET, pid, NT_X86_SHSTK, &iov)) {
+ printf("[INFO]\tFailed to PTRACE_SETREGS\n");
+ goto out_kill;
+ }
+
+ if (ptrace(PTRACE_DETACH, pid, NULL, NULL)) {
+ printf("[INFO]\tFailed to PTRACE_DETACH\n");
+ goto out_kill;
+ }
+
+ waitpid(pid, &status, 0);
+ if (WEXITSTATUS(status))
+ return 1;
+
+ printf("[OK]\tPtrace test\n");
+ return 0;
+
+out_kill:
+ kill(pid, SIGKILL);
+ return 1;
+}
+
+#if (__GNUC__ < 8) || (__GNUC__ == 8 && __GNUC_MINOR__ < 5)
+int shadow_stack_run_tests(enum shadow_stack_test unused)
+{
+ printf("[SKIP]\tCompiler does not support CET.\n");
+ return 0;
+}
+#else
+int shadow_stack_run_tests(enum shadow_stack_test which_test)
+{
+ int ret = 0;
+
+ if (SHADOW_STACK_ENABLE(which_test)) {
+ printf("[SKIP]\tCould not enable Shadow stack\n");
+ return 1;
+ }
+
+ if (SHADOW_STACK_DISABLE(which_test)) {
+ ret = 1;
+ printf("[FAIL]\tDisabling shadow stack failed\n");
+ }
+
+ if (SHADOW_STACK_ENABLE(which_test)) {
+ printf("[SKIP]\tCould not re-enable Shadow stack\n");
+ return 1;
+ }
+
+ if (SHADOW_STACK_ENABLE_WRITE(which_test)) {
+ printf("[SKIP]\tCould not enable WRSS\n");
+ ret = 1;
+ goto out;
+ }
+
+ /* Should have succeeded if here, but this is a test, so double check. */
+ if (!get_ssp()) {
+ printf("[FAIL]\tShadow stack disabled\n");
+ return 1;
+ }
+
+ if (test_shstk_pivot()) {
+ ret = 1;
+ printf("[FAIL]\tShadow stack pivot\n");
+ goto out;
+ }
+
+ if (test_shstk_faults()) {
+ ret = 1;
+ printf("[FAIL]\tShadow stack fault test\n");
+ goto out;
+ }
+
+ if (test_shstk_violation()) {
+ ret = 1;
+ printf("[FAIL]\tShadow stack violation test\n");
+ goto out;
+ }
+
+ if (test_gup()) {
+ ret = 1;
+ printf("[FAIL]\tShadow shadow stack gup\n");
+ goto out;
+ }
+
+ if (test_mprotect()) {
+ ret = 1;
+ printf("[FAIL]\tShadow shadow mprotect test\n");
+ goto out;
+ }
+
+ if (test_userfaultfd()) {
+ ret = 1;
+ printf("[FAIL]\tUserfaultfd test\n");
+ goto out;
+ }
+
+ if (test_guard_gap_other_gaps()) {
+ ret = 1;
+ printf("[FAIL]\tGuard gap test, other mappings' gaps\n");
+ goto out;
+ }
+
+ if (test_guard_gap_new_mappings_gaps()) {
+ ret = 1;
+ printf("[FAIL]\tGuard gap test, placement mapping's gaps\n");
+ goto out;
+ }
+
+ if (test_ptrace()) {
+ ret = 1;
+ printf("[FAIL]\tptrace test\n");
+ }
+
+ if (test_32bit()) {
+ ret = 1;
+ printf("[FAIL]\t32 bit test\n");
+ goto out;
+ }
+
+ if (test_uretprobe(which_test)) {
+ ret = 1;
+ printf("[FAIL]\turetprobe test\n");
+ goto out;
+ }
+
+ if (test_uprobe_call(which_test)) {
+ ret = 1;
+ printf("[FAIL]\tuprobe on CALL test\n");
+ goto out;
+ }
+
+ return ret;
+
+out:
+ /*
+ * Disable shadow stack before the function returns, or there will be a
+ * shadow stack violation.
+ */
+ if (SHADOW_STACK_DISABLE(which_test)) {
+ ret = 1;
+ printf("[FAIL]\tDisabling shadow stack failed\n");
+ }
+
+ return ret;
+}
+#endif
diff --git a/tools/testing/selftests/x86/shadow_stack.h b/tools/testing/selftests/x86/shadow_stack.h
new file mode 100644
index 0000000000000..d36a44df73874
--- /dev/null
+++ b/tools/testing/selftests/x86/shadow_stack.h
@@ -0,0 +1,14 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#ifndef TOOLS_TESTING_SELFTESTS_X86_SHADOW_STACK_H_
+#define TOOLS_TESTING_SELFTESTS_X86_SHADOW_STACK_H_
+
+enum shadow_stack_test {
+ SHADOW_STACK_TEST_ARCH_PRCTL,
+ SHADOW_STACK_TEST_PRCTL,
+ SHADOW_STACK_TEST_COUNT,
+};
+
+int shadow_stack_run_tests(enum shadow_stack_test which_test);
+
+#endif /* TOOLS_TESTING_SELFTESTS_X86_SHADOW_STACK_H_ */
diff --git a/tools/testing/selftests/x86/test_shadow_stack.c b/tools/testing/selftests/x86/test_shadow_stack.c
index 3d6ca33edba42..d89c04623001c 100644
--- a/tools/testing/selftests/x86/test_shadow_stack.c
+++ b/tools/testing/selftests/x86/test_shadow_stack.c
@@ -1,1174 +1,21 @@
// SPDX-License-Identifier: GPL-2.0
+
/*
* This program test's basic kernel shadow stack support. It enables shadow
* stack manual via the arch_prctl(), instead of relying on glibc. It's
* Makefile doesn't compile with shadow stack support, so it doesn't rely on
* any particular glibc. As a result it can't do any operations that require
* special glibc shadow stack support (longjmp(), swapcontext(), etc). Just
- * stick to the basics and hope the compiler doesn't do anything strange.
+ * stick to the basics and hope the compiler doesn't do anything strange. It
+ * uses the x86 specific interface for ARCH_PCTL, whereas
+ * test_shadow_stack_prctl.c uses the generic PRCTL interface mixed with x86
+ * specific code.
*/
-
#define _GNU_SOURCE
-#include <sys/syscall.h>
-#include <asm/mman.h>
-#include <sys/mman.h>
-#include <sys/stat.h>
-#include <sys/wait.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <fcntl.h>
-#include <unistd.h>
-#include <string.h>
-#include <errno.h>
-#include <stdbool.h>
-#include <x86intrin.h>
-#include <asm/prctl.h>
-#include <sys/prctl.h>
-#include <stdint.h>
-#include <signal.h>
-#include <pthread.h>
-#include <sys/ioctl.h>
-#include <linux/userfaultfd.h>
-#include <setjmp.h>
-#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
- * without building the headers.
- */
-#ifndef __NR_map_shadow_stack
-#define __NR_map_shadow_stack 453
-
-#define SHADOW_STACK_SET_TOKEN (1ULL << 0)
-
-#define ARCH_SHSTK_ENABLE 0x5001
-#define ARCH_SHSTK_DISABLE 0x5002
-#define ARCH_SHSTK_LOCK 0x5003
-#define ARCH_SHSTK_UNLOCK 0x5004
-#define ARCH_SHSTK_STATUS 0x5005
-
-#define ARCH_SHSTK_SHSTK (1ULL << 0)
-#define ARCH_SHSTK_WRSS (1ULL << 1)
-
-#define NT_X86_SHSTK 0x204
-#endif
-
-#define SS_SIZE 0x200000
-#define PAGE_SIZE 0x1000
-
-#if (__GNUC__ < 8) || (__GNUC__ == 8 && __GNUC_MINOR__ < 5)
-int main(int argc, char *argv[])
-{
- printf("[SKIP]\tCompiler does not support CET.\n");
- return 0;
-}
-#else
-void write_shstk(unsigned long *addr, unsigned long val)
-{
- asm volatile("wrssq %[val], (%[addr])\n"
- : "=m" (addr)
- : [addr] "r" (addr), [val] "r" (val));
-}
-
-static inline unsigned long __attribute__((always_inline)) get_ssp(void)
-{
- unsigned long ret = 0;
-
- asm volatile("xor %0, %0; rdsspq %0" : "=r" (ret));
- return ret;
-}
-
-/*
- * For use in inline enablement of shadow stack.
- *
- * The program can't return from the point where shadow stack gets enabled
- * because there will be no address on the shadow stack. So it can't use
- * syscall() for enablement, since it is a function.
- *
- * Based on code from nolibc.h. Keep a copy here because this can't pull in all
- * of nolibc.h.
- */
-#define ARCH_PRCTL(arg1, arg2) \
-({ \
- long _ret; \
- register long _num asm("eax") = __NR_arch_prctl; \
- register long _arg1 asm("rdi") = (long)(arg1); \
- register long _arg2 asm("rsi") = (long)(arg2); \
- \
- asm volatile ( \
- "syscall\n" \
- : "=a"(_ret) \
- : "r"(_arg1), "r"(_arg2), \
- "0"(_num) \
- : "rcx", "r11", "memory", "cc" \
- ); \
- _ret; \
-})
-
-void *create_shstk(void *addr)
-{
- return (void *)syscall(__NR_map_shadow_stack, addr, SS_SIZE, SHADOW_STACK_SET_TOKEN);
-}
-
-void *create_normal_mem(void *addr)
-{
- return mmap(addr, SS_SIZE, PROT_READ | PROT_WRITE,
- MAP_PRIVATE | MAP_ANONYMOUS, 0, 0);
-}
-
-void free_shstk(void *shstk)
-{
- munmap(shstk, SS_SIZE);
-}
-
-int reset_shstk(void *shstk)
-{
- return madvise(shstk, SS_SIZE, MADV_DONTNEED);
-}
-
-void try_shstk(unsigned long new_ssp)
-{
- unsigned long ssp;
-
- printf("[INFO]\tnew_ssp = %lx, *new_ssp = %lx\n",
- new_ssp, *((unsigned long *)new_ssp));
-
- ssp = get_ssp();
- printf("[INFO]\tchanging ssp from %lx to %lx\n", ssp, new_ssp);
-
- asm volatile("rstorssp (%0)\n":: "r" (new_ssp));
- asm volatile("saveprevssp");
- printf("[INFO]\tssp is now %lx\n", get_ssp());
-
- /* Switch back to original shadow stack */
- ssp -= 8;
- asm volatile("rstorssp (%0)\n":: "r" (ssp));
- asm volatile("saveprevssp");
-}
-
-int test_shstk_pivot(void)
-{
- void *shstk = create_shstk(0);
-
- if (shstk == MAP_FAILED) {
- printf("[FAIL]\tError creating shadow stack: %d\n", errno);
- return 1;
- }
- try_shstk((unsigned long)shstk + SS_SIZE - 8);
- free_shstk(shstk);
-
- printf("[OK]\tShadow stack pivot\n");
- return 0;
-}
-
-int test_shstk_faults(void)
-{
- unsigned long *shstk = create_shstk(0);
-
- /* Read shadow stack, test if it's zero to not get read optimized out */
- if (*shstk != 0)
- goto err;
-
- /* Wrss memory that was already read. */
- write_shstk(shstk, 1);
- if (*shstk != 1)
- goto err;
-
- /* Page out memory, so we can wrss it again. */
- if (reset_shstk((void *)shstk))
- goto err;
-
- write_shstk(shstk, 1);
- if (*shstk != 1)
- goto err;
-
- printf("[OK]\tShadow stack faults\n");
- return 0;
-
-err:
- return 1;
-}
-
-unsigned long saved_ssp;
-unsigned long saved_ssp_val;
-volatile bool segv_triggered;
-
-void __attribute__((noinline)) violate_ss(void)
-{
- saved_ssp = get_ssp();
- saved_ssp_val = *(unsigned long *)saved_ssp;
-
- /* Corrupt shadow stack */
- printf("[INFO]\tCorrupting shadow stack\n");
- write_shstk((void *)saved_ssp, 0);
-}
-
-void segv_handler(int signum, siginfo_t *si, void *uc)
-{
- printf("[INFO]\tGenerated shadow stack violation successfully\n");
-
- segv_triggered = true;
-
- /* Fix shadow stack */
- write_shstk((void *)saved_ssp, saved_ssp_val);
-}
-
-int test_shstk_violation(void)
-{
- struct sigaction sa = {};
-
- sa.sa_sigaction = segv_handler;
- sa.sa_flags = SA_SIGINFO;
- if (sigaction(SIGSEGV, &sa, NULL))
- return 1;
-
- segv_triggered = false;
-
- /* Make sure segv_triggered is set before violate_ss() */
- asm volatile("" : : : "memory");
-
- violate_ss();
-
- signal(SIGSEGV, SIG_DFL);
-
- printf("[OK]\tShadow stack violation test\n");
-
- return !segv_triggered;
-}
-
-/* Gup test state */
-#define MAGIC_VAL 0x12345678
-bool is_shstk_access;
-void *shstk_ptr;
-int fd;
-
-void reset_test_shstk(void *addr)
-{
- if (shstk_ptr)
- free_shstk(shstk_ptr);
- shstk_ptr = create_shstk(addr);
-}
-
-void test_access_fix_handler(int signum, siginfo_t *si, void *uc)
-{
- printf("[INFO]\tViolation from %s\n", is_shstk_access ? "shstk access" : "normal write");
-
- segv_triggered = true;
-
- /* Fix shadow stack */
- if (is_shstk_access) {
- reset_test_shstk(shstk_ptr);
- return;
- }
-
- free_shstk(shstk_ptr);
- create_normal_mem(shstk_ptr);
-}
-
-bool test_shstk_access(void *ptr)
-{
- is_shstk_access = true;
- segv_triggered = false;
- write_shstk(ptr, MAGIC_VAL);
-
- asm volatile("" : : : "memory");
-
- return segv_triggered;
-}
-
-bool test_write_access(void *ptr)
-{
- is_shstk_access = false;
- segv_triggered = false;
- *(unsigned long *)ptr = MAGIC_VAL;
-
- asm volatile("" : : : "memory");
-
- return segv_triggered;
-}
-
-bool gup_write(void *ptr)
-{
- unsigned long val;
-
- lseek(fd, (unsigned long)ptr, SEEK_SET);
- if (write(fd, &val, sizeof(val)) < 0)
- return 1;
-
- return 0;
-}
-
-bool gup_read(void *ptr)
-{
- unsigned long val;
-
- lseek(fd, (unsigned long)ptr, SEEK_SET);
- if (read(fd, &val, sizeof(val)) < 0)
- return 1;
-
- return 0;
-}
-
-int test_gup(void)
-{
- struct sigaction sa = {};
- int status;
- pid_t pid;
-
- sa.sa_sigaction = test_access_fix_handler;
- sa.sa_flags = SA_SIGINFO;
- if (sigaction(SIGSEGV, &sa, NULL))
- return 1;
-
- segv_triggered = false;
-
- fd = open("/proc/self/mem", O_RDWR);
- if (fd == -1)
- return 1;
-
- reset_test_shstk(0);
- if (gup_read(shstk_ptr))
- return 1;
- if (test_shstk_access(shstk_ptr))
- return 1;
- printf("[INFO]\tGup read -> shstk access success\n");
-
- reset_test_shstk(0);
- if (gup_write(shstk_ptr))
- return 1;
- if (test_shstk_access(shstk_ptr))
- return 1;
- printf("[INFO]\tGup write -> shstk access success\n");
-
- reset_test_shstk(0);
- if (gup_read(shstk_ptr))
- return 1;
- if (!test_write_access(shstk_ptr))
- return 1;
- printf("[INFO]\tGup read -> write access success\n");
-
- reset_test_shstk(0);
- if (gup_write(shstk_ptr))
- return 1;
- if (!test_write_access(shstk_ptr))
- return 1;
- printf("[INFO]\tGup write -> write access success\n");
-
- close(fd);
-
- /* COW/gup test */
- reset_test_shstk(0);
- pid = fork();
- if (!pid) {
- fd = open("/proc/self/mem", O_RDWR);
- if (fd == -1)
- exit(1);
-
- if (gup_write(shstk_ptr)) {
- close(fd);
- exit(1);
- }
- close(fd);
- exit(0);
- }
- waitpid(pid, &status, 0);
- if (WEXITSTATUS(status)) {
- printf("[FAIL]\tWrite in child failed\n");
- return 1;
- }
- if (*(unsigned long *)shstk_ptr == MAGIC_VAL) {
- printf("[FAIL]\tWrite in child wrote through to shared memory\n");
- return 1;
- }
-
- printf("[INFO]\tCow gup write -> write access success\n");
-
- free_shstk(shstk_ptr);
-
- signal(SIGSEGV, SIG_DFL);
-
- printf("[OK]\tShadow gup test\n");
-
- return 0;
-}
-
-int test_mprotect(void)
-{
- struct sigaction sa = {};
-
- sa.sa_sigaction = test_access_fix_handler;
- sa.sa_flags = SA_SIGINFO;
- if (sigaction(SIGSEGV, &sa, NULL))
- return 1;
-
- segv_triggered = false;
-
- /* mprotect a shadow stack as read only */
- reset_test_shstk(0);
- if (mprotect(shstk_ptr, SS_SIZE, PROT_READ) < 0) {
- printf("[FAIL]\tmprotect(PROT_READ) failed\n");
- return 1;
- }
-
- /* try to wrss it and fail */
- if (!test_shstk_access(shstk_ptr)) {
- printf("[FAIL]\tShadow stack access to read-only memory succeeded\n");
- return 1;
- }
-
- /*
- * The shadow stack was reset above to resolve the fault, make the new one
- * read-only.
- */
- if (mprotect(shstk_ptr, SS_SIZE, PROT_READ) < 0) {
- printf("[FAIL]\tmprotect(PROT_READ) failed\n");
- return 1;
- }
-
- /* then back to writable */
- if (mprotect(shstk_ptr, SS_SIZE, PROT_WRITE | PROT_READ) < 0) {
- printf("[FAIL]\tmprotect(PROT_WRITE) failed\n");
- return 1;
- }
-
- /* then wrss to it and succeed */
- if (test_shstk_access(shstk_ptr)) {
- printf("[FAIL]\tShadow stack access to mprotect() writable memory failed\n");
- return 1;
- }
-
- free_shstk(shstk_ptr);
-
- signal(SIGSEGV, SIG_DFL);
-
- printf("[OK]\tmprotect() test\n");
-
- return 0;
-}
-
-char zero[4096];
-
-static void *uffd_thread(void *arg)
-{
- struct uffdio_copy req;
- int uffd = *(int *)arg;
- struct uffd_msg msg;
- int ret;
-
- while (1) {
- ret = read(uffd, &msg, sizeof(msg));
- if (ret > 0)
- break;
- else if (errno == EAGAIN)
- continue;
- return (void *)1;
- }
-
- req.dst = msg.arg.pagefault.address;
- req.src = (__u64)zero;
- req.len = 4096;
- req.mode = 0;
-
- if (ioctl(uffd, UFFDIO_COPY, &req))
- return (void *)1;
-
- return (void *)0;
-}
-
-int test_userfaultfd(void)
-{
- struct uffdio_register uffdio_register;
- struct uffdio_api uffdio_api;
- struct sigaction sa = {};
- pthread_t thread;
- void *res;
- int uffd;
-
- sa.sa_sigaction = test_access_fix_handler;
- sa.sa_flags = SA_SIGINFO;
- if (sigaction(SIGSEGV, &sa, NULL))
- return 1;
-
- uffd = syscall(__NR_userfaultfd, O_CLOEXEC | O_NONBLOCK);
- if (uffd < 0) {
- printf("[SKIP]\tUserfaultfd unavailable.\n");
- return 0;
- }
-
- reset_test_shstk(0);
-
- uffdio_api.api = UFFD_API;
- uffdio_api.features = 0;
- if (ioctl(uffd, UFFDIO_API, &uffdio_api))
- goto err;
-
- uffdio_register.range.start = (__u64)shstk_ptr;
- uffdio_register.range.len = 4096;
- uffdio_register.mode = UFFDIO_REGISTER_MODE_MISSING;
- if (ioctl(uffd, UFFDIO_REGISTER, &uffdio_register))
- goto err;
-
- if (pthread_create(&thread, NULL, &uffd_thread, &uffd))
- goto err;
-
- reset_shstk(shstk_ptr);
- test_shstk_access(shstk_ptr);
-
- if (pthread_join(thread, &res))
- goto err;
-
- if (test_shstk_access(shstk_ptr))
- goto err;
-
- free_shstk(shstk_ptr);
-
- signal(SIGSEGV, SIG_DFL);
-
- if (!res)
- printf("[OK]\tUserfaultfd test\n");
- return !!res;
-err:
- free_shstk(shstk_ptr);
- close(uffd);
- signal(SIGSEGV, SIG_DFL);
- return 1;
-}
-
-/* Simple linked list for keeping track of mappings in test_guard_gap() */
-struct node {
- struct node *next;
- void *mapping;
-};
-
-/*
- * This tests whether mmap will place other mappings in a shadow stack's guard
- * gap. The steps are:
- * 1. Finds an empty place by mapping and unmapping something.
- * 2. Map a shadow stack in the middle of the known empty area.
- * 3. Map a bunch of PAGE_SIZE mappings. These will use the search down
- * direction, filling any gaps until it encounters the shadow stack's
- * guard gap.
- * 4. When a mapping lands below the shadow stack from step 2, then all
- * of the above gaps are filled. The search down algorithm will have
- * looked at the shadow stack gaps.
- * 5. See if it landed in the gap.
- */
-int test_guard_gap_other_gaps(void)
-{
- void *free_area, *shstk, *test_map = (void *)0xFFFFFFFFFFFFFFFF;
- struct node *head = NULL, *cur;
-
- free_area = mmap(0, SS_SIZE * 3, PROT_READ | PROT_WRITE,
- MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
- munmap(free_area, SS_SIZE * 3);
-
- shstk = create_shstk(free_area + SS_SIZE);
- if (shstk == MAP_FAILED)
- return 1;
-
- while (test_map > shstk) {
- test_map = mmap(0, PAGE_SIZE, PROT_READ | PROT_WRITE,
- MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
- if (test_map == MAP_FAILED)
- return 1;
- cur = malloc(sizeof(*cur));
- cur->mapping = test_map;
-
- cur->next = head;
- head = cur;
- }
-
- while (head) {
- cur = head;
- head = cur->next;
- munmap(cur->mapping, PAGE_SIZE);
- free(cur);
- }
-
- free_shstk(shstk);
-
- if (shstk - test_map - PAGE_SIZE != PAGE_SIZE)
- return 1;
-
- printf("[OK]\tGuard gap test, other mapping's gaps\n");
-
- return 0;
-}
-
-/* Tests respecting the guard gap of the mapping getting placed */
-int test_guard_gap_new_mappings_gaps(void)
-{
- void *free_area, *shstk_start, *test_map = (void *)0xFFFFFFFFFFFFFFFF;
- struct node *head = NULL, *cur;
- int ret = 0;
-
- free_area = mmap(0, PAGE_SIZE * 4, PROT_READ | PROT_WRITE,
- MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
- munmap(free_area, PAGE_SIZE * 4);
-
- /* Test letting map_shadow_stack find a free space */
- shstk_start = mmap(free_area, PAGE_SIZE, PROT_READ | PROT_WRITE,
- MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
- if (shstk_start == MAP_FAILED || shstk_start != free_area)
- return 1;
-
- while (test_map > shstk_start) {
- test_map = (void *)syscall(__NR_map_shadow_stack, 0, PAGE_SIZE, 0);
- if (test_map == MAP_FAILED) {
- printf("[INFO]\tmap_shadow_stack MAP_FAILED\n");
- ret = 1;
- break;
- }
-
- cur = malloc(sizeof(*cur));
- cur->mapping = test_map;
-
- cur->next = head;
- head = cur;
-
- if (test_map == free_area + PAGE_SIZE) {
- printf("[INFO]\tNew mapping has other mapping in guard gap!\n");
- ret = 1;
- break;
- }
- }
-
- while (head) {
- cur = head;
- head = cur->next;
- munmap(cur->mapping, PAGE_SIZE);
- free(cur);
- }
-
- munmap(shstk_start, PAGE_SIZE);
-
- if (!ret)
- printf("[OK]\tGuard gap test, placement mapping's gaps\n");
-
- return ret;
-}
-
-/*
- * Too complicated to pull it out of the 32 bit header, but also get the
- * 64 bit one needed above. Just define a copy here.
- */
-#define __NR_compat_sigaction 67
-
-/*
- * Call 32 bit signal handler to get 32 bit signals ABI. Make sure
- * to push the registers that will get clobbered.
- */
-int sigaction32(int signum, const struct sigaction *restrict act,
- struct sigaction *restrict oldact)
-{
- register long syscall_reg asm("eax") = __NR_compat_sigaction;
- register long signum_reg asm("ebx") = signum;
- register long act_reg asm("ecx") = (long)act;
- register long oldact_reg asm("edx") = (long)oldact;
- int ret = 0;
-
- asm volatile ("int $0x80;"
- : "=a"(ret), "=m"(oldact)
- : "r"(syscall_reg), "r"(signum_reg), "r"(act_reg),
- "r"(oldact_reg)
- : "r8", "r9", "r10", "r11"
- );
-
- return ret;
-}
-
-sigjmp_buf jmp_buffer;
-
-void segv_gp_handler(int signum, siginfo_t *si, void *uc)
-{
- segv_triggered = true;
-
- /*
- * To work with old glibc, this can't rely on siglongjmp working with
- * shadow stack enabled, so disable shadow stack before siglongjmp().
- */
- ARCH_PRCTL(ARCH_SHSTK_DISABLE, ARCH_SHSTK_SHSTK);
- siglongjmp(jmp_buffer, -1);
-}
-
-/*
- * Transition to 32 bit mode and check that a #GP triggers a segfault.
- */
-int test_32bit(void)
-{
- struct sigaction sa = {};
- struct sigaction *sa32;
-
- /* Create sigaction in 32 bit address range */
- sa32 = mmap(0, 4096, PROT_READ | PROT_WRITE,
- MAP_32BIT | MAP_PRIVATE | MAP_ANONYMOUS, 0, 0);
- sa32->sa_flags = SA_SIGINFO;
-
- sa.sa_sigaction = segv_gp_handler;
- sa.sa_flags = SA_SIGINFO;
- if (sigaction(SIGSEGV, &sa, NULL))
- return 1;
-
-
- segv_triggered = false;
-
- /* Make sure segv_triggered is set before triggering the #GP */
- asm volatile("" : : : "memory");
-
- /*
- * Set handler to somewhere in 32 bit address space
- */
- sa32->sa_handler = (void *)sa32;
- if (sigaction32(SIGUSR1, sa32, NULL))
- return 1;
-
- if (!sigsetjmp(jmp_buffer, 1))
- raise(SIGUSR1);
-
- if (segv_triggered)
- printf("[OK]\t32 bit test\n");
-
- 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;
-}
-
-/* Keep the CALL first so the function address is exactly the probed CALL. */
-extern void uprobe_call_trigger(void);
-asm (".pushsection .text\n"
- ".global uprobe_call_target\n"
- ".type uprobe_call_target, @function\n"
- "uprobe_call_target:\n"
- " ret\n"
- ".size uprobe_call_target, .-uprobe_call_target\n"
-
- ".global uprobe_call_trigger\n"
- ".type uprobe_call_trigger, @function\n"
- "uprobe_call_trigger:\n"
- " call uprobe_call_target\n"
- " ret\n"
- ".size uprobe_call_trigger, .-uprobe_call_trigger\n"
- ".popsection\n"
-);
-
-/* If CALL emulation misses the shadow stack update, this exits via SIGSEGV. */
-static int test_uprobe_call(void)
-{
- const size_t attr_sz = sizeof(struct perf_event_attr);
- const char *file = "/proc/self/exe";
- int fd = -1, 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]\tUprobe on CALL test, uprobes are not available\n");
- return 0;
- }
-
- offset = get_uprobe_offset(uprobe_call_trigger);
- if (offset < 0)
- return 1;
-
- sa.sa_sigaction = segv_gp_handler;
- sa.sa_flags = SA_SIGINFO;
- if (sigaction(SIGSEGV, &sa, NULL))
- return 1;
-
- /* Setup entry uprobe through perf event interface. */
- memset(&attr, 0, attr_sz);
- attr.size = attr_sz;
- attr.type = type;
- attr.config = 0;
- 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;
-
- if (ARCH_PRCTL(ARCH_SHSTK_ENABLE, ARCH_SHSTK_SHSTK))
- goto out;
-
- /*
- * This either segfaults and goes through sigsetjmp above
- * or succeeds and we're good.
- */
- uprobe_call_trigger();
-
- printf("[OK]\tUprobe on CALL test\n");
- err = 0;
-
-out:
- ARCH_PRCTL(ARCH_SHSTK_DISABLE, ARCH_SHSTK_SHSTK);
- signal(SIGSEGV, SIG_DFL);
- if (fd >= 0)
- close(fd);
- return err;
-}
-
-void segv_handler_ptrace(int signum, siginfo_t *si, void *uc)
-{
- /* The SSP adjustment caused a segfault. */
- exit(0);
-}
-
-int test_ptrace(void)
-{
- unsigned long saved_ssp, ssp = 0;
- struct sigaction sa= {};
- struct iovec iov;
- int status;
- int pid;
-
- iov.iov_base = &ssp;
- iov.iov_len = sizeof(ssp);
-
- pid = fork();
- if (!pid) {
- ssp = get_ssp();
-
- sa.sa_sigaction = segv_handler_ptrace;
- sa.sa_flags = SA_SIGINFO;
- if (sigaction(SIGSEGV, &sa, NULL))
- return 1;
-
- ptrace(PTRACE_TRACEME, NULL, NULL, NULL);
- /*
- * The parent will tweak the SSP and return from this function
- * will #CP.
- */
- raise(SIGTRAP);
-
- exit(1);
- }
-
- while (waitpid(pid, &status, 0) != -1 && WSTOPSIG(status) != SIGTRAP);
-
- if (ptrace(PTRACE_GETREGSET, pid, NT_X86_SHSTK, &iov)) {
- printf("[INFO]\tFailed to PTRACE_GETREGS\n");
- goto out_kill;
- }
-
- if (!ssp) {
- printf("[INFO]\tPtrace child SSP was 0\n");
- goto out_kill;
- }
-
- saved_ssp = ssp;
-
- iov.iov_len = 0;
- if (!ptrace(PTRACE_SETREGSET, pid, NT_X86_SHSTK, &iov)) {
- printf("[INFO]\tToo small size accepted via PTRACE_SETREGS\n");
- goto out_kill;
- }
-
- iov.iov_len = sizeof(ssp) + 1;
- if (!ptrace(PTRACE_SETREGSET, pid, NT_X86_SHSTK, &iov)) {
- printf("[INFO]\tToo large size accepted via PTRACE_SETREGS\n");
- goto out_kill;
- }
-
- ssp += 1;
- if (!ptrace(PTRACE_SETREGSET, pid, NT_X86_SHSTK, &iov)) {
- printf("[INFO]\tUnaligned SSP written via PTRACE_SETREGS\n");
- goto out_kill;
- }
-
- ssp = 0xFFFFFFFFFFFF0000;
- if (!ptrace(PTRACE_SETREGSET, pid, NT_X86_SHSTK, &iov)) {
- printf("[INFO]\tKernel range SSP written via PTRACE_SETREGS\n");
- goto out_kill;
- }
-
- /*
- * Tweak the SSP so the child with #CP when it resumes and returns
- * from raise()
- */
- ssp = saved_ssp + 8;
- iov.iov_len = sizeof(ssp);
- if (ptrace(PTRACE_SETREGSET, pid, NT_X86_SHSTK, &iov)) {
- printf("[INFO]\tFailed to PTRACE_SETREGS\n");
- goto out_kill;
- }
-
- if (ptrace(PTRACE_DETACH, pid, NULL, NULL)) {
- printf("[INFO]\tFailed to PTRACE_DETACH\n");
- goto out_kill;
- }
-
- waitpid(pid, &status, 0);
- if (WEXITSTATUS(status))
- return 1;
-
- printf("[OK]\tPtrace test\n");
- return 0;
-
-out_kill:
- kill(pid, SIGKILL);
- return 1;
-}
+#include "shadow_stack.h"
int main(int argc, char *argv[])
{
- int ret = 0;
-
- if (ARCH_PRCTL(ARCH_SHSTK_ENABLE, ARCH_SHSTK_SHSTK)) {
- printf("[SKIP]\tCould not enable Shadow stack\n");
- return 1;
- }
-
- if (ARCH_PRCTL(ARCH_SHSTK_DISABLE, ARCH_SHSTK_SHSTK)) {
- ret = 1;
- printf("[FAIL]\tDisabling shadow stack failed\n");
- }
-
- if (ARCH_PRCTL(ARCH_SHSTK_ENABLE, ARCH_SHSTK_SHSTK)) {
- printf("[SKIP]\tCould not re-enable Shadow stack\n");
- return 1;
- }
-
- if (ARCH_PRCTL(ARCH_SHSTK_ENABLE, ARCH_SHSTK_WRSS)) {
- printf("[SKIP]\tCould not enable WRSS\n");
- ret = 1;
- goto out;
- }
-
- /* Should have succeeded if here, but this is a test, so double check. */
- if (!get_ssp()) {
- printf("[FAIL]\tShadow stack disabled\n");
- return 1;
- }
-
- if (test_shstk_pivot()) {
- ret = 1;
- printf("[FAIL]\tShadow stack pivot\n");
- goto out;
- }
-
- if (test_shstk_faults()) {
- ret = 1;
- printf("[FAIL]\tShadow stack fault test\n");
- goto out;
- }
-
- if (test_shstk_violation()) {
- ret = 1;
- printf("[FAIL]\tShadow stack violation test\n");
- goto out;
- }
-
- if (test_gup()) {
- ret = 1;
- printf("[FAIL]\tShadow shadow stack gup\n");
- goto out;
- }
-
- if (test_mprotect()) {
- ret = 1;
- printf("[FAIL]\tShadow shadow mprotect test\n");
- goto out;
- }
-
- if (test_userfaultfd()) {
- ret = 1;
- printf("[FAIL]\tUserfaultfd test\n");
- goto out;
- }
-
- if (test_guard_gap_other_gaps()) {
- ret = 1;
- printf("[FAIL]\tGuard gap test, other mappings' gaps\n");
- goto out;
- }
-
- if (test_guard_gap_new_mappings_gaps()) {
- ret = 1;
- printf("[FAIL]\tGuard gap test, placement mapping's gaps\n");
- goto out;
- }
-
- if (test_ptrace()) {
- ret = 1;
- printf("[FAIL]\tptrace test\n");
- }
-
- if (test_32bit()) {
- ret = 1;
- printf("[FAIL]\t32 bit test\n");
- goto out;
- }
-
- if (test_uretprobe()) {
- ret = 1;
- printf("[FAIL]\turetprobe test\n");
- goto out;
- }
-
- if (test_uprobe_call()) {
- ret = 1;
- printf("[FAIL]\tuprobe on CALL test\n");
- goto out;
- }
-
- return ret;
-
-out:
- /*
- * Disable shadow stack before the function returns, or there will be a
- * shadow stack violation.
- */
- if (ARCH_PRCTL(ARCH_SHSTK_DISABLE, ARCH_SHSTK_SHSTK)) {
- ret = 1;
- printf("[FAIL]\tDisabling shadow stack failed\n");
- }
-
- return ret;
+ return shadow_stack_run_tests(SHADOW_STACK_TEST_ARCH_PRCTL);
}
-#endif
diff --git a/tools/testing/selftests/x86/test_shadow_stack_prctl.c b/tools/testing/selftests/x86/test_shadow_stack_prctl.c
new file mode 100644
index 0000000000000..4641a3b1ca036
--- /dev/null
+++ b/tools/testing/selftests/x86/test_shadow_stack_prctl.c
@@ -0,0 +1,12 @@
+// SPDX-License-Identifier: GPL-2.0
+
+/*
+ * Same as test_shadow_stack.c but uses the PRCTL interface
+ * instead of ARCH_PRCTL.
+ */
+#include "shadow_stack.h"
+
+int main(int argc, char *argv[])
+{
+ return shadow_stack_run_tests(SHADOW_STACK_TEST_PRCTL);
+}
|
| 3/2 | 2026/08/12 19:51 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 27286 output: 23 thoughts: 183 Thoughts: **My Analysis of the Shadow Stack Patch** Okay, so we've got a patch here that's touching `arch/x86/kernel/shstk.c` and adding some tests in the selftests directory. Sounds like it's implementing a generic `prctl` interface for shadow stacks, specifically with `PR_SET_SHADOW_STACK_STATUS`, `PR_LOCK_SHADOW_STACK_STATUS`, and `PR_GET_SHADOW_STACK_STATUS`. That's smart; abstracting it out with `prctl` keeps things clean. The patch is defining the arch-specific implementations: `arch_set_shadow_stack_status`, `arch_lock_shadow_stack_status`, and `arch_get_shadow_stack_status`. Now, I need to make sure this is wired up correctly. The question is, are these functions actually *called*? `arch_set_shadow_stack_status`, in particular, is the one I want to zero in on. My initial thought is it would be called from the generic `prctl` handling in something like `kernel/sys.c`, or maybe some other generic `prctl` code. Let's fire up a grep and see if the source code confirms my expectations. Time to check for that function call! |
| 4/2 | 2026/08/12 19:51 | tool | grepper |
0mArgs:{
"Expression": "arch_set_shadow_stack_status"
}Results: {
"Output": "arch/arm64/mm/gcs.c=143=void gcs_free(struct task_struct *task)\n--\narch/arm64/mm/gcs.c-158-\narch/arm64/mm/gcs.c:159:int arch_set_shadow_stack_status(struct task_struct *task, unsigned long arg)\narch/arm64/mm/gcs.c-160-{\n--\narch/riscv/kernel/usercfi.c=371=int arch_get_shadow_stack_status(struct task_struct *t, unsigned long __user *status)\n--\narch/riscv/kernel/usercfi.c-383-\narch/riscv/kernel/usercfi.c:384:int arch_set_shadow_stack_status(struct task_struct *t, unsigned long status)\narch/riscv/kernel/usercfi.c-385-{\n--\narch/x86/kernel/shstk.c=630=bool shstk_is_enabled(void)\n--\narch/x86/kernel/shstk.c-638-/* Handles the generic prctl interface for PR_SET_SHADOW_STACK_STATUS and its feature bits */\narch/x86/kernel/shstk.c:639:int arch_set_shadow_stack_status(struct task_struct *t, unsigned long status)\narch/x86/kernel/shstk.c-640-{\n--\ninclude/linux/mm.h=5187=int arch_get_shadow_stack_status(struct task_struct *t, unsigned long __user *status);\ninclude/linux/mm.h:5188:int arch_set_shadow_stack_status(struct task_struct *t, unsigned long status);\ninclude/linux/mm.h-5189-int arch_lock_shadow_stack_status(struct task_struct *t, unsigned long status);\n--\nkernel/sys.c=2376=int __weak arch_get_shadow_stack_status(struct task_struct *t, unsigned long __user *status)\n--\nkernel/sys.c-2380-\nkernel/sys.c:2381:int __weak arch_set_shadow_stack_status(struct task_struct *t, unsigned long status)\nkernel/sys.c-2382-{\n--\nkernel/sys.c=2534=SYSCALL_DEFINE5(prctl, int, option, unsigned long, arg2, unsigned long, arg3,\n--\nkernel/sys.c-2871-\t\t\treturn -EINVAL;\nkernel/sys.c:2872:\t\terror = arch_set_shadow_stack_status(me, arg2);\nkernel/sys.c-2873-\t\tbreak;\n"
} |
| 5/2 | 2026/08/12 19:51 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 28474 output: 25 thoughts: 130 Thoughts: **X86 Shadow Stack Implementation Thoughts** Okay, so this patch is adding the core functions for shadow stack management on x86 – specifically, `arch_set_shadow_stack_status`, `arch_lock_shadow_stack_status`, and `arch_get_shadow_stack_status`. That's the meat of it. I see these are definitely hooked up to the `prctl` syscall, which makes sense for user-space control. Good, good. They've also included `test_shadow_stack_prctl.c`, which is the correct approach to ensure everything functions as it should. Always good to see proper testing. Now, I need to verify how the x86 shadow stack is configured. Time to check the relevant config options. |
| 6/2 | 2026/08/12 19:52 | tool | grepper |
0mArgs:{
"Expression": "config X86_USER_SHADOW_STACK"
}Results: {
"Output": "arch/x86/Kconfig=1827=config X86_SGX\n--\narch/x86/Kconfig-1843-\narch/x86/Kconfig:1844:config X86_USER_SHADOW_STACK\narch/x86/Kconfig-1845-\tbool \"X86 userspace shadow stack\"\n"
} |
| 7/2 | 2026/08/12 19:52 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 28895 output: 37 thoughts: 22 Thoughts: Here's a summary of my thought process: **Diving into the x86 Kernel Configuration** Okay, so I need to understand the dependencies and configuration options within the x86 architecture. The most logical starting point is the `arch/x86/Kconfig` file. That's where the core configuration options specific to the x86 architecture are defined. By examining this file, I can build a mental model of which features are enabled or disabled based on various hardware configurations, compiler flags, and other kernel options. I'll need to pay close attention to the `depends on` statements within that file. They'll tell me precisely what other kernel components, features, or configurations must be present for any given x86-specific option to become active. |
| 8/2 | 2026/08/12 19:52 | tool | read-file |
0mArgs:{
"File": "arch/x86/Kconfig",
"FirstLine": 1840,
"LineCount": 20
}Results: {
"Contents": "1840:\t\t hardware.\n1841:\t\n1842:\t\t If unsure, say N.\n1843:\t\n1844:\tconfig X86_USER_SHADOW_STACK\n1845:\t\tbool \"X86 userspace shadow stack\"\n1846:\t\tdepends on AS_WRUSS\n1847:\t\tdepends on X86_64\n1848:\t\tdepends on PER_VMA_LOCK\n1849:\t\tselect ARCH_USES_HIGH_VMA_FLAGS\n1850:\t\tselect ARCH_HAS_USER_SHADOW_STACK\n1851:\t\tselect X86_CET\n1852:\t\thelp\n1853:\t\t Shadow stack protection is a hardware feature that detects function\n1854:\t\t return address corruption. This helps mitigate ROP attacks.\n1855:\t\t Applications must be enabled to use it, and old userspace does not\n1856:\t\t get protection \"for free\".\n1857:\t\n1858:\t\t CPUs supporting shadow stacks were first released in 2020.\n1859:\t\n"
} |
| 9/2 | 2026/08/12 19:52 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 29413 output: 19 thoughts: 51 Thoughts: **Examining the Shadow Stack Configuration** Okay, so the task at hand requires `X86_USER_SHADOW_STACK`. My immediate question is, are there any other related configurations I should be aware of? Let's not assume this is the only relevant flag. The next logical step is to dive into the kernel source, specifically `arch/x86/kernel/shstk.c`. I need to see if the compilation of this functionality is conditional – are there dependencies or other factors that dictate whether the shadow stack is even built? Knowing the conditional logic will give me a better understanding of the overall picture. This should point me in the right direction. |
| 10/2 | 2026/08/12 19:52 | tool | grepper |
0mArgs:{
"Expression": "shstk\\.o"
}Results: {
"Output": "arch/x86/kernel/Makefile=169=obj-$(CONFIG_X86_CET)\t\t\t+= cet.o\narch/x86/kernel/Makefile-170-\narch/x86/kernel/Makefile:171:obj-$(CONFIG_X86_USER_SHADOW_STACK)\t+= shstk.o\narch/x86/kernel/Makefile-172-\n"
} |
| 11/2 | 2026/08/12 19:52 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 29734 output: 154 thoughts: 470 Thoughts: **Shadow Stack Functions - Analysis and Fuzzing Strategy** Okay, so we're dealing with `CONFIG_X86_USER_SHADOW_STACK` – that's the key. My focus, for this particular analysis, needs to be on these three core functions: `arch_set_shadow_stack_status`, `arch_lock_shadow_stack_status`, and `arch_get_shadow_stack_status`. We need to verify that these are correctly implemented and handling all possible inputs. Let's quickly confirm they're publicly accessible, rather than being static within a module. Yes, they are indeed defined in `arch/x86/kernel/shstk.c` and are available for external use. Alright, double-checking for any other relevant configurations. `X86_USER_SHADOW_STACK` depends on `AS_WRUSS`, `X86_64`, and `PER_VMA_LOCK`. `X86_64` is a given; it's a fundamental architectural element. `AS_WRUSS` is handled by the assembler itself, so no direct configuration there. `PER_VMA_LOCK` is selected by `MMU` and `SMP` which are expected to be on, so we should be good. So, in essence, enabling `X86_USER_SHADOW_STACK` is sufficient. Now, let's prepare the fuzzing configuration. We absolutely need to fuzz these functions, as they are now exposed through the `prctl` system call. The fuzzing target will have `WorthFuzzing: true`, and the `FocusSymbols` will be: * `arch_set_shadow_stack_status` * `arch_lock_shadow_stack_status` * `arch_get_shadow_stack_status` The `EnableConfigs` will be: * `X86_USER_SHADOW_STACK` The rationale is clear. We are introducing generic `prctl` support for the x86 shadow stack. These specific functions are the interfaces via the `prctl` system call to interact with the new functionality. Thorough fuzzing of these functions is crucial to ensure their correct handling of different `prctl` options and avoid any potential vulnerabilities. |
| 12/2 | 2026/08/12 19:52 | tool | set-results |
0mArgs:{
"EnableConfigs": [
"X86_USER_SHADOW_STACK"
],
"FocusSymbols": [
"arch_set_shadow_stack_status",
"arch_lock_shadow_stack_status",
"arch_get_shadow_stack_status"
],
"Reasoning": "The patch adds generic prctl support for x86 shadow stack. The functions `arch_set_shadow_stack_status`, `arch_lock_shadow_stack_status`, and `arch_get_shadow_stack_status` are reachable via the `prctl` syscall and should be fuzzed to ensure correct handling of the new prctl options.",
"WorthFuzzing": true
}Results: {
"EnableConfigs": [
"X86_USER_SHADOW_STACK"
],
"FocusSymbols": [
"arch_set_shadow_stack_status",
"arch_lock_shadow_stack_status",
"arch_get_shadow_stack_status"
],
"Reasoning": "The patch adds generic prctl support for x86 shadow stack. The functions `arch_set_shadow_stack_status`, `arch_lock_shadow_stack_status`, and `arch_get_shadow_stack_status` are reachable via the `prctl` syscall and should be fuzzed to ensure correct handling of the new prctl options.",
"WorthFuzzing": true
} |
| 13/1 | 2026/08/12 19:52 | action | If |
1mArgs:{
"WorthFuzzing": true
}Results: null |
| 14/2 | 2026/08/12 19:52 | agent | kmsan-evaluator |
1mModel:best-expensive Args: null Results: {
"KMSANReasoning": "The patch introduces generic `prctl` handlers for shadow stack on x86 (`arch_set_shadow_stack_status`, `arch_lock_shadow_stack_status`, `arch_get_shadow_stack_status`) and adds corresponding selftests. The only data copied to user space is a scalar `unsigned long` (`task-\u003ethread.features`) via `put_user()` in `shstk_prctl()`. This does not involve any struct padding, uninitialized memory, or complex allocations. While there is a potential operator precedence bug (`!status \u0026 PR_SHADOW_STACK_ENABLE`), it is a functional logic bug and does not expose uninitialized memory. Thus, a dedicated KMSAN fuzzing session is not required.",
"NeedsKMSAN": false
}Instruction: You are an expert Linux kernel security engineer specializing in kernel memory
error detectors (KASAN and KMSAN). Your job is to review the provided patch series and
determine if the code changes justify spawning a dedicated KMSAN (KernelMemorySanitizer)
fuzzing session in addition to standard KASAN fuzzing.
CRITICAL DISTINCTION BETWEEN KASAN AND KMSAN:
- Standard KASAN kernel builds (upstream-apparmor-kasan.config) already enable
a comprehensive suite of debugging tools and sanitizers, including KASAN
(out-of-bounds accesses, use-after-free, double free, invalid free), LOCKDEP
(locking bugs and deadlocks), UB-sanitizers, and memory corruption checks.
- KMSAN (KernelMemorySanitizer) detects reads of UNINITIALIZED memory (stack, heap,
or page allocations) and kernel-to-user memory info-leaks.
Rule: THERE IS NO SENSE IN RUNNING A KMSAN SESSION IF A BUG CAN BE CAUGHT BY KASAN,
LOCKDEP, OR OTHER STANDARD BUG DETECTORS.
A dedicated KMSAN fuzzing session incurs significant resource costs. You must ONLY
set NeedsKMSAN=true if the code changes introduce or expose UNINITIALIZED MEMORY risks
that are detected ONLY by KMSAN.
Look holistically at the patch series and surrounding code. Even if no direct
uninitialized field accesses or new buffer allocations are added in the diff itself,
a patch may alter control flow, bounds checking, or data length calculations in ways
that change how the rest of the code operates on existing buffers (e.g. allowing
uninitialized stack/heap memory to be read, copied to user space, or used in control
flow). Do not hesitate to use your code access tools to inspect the surrounding code,
called functions, and callers.
Set NeedsKMSAN=true ONLY IF the patch introduces or modifies:
1. Kernel structures sent to user space (via copy_to_user, put_user, netlink skb
attributes, ioctl output arguments, socket options, or BPF buffers) where fields
or structure padding might not be fully initialized/zeroed.
2. Conditional logic or branching that depends on potentially uninitialized variables
or struct fields.
3. Allocation or initialization of complex data structures where uninitialized fields
could be read later in reachable code paths.
4. Bounds checks, lengths, or logic in a way that allows surrounding code to access
uninitialized bytes of existing buffers.
Set NeedsKMSAN=false IF:
- The code changes primarily risk out-of-bounds access, array overflows, NULL pointer
dereferences, locking deadlocks, or use-after-free bugs (these are already caught
by KASAN, LOCKDEP, or standard bug detectors).
- All stack/heap structures touched or introduced by the patch are fully zeroed
or initialized (e.g. using = {0}, memset, kzalloc) before being read or copied.
- The patch does not introduce any risk of uninitialized memory usage or info-leaks.
Use your code access tools to inspect the surrounding code if necessary, then provide
detailed KMSANReasoning contrasting KASAN vs KMSAN applicability for this patch.
Prefer calling several tools at the same time to save round-trips.
Use set-results tool to provide results of the analysis.
It must be called exactly once before the final reply.
Ignore results of this tool.
Prompt: For your convenience, here is the diff of the changes:
commit 079ec6fe340458147e0fc94e583f8c31b1ddb2e8
Author: syz-cluster <triage@syzkaller.com>
Date: Wed Aug 12 19:51:31 2026 +0000
syz-cluster: applied patch under review
diff --git a/arch/x86/kernel/shstk.c b/arch/x86/kernel/shstk.c
index 0ca64900192f8..c34ba3701dd06 100644
--- a/arch/x86/kernel/shstk.c
+++ b/arch/x86/kernel/shstk.c
@@ -18,6 +18,7 @@
#include <linux/sizes.h>
#include <linux/user.h>
#include <linux/syscalls.h>
+#include <linux/prctl.h>
#include <asm/msr.h>
#include <asm/fpu/xstate.h>
#include <asm/fpu/types.h>
@@ -630,3 +631,39 @@ bool shstk_is_enabled(void)
{
return features_enabled(ARCH_SHSTK_SHSTK);
}
+
+#define PR_SHADOW_STACK_SUPPORTED_STATUS_MASK \
+ (PR_SHADOW_STACK_ENABLE | PR_SHADOW_STACK_WRITE | PR_SHADOW_STACK_PUSH)
+
+/* Handles the generic prctl interface for PR_SET_SHADOW_STACK_STATUS and its feature bits */
+int arch_set_shadow_stack_status(struct task_struct *t, unsigned long status)
+{
+ int rc;
+
+ if (status & ~PR_SHADOW_STACK_SUPPORTED_STATUS_MASK)
+ return -EINVAL;
+
+ /* x86 arch_prctl is single bit at a time, so handle these one at time */
+ if (!status & PR_SHADOW_STACK_ENABLE)
+ return shstk_prctl(t, ARCH_SHSTK_DISABLE, ARCH_SHSTK_SHSTK);
+
+ rc = shstk_prctl(t, ARCH_SHSTK_ENABLE, ARCH_SHSTK_SHSTK);
+ if (rc)
+ return rc;
+
+ if (status & PR_SHADOW_STACK_WRITE)
+ return shstk_prctl(t, ARCH_SHSTK_ENABLE, ARCH_SHSTK_WRSS);
+
+ return shstk_prctl(t, ARCH_SHSTK_DISABLE, ARCH_SHSTK_WRSS);
+}
+
+/* Handles the generic prctl interface for PR_LOCK_SHADOW_STACK_STATUS and its feature bits */
+int arch_lock_shadow_stack_status(struct task_struct *t, unsigned long status)
+{
+ return shstk_prctl(t, ARCH_SHSTK_LOCK, status);
+}
+
+int arch_get_shadow_stack_status(struct task_struct *t, unsigned long __user *status)
+{
+ return shstk_prctl(t, ARCH_SHSTK_STATUS, (unsigned long)status);
+}
diff --git a/tools/testing/selftests/x86/Makefile b/tools/testing/selftests/x86/Makefile
index 434065215d127..8748e2f4a915d 100644
--- a/tools/testing/selftests/x86/Makefile
+++ b/tools/testing/selftests/x86/Makefile
@@ -19,7 +19,8 @@ TARGETS_C_32BIT_ONLY := entry_from_vm86 test_syscall_vdso unwind_vdso \
test_FCMOV test_FCOMI test_FISTTP \
vdso_restorer
TARGETS_C_64BIT_ONLY := fsgsbase sysret_rip syscall_numbering \
- corrupt_xstate_header amx lam test_shadow_stack avx apx
+ corrupt_xstate_header amx lam test_shadow_stack avx apx \
+ test_shadow_stack_prctl
# Some selftests require 32bit support enabled also on 64bit systems
TARGETS_C_32BIT_NEEDED := ldt_gdt ptrace_syscall
@@ -138,3 +139,5 @@ $(OUTPUT)/avx_64: CFLAGS += -mno-avx -mno-avx512f
$(OUTPUT)/amx_64: EXTRA_FILES += xstate.c
$(OUTPUT)/avx_64: EXTRA_FILES += xstate.c
$(OUTPUT)/apx_64: EXTRA_FILES += xstate.c
+$(OUTPUT)/test_shadow_stack_64: EXTRA_FILES += shadow_stack.c
+$(OUTPUT)/test_shadow_stack_prctl_64: EXTRA_FILES += shadow_stack.c
diff --git a/tools/testing/selftests/x86/shadow_stack.c b/tools/testing/selftests/x86/shadow_stack.c
new file mode 100644
index 0000000000000..a375c4ff7a6ac
--- /dev/null
+++ b/tools/testing/selftests/x86/shadow_stack.c
@@ -0,0 +1,1205 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#define _GNU_SOURCE
+
+#include <sys/syscall.h>
+#include <asm/mman.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+#include <stdbool.h>
+#include <x86intrin.h>
+#include <asm/prctl.h>
+#include <sys/prctl.h>
+#include <stdint.h>
+#include <signal.h>
+#include <pthread.h>
+#include <sys/ioctl.h>
+#include <linux/userfaultfd.h>
+#include <setjmp.h>
+#include <sys/ptrace.h>
+#include <sys/signal.h>
+#include <linux/elf.h>
+#include <linux/perf_event.h>
+
+#include "shadow_stack.h"
+
+/*
+ * Define the ABI defines if needed, so people can run the tests
+ * without building the headers.
+ */
+#ifndef __NR_map_shadow_stack
+#define __NR_map_shadow_stack 453
+
+#define SHADOW_STACK_SET_TOKEN (1ULL << 0)
+
+#define ARCH_SHSTK_ENABLE 0x5001
+#define ARCH_SHSTK_DISABLE 0x5002
+#define ARCH_SHSTK_LOCK 0x5003
+#define ARCH_SHSTK_UNLOCK 0x5004
+#define ARCH_SHSTK_STATUS 0x5005
+
+#define ARCH_SHSTK_SHSTK (1ULL << 0)
+#define ARCH_SHSTK_WRSS (1ULL << 1)
+
+#define NT_X86_SHSTK 0x204
+#endif
+
+#define SS_SIZE 0x200000
+#define PAGE_SIZE 0x1000
+
+void write_shstk(unsigned long *addr, unsigned long val)
+{
+ asm volatile("wrssq %[val], (%[addr])\n"
+ : "=m" (addr)
+ : [addr] "r" (addr), [val] "r" (val));
+}
+
+static inline unsigned long __attribute__((always_inline)) get_ssp(void)
+{
+ unsigned long ret = 0;
+
+ asm volatile("xor %0, %0; rdsspq %0" : "=r" (ret));
+ return ret;
+}
+
+/*
+ * For use in inline enablement of shadow stack.
+ *
+ * The program can't return from the point where shadow stack gets enabled
+ * because there will be no address on the shadow stack. So it can't use
+ * syscall() for enablement, since it is a function.
+ *
+ * Based on code from nolibc.h. Keep a copy here because this can't pull in all
+ * of nolibc.h.
+ */
+#define ARCH_PRCTL(arg1, arg2) \
+({ \
+ long _ret; \
+ register long _num asm("eax") = __NR_arch_prctl; \
+ register long _arg1 asm("rdi") = (long)(arg1); \
+ register long _arg2 asm("rsi") = (long)(arg2); \
+ \
+ asm volatile ( \
+ "syscall\n" \
+ : "=a"(_ret) \
+ : "r"(_arg1), "r"(_arg2), \
+ "0"(_num) \
+ : "rcx", "r11", "memory", "cc" \
+ ); \
+ _ret; \
+})
+
+#define PRCTL(option, arg2, arg3, arg4, arg5) \
+({ \
+ long _ret; \
+ register long _num asm("rax") = __NR_prctl; \
+ register long _arg1 asm("rdi") = (long)(option); \
+ register long _arg2 asm("rsi") = (long)(arg2); \
+ register long _arg3 asm("rdx") = (long)(arg3); \
+ register long _arg4 asm("r10") = (long)(arg4); \
+ register long _arg5 asm("r8") = (long)(arg5); \
+ \
+ asm volatile ( \
+ "syscall" \
+ : "=a"(_ret) \
+ : "0"(_num), \
+ "r"(_arg1), "r"(_arg2), "r"(_arg3), \
+ "r"(_arg4), "r"(_arg5) \
+ : "rcx", "r11", "memory", "cc" \
+ ); \
+ _ret; \
+})
+
+#define SHADOW_STACK_DISABLE(which_test) \
+ ((which_test) == SHADOW_STACK_TEST_ARCH_PRCTL ? \
+ ARCH_PRCTL(ARCH_SHSTK_DISABLE, ARCH_SHSTK_SHSTK) : \
+ PRCTL(PR_SET_SHADOW_STACK_STATUS, 0, 0, 0, 0))
+
+#define SHADOW_STACK_ENABLE(which_test) \
+ ((which_test) == SHADOW_STACK_TEST_ARCH_PRCTL ? \
+ ARCH_PRCTL(ARCH_SHSTK_ENABLE, ARCH_SHSTK_SHSTK) : \
+ PRCTL(PR_SET_SHADOW_STACK_STATUS, PR_SHADOW_STACK_ENABLE, 0, 0, 0))
+
+#define SHADOW_STACK_ENABLE_WRITE(which_test) \
+ ((which_test) == SHADOW_STACK_TEST_ARCH_PRCTL ? \
+ ARCH_PRCTL(ARCH_SHSTK_ENABLE, ARCH_SHSTK_WRSS) : \
+ PRCTL(PR_SET_SHADOW_STACK_STATUS, \
+ PR_SHADOW_STACK_ENABLE|PR_SHADOW_STACK_WRITE, 0, 0, 0))
+
+void *create_shstk(void *addr)
+{
+ return (void *)syscall(__NR_map_shadow_stack, addr, SS_SIZE, SHADOW_STACK_SET_TOKEN);
+}
+
+void *create_normal_mem(void *addr)
+{
+ return mmap(addr, SS_SIZE, PROT_READ | PROT_WRITE,
+ MAP_PRIVATE | MAP_ANONYMOUS, 0, 0);
+}
+
+void free_shstk(void *shstk)
+{
+ munmap(shstk, SS_SIZE);
+}
+
+int reset_shstk(void *shstk)
+{
+ return madvise(shstk, SS_SIZE, MADV_DONTNEED);
+}
+
+void try_shstk(unsigned long new_ssp)
+{
+ unsigned long ssp;
+
+ printf("[INFO]\tnew_ssp = %lx, *new_ssp = %lx\n",
+ new_ssp, *((unsigned long *)new_ssp));
+
+ ssp = get_ssp();
+ printf("[INFO]\tchanging ssp from %lx to %lx\n", ssp, new_ssp);
+
+ asm volatile("rstorssp (%0)\n":: "r" (new_ssp));
+ asm volatile("saveprevssp");
+ printf("[INFO]\tssp is now %lx\n", get_ssp());
+
+ /* Switch back to original shadow stack */
+ ssp -= 8;
+ asm volatile("rstorssp (%0)\n":: "r" (ssp));
+ asm volatile("saveprevssp");
+}
+
+int test_shstk_pivot(void)
+{
+ void *shstk = create_shstk(0);
+
+ if (shstk == MAP_FAILED) {
+ printf("[FAIL]\tError creating shadow stack: %d\n", errno);
+ return 1;
+ }
+ try_shstk((unsigned long)shstk + SS_SIZE - 8);
+ free_shstk(shstk);
+
+ printf("[OK]\tShadow stack pivot\n");
+ return 0;
+}
+
+int test_shstk_faults(void)
+{
+ unsigned long *shstk = create_shstk(0);
+
+ /* Read shadow stack, test if it's zero to not get read optimized out */
+ if (*shstk != 0)
+ goto err;
+
+ /* Wrss memory that was already read. */
+ write_shstk(shstk, 1);
+ if (*shstk != 1)
+ goto err;
+
+ /* Page out memory, so we can wrss it again. */
+ if (reset_shstk((void *)shstk))
+ goto err;
+
+ write_shstk(shstk, 1);
+ if (*shstk != 1)
+ goto err;
+
+ printf("[OK]\tShadow stack faults\n");
+ return 0;
+
+err:
+ return 1;
+}
+
+unsigned long saved_ssp;
+unsigned long saved_ssp_val;
+volatile bool segv_triggered;
+
+void __attribute__((noinline)) violate_ss(void)
+{
+ saved_ssp = get_ssp();
+ saved_ssp_val = *(unsigned long *)saved_ssp;
+
+ /* Corrupt shadow stack */
+ printf("[INFO]\tCorrupting shadow stack\n");
+ write_shstk((void *)saved_ssp, 0);
+}
+
+void segv_handler(int signum, siginfo_t *si, void *uc)
+{
+ printf("[INFO]\tGenerated shadow stack violation successfully\n");
+
+ segv_triggered = true;
+
+ /* Fix shadow stack */
+ write_shstk((void *)saved_ssp, saved_ssp_val);
+}
+
+int test_shstk_violation(void)
+{
+ struct sigaction sa = {};
+
+ sa.sa_sigaction = segv_handler;
+ sa.sa_flags = SA_SIGINFO;
+ if (sigaction(SIGSEGV, &sa, NULL))
+ return 1;
+
+ segv_triggered = false;
+
+ /* Make sure segv_triggered is set before violate_ss() */
+ asm volatile("" : : : "memory");
+
+ violate_ss();
+
+ signal(SIGSEGV, SIG_DFL);
+
+ printf("[OK]\tShadow stack violation test\n");
+
+ return !segv_triggered;
+}
+
+/* Gup test state */
+#define MAGIC_VAL 0x12345678
+bool is_shstk_access;
+void *shstk_ptr;
+int fd;
+
+void reset_test_shstk(void *addr)
+{
+ if (shstk_ptr)
+ free_shstk(shstk_ptr);
+ shstk_ptr = create_shstk(addr);
+}
+
+void test_access_fix_handler(int signum, siginfo_t *si, void *uc)
+{
+ printf("[INFO]\tViolation from %s\n", is_shstk_access ? "shstk access" : "normal write");
+
+ segv_triggered = true;
+
+ /* Fix shadow stack */
+ if (is_shstk_access) {
+ reset_test_shstk(shstk_ptr);
+ return;
+ }
+
+ free_shstk(shstk_ptr);
+ create_normal_mem(shstk_ptr);
+}
+
+bool test_shstk_access(void *ptr)
+{
+ is_shstk_access = true;
+ segv_triggered = false;
+ write_shstk(ptr, MAGIC_VAL);
+
+ asm volatile("" : : : "memory");
+
+ return segv_triggered;
+}
+
+bool test_write_access(void *ptr)
+{
+ is_shstk_access = false;
+ segv_triggered = false;
+ *(unsigned long *)ptr = MAGIC_VAL;
+
+ asm volatile("" : : : "memory");
+
+ return segv_triggered;
+}
+
+bool gup_write(void *ptr)
+{
+ unsigned long val;
+
+ lseek(fd, (unsigned long)ptr, SEEK_SET);
+ if (write(fd, &val, sizeof(val)) < 0)
+ return 1;
+
+ return 0;
+}
+
+bool gup_read(void *ptr)
+{
+ unsigned long val;
+
+ lseek(fd, (unsigned long)ptr, SEEK_SET);
+ if (read(fd, &val, sizeof(val)) < 0)
+ return 1;
+
+ return 0;
+}
+
+int test_gup(void)
+{
+ struct sigaction sa = {};
+ int status;
+ pid_t pid;
+
+ sa.sa_sigaction = test_access_fix_handler;
+ sa.sa_flags = SA_SIGINFO;
+ if (sigaction(SIGSEGV, &sa, NULL))
+ return 1;
+
+ segv_triggered = false;
+
+ fd = open("/proc/self/mem", O_RDWR);
+ if (fd == -1)
+ return 1;
+
+ reset_test_shstk(0);
+ if (gup_read(shstk_ptr))
+ return 1;
+ if (test_shstk_access(shstk_ptr))
+ return 1;
+ printf("[INFO]\tGup read -> shstk access success\n");
+
+ reset_test_shstk(0);
+ if (gup_write(shstk_ptr))
+ return 1;
+ if (test_shstk_access(shstk_ptr))
+ return 1;
+ printf("[INFO]\tGup write -> shstk access success\n");
+
+ reset_test_shstk(0);
+ if (gup_read(shstk_ptr))
+ return 1;
+ if (!test_write_access(shstk_ptr))
+ return 1;
+ printf("[INFO]\tGup read -> write access success\n");
+
+ reset_test_shstk(0);
+ if (gup_write(shstk_ptr))
+ return 1;
+ if (!test_write_access(shstk_ptr))
+ return 1;
+ printf("[INFO]\tGup write -> write access success\n");
+
+ close(fd);
+
+ /* COW/gup test */
+ reset_test_shstk(0);
+ pid = fork();
+ if (!pid) {
+ fd = open("/proc/self/mem", O_RDWR);
+ if (fd == -1)
+ exit(1);
+
+ if (gup_write(shstk_ptr)) {
+ close(fd);
+ exit(1);
+ }
+ close(fd);
+ exit(0);
+ }
+ waitpid(pid, &status, 0);
+ if (WEXITSTATUS(status)) {
+ printf("[FAIL]\tWrite in child failed\n");
+ return 1;
+ }
+ if (*(unsigned long *)shstk_ptr == MAGIC_VAL) {
+ printf("[FAIL]\tWrite in child wrote through to shared memory\n");
+ return 1;
+ }
+
+ printf("[INFO]\tCow gup write -> write access success\n");
+
+ free_shstk(shstk_ptr);
+
+ signal(SIGSEGV, SIG_DFL);
+
+ printf("[OK]\tShadow gup test\n");
+
+ return 0;
+}
+
+int test_mprotect(void)
+{
+ struct sigaction sa = {};
+
+ sa.sa_sigaction = test_access_fix_handler;
+ sa.sa_flags = SA_SIGINFO;
+ if (sigaction(SIGSEGV, &sa, NULL))
+ return 1;
+
+ segv_triggered = false;
+
+ /* mprotect a shadow stack as read only */
+ reset_test_shstk(0);
+ if (mprotect(shstk_ptr, SS_SIZE, PROT_READ) < 0) {
+ printf("[FAIL]\tmprotect(PROT_READ) failed\n");
+ return 1;
+ }
+
+ /* try to wrss it and fail */
+ if (!test_shstk_access(shstk_ptr)) {
+ printf("[FAIL]\tShadow stack access to read-only memory succeeded\n");
+ return 1;
+ }
+
+ /*
+ * The shadow stack was reset above to resolve the fault, make the new one
+ * read-only.
+ */
+ if (mprotect(shstk_ptr, SS_SIZE, PROT_READ) < 0) {
+ printf("[FAIL]\tmprotect(PROT_READ) failed\n");
+ return 1;
+ }
+
+ /* then back to writable */
+ if (mprotect(shstk_ptr, SS_SIZE, PROT_WRITE | PROT_READ) < 0) {
+ printf("[FAIL]\tmprotect(PROT_WRITE) failed\n");
+ return 1;
+ }
+
+ /* then wrss to it and succeed */
+ if (test_shstk_access(shstk_ptr)) {
+ printf("[FAIL]\tShadow stack access to mprotect() writable memory failed\n");
+ return 1;
+ }
+
+ free_shstk(shstk_ptr);
+
+ signal(SIGSEGV, SIG_DFL);
+
+ printf("[OK]\tmprotect() test\n");
+
+ return 0;
+}
+
+char zero[4096];
+
+static void *uffd_thread(void *arg)
+{
+ struct uffdio_copy req;
+ int uffd = *(int *)arg;
+ struct uffd_msg msg;
+ int ret;
+
+ while (1) {
+ ret = read(uffd, &msg, sizeof(msg));
+ if (ret > 0)
+ break;
+ else if (errno == EAGAIN)
+ continue;
+ return (void *)1;
+ }
+
+ req.dst = msg.arg.pagefault.address;
+ req.src = (__u64)zero;
+ req.len = 4096;
+ req.mode = 0;
+
+ if (ioctl(uffd, UFFDIO_COPY, &req))
+ return (void *)1;
+
+ return (void *)0;
+}
+
+int test_userfaultfd(void)
+{
+ struct uffdio_register uffdio_register;
+ struct uffdio_api uffdio_api;
+ struct sigaction sa = {};
+ pthread_t thread;
+ void *res;
+ int uffd;
+
+ sa.sa_sigaction = test_access_fix_handler;
+ sa.sa_flags = SA_SIGINFO;
+ if (sigaction(SIGSEGV, &sa, NULL))
+ return 1;
+
+ uffd = syscall(__NR_userfaultfd, O_CLOEXEC | O_NONBLOCK);
+ if (uffd < 0) {
+ printf("[SKIP]\tUserfaultfd unavailable.\n");
+ return 0;
+ }
+
+ reset_test_shstk(0);
+
+ uffdio_api.api = UFFD_API;
+ uffdio_api.features = 0;
+ if (ioctl(uffd, UFFDIO_API, &uffdio_api))
+ goto err;
+
+ uffdio_register.range.start = (__u64)shstk_ptr;
+ uffdio_register.range.len = 4096;
+ uffdio_register.mode = UFFDIO_REGISTER_MODE_MISSING;
+ if (ioctl(uffd, UFFDIO_REGISTER, &uffdio_register))
+ goto err;
+
+ if (pthread_create(&thread, NULL, &uffd_thread, &uffd))
+ goto err;
+
+ reset_shstk(shstk_ptr);
+ test_shstk_access(shstk_ptr);
+
+ if (pthread_join(thread, &res))
+ goto err;
+
+ if (test_shstk_access(shstk_ptr))
+ goto err;
+
+ free_shstk(shstk_ptr);
+
+ signal(SIGSEGV, SIG_DFL);
+
+ if (!res)
+ printf("[OK]\tUserfaultfd test\n");
+ return !!res;
+err:
+ free_shstk(shstk_ptr);
+ close(uffd);
+ signal(SIGSEGV, SIG_DFL);
+ return 1;
+}
+
+/* Simple linked list for keeping track of mappings in test_guard_gap() */
+struct node {
+ struct node *next;
+ void *mapping;
+};
+
+/*
+ * This tests whether mmap will place other mappings in a shadow stack's guard
+ * gap. The steps are:
+ * 1. Finds an empty place by mapping and unmapping something.
+ * 2. Map a shadow stack in the middle of the known empty area.
+ * 3. Map a bunch of PAGE_SIZE mappings. These will use the search down
+ * direction, filling any gaps until it encounters the shadow stack's
+ * guard gap.
+ * 4. When a mapping lands below the shadow stack from step 2, then all
+ * of the above gaps are filled. The search down algorithm will have
+ * looked at the shadow stack gaps.
+ * 5. See if it landed in the gap.
+ */
+int test_guard_gap_other_gaps(void)
+{
+ void *free_area, *shstk, *test_map = (void *)0xFFFFFFFFFFFFFFFF;
+ struct node *head = NULL, *cur;
+
+ free_area = mmap(0, SS_SIZE * 3, PROT_READ | PROT_WRITE,
+ MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
+ munmap(free_area, SS_SIZE * 3);
+
+ shstk = create_shstk(free_area + SS_SIZE);
+ if (shstk == MAP_FAILED)
+ return 1;
+
+ while (test_map > shstk) {
+ test_map = mmap(0, PAGE_SIZE, PROT_READ | PROT_WRITE,
+ MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
+ if (test_map == MAP_FAILED)
+ return 1;
+ cur = malloc(sizeof(*cur));
+ cur->mapping = test_map;
+
+ cur->next = head;
+ head = cur;
+ }
+
+ while (head) {
+ cur = head;
+ head = cur->next;
+ munmap(cur->mapping, PAGE_SIZE);
+ free(cur);
+ }
+
+ free_shstk(shstk);
+
+ if (shstk - test_map - PAGE_SIZE != PAGE_SIZE)
+ return 1;
+
+ printf("[OK]\tGuard gap test, other mapping's gaps\n");
+
+ return 0;
+}
+
+/* Tests respecting the guard gap of the mapping getting placed */
+int test_guard_gap_new_mappings_gaps(void)
+{
+ void *free_area, *shstk_start, *test_map = (void *)0xFFFFFFFFFFFFFFFF;
+ struct node *head = NULL, *cur;
+ int ret = 0;
+
+ free_area = mmap(0, PAGE_SIZE * 4, PROT_READ | PROT_WRITE,
+ MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
+ munmap(free_area, PAGE_SIZE * 4);
+
+ /* Test letting map_shadow_stack find a free space */
+ shstk_start = mmap(free_area, PAGE_SIZE, PROT_READ | PROT_WRITE,
+ MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
+ if (shstk_start == MAP_FAILED || shstk_start != free_area)
+ return 1;
+
+ while (test_map > shstk_start) {
+ test_map = (void *)syscall(__NR_map_shadow_stack, 0, PAGE_SIZE, 0);
+ if (test_map == MAP_FAILED) {
+ printf("[INFO]\tmap_shadow_stack MAP_FAILED\n");
+ ret = 1;
+ break;
+ }
+
+ cur = malloc(sizeof(*cur));
+ cur->mapping = test_map;
+
+ cur->next = head;
+ head = cur;
+
+ if (test_map == free_area + PAGE_SIZE) {
+ printf("[INFO]\tNew mapping has other mapping in guard gap!\n");
+ ret = 1;
+ break;
+ }
+ }
+
+ while (head) {
+ cur = head;
+ head = cur->next;
+ munmap(cur->mapping, PAGE_SIZE);
+ free(cur);
+ }
+
+ munmap(shstk_start, PAGE_SIZE);
+
+ if (!ret)
+ printf("[OK]\tGuard gap test, placement mapping's gaps\n");
+
+ return ret;
+}
+
+/*
+ * Too complicated to pull it out of the 32 bit header, but also get the
+ * 64 bit one needed above. Just define a copy here.
+ */
+#define __NR_compat_sigaction 67
+
+/*
+ * Call 32 bit signal handler to get 32 bit signals ABI. Make sure
+ * to push the registers that will get clobbered.
+ */
+int sigaction32(int signum, const struct sigaction *restrict act,
+ struct sigaction *restrict oldact)
+{
+ register long syscall_reg asm("eax") = __NR_compat_sigaction;
+ register long signum_reg asm("ebx") = signum;
+ register long act_reg asm("ecx") = (long)act;
+ register long oldact_reg asm("edx") = (long)oldact;
+ int ret = 0;
+
+ asm volatile ("int $0x80;"
+ : "=a"(ret), "=m"(oldact)
+ : "r"(syscall_reg), "r"(signum_reg), "r"(act_reg),
+ "r"(oldact_reg)
+ : "r8", "r9", "r10", "r11"
+ );
+
+ return ret;
+}
+
+sigjmp_buf jmp_buffer;
+enum shadow_stack_test _which_test;
+void segv_gp_handler(int signum, siginfo_t *si, void *uc)
+{
+ segv_triggered = true;
+
+ /*
+ * To work with old glibc, this can't rely on siglongjmp working with
+ * shadow stack enabled, so disable shadow stack before siglongjmp().
+ */
+ SHADOW_STACK_DISABLE(_which_test);
+ siglongjmp(jmp_buffer, -1);
+}
+
+/*
+ * Transition to 32 bit mode and check that a #GP triggers a segfault.
+ */
+int test_32bit(void)
+{
+ struct sigaction sa = {};
+ struct sigaction *sa32;
+
+ /* Create sigaction in 32 bit address range */
+ sa32 = mmap(0, 4096, PROT_READ | PROT_WRITE,
+ MAP_32BIT | MAP_PRIVATE | MAP_ANONYMOUS, 0, 0);
+ sa32->sa_flags = SA_SIGINFO;
+
+ sa.sa_sigaction = segv_gp_handler;
+ sa.sa_flags = SA_SIGINFO;
+ if (sigaction(SIGSEGV, &sa, NULL))
+ return 1;
+
+
+ segv_triggered = false;
+
+ /* Make sure segv_triggered is set before triggering the #GP */
+ asm volatile("" : : : "memory");
+
+ /*
+ * Set handler to somewhere in 32 bit address space
+ */
+ sa32->sa_handler = (void *)sa32;
+ if (sigaction32(SIGUSR1, sa32, NULL))
+ return 1;
+
+ if (!sigsetjmp(jmp_buffer, 1))
+ raise(SIGUSR1);
+
+ if (segv_triggered)
+ printf("[OK]\t32 bit test\n");
+
+ 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(enum shadow_stack_test which_test)
+{
+ 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;
+
+ SHADOW_STACK_ENABLE(which_test);
+
+ /*
+ * This either segfaults and goes through sigsetjmp above
+ * or succeeds and we're good.
+ */
+ uretprobe_trigger();
+
+ printf("[OK]\tUretprobe test\n");
+ err = 0;
+
+out:
+ SHADOW_STACK_DISABLE(which_test);
+ signal(SIGSEGV, SIG_DFL);
+ if (fd)
+ close(fd);
+ return err;
+}
+
+/* Keep the CALL first so the function address is exactly the probed CALL. */
+extern void uprobe_call_trigger(void);
+asm (".pushsection .text\n"
+ ".global uprobe_call_target\n"
+ ".type uprobe_call_target, @function\n"
+ "uprobe_call_target:\n"
+ " ret\n"
+ ".size uprobe_call_target, .-uprobe_call_target\n"
+
+ ".global uprobe_call_trigger\n"
+ ".type uprobe_call_trigger, @function\n"
+ "uprobe_call_trigger:\n"
+ " call uprobe_call_target\n"
+ " ret\n"
+ ".size uprobe_call_trigger, .-uprobe_call_trigger\n"
+ ".popsection\n"
+);
+
+/* If CALL emulation misses the shadow stack update, this exits via SIGSEGV. */
+static int test_uprobe_call(enum shadow_stack_test which_test)
+{
+ const size_t attr_sz = sizeof(struct perf_event_attr);
+ const char *file = "/proc/self/exe";
+ int fd = -1, 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]\tUprobe on CALL test, uprobes are not available\n");
+ return 0;
+ }
+
+ offset = get_uprobe_offset(uprobe_call_trigger);
+ if (offset < 0)
+ return 1;
+
+ sa.sa_sigaction = segv_gp_handler;
+ sa.sa_flags = SA_SIGINFO;
+ if (sigaction(SIGSEGV, &sa, NULL))
+ return 1;
+
+ /* Setup entry uprobe through perf event interface. */
+ memset(&attr, 0, attr_sz);
+ attr.size = attr_sz;
+ attr.type = type;
+ attr.config = 0;
+ 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;
+
+ if (SHADOW_STACK_ENABLE(which_test))
+ goto out;
+
+ /*
+ * This either segfaults and goes through sigsetjmp above
+ * or succeeds and we're good.
+ */
+ uprobe_call_trigger();
+
+ printf("[OK]\tUprobe on CALL test\n");
+ err = 0;
+
+out:
+ SHADOW_STACK_DISABLE(which_test);
+ signal(SIGSEGV, SIG_DFL);
+ if (fd >= 0)
+ close(fd);
+ return err;
+}
+
+void segv_handler_ptrace(int signum, siginfo_t *si, void *uc)
+{
+ /* The SSP adjustment caused a segfault. */
+ exit(0);
+}
+
+int test_ptrace(void)
+{
+ unsigned long saved_ssp, ssp = 0;
+ struct sigaction sa = {};
+ struct iovec iov;
+ int status;
+ int pid;
+
+ iov.iov_base = &ssp;
+ iov.iov_len = sizeof(ssp);
+
+ pid = fork();
+ if (!pid) {
+ ssp = get_ssp();
+
+ sa.sa_sigaction = segv_handler_ptrace;
+ sa.sa_flags = SA_SIGINFO;
+ if (sigaction(SIGSEGV, &sa, NULL))
+ return 1;
+
+ ptrace(PTRACE_TRACEME, NULL, NULL, NULL);
+ /*
+ * The parent will tweak the SSP and return from this function
+ * will #CP.
+ */
+ raise(SIGTRAP);
+
+ exit(1);
+ }
+
+ while (waitpid(pid, &status, 0) != -1 && WSTOPSIG(status) != SIGTRAP);
+
+ if (ptrace(PTRACE_GETREGSET, pid, NT_X86_SHSTK, &iov)) {
+ printf("[INFO]\tFailed to PTRACE_GETREGS\n");
+ goto out_kill;
+ }
+
+ if (!ssp) {
+ printf("[INFO]\tPtrace child SSP was 0\n");
+ goto out_kill;
+ }
+
+ saved_ssp = ssp;
+
+ iov.iov_len = 0;
+ if (!ptrace(PTRACE_SETREGSET, pid, NT_X86_SHSTK, &iov)) {
+ printf("[INFO]\tToo small size accepted via PTRACE_SETREGS\n");
+ goto out_kill;
+ }
+
+ iov.iov_len = sizeof(ssp) + 1;
+ if (!ptrace(PTRACE_SETREGSET, pid, NT_X86_SHSTK, &iov)) {
+ printf("[INFO]\tToo large size accepted via PTRACE_SETREGS\n");
+ goto out_kill;
+ }
+
+ ssp += 1;
+ if (!ptrace(PTRACE_SETREGSET, pid, NT_X86_SHSTK, &iov)) {
+ printf("[INFO]\tUnaligned SSP written via PTRACE_SETREGS\n");
+ goto out_kill;
+ }
+
+ ssp = 0xFFFFFFFFFFFF0000;
+ if (!ptrace(PTRACE_SETREGSET, pid, NT_X86_SHSTK, &iov)) {
+ printf("[INFO]\tKernel range SSP written via PTRACE_SETREGS\n");
+ goto out_kill;
+ }
+
+ /*
+ * Tweak the SSP so the child with #CP when it resumes and returns
+ * from raise()
+ */
+ ssp = saved_ssp + 8;
+ iov.iov_len = sizeof(ssp);
+ if (ptrace(PTRACE_SETREGSET, pid, NT_X86_SHSTK, &iov)) {
+ printf("[INFO]\tFailed to PTRACE_SETREGS\n");
+ goto out_kill;
+ }
+
+ if (ptrace(PTRACE_DETACH, pid, NULL, NULL)) {
+ printf("[INFO]\tFailed to PTRACE_DETACH\n");
+ goto out_kill;
+ }
+
+ waitpid(pid, &status, 0);
+ if (WEXITSTATUS(status))
+ return 1;
+
+ printf("[OK]\tPtrace test\n");
+ return 0;
+
+out_kill:
+ kill(pid, SIGKILL);
+ return 1;
+}
+
+#if (__GNUC__ < 8) || (__GNUC__ == 8 && __GNUC_MINOR__ < 5)
+int shadow_stack_run_tests(enum shadow_stack_test unused)
+{
+ printf("[SKIP]\tCompiler does not support CET.\n");
+ return 0;
+}
+#else
+int shadow_stack_run_tests(enum shadow_stack_test which_test)
+{
+ int ret = 0;
+
+ if (SHADOW_STACK_ENABLE(which_test)) {
+ printf("[SKIP]\tCould not enable Shadow stack\n");
+ return 1;
+ }
+
+ if (SHADOW_STACK_DISABLE(which_test)) {
+ ret = 1;
+ printf("[FAIL]\tDisabling shadow stack failed\n");
+ }
+
+ if (SHADOW_STACK_ENABLE(which_test)) {
+ printf("[SKIP]\tCould not re-enable Shadow stack\n");
+ return 1;
+ }
+
+ if (SHADOW_STACK_ENABLE_WRITE(which_test)) {
+ printf("[SKIP]\tCould not enable WRSS\n");
+ ret = 1;
+ goto out;
+ }
+
+ /* Should have succeeded if here, but this is a test, so double check. */
+ if (!get_ssp()) {
+ printf("[FAIL]\tShadow stack disabled\n");
+ return 1;
+ }
+
+ if (test_shstk_pivot()) {
+ ret = 1;
+ printf("[FAIL]\tShadow stack pivot\n");
+ goto out;
+ }
+
+ if (test_shstk_faults()) {
+ ret = 1;
+ printf("[FAIL]\tShadow stack fault test\n");
+ goto out;
+ }
+
+ if (test_shstk_violation()) {
+ ret = 1;
+ printf("[FAIL]\tShadow stack violation test\n");
+ goto out;
+ }
+
+ if (test_gup()) {
+ ret = 1;
+ printf("[FAIL]\tShadow shadow stack gup\n");
+ goto out;
+ }
+
+ if (test_mprotect()) {
+ ret = 1;
+ printf("[FAIL]\tShadow shadow mprotect test\n");
+ goto out;
+ }
+
+ if (test_userfaultfd()) {
+ ret = 1;
+ printf("[FAIL]\tUserfaultfd test\n");
+ goto out;
+ }
+
+ if (test_guard_gap_other_gaps()) {
+ ret = 1;
+ printf("[FAIL]\tGuard gap test, other mappings' gaps\n");
+ goto out;
+ }
+
+ if (test_guard_gap_new_mappings_gaps()) {
+ ret = 1;
+ printf("[FAIL]\tGuard gap test, placement mapping's gaps\n");
+ goto out;
+ }
+
+ if (test_ptrace()) {
+ ret = 1;
+ printf("[FAIL]\tptrace test\n");
+ }
+
+ if (test_32bit()) {
+ ret = 1;
+ printf("[FAIL]\t32 bit test\n");
+ goto out;
+ }
+
+ if (test_uretprobe(which_test)) {
+ ret = 1;
+ printf("[FAIL]\turetprobe test\n");
+ goto out;
+ }
+
+ if (test_uprobe_call(which_test)) {
+ ret = 1;
+ printf("[FAIL]\tuprobe on CALL test\n");
+ goto out;
+ }
+
+ return ret;
+
+out:
+ /*
+ * Disable shadow stack before the function returns, or there will be a
+ * shadow stack violation.
+ */
+ if (SHADOW_STACK_DISABLE(which_test)) {
+ ret = 1;
+ printf("[FAIL]\tDisabling shadow stack failed\n");
+ }
+
+ return ret;
+}
+#endif
diff --git a/tools/testing/selftests/x86/shadow_stack.h b/tools/testing/selftests/x86/shadow_stack.h
new file mode 100644
index 0000000000000..d36a44df73874
--- /dev/null
+++ b/tools/testing/selftests/x86/shadow_stack.h
@@ -0,0 +1,14 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#ifndef TOOLS_TESTING_SELFTESTS_X86_SHADOW_STACK_H_
+#define TOOLS_TESTING_SELFTESTS_X86_SHADOW_STACK_H_
+
+enum shadow_stack_test {
+ SHADOW_STACK_TEST_ARCH_PRCTL,
+ SHADOW_STACK_TEST_PRCTL,
+ SHADOW_STACK_TEST_COUNT,
+};
+
+int shadow_stack_run_tests(enum shadow_stack_test which_test);
+
+#endif /* TOOLS_TESTING_SELFTESTS_X86_SHADOW_STACK_H_ */
diff --git a/tools/testing/selftests/x86/test_shadow_stack.c b/tools/testing/selftests/x86/test_shadow_stack.c
index 3d6ca33edba42..d89c04623001c 100644
--- a/tools/testing/selftests/x86/test_shadow_stack.c
+++ b/tools/testing/selftests/x86/test_shadow_stack.c
@@ -1,1174 +1,21 @@
// SPDX-License-Identifier: GPL-2.0
+
/*
* This program test's basic kernel shadow stack support. It enables shadow
* stack manual via the arch_prctl(), instead of relying on glibc. It's
* Makefile doesn't compile with shadow stack support, so it doesn't rely on
* any particular glibc. As a result it can't do any operations that require
* special glibc shadow stack support (longjmp(), swapcontext(), etc). Just
- * stick to the basics and hope the compiler doesn't do anything strange.
+ * stick to the basics and hope the compiler doesn't do anything strange. It
+ * uses the x86 specific interface for ARCH_PCTL, whereas
+ * test_shadow_stack_prctl.c uses the generic PRCTL interface mixed with x86
+ * specific code.
*/
-
#define _GNU_SOURCE
-#include <sys/syscall.h>
-#include <asm/mman.h>
-#include <sys/mman.h>
-#include <sys/stat.h>
-#include <sys/wait.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <fcntl.h>
-#include <unistd.h>
-#include <string.h>
-#include <errno.h>
-#include <stdbool.h>
-#include <x86intrin.h>
-#include <asm/prctl.h>
-#include <sys/prctl.h>
-#include <stdint.h>
-#include <signal.h>
-#include <pthread.h>
-#include <sys/ioctl.h>
-#include <linux/userfaultfd.h>
-#include <setjmp.h>
-#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
- * without building the headers.
- */
-#ifndef __NR_map_shadow_stack
-#define __NR_map_shadow_stack 453
-
-#define SHADOW_STACK_SET_TOKEN (1ULL << 0)
-
-#define ARCH_SHSTK_ENABLE 0x5001
-#define ARCH_SHSTK_DISABLE 0x5002
-#define ARCH_SHSTK_LOCK 0x5003
-#define ARCH_SHSTK_UNLOCK 0x5004
-#define ARCH_SHSTK_STATUS 0x5005
-
-#define ARCH_SHSTK_SHSTK (1ULL << 0)
-#define ARCH_SHSTK_WRSS (1ULL << 1)
-
-#define NT_X86_SHSTK 0x204
-#endif
-
-#define SS_SIZE 0x200000
-#define PAGE_SIZE 0x1000
-
-#if (__GNUC__ < 8) || (__GNUC__ == 8 && __GNUC_MINOR__ < 5)
-int main(int argc, char *argv[])
-{
- printf("[SKIP]\tCompiler does not support CET.\n");
- return 0;
-}
-#else
-void write_shstk(unsigned long *addr, unsigned long val)
-{
- asm volatile("wrssq %[val], (%[addr])\n"
- : "=m" (addr)
- : [addr] "r" (addr), [val] "r" (val));
-}
-
-static inline unsigned long __attribute__((always_inline)) get_ssp(void)
-{
- unsigned long ret = 0;
-
- asm volatile("xor %0, %0; rdsspq %0" : "=r" (ret));
- return ret;
-}
-
-/*
- * For use in inline enablement of shadow stack.
- *
- * The program can't return from the point where shadow stack gets enabled
- * because there will be no address on the shadow stack. So it can't use
- * syscall() for enablement, since it is a function.
- *
- * Based on code from nolibc.h. Keep a copy here because this can't pull in all
- * of nolibc.h.
- */
-#define ARCH_PRCTL(arg1, arg2) \
-({ \
- long _ret; \
- register long _num asm("eax") = __NR_arch_prctl; \
- register long _arg1 asm("rdi") = (long)(arg1); \
- register long _arg2 asm("rsi") = (long)(arg2); \
- \
- asm volatile ( \
- "syscall\n" \
- : "=a"(_ret) \
- : "r"(_arg1), "r"(_arg2), \
- "0"(_num) \
- : "rcx", "r11", "memory", "cc" \
- ); \
- _ret; \
-})
-
-void *create_shstk(void *addr)
-{
- return (void *)syscall(__NR_map_shadow_stack, addr, SS_SIZE, SHADOW_STACK_SET_TOKEN);
-}
-
-void *create_normal_mem(void *addr)
-{
- return mmap(addr, SS_SIZE, PROT_READ | PROT_WRITE,
- MAP_PRIVATE | MAP_ANONYMOUS, 0, 0);
-}
-
-void free_shstk(void *shstk)
-{
- munmap(shstk, SS_SIZE);
-}
-
-int reset_shstk(void *shstk)
-{
- return madvise(shstk, SS_SIZE, MADV_DONTNEED);
-}
-
-void try_shstk(unsigned long new_ssp)
-{
- unsigned long ssp;
-
- printf("[INFO]\tnew_ssp = %lx, *new_ssp = %lx\n",
- new_ssp, *((unsigned long *)new_ssp));
-
- ssp = get_ssp();
- printf("[INFO]\tchanging ssp from %lx to %lx\n", ssp, new_ssp);
-
- asm volatile("rstorssp (%0)\n":: "r" (new_ssp));
- asm volatile("saveprevssp");
- printf("[INFO]\tssp is now %lx\n", get_ssp());
-
- /* Switch back to original shadow stack */
- ssp -= 8;
- asm volatile("rstorssp (%0)\n":: "r" (ssp));
- asm volatile("saveprevssp");
-}
-
-int test_shstk_pivot(void)
-{
- void *shstk = create_shstk(0);
-
- if (shstk == MAP_FAILED) {
- printf("[FAIL]\tError creating shadow stack: %d\n", errno);
- return 1;
- }
- try_shstk((unsigned long)shstk + SS_SIZE - 8);
- free_shstk(shstk);
-
- printf("[OK]\tShadow stack pivot\n");
- return 0;
-}
-
-int test_shstk_faults(void)
-{
- unsigned long *shstk = create_shstk(0);
-
- /* Read shadow stack, test if it's zero to not get read optimized out */
- if (*shstk != 0)
- goto err;
-
- /* Wrss memory that was already read. */
- write_shstk(shstk, 1);
- if (*shstk != 1)
- goto err;
-
- /* Page out memory, so we can wrss it again. */
- if (reset_shstk((void *)shstk))
- goto err;
-
- write_shstk(shstk, 1);
- if (*shstk != 1)
- goto err;
-
- printf("[OK]\tShadow stack faults\n");
- return 0;
-
-err:
- return 1;
-}
-
-unsigned long saved_ssp;
-unsigned long saved_ssp_val;
-volatile bool segv_triggered;
-
-void __attribute__((noinline)) violate_ss(void)
-{
- saved_ssp = get_ssp();
- saved_ssp_val = *(unsigned long *)saved_ssp;
-
- /* Corrupt shadow stack */
- printf("[INFO]\tCorrupting shadow stack\n");
- write_shstk((void *)saved_ssp, 0);
-}
-
-void segv_handler(int signum, siginfo_t *si, void *uc)
-{
- printf("[INFO]\tGenerated shadow stack violation successfully\n");
-
- segv_triggered = true;
-
- /* Fix shadow stack */
- write_shstk((void *)saved_ssp, saved_ssp_val);
-}
-
-int test_shstk_violation(void)
-{
- struct sigaction sa = {};
-
- sa.sa_sigaction = segv_handler;
- sa.sa_flags = SA_SIGINFO;
- if (sigaction(SIGSEGV, &sa, NULL))
- return 1;
-
- segv_triggered = false;
-
- /* Make sure segv_triggered is set before violate_ss() */
- asm volatile("" : : : "memory");
-
- violate_ss();
-
- signal(SIGSEGV, SIG_DFL);
-
- printf("[OK]\tShadow stack violation test\n");
-
- return !segv_triggered;
-}
-
-/* Gup test state */
-#define MAGIC_VAL 0x12345678
-bool is_shstk_access;
-void *shstk_ptr;
-int fd;
-
-void reset_test_shstk(void *addr)
-{
- if (shstk_ptr)
- free_shstk(shstk_ptr);
- shstk_ptr = create_shstk(addr);
-}
-
-void test_access_fix_handler(int signum, siginfo_t *si, void *uc)
-{
- printf("[INFO]\tViolation from %s\n", is_shstk_access ? "shstk access" : "normal write");
-
- segv_triggered = true;
-
- /* Fix shadow stack */
- if (is_shstk_access) {
- reset_test_shstk(shstk_ptr);
- return;
- }
-
- free_shstk(shstk_ptr);
- create_normal_mem(shstk_ptr);
-}
-
-bool test_shstk_access(void *ptr)
-{
- is_shstk_access = true;
- segv_triggered = false;
- write_shstk(ptr, MAGIC_VAL);
-
- asm volatile("" : : : "memory");
-
- return segv_triggered;
-}
-
-bool test_write_access(void *ptr)
-{
- is_shstk_access = false;
- segv_triggered = false;
- *(unsigned long *)ptr = MAGIC_VAL;
-
- asm volatile("" : : : "memory");
-
- return segv_triggered;
-}
-
-bool gup_write(void *ptr)
-{
- unsigned long val;
-
- lseek(fd, (unsigned long)ptr, SEEK_SET);
- if (write(fd, &val, sizeof(val)) < 0)
- return 1;
-
- return 0;
-}
-
-bool gup_read(void *ptr)
-{
- unsigned long val;
-
- lseek(fd, (unsigned long)ptr, SEEK_SET);
- if (read(fd, &val, sizeof(val)) < 0)
- return 1;
-
- return 0;
-}
-
-int test_gup(void)
-{
- struct sigaction sa = {};
- int status;
- pid_t pid;
-
- sa.sa_sigaction = test_access_fix_handler;
- sa.sa_flags = SA_SIGINFO;
- if (sigaction(SIGSEGV, &sa, NULL))
- return 1;
-
- segv_triggered = false;
-
- fd = open("/proc/self/mem", O_RDWR);
- if (fd == -1)
- return 1;
-
- reset_test_shstk(0);
- if (gup_read(shstk_ptr))
- return 1;
- if (test_shstk_access(shstk_ptr))
- return 1;
- printf("[INFO]\tGup read -> shstk access success\n");
-
- reset_test_shstk(0);
- if (gup_write(shstk_ptr))
- return 1;
- if (test_shstk_access(shstk_ptr))
- return 1;
- printf("[INFO]\tGup write -> shstk access success\n");
-
- reset_test_shstk(0);
- if (gup_read(shstk_ptr))
- return 1;
- if (!test_write_access(shstk_ptr))
- return 1;
- printf("[INFO]\tGup read -> write access success\n");
-
- reset_test_shstk(0);
- if (gup_write(shstk_ptr))
- return 1;
- if (!test_write_access(shstk_ptr))
- return 1;
- printf("[INFO]\tGup write -> write access success\n");
-
- close(fd);
-
- /* COW/gup test */
- reset_test_shstk(0);
- pid = fork();
- if (!pid) {
- fd = open("/proc/self/mem", O_RDWR);
- if (fd == -1)
- exit(1);
-
- if (gup_write(shstk_ptr)) {
- close(fd);
- exit(1);
- }
- close(fd);
- exit(0);
- }
- waitpid(pid, &status, 0);
- if (WEXITSTATUS(status)) {
- printf("[FAIL]\tWrite in child failed\n");
- return 1;
- }
- if (*(unsigned long *)shstk_ptr == MAGIC_VAL) {
- printf("[FAIL]\tWrite in child wrote through to shared memory\n");
- return 1;
- }
-
- printf("[INFO]\tCow gup write -> write access success\n");
-
- free_shstk(shstk_ptr);
-
- signal(SIGSEGV, SIG_DFL);
-
- printf("[OK]\tShadow gup test\n");
-
- return 0;
-}
-
-int test_mprotect(void)
-{
- struct sigaction sa = {};
-
- sa.sa_sigaction = test_access_fix_handler;
- sa.sa_flags = SA_SIGINFO;
- if (sigaction(SIGSEGV, &sa, NULL))
- return 1;
-
- segv_triggered = false;
-
- /* mprotect a shadow stack as read only */
- reset_test_shstk(0);
- if (mprotect(shstk_ptr, SS_SIZE, PROT_READ) < 0) {
- printf("[FAIL]\tmprotect(PROT_READ) failed\n");
- return 1;
- }
-
- /* try to wrss it and fail */
- if (!test_shstk_access(shstk_ptr)) {
- printf("[FAIL]\tShadow stack access to read-only memory succeeded\n");
- return 1;
- }
-
- /*
- * The shadow stack was reset above to resolve the fault, make the new one
- * read-only.
- */
- if (mprotect(shstk_ptr, SS_SIZE, PROT_READ) < 0) {
- printf("[FAIL]\tmprotect(PROT_READ) failed\n");
- return 1;
- }
-
- /* then back to writable */
- if (mprotect(shstk_ptr, SS_SIZE, PROT_WRITE | PROT_READ) < 0) {
- printf("[FAIL]\tmprotect(PROT_WRITE) failed\n");
- return 1;
- }
-
- /* then wrss to it and succeed */
- if (test_shstk_access(shstk_ptr)) {
- printf("[FAIL]\tShadow stack access to mprotect() writable memory failed\n");
- return 1;
- }
-
- free_shstk(shstk_ptr);
-
- signal(SIGSEGV, SIG_DFL);
-
- printf("[OK]\tmprotect() test\n");
-
- return 0;
-}
-
-char zero[4096];
-
-static void *uffd_thread(void *arg)
-{
- struct uffdio_copy req;
- int uffd = *(int *)arg;
- struct uffd_msg msg;
- int ret;
-
- while (1) {
- ret = read(uffd, &msg, sizeof(msg));
- if (ret > 0)
- break;
- else if (errno == EAGAIN)
- continue;
- return (void *)1;
- }
-
- req.dst = msg.arg.pagefault.address;
- req.src = (__u64)zero;
- req.len = 4096;
- req.mode = 0;
-
- if (ioctl(uffd, UFFDIO_COPY, &req))
- return (void *)1;
-
- return (void *)0;
-}
-
-int test_userfaultfd(void)
-{
- struct uffdio_register uffdio_register;
- struct uffdio_api uffdio_api;
- struct sigaction sa = {};
- pthread_t thread;
- void *res;
- int uffd;
-
- sa.sa_sigaction = test_access_fix_handler;
- sa.sa_flags = SA_SIGINFO;
- if (sigaction(SIGSEGV, &sa, NULL))
- return 1;
-
- uffd = syscall(__NR_userfaultfd, O_CLOEXEC | O_NONBLOCK);
- if (uffd < 0) {
- printf("[SKIP]\tUserfaultfd unavailable.\n");
- return 0;
- }
-
- reset_test_shstk(0);
-
- uffdio_api.api = UFFD_API;
- uffdio_api.features = 0;
- if (ioctl(uffd, UFFDIO_API, &uffdio_api))
- goto err;
-
- uffdio_register.range.start = (__u64)shstk_ptr;
- uffdio_register.range.len = 4096;
- uffdio_register.mode = UFFDIO_REGISTER_MODE_MISSING;
- if (ioctl(uffd, UFFDIO_REGISTER, &uffdio_register))
- goto err;
-
- if (pthread_create(&thread, NULL, &uffd_thread, &uffd))
- goto err;
-
- reset_shstk(shstk_ptr);
- test_shstk_access(shstk_ptr);
-
- if (pthread_join(thread, &res))
- goto err;
-
- if (test_shstk_access(shstk_ptr))
- goto err;
-
- free_shstk(shstk_ptr);
-
- signal(SIGSEGV, SIG_DFL);
-
- if (!res)
- printf("[OK]\tUserfaultfd test\n");
- return !!res;
-err:
- free_shstk(shstk_ptr);
- close(uffd);
- signal(SIGSEGV, SIG_DFL);
- return 1;
-}
-
-/* Simple linked list for keeping track of mappings in test_guard_gap() */
-struct node {
- struct node *next;
- void *mapping;
-};
-
-/*
- * This tests whether mmap will place other mappings in a shadow stack's guard
- * gap. The steps are:
- * 1. Finds an empty place by mapping and unmapping something.
- * 2. Map a shadow stack in the middle of the known empty area.
- * 3. Map a bunch of PAGE_SIZE mappings. These will use the search down
- * direction, filling any gaps until it encounters the shadow stack's
- * guard gap.
- * 4. When a mapping lands below the shadow stack from step 2, then all
- * of the above gaps are filled. The search down algorithm will have
- * looked at the shadow stack gaps.
- * 5. See if it landed in the gap.
- */
-int test_guard_gap_other_gaps(void)
-{
- void *free_area, *shstk, *test_map = (void *)0xFFFFFFFFFFFFFFFF;
- struct node *head = NULL, *cur;
-
- free_area = mmap(0, SS_SIZE * 3, PROT_READ | PROT_WRITE,
- MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
- munmap(free_area, SS_SIZE * 3);
-
- shstk = create_shstk(free_area + SS_SIZE);
- if (shstk == MAP_FAILED)
- return 1;
-
- while (test_map > shstk) {
- test_map = mmap(0, PAGE_SIZE, PROT_READ | PROT_WRITE,
- MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
- if (test_map == MAP_FAILED)
- return 1;
- cur = malloc(sizeof(*cur));
- cur->mapping = test_map;
-
- cur->next = head;
- head = cur;
- }
-
- while (head) {
- cur = head;
- head = cur->next;
- munmap(cur->mapping, PAGE_SIZE);
- free(cur);
- }
-
- free_shstk(shstk);
-
- if (shstk - test_map - PAGE_SIZE != PAGE_SIZE)
- return 1;
-
- printf("[OK]\tGuard gap test, other mapping's gaps\n");
-
- return 0;
-}
-
-/* Tests respecting the guard gap of the mapping getting placed */
-int test_guard_gap_new_mappings_gaps(void)
-{
- void *free_area, *shstk_start, *test_map = (void *)0xFFFFFFFFFFFFFFFF;
- struct node *head = NULL, *cur;
- int ret = 0;
-
- free_area = mmap(0, PAGE_SIZE * 4, PROT_READ | PROT_WRITE,
- MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
- munmap(free_area, PAGE_SIZE * 4);
-
- /* Test letting map_shadow_stack find a free space */
- shstk_start = mmap(free_area, PAGE_SIZE, PROT_READ | PROT_WRITE,
- MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
- if (shstk_start == MAP_FAILED || shstk_start != free_area)
- return 1;
-
- while (test_map > shstk_start) {
- test_map = (void *)syscall(__NR_map_shadow_stack, 0, PAGE_SIZE, 0);
- if (test_map == MAP_FAILED) {
- printf("[INFO]\tmap_shadow_stack MAP_FAILED\n");
- ret = 1;
- break;
- }
-
- cur = malloc(sizeof(*cur));
- cur->mapping = test_map;
-
- cur->next = head;
- head = cur;
-
- if (test_map == free_area + PAGE_SIZE) {
- printf("[INFO]\tNew mapping has other mapping in guard gap!\n");
- ret = 1;
- break;
- }
- }
-
- while (head) {
- cur = head;
- head = cur->next;
- munmap(cur->mapping, PAGE_SIZE);
- free(cur);
- }
-
- munmap(shstk_start, PAGE_SIZE);
-
- if (!ret)
- printf("[OK]\tGuard gap test, placement mapping's gaps\n");
-
- return ret;
-}
-
-/*
- * Too complicated to pull it out of the 32 bit header, but also get the
- * 64 bit one needed above. Just define a copy here.
- */
-#define __NR_compat_sigaction 67
-
-/*
- * Call 32 bit signal handler to get 32 bit signals ABI. Make sure
- * to push the registers that will get clobbered.
- */
-int sigaction32(int signum, const struct sigaction *restrict act,
- struct sigaction *restrict oldact)
-{
- register long syscall_reg asm("eax") = __NR_compat_sigaction;
- register long signum_reg asm("ebx") = signum;
- register long act_reg asm("ecx") = (long)act;
- register long oldact_reg asm("edx") = (long)oldact;
- int ret = 0;
-
- asm volatile ("int $0x80;"
- : "=a"(ret), "=m"(oldact)
- : "r"(syscall_reg), "r"(signum_reg), "r"(act_reg),
- "r"(oldact_reg)
- : "r8", "r9", "r10", "r11"
- );
-
- return ret;
-}
-
-sigjmp_buf jmp_buffer;
-
-void segv_gp_handler(int signum, siginfo_t *si, void *uc)
-{
- segv_triggered = true;
-
- /*
- * To work with old glibc, this can't rely on siglongjmp working with
- * shadow stack enabled, so disable shadow stack before siglongjmp().
- */
- ARCH_PRCTL(ARCH_SHSTK_DISABLE, ARCH_SHSTK_SHSTK);
- siglongjmp(jmp_buffer, -1);
-}
-
-/*
- * Transition to 32 bit mode and check that a #GP triggers a segfault.
- */
-int test_32bit(void)
-{
- struct sigaction sa = {};
- struct sigaction *sa32;
-
- /* Create sigaction in 32 bit address range */
- sa32 = mmap(0, 4096, PROT_READ | PROT_WRITE,
- MAP_32BIT | MAP_PRIVATE | MAP_ANONYMOUS, 0, 0);
- sa32->sa_flags = SA_SIGINFO;
-
- sa.sa_sigaction = segv_gp_handler;
- sa.sa_flags = SA_SIGINFO;
- if (sigaction(SIGSEGV, &sa, NULL))
- return 1;
-
-
- segv_triggered = false;
-
- /* Make sure segv_triggered is set before triggering the #GP */
- asm volatile("" : : : "memory");
-
- /*
- * Set handler to somewhere in 32 bit address space
- */
- sa32->sa_handler = (void *)sa32;
- if (sigaction32(SIGUSR1, sa32, NULL))
- return 1;
-
- if (!sigsetjmp(jmp_buffer, 1))
- raise(SIGUSR1);
-
- if (segv_triggered)
- printf("[OK]\t32 bit test\n");
-
- 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;
-}
-
-/* Keep the CALL first so the function address is exactly the probed CALL. */
-extern void uprobe_call_trigger(void);
-asm (".pushsection .text\n"
- ".global uprobe_call_target\n"
- ".type uprobe_call_target, @function\n"
- "uprobe_call_target:\n"
- " ret\n"
- ".size uprobe_call_target, .-uprobe_call_target\n"
-
- ".global uprobe_call_trigger\n"
- ".type uprobe_call_trigger, @function\n"
- "uprobe_call_trigger:\n"
- " call uprobe_call_target\n"
- " ret\n"
- ".size uprobe_call_trigger, .-uprobe_call_trigger\n"
- ".popsection\n"
-);
-
-/* If CALL emulation misses the shadow stack update, this exits via SIGSEGV. */
-static int test_uprobe_call(void)
-{
- const size_t attr_sz = sizeof(struct perf_event_attr);
- const char *file = "/proc/self/exe";
- int fd = -1, 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]\tUprobe on CALL test, uprobes are not available\n");
- return 0;
- }
-
- offset = get_uprobe_offset(uprobe_call_trigger);
- if (offset < 0)
- return 1;
-
- sa.sa_sigaction = segv_gp_handler;
- sa.sa_flags = SA_SIGINFO;
- if (sigaction(SIGSEGV, &sa, NULL))
- return 1;
-
- /* Setup entry uprobe through perf event interface. */
- memset(&attr, 0, attr_sz);
- attr.size = attr_sz;
- attr.type = type;
- attr.config = 0;
- 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;
-
- if (ARCH_PRCTL(ARCH_SHSTK_ENABLE, ARCH_SHSTK_SHSTK))
- goto out;
-
- /*
- * This either segfaults and goes through sigsetjmp above
- * or succeeds and we're good.
- */
- uprobe_call_trigger();
-
- printf("[OK]\tUprobe on CALL test\n");
- err = 0;
-
-out:
- ARCH_PRCTL(ARCH_SHSTK_DISABLE, ARCH_SHSTK_SHSTK);
- signal(SIGSEGV, SIG_DFL);
- if (fd >= 0)
- close(fd);
- return err;
-}
-
-void segv_handler_ptrace(int signum, siginfo_t *si, void *uc)
-{
- /* The SSP adjustment caused a segfault. */
- exit(0);
-}
-
-int test_ptrace(void)
-{
- unsigned long saved_ssp, ssp = 0;
- struct sigaction sa= {};
- struct iovec iov;
- int status;
- int pid;
-
- iov.iov_base = &ssp;
- iov.iov_len = sizeof(ssp);
-
- pid = fork();
- if (!pid) {
- ssp = get_ssp();
-
- sa.sa_sigaction = segv_handler_ptrace;
- sa.sa_flags = SA_SIGINFO;
- if (sigaction(SIGSEGV, &sa, NULL))
- return 1;
-
- ptrace(PTRACE_TRACEME, NULL, NULL, NULL);
- /*
- * The parent will tweak the SSP and return from this function
- * will #CP.
- */
- raise(SIGTRAP);
-
- exit(1);
- }
-
- while (waitpid(pid, &status, 0) != -1 && WSTOPSIG(status) != SIGTRAP);
-
- if (ptrace(PTRACE_GETREGSET, pid, NT_X86_SHSTK, &iov)) {
- printf("[INFO]\tFailed to PTRACE_GETREGS\n");
- goto out_kill;
- }
-
- if (!ssp) {
- printf("[INFO]\tPtrace child SSP was 0\n");
- goto out_kill;
- }
-
- saved_ssp = ssp;
-
- iov.iov_len = 0;
- if (!ptrace(PTRACE_SETREGSET, pid, NT_X86_SHSTK, &iov)) {
- printf("[INFO]\tToo small size accepted via PTRACE_SETREGS\n");
- goto out_kill;
- }
-
- iov.iov_len = sizeof(ssp) + 1;
- if (!ptrace(PTRACE_SETREGSET, pid, NT_X86_SHSTK, &iov)) {
- printf("[INFO]\tToo large size accepted via PTRACE_SETREGS\n");
- goto out_kill;
- }
-
- ssp += 1;
- if (!ptrace(PTRACE_SETREGSET, pid, NT_X86_SHSTK, &iov)) {
- printf("[INFO]\tUnaligned SSP written via PTRACE_SETREGS\n");
- goto out_kill;
- }
-
- ssp = 0xFFFFFFFFFFFF0000;
- if (!ptrace(PTRACE_SETREGSET, pid, NT_X86_SHSTK, &iov)) {
- printf("[INFO]\tKernel range SSP written via PTRACE_SETREGS\n");
- goto out_kill;
- }
-
- /*
- * Tweak the SSP so the child with #CP when it resumes and returns
- * from raise()
- */
- ssp = saved_ssp + 8;
- iov.iov_len = sizeof(ssp);
- if (ptrace(PTRACE_SETREGSET, pid, NT_X86_SHSTK, &iov)) {
- printf("[INFO]\tFailed to PTRACE_SETREGS\n");
- goto out_kill;
- }
-
- if (ptrace(PTRACE_DETACH, pid, NULL, NULL)) {
- printf("[INFO]\tFailed to PTRACE_DETACH\n");
- goto out_kill;
- }
-
- waitpid(pid, &status, 0);
- if (WEXITSTATUS(status))
- return 1;
-
- printf("[OK]\tPtrace test\n");
- return 0;
-
-out_kill:
- kill(pid, SIGKILL);
- return 1;
-}
+#include "shadow_stack.h"
int main(int argc, char *argv[])
{
- int ret = 0;
-
- if (ARCH_PRCTL(ARCH_SHSTK_ENABLE, ARCH_SHSTK_SHSTK)) {
- printf("[SKIP]\tCould not enable Shadow stack\n");
- return 1;
- }
-
- if (ARCH_PRCTL(ARCH_SHSTK_DISABLE, ARCH_SHSTK_SHSTK)) {
- ret = 1;
- printf("[FAIL]\tDisabling shadow stack failed\n");
- }
-
- if (ARCH_PRCTL(ARCH_SHSTK_ENABLE, ARCH_SHSTK_SHSTK)) {
- printf("[SKIP]\tCould not re-enable Shadow stack\n");
- return 1;
- }
-
- if (ARCH_PRCTL(ARCH_SHSTK_ENABLE, ARCH_SHSTK_WRSS)) {
- printf("[SKIP]\tCould not enable WRSS\n");
- ret = 1;
- goto out;
- }
-
- /* Should have succeeded if here, but this is a test, so double check. */
- if (!get_ssp()) {
- printf("[FAIL]\tShadow stack disabled\n");
- return 1;
- }
-
- if (test_shstk_pivot()) {
- ret = 1;
- printf("[FAIL]\tShadow stack pivot\n");
- goto out;
- }
-
- if (test_shstk_faults()) {
- ret = 1;
- printf("[FAIL]\tShadow stack fault test\n");
- goto out;
- }
-
- if (test_shstk_violation()) {
- ret = 1;
- printf("[FAIL]\tShadow stack violation test\n");
- goto out;
- }
-
- if (test_gup()) {
- ret = 1;
- printf("[FAIL]\tShadow shadow stack gup\n");
- goto out;
- }
-
- if (test_mprotect()) {
- ret = 1;
- printf("[FAIL]\tShadow shadow mprotect test\n");
- goto out;
- }
-
- if (test_userfaultfd()) {
- ret = 1;
- printf("[FAIL]\tUserfaultfd test\n");
- goto out;
- }
-
- if (test_guard_gap_other_gaps()) {
- ret = 1;
- printf("[FAIL]\tGuard gap test, other mappings' gaps\n");
- goto out;
- }
-
- if (test_guard_gap_new_mappings_gaps()) {
- ret = 1;
- printf("[FAIL]\tGuard gap test, placement mapping's gaps\n");
- goto out;
- }
-
- if (test_ptrace()) {
- ret = 1;
- printf("[FAIL]\tptrace test\n");
- }
-
- if (test_32bit()) {
- ret = 1;
- printf("[FAIL]\t32 bit test\n");
- goto out;
- }
-
- if (test_uretprobe()) {
- ret = 1;
- printf("[FAIL]\turetprobe test\n");
- goto out;
- }
-
- if (test_uprobe_call()) {
- ret = 1;
- printf("[FAIL]\tuprobe on CALL test\n");
- goto out;
- }
-
- return ret;
-
-out:
- /*
- * Disable shadow stack before the function returns, or there will be a
- * shadow stack violation.
- */
- if (ARCH_PRCTL(ARCH_SHSTK_DISABLE, ARCH_SHSTK_SHSTK)) {
- ret = 1;
- printf("[FAIL]\tDisabling shadow stack failed\n");
- }
-
- return ret;
+ return shadow_stack_run_tests(SHADOW_STACK_TEST_ARCH_PRCTL);
}
-#endif
diff --git a/tools/testing/selftests/x86/test_shadow_stack_prctl.c b/tools/testing/selftests/x86/test_shadow_stack_prctl.c
new file mode 100644
index 0000000000000..4641a3b1ca036
--- /dev/null
+++ b/tools/testing/selftests/x86/test_shadow_stack_prctl.c
@@ -0,0 +1,12 @@
+// SPDX-License-Identifier: GPL-2.0
+
+/*
+ * Same as test_shadow_stack.c but uses the PRCTL interface
+ * instead of ARCH_PRCTL.
+ */
+#include "shadow_stack.h"
+
+int main(int argc, char *argv[])
+{
+ return shadow_stack_run_tests(SHADOW_STACK_TEST_PRCTL);
+}
|
| 15/3 | 2026/08/12 19:52 | llm | kmsan-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 27283 output: 39 thoughts: 714 Thoughts: **Analyzing Potential Uninitialized Memory Risks in Shadow Stack Patch** Okay, so I need to assess this patch series to see if it introduces any uninitialized memory risks significant enough to warrant a KMSAN run. The patch touches `arch/x86/kernel/shstk.c` and adds a few new functions: `arch_set_shadow_stack_status`, `arch_lock_shadow_stack_status`, and `arch_get_shadow_stack_status`. These seem to be handlers for the generic `prctl` interface, specifically the shadow stack-related `PR_SET_SHADOW_STACK_STATUS`, `PR_LOCK_SHADOW_STACK_STATUS`, and `PR_GET_SHADOW_STACK_STATUS` commands. It also includes refactoring for shadow stack selftests, updating them to use both `arch_prctl` and `prctl` interfaces. Let's dive into the code. I see a new mask, `PR_SHADOW_STACK_SUPPORTED_STATUS_MASK`, which seems reasonable for defining supported status bits. The `arch_set_shadow_stack_status` function looks like a wrapper that handles the setting of shadow stack status through a series of calls to `shstk_prctl`. Similarly, `arch_lock_shadow_stack_status` and `arch_get_shadow_stack_status` also act as `prctl` handlers, passing the information along to `shstk_prctl`. Aha! `arch_get_shadow_stack_status` is interesting. It takes an `unsigned long __user *status` as input and calls `shstk_prctl` with `(unsigned long)status`. This is where I need to focus: passing a user-space pointer to the kernel. This is a common area for potential uninitialized memory issues. Now, let me check the implementation of `shstk_prctl`. Let's use `default_api:grepper` to locate that function's definition. |
| 16/3 | 2026/08/12 19:52 | tool | grepper |
0mArgs:{
"Expression": "shstk_prctl",
"PathPrefix": "arch/x86/kernel/shstk.c"
}Results: {
"Output": "arch/x86/kernel/shstk.c=543=SYSCALL_DEFINE3(map_shadow_stack, unsigned long, addr, unsigned long, size, unsigned int, flags)\n--\narch/x86/kernel/shstk.c-572-\narch/x86/kernel/shstk.c:573:long shstk_prctl(struct task_struct *task, int option, unsigned long arg2)\narch/x86/kernel/shstk.c-574-{\n--\narch/x86/kernel/shstk.c=639=int arch_set_shadow_stack_status(struct task_struct *t, unsigned long status)\n--\narch/x86/kernel/shstk.c-647-\tif (!status \u0026 PR_SHADOW_STACK_ENABLE)\narch/x86/kernel/shstk.c:648:\t\treturn shstk_prctl(t, ARCH_SHSTK_DISABLE, ARCH_SHSTK_SHSTK);\narch/x86/kernel/shstk.c-649-\narch/x86/kernel/shstk.c:650:\trc = shstk_prctl(t, ARCH_SHSTK_ENABLE, ARCH_SHSTK_SHSTK);\narch/x86/kernel/shstk.c-651-\tif (rc)\n--\narch/x86/kernel/shstk.c-654-\tif (status \u0026 PR_SHADOW_STACK_WRITE)\narch/x86/kernel/shstk.c:655:\t\treturn shstk_prctl(t, ARCH_SHSTK_ENABLE, ARCH_SHSTK_WRSS);\narch/x86/kernel/shstk.c-656-\narch/x86/kernel/shstk.c:657:\treturn shstk_prctl(t, ARCH_SHSTK_DISABLE, ARCH_SHSTK_WRSS);\narch/x86/kernel/shstk.c-658-}\n--\narch/x86/kernel/shstk.c=661=int arch_lock_shadow_stack_status(struct task_struct *t, unsigned long status)\narch/x86/kernel/shstk.c-662-{\narch/x86/kernel/shstk.c:663:\treturn shstk_prctl(t, ARCH_SHSTK_LOCK, status);\narch/x86/kernel/shstk.c-664-}\n--\narch/x86/kernel/shstk.c=666=int arch_get_shadow_stack_status(struct task_struct *t, unsigned long __user *status)\narch/x86/kernel/shstk.c-667-{\narch/x86/kernel/shstk.c:668:\treturn shstk_prctl(t, ARCH_SHSTK_STATUS, (unsigned long)status);\narch/x86/kernel/shstk.c-669-}\n"
} |
| 17/3 | 2026/08/12 19:52 | llm | kmsan-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 29243 output: 42 thoughts: 24 Thoughts: Okay, let's dive into this. Here's my take on those thoughts, as if they're my own: **Investigating `shstk_prctl` and `ARCH_SHSTK_STATUS`** Right, let's dissect this. My first instinct is to understand the interplay between `shstk_prctl` and `ARCH_SHSTK_STATUS`. It's a fundamental part of the security architecture, so figuring out exactly *how* they work together is key. I need to refresh my memory on the purpose of `shstk_prctl`; it's a powerful tool, so I'll start there. Then, I can focus on the role of `ARCH_SHSTK_STATUS`. What information does it provide? What actions does it trigger? I need to understand its behaviour and implications fully. It's time to dig into the documentation, source code, and perhaps even some test cases to get a complete picture. This is crucial for evaluating its effectiveness and potential vulnerabilities. I'll need to think about how these pieces fit into the broader context of stack protection. |
| 18/3 | 2026/08/12 19:52 | tool | read-file |
0mArgs:{
"File": "arch/x86/kernel/shstk.c",
"FirstLine": 573,
"LineCount": 100
}Results: {
"Contents": " 573:\tlong shstk_prctl(struct task_struct *task, int option, unsigned long arg2)\n 574:\t{\n 575:\t\tunsigned long features = arg2;\n 576:\t\n 577:\t\tif (option == ARCH_SHSTK_STATUS) {\n 578:\t\t\treturn put_user(task-\u003ethread.features, (unsigned long __user *)arg2);\n 579:\t\t}\n 580:\t\n 581:\t\tif (option == ARCH_SHSTK_LOCK) {\n 582:\t\t\ttask-\u003ethread.features_locked |= features;\n 583:\t\t\treturn 0;\n 584:\t\t}\n 585:\t\n 586:\t\t/* Only allow via ptrace */\n 587:\t\tif (task != current) {\n 588:\t\t\tif (option == ARCH_SHSTK_UNLOCK \u0026\u0026 IS_ENABLED(CONFIG_CHECKPOINT_RESTORE)) {\n 589:\t\t\t\ttask-\u003ethread.features_locked \u0026= ~features;\n 590:\t\t\t\treturn 0;\n 591:\t\t\t}\n 592:\t\t\treturn -EINVAL;\n 593:\t\t}\n 594:\t\n 595:\t\t/* Do not allow to change locked features */\n 596:\t\tif (features \u0026 task-\u003ethread.features_locked)\n 597:\t\t\treturn -EPERM;\n 598:\t\n 599:\t\t/* Only support enabling/disabling one feature at a time. */\n 600:\t\tif (hweight_long(features) \u003e 1)\n 601:\t\t\treturn -EINVAL;\n 602:\t\n 603:\t\tif (option == ARCH_SHSTK_DISABLE) {\n 604:\t\t\tif (features \u0026 ARCH_SHSTK_WRSS)\n 605:\t\t\t\treturn wrss_control(false);\n 606:\t\t\tif (features \u0026 ARCH_SHSTK_SHSTK)\n 607:\t\t\t\treturn shstk_disable();\n 608:\t\t\treturn -EINVAL;\n 609:\t\t}\n 610:\t\n 611:\t\t/* Handle ARCH_SHSTK_ENABLE */\n 612:\t\tif (features \u0026 ARCH_SHSTK_SHSTK)\n 613:\t\t\treturn shstk_setup();\n 614:\t\tif (features \u0026 ARCH_SHSTK_WRSS)\n 615:\t\t\treturn wrss_control(true);\n 616:\t\treturn -EINVAL;\n 617:\t}\n 618:\t\n 619:\tint shstk_update_last_frame(unsigned long val)\n 620:\t{\n 621:\t\tunsigned long ssp;\n 622:\t\n 623:\t\tif (!features_enabled(ARCH_SHSTK_SHSTK))\n 624:\t\t\treturn 0;\n 625:\t\n 626:\t\tssp = get_user_shstk_addr();\n 627:\t\treturn write_user_shstk_64((u64 __user *)ssp, (u64)val);\n 628:\t}\n 629:\t\n 630:\tbool shstk_is_enabled(void)\n 631:\t{\n 632:\t\treturn features_enabled(ARCH_SHSTK_SHSTK);\n 633:\t}\n 634:\t\n 635:\t#define PR_SHADOW_STACK_SUPPORTED_STATUS_MASK \\\n 636:\t\t\t(PR_SHADOW_STACK_ENABLE | PR_SHADOW_STACK_WRITE | PR_SHADOW_STACK_PUSH)\n 637:\t\n 638:\t/* Handles the generic prctl interface for PR_SET_SHADOW_STACK_STATUS and its feature bits */\n 639:\tint arch_set_shadow_stack_status(struct task_struct *t, unsigned long status)\n 640:\t{\n 641:\t\tint rc;\n 642:\t\n 643:\t\tif (status \u0026 ~PR_SHADOW_STACK_SUPPORTED_STATUS_MASK)\n 644:\t\t\treturn -EINVAL;\n 645:\t\n 646:\t\t/* x86 arch_prctl is single bit at a time, so handle these one at time */\n 647:\t\tif (!status \u0026 PR_SHADOW_STACK_ENABLE)\n 648:\t\t\treturn shstk_prctl(t, ARCH_SHSTK_DISABLE, ARCH_SHSTK_SHSTK);\n 649:\t\n 650:\t\trc = shstk_prctl(t, ARCH_SHSTK_ENABLE, ARCH_SHSTK_SHSTK);\n 651:\t\tif (rc)\n 652:\t\t\treturn rc;\n 653:\t\n 654:\t\tif (status \u0026 PR_SHADOW_STACK_WRITE)\n 655:\t\t\treturn shstk_prctl(t, ARCH_SHSTK_ENABLE, ARCH_SHSTK_WRSS);\n 656:\t\n 657:\t\treturn shstk_prctl(t, ARCH_SHSTK_DISABLE, ARCH_SHSTK_WRSS);\n 658:\t}\n 659:\t\n 660:\t/* Handles the generic prctl interface for PR_LOCK_SHADOW_STACK_STATUS and its feature bits */\n 661:\tint arch_lock_shadow_stack_status(struct task_struct *t, unsigned long status)\n 662:\t{\n 663:\t\treturn shstk_prctl(t, ARCH_SHSTK_LOCK, status);\n 664:\t}\n 665:\t\n 666:\tint arch_get_shadow_stack_status(struct task_struct *t, unsigned long __user *status)\n 667:\t{\n 668:\t\treturn shstk_prctl(t, ARCH_SHSTK_STATUS, (unsigned long)status);\n 669:\t}\n"
} |
| 19/3 | 2026/08/12 19:52 | llm | kmsan-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 31083 output: 37 thoughts: 118 Thoughts: **Examining `shstk_prctl` and Feature Tracking** Okay, so looking at this `shstk_prctl` code, it seems the `ARCH_SHSTK_STATUS` option is handled quite simply: it returns `task->thread.features` to userspace. That makes sense, a straightforward way to expose the status. However, a slight doubt creeps in: is `task->thread.features` actually an `unsigned long` or a `u64`? It's crucial to confirm this size, as incorrect assumptions here would lead to serious problems with data truncation or even completely wrong interpretations of the feature flags on architectures with differing pointer sizes. To ensure complete confidence in this implementation, I should consult the definitive source – `struct thread_struct` definition. That's in `arch/x86/include/asm/processor.h`, which should give me the precise declaration and data type. Let's make sure our assumptions are completely grounded. |
| 20/3 | 2026/08/12 19:52 | tool | grepper |
0mError:no matches Args: {
"Expression": "unsigned long features;",
"PathPrefix": "arch/x86/include/asm/processor.h"
}Results: {
"error": "no matches"
} |
| 21/3 | 2026/08/12 19:52 | llm | kmsan-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 31459 output: 35 thoughts: 13 Thoughts: Okay, here's my take on those thoughts, tailored for an expert audience: **Diving into the Thread's Features** Alright, let's get down to brass tacks. I'm thinking, "Okay, we're at this point in the code, let's double-check how `features` are being handled within the context of the `thread_struct`." I know this structure like the back of my hand, but a quick sanity check is always a good idea, especially when we're dealing with concurrent execution. There might be some subtle interplay or potential race conditions that I want to catch before they become a nasty bug down the line. We need to make sure those features are being propagated, accessed, and modified safely within this specific thread's context. I'm focused on potential edge cases and how they relate to the overall system's stability. Time to open the debugger and see what's what. |
| 22/3 | 2026/08/12 19:53 | tool | grepper |
0mArgs:{
"Expression": "features;",
"PathPrefix": "arch/x86/include/asm/processor.h"
}Results: {
"Output": "arch/x86/include/asm/processor.h=454=struct thread_struct {\n--\narch/x86/include/asm/processor.h-517-#ifdef CONFIG_X86_USER_SHADOW_STACK\narch/x86/include/asm/processor.h:518:\tunsigned long\t\tfeatures;\narch/x86/include/asm/processor.h-519-\tunsigned long\t\tfeatures_locked;\n"
} |
| 23/3 | 2026/08/12 19:53 | llm | kmsan-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 31826 output: 35 thoughts: 100 Thoughts: **Analyzing the Shadow Stack Status Retrieval** Okay, so I'm looking at this code snippet, and my initial thought is: `features` is defined as an `unsigned long`. The `put_user` function is copying `task->thread.features` into user space. My immediate concern is whether we're dealing with any uninitialized memory here. However, I recall that `task->thread.features` is definitely initialized when the thread is first created, which should mitigate that risk. Now, where is this getting called? Oh, right! The `arch_get_shadow_stack_status` function is called from the `prctl` system call. Let me quickly check `kernel/sys.c` to confirm where `PR_GET_SHADOW_STACK_STATUS` is defined and how this flow works. I need to understand the full context here. |
| 24/3 | 2026/08/12 19:53 | tool | grepper |
0mArgs:{
"Expression": "PR_GET_SHADOW_STACK_STATUS",
"PathPrefix": "kernel/sys.c"
}Results: {
"Output": "kernel/sys.c=2534=SYSCALL_DEFINE5(prctl, int, option, unsigned long, arg2, unsigned long, arg3,\n--\nkernel/sys.c-2863-\t\tbreak;\nkernel/sys.c:2864:\tcase PR_GET_SHADOW_STACK_STATUS:\nkernel/sys.c-2865-\t\tif (arg3 || arg4 || arg5)\n"
} |
| 25/3 | 2026/08/12 19:53 | llm | kmsan-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 32259 output: 34 thoughts: 24 Thoughts: Here's my attempt at summarizing the thought process, tailored for an expert audience: **Diving into `PR_GET_SHADOW_STACK_STATUS` in `kernel/sys.c`** Okay, so I'm focusing in on `PR_GET_SHADOW_STACK_STATUS` within `kernel/sys.c`. This suggests we're dealing with shadow stack functionality, which usually implies some form of control-flow integrity (CFI) mechanism, likely leveraging hardware features or a software emulation of them. I immediately want to understand how the system is exposing the current state of this shadow stack implementation – is it providing granular details about the shadow stack's status, or just a binary on/off indication? I’m guessing this system call will likely involve retrieving some data from per-process kernel structures. We might see things like the base address of the shadow stack, its current size or usage, and maybe flags indicating the protection mode (e.g., read-only, read/write, execute-protected) or the CFI enforcement level. Perhaps there are counters for errors related to shadow stack use - maybe stack pointer mismatches, or violations of return-address integrity. The name `PR_GET_SHADOW_STACK_STATUS` implies that we're retrieving, not setting, information, which is consistent with this type of system call providing a view into the system's runtime behavior. Now I'm mentally parsing where the actual work gets done. I'll be looking for memory accesses to process-specific data - that's where the heart of shadow stack tracking lies. I expect this function will provide a snapshot of the current state, and I want to see if the implementation makes guarantees about the consistency of that view. Is there any locking involved to avoid race conditions when several threads are accessing the shadow stack at once? I will also be looking for interactions with any hardware mechanisms for shadow stack support (like Intel's Control-flow Enforcement Technology or similar). I’m expecting pointer chasing, structure dereferencing, and probably some bit-field manipulation to pack all the status details into a return structure. This is all standard stuff, just need to see how they’ve implemented it specifically within this kernel. |
| 26/3 | 2026/08/12 19:53 | tool | read-file |
0mArgs:{
"File": "kernel/sys.c",
"FirstLine": 2860,
"LineCount": 20
}Results: {
"Contents": "2860:\t\t\tbreak;\n2861:\t\tcase PR_RISCV_SET_ICACHE_FLUSH_CTX:\n2862:\t\t\terror = RISCV_SET_ICACHE_FLUSH_CTX(arg2, arg3);\n2863:\t\t\tbreak;\n2864:\t\tcase PR_GET_SHADOW_STACK_STATUS:\n2865:\t\t\tif (arg3 || arg4 || arg5)\n2866:\t\t\t\treturn -EINVAL;\n2867:\t\t\terror = arch_get_shadow_stack_status(me, (unsigned long __user *) arg2);\n2868:\t\t\tbreak;\n2869:\t\tcase PR_SET_SHADOW_STACK_STATUS:\n2870:\t\t\tif (arg3 || arg4 || arg5)\n2871:\t\t\t\treturn -EINVAL;\n2872:\t\t\terror = arch_set_shadow_stack_status(me, arg2);\n2873:\t\t\tbreak;\n2874:\t\tcase PR_LOCK_SHADOW_STACK_STATUS:\n2875:\t\t\tif (arg3 || arg4 || arg5)\n2876:\t\t\t\treturn -EINVAL;\n2877:\t\t\terror = arch_lock_shadow_stack_status(me, arg2);\n2878:\t\t\tbreak;\n2879:\t\tcase PR_TIMER_CREATE_RESTORE_IDS:\n"
} |
| 27/3 | 2026/08/12 19:53 | llm | kmsan-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 33136 output: 39 thoughts: 432 Thoughts: **Shadow Stack Status: Initial Thoughts and Potential Bug** Okay, so this patch is just adding the generic `prctl` handlers for x86 related to shadow stacks. I see `arch_get_shadow_stack_status` calls `shstk_prctl`, and that function ultimately uses `put_user` to copy `task->thread.features` to userspace. I was briefly concerned about potential uninitialized memory leaks there, but it looks like `task->thread.features` is just an `unsigned long`, and `put_user` copies exactly `sizeof(unsigned long)` bytes. No struct padding or uninitialized memory is in play, as `task->thread.features` is initialized during thread creation, so that's fine. Also, the `arch_set_shadow_stack_status` and `arch_lock_shadow_stack_status` functions just read and then pass the scalar `status` to `shstk_prctl`. But wait, let me focus on `arch_set_shadow_stack_status`. This bit of code stands out: ```c if (!status & PR_SHADOW_STACK_ENABLE) return shstk_prctl(t, ARCH_SHSTK_DISABLE, ARCH_SHSTK_SHSTK); ``` That `!status & PR_SHADOW_STACK_ENABLE`... that's a problem, isn't it? `!status` will evaluate to either 0 or 1, and then it's bitwise ANDed with `PR_SHADOW_STACK_ENABLE`. If `PR_SHADOW_STACK_ENABLE` is 1, then the condition will be true if `status` is 0, which could be correct. However, if `PR_SHADOW_STACK_ENABLE` is not 1, this condition is always going to be false. I need to confirm the value of `PR_SHADOW_STACK_ENABLE` to be sure. |
| 28/3 | 2026/08/12 19:53 | tool | grepper |
0mArgs:{
"Expression": "PR_SHADOW_STACK_ENABLE",
"PathPrefix": "include/uapi/linux/prctl.h"
}Results: {
"Output": "include/uapi/linux/prctl.h=134=struct prctl_mm_map {\n--\ninclude/uapi/linux/prctl.h-361-#define PR_SET_SHADOW_STACK_STATUS 75\ninclude/uapi/linux/prctl.h:362:# define PR_SHADOW_STACK_ENABLE (1UL \u003c\u003c 0)\ninclude/uapi/linux/prctl.h-363-# define PR_SHADOW_STACK_WRITE\t\t(1UL \u003c\u003c 1)\n"
} |
| 29/3 | 2026/08/12 19:53 | llm | kmsan-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 34169 output: 183 thoughts: 1166 Thoughts: **Shadow Stack Prctl Handler Analysis** Okay, so I'm looking at this shadow stack `prctl` handler, and the logic here is a bit... off. `PR_SHADOW_STACK_ENABLE` is set to 1. The code's using `!status & PR_SHADOW_STACK_ENABLE` to determine whether to enable or disable the shadow stack. At first glance, it seems that if `status` is 0, the check works fine, and the shadow stack gets disabled. If it's something non-zero, it skips the disable and does nothing or enables which seems strange. But then it hits me: this is probably meant to be `!(status & PR_SHADOW_STACK_ENABLE)`. Operator precedence is messing things up. That `!` is binding too tightly. So, this is definitely a bug – it's going to enable/disable the stack when it shouldn't in some cases. But the key question for me is: does this introduce a *memory safety* issue, specifically, an uninitialized memory read? It's easy to get lost in the weeds on these things. Let's break it down again. There's this `arch/x86/kernel/shstk.c` file and the `tools/testing/selftests/x86/*` directory. Nothing in here looks like it's exposing uninitialized data to userspace. There aren't new structs being copied, no complex allocations going on. All the data that's *actually* sent to user space is just `task->thread.features`, which is a simple unsigned long. KMSAN *specifically* isn't going to catch this. This is more of a logic bug; KASAN or LOCKDEP probably wouldn't trip on this either. Maybe the selftests will. So, with that in mind: * If `status` is 2 (`PR_SHADOW_STACK_WRITE`), the code will call `shstk_prctl` to enable the shadow stack which it shouldn't do * If `status` is 1 (`PR_SHADOW_STACK_ENABLE`), the code will call `shstk_prctl` to enable the shadow stack which it shouldn't do * If `status` is 0, the code will call `shstk_prctl` to disable the shadow stack, and that is right But, now I realize this might not be *that* bad, since `PR_SHADOW_STACK_WRITE` without `PR_SHADOW_STACK_ENABLE` is probably invalid anyway. It seems this check skips invalid `status` values and calls `ENABLE` when the code should have called `DISABLE`. There is a functional bug in this code. And, what if `status` is `PR_SHADOW_STACK_ENABLE | PR_SHADOW_STACK_WRITE`? (3)? Same issue. So the bug seems to affect the values of `2` and `3`. The mask isn't correctly testing the status of the flags. This is still a functional bug and not an uninitialized memory bug. Therefore, this patch doesn't *need* KMSAN fuzzing. It's a functional bug, not a memory safety bug. **Reasoning** The patch includes generic `prctl` handlers for the shadow stack on x86, which sets the stack status, locks it, and reads its status, with corresponding self tests. The data sent to user space is just an unsigned long: `task->thread.features`. There's no complex data allocation or transfer of uninitialized memory. The only bug is in the condition, `!status & PR_SHADOW_STACK_ENABLE`, which is a functional bug, not a KMSAN-relevant memory issue. So, this patch doesn't expose any uninitialized memory to user space. |
| 30/3 | 2026/08/12 19:53 | tool | set-results |
0mArgs:{
"KMSANReasoning": "The patch introduces generic `prctl` handlers for shadow stack on x86 (`arch_set_shadow_stack_status`, `arch_lock_shadow_stack_status`, `arch_get_shadow_stack_status`) and adds corresponding selftests. The only data copied to user space is a scalar `unsigned long` (`task-\u003ethread.features`) via `put_user()` in `shstk_prctl()`. This does not involve any struct padding, uninitialized memory, or complex allocations. While there is a potential operator precedence bug (`!status \u0026 PR_SHADOW_STACK_ENABLE`), it is a functional logic bug and does not expose uninitialized memory. Thus, a dedicated KMSAN fuzzing session is not required.",
"NeedsKMSAN": false
}Results: {
"KMSANReasoning": "The patch introduces generic `prctl` handlers for shadow stack on x86 (`arch_set_shadow_stack_status`, `arch_lock_shadow_stack_status`, `arch_get_shadow_stack_status`) and adds corresponding selftests. The only data copied to user space is a scalar `unsigned long` (`task-\u003ethread.features`) via `put_user()` in `shstk_prctl()`. This does not involve any struct padding, uninitialized memory, or complex allocations. While there is a potential operator precedence bug (`!status \u0026 PR_SHADOW_STACK_ENABLE`), it is a functional logic bug and does not expose uninitialized memory. Thus, a dedicated KMSAN fuzzing session is not required.",
"NeedsKMSAN": false
} |
| Total Calls | Total Tokens | Avg Tokens | Total Duration (Seconds) | Avg Duration (Seconds) |
|---|
| Total Calls | Total Duration (Seconds) | Avg Duration (Seconds) |
|---|