From: Richard Patel <ripatel@wii.dev>
To: Rick Edgecombe <rick.p.edgecombe@intel.com>,
Thomas Gleixner <tglx@kernel.org>, Ingo Molnar <mingo@redhat.com>,
Borislav Petkov <bp@alien8.de>,
Dave Hansen <dave.hansen@linux.intel.com>,
x86@kernel.org
Cc: "H . Peter Anvin" <hpa@zytor.com>, Kees Cook <kees@kernel.org>,
Shuah Khan <shuah@kernel.org>,
linux-kernel@vger.kernel.org, linux-kselftest@vger.kernel.org,
Richard Patel <ripatel@wii.dev>
Subject: [PATCH 2/3] selftests/x86: test shadow stack sigreturn protection
Date: Thu, 8 Oct 2026 20:16:09 +0000 [thread overview]
Message-ID: <20261008201610.1003569-3-ripatel@wii.dev> (raw)
In-Reply-To: <20261008201610.1003569-1-ripatel@wii.dev>
Check that a crafted signal frame return address (rip) is rejected
by rt_sigreturn when shadow stack is enabled. Test both 'syscall'
and 'int $0x80' (CONFIG_IA32_EMULATION) with and without SHSTK.
Signed-off-by: Richard Patel <ripatel@wii.dev>
---
.../testing/selftests/x86/test_shadow_stack.c | 110 ++++++++++++++++++
1 file changed, 110 insertions(+)
diff --git a/tools/testing/selftests/x86/test_shadow_stack.c b/tools/testing/selftests/x86/test_shadow_stack.c
index 3d6ca33edba4..67baebbdbd87 100644
--- a/tools/testing/selftests/x86/test_shadow_stack.c
+++ b/tools/testing/selftests/x86/test_shadow_stack.c
@@ -735,6 +735,110 @@ int test_32bit(void)
return !segv_triggered;
}
+/*
+ * Fork and sigreturn with a crafted signal frame.
+ * Returns the child's exit code, or 0x100+signal if it was killed.
+ */
+static int crafted_sigreturn(unsigned long sp, bool ia32, bool shstk)
+{
+ int status;
+ pid_t pid;
+
+ pid = fork();
+ if (!pid) {
+ signal(SIGSEGV, SIG_DFL);
+ if (ARCH_PRCTL(shstk ? ARCH_SHSTK_ENABLE : ARCH_SHSTK_DISABLE,
+ ARCH_SHSTK_SHSTK))
+ _exit(1);
+ if (ia32) /* ia32 rt_sigreturn */
+ asm volatile("movq %0, %%rsp; int $0x80; ud2"
+ : : "r" (sp), "a" (173));
+ else /* rt_sigreturn */
+ asm volatile("movq %0, %%rsp; syscall; ud2"
+ : : "r" (sp), "a" (__NR_rt_sigreturn));
+ __builtin_unreachable();
+ }
+
+ if (pid < 0 || waitpid(pid, &status, 0) != pid)
+ return -1;
+ return WIFSIGNALED(status) ? 0x100 + WTERMSIG(status) : WEXITSTATUS(status);
+}
+
+struct rt_sigframe_ia32 {
+ uint32_t pretcode, sig, pinfo, puc;
+ uint8_t info[128];
+ uint32_t uc_flags, uc_link, ss_sp, ss_flags, ss_size;
+ uint16_t gs, __gsh, fs, __fsh, es, __esh, ds, __dsh;
+ uint32_t di, si, bp, sp, bx, dx, cx, ax, trapno, err, ip;
+ uint16_t cs, __csh;
+ uint32_t flags, sp_at_signal;
+ uint16_t ss, __ssh;
+ uint32_t fpstate, oldmask, cr2;
+ uint32_t uc_sigmask[2];
+ uint8_t retcode[8];
+};
+
+_Static_assert(sizeof(struct rt_sigframe_ia32) == 268, "ia32 rt_sigframe layout");
+
+/* This tests whether shadow stack protects sigreturn */
+int test_sigreturn(void)
+{
+ static const uint8_t target_routine[] = {
+ 0xbf, 0x2a, 0x00, 0x00, 0x00, /* mov $42, %edi */
+ 0xb8, 0xe7, 0x00, 0x00, 0x00, /* mov $231, %eax (exit_group) */
+ 0x0f, 0x05, /* syscall */
+ };
+
+ struct { uint64_t pretcode; ucontext_t uc; } *f64;
+ struct rt_sigframe_ia32 *f32;
+ void *retsite;
+ int ret = 1;
+
+ /* ia32 sigreturn truncates RIP and RSP to 32 bits */
+ retsite = mmap(0, PAGE_SIZE, PROT_READ | PROT_WRITE | PROT_EXEC,
+ MAP_32BIT | MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
+ if (retsite == MAP_FAILED)
+ return 1;
+ memcpy(retsite, target_routine, sizeof(target_routine));
+
+ f64 = retsite + 0x100;
+ f64->uc.uc_mcontext.gregs[REG_RIP] = (unsigned long)retsite;
+ f64->uc.uc_mcontext.gregs[REG_RSP] = (unsigned long)retsite + PAGE_SIZE;
+ f64->uc.uc_mcontext.gregs[REG_CSGSFS] = 0x33; /* __USER_CS */
+
+ f32 = retsite + 0x800;
+ f32->ip = (unsigned long)retsite;
+ f32->sp = (unsigned long)retsite + PAGE_SIZE;
+ f32->cs = 0x33; /* __USER_CS (64-bit) */
+ f32->ss = 0x2b; /* __USER_DS */
+
+ if (crafted_sigreturn((unsigned long)f64 + 8, false, false) != 42) {
+ printf("[FAIL]\trt_sigreturn protection (hijack failed without shadow stack)\n");
+ goto out;
+ }
+ if (crafted_sigreturn((unsigned long)f64 + 8, false, true) != 0x100 + SIGSEGV) {
+ printf("[FAIL]\trt_sigreturn protection\n");
+ goto out;
+ }
+ printf("[OK]\trt_sigreturn protection\n");
+
+ if (crafted_sigreturn((unsigned long)f32 + 4, true, false) != 42) {
+ printf("[SKIP]\tia32 rt_sigreturn protection (int $0x80 unavailable)\n");
+ ret = 0;
+ goto out;
+ }
+ if (crafted_sigreturn((unsigned long)f32 + 4, true, true) != 0x100 + SIGSEGV) {
+ printf("[FAIL]\tia32 rt_sigreturn protection\n");
+ goto out;
+ }
+ printf("[OK]\tia32 rt_sigreturn protection\n");
+ ret = 0;
+
+out:
+ munmap(retsite, PAGE_SIZE);
+ return ret;
+}
+
static int parse_uint_from_file(const char *file, const char *fmt)
{
int err, ret;
@@ -1145,6 +1249,12 @@ int main(int argc, char *argv[])
goto out;
}
+ if (test_sigreturn()) {
+ ret = 1;
+ printf("[FAIL]\tsigreturn test\n");
+ goto out;
+ }
+
if (test_uretprobe()) {
ret = 1;
printf("[FAIL]\turetprobe test\n");
--
2.52.0
next prev parent reply other threads:[~2026-10-08 20:22 UTC|newest]
Thread overview: 13+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-10-08 20:16 [PATCH 0/3] x86/shstk: ban ia32 sigreturn Richard Patel
2026-10-08 20:16 ` [PATCH 1/3] x86/shstk: ban ia32 sigreturn when shadow stack is enabled Richard Patel
2026-10-08 20:59 ` Edgecombe, Rick P
2026-10-08 21:50 ` Richard Patel
2026-10-08 22:34 ` Edgecombe, Rick P
2026-10-08 22:47 ` Richard Patel
2026-10-08 23:11 ` Edgecombe, Rick P
2026-10-08 23:29 ` Richard Patel
2026-10-09 11:36 ` Richard Patel
2026-10-09 22:14 ` Edgecombe, Rick P
2026-10-08 20:16 ` Richard Patel [this message]
2026-10-08 20:16 ` [PATCH 3/3] selftests/x86: skip shstk tests where perf_event_open() fails Richard Patel
2026-10-08 21:00 ` [PATCH 0/3] x86/shstk: ban ia32 sigreturn Edgecombe, Rick P
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20261008201610.1003569-3-ripatel@wii.dev \
--to=ripatel@wii.dev \
--cc=bp@alien8.de \
--cc=dave.hansen@linux.intel.com \
--cc=hpa@zytor.com \
--cc=kees@kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-kselftest@vger.kernel.org \
--cc=mingo@redhat.com \
--cc=rick.p.edgecombe@intel.com \
--cc=shuah@kernel.org \
--cc=tglx@kernel.org \
--cc=x86@kernel.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox