[PATCH v3 10/16] riscv/cfi: Adjust setjmp/longjmp for shadow stack to work

Deepak Gupta debug@rivosinc.com
Tue Oct 28 01:01:31 GMT 2025


On Sun, Oct 26, 2025 at 09:26:28PM -0700, Jesse Huang wrote:
>Since longjmp to a previous setjmp'ed state could change the stack
>frame and involves stack frame unwinding, shadow stacks is also required
>to be unwinded.
>
>The unwinding is implemented according to the zicfiss spec by increasing
>the ssp by a page size (4K) at most, to prevent from accidentally point
>to another legal shadow stack page after the adjustment.
>
>Shadow stack pointer is stored in to a wrapped sigset_t, by defining
>within an union, we can avoid changing the size of sigset_t hence
>jmp_buf.

Reviewed-by: Deepak Gupta <debug@rivosinc.com>
>---
> sysdeps/riscv/Makefile                        |  4 +
> sysdeps/riscv/__longjmp.S                     | 30 +++++++
> sysdeps/riscv/setjmp.S                        |  7 ++
> sysdeps/unix/sysv/linux/riscv/jmp_buf-ssp.sym |  7 ++
> sysdeps/unix/sysv/linux/riscv/setjmpP.h       | 78 +++++++++++++++++++
> 5 files changed, 126 insertions(+)
> create mode 100644 sysdeps/unix/sysv/linux/riscv/jmp_buf-ssp.sym
> create mode 100644 sysdeps/unix/sysv/linux/riscv/setjmpP.h
>
>diff --git a/sysdeps/riscv/Makefile b/sysdeps/riscv/Makefile
>index b1f074a3eb..94e224615c 100644
>--- a/sysdeps/riscv/Makefile
>+++ b/sysdeps/riscv/Makefile
>@@ -11,6 +11,10 @@ endif
> # of some assembler macros.
> ASFLAGS-.os += $(pic-ccflag)
>
>+ifeq ($(subdir),setjmp)
>+gen-as-const-headers += jmp_buf-ssp.sym
>+endif
>+
> ifeq (no,$(riscv-r-align))
> ASFLAGS-.os += -Wa,-mno-relax
> ASFLAGS-.o += -Wa,-mno-relax
>diff --git a/sysdeps/riscv/__longjmp.S b/sysdeps/riscv/__longjmp.S
>index 47ff3aa09e..1c284cf57d 100644
>--- a/sysdeps/riscv/__longjmp.S
>+++ b/sysdeps/riscv/__longjmp.S
>@@ -18,6 +18,7 @@
>
> #include <sysdep.h>
> #include <sys/asm.h>
>+#include <jmp_buf-ssp.h>
>
> ENTRY (__longjmp)
> 	LPAD
>@@ -51,6 +52,35 @@ ENTRY (__longjmp)
> 	FREG_L fs11,14*SZREG+11*SZFREG(a0)
> #endif
>
>+#ifdef __riscv_shadow_stack
>+	/* skip unwinding if ss is not enabled  */
>+	ssrdp t0
>+	beqz  t0, .Lunwind_fin
>+	REG_L t1, SSP_OFFSET(a0)
>+.Lunwind:
>+	bleu  t1, t0, .Lunwind_fin
>+	/* Increase ssp by at most a page size to ensure always run into a
>+	   guard page before accidentally point to another legal shadow stack
>+	   page  */
>+	/* t0 = (t1 - t0 >= 4096) ? t0 + 4096 : t1  */
>+	lui   a0, 1
>+	add   t0, t0, a0
>+	bleu  t0, t1, 1f
>+	mv    t0, t1
>+1:
>+	csrw  ssp, t0
>+	/* Test if the location pointed by ssp is legal  */
>+	sspush x5
>+	sspopchk x5
>+	j .Lunwind
>+.Lunwind_fin:
>+	seqz a0, a1
>+	add  a0, a0, a1   # a0 = (a1 == 0) ? 1 : a1
>+	/* Use indirect branch if CFI is enabled  */
>+	mv   t1, ra
>+	jr   t1
>+#endif
>+
> 	seqz a0, a1
> 	add  a0, a0, a1   # a0 = (a1 == 0) ? 1 : a1
> 	ret
>diff --git a/sysdeps/riscv/setjmp.S b/sysdeps/riscv/setjmp.S
>index df048cb544..858829d819 100644
>--- a/sysdeps/riscv/setjmp.S
>+++ b/sysdeps/riscv/setjmp.S
>@@ -18,6 +18,7 @@
>
> #include <sysdep.h>
> #include <sys/asm.h>
>+#include <jmp_buf-ssp.h>
>
> ENTRY (_setjmp)
>   LPAD
>@@ -61,6 +62,12 @@ ENTRY (__sigsetjmp)
> 	FREG_S fs11,14*SZREG+11*SZFREG(a0)
> #endif
>
>+#ifdef __riscv_shadow_stack
>+        /* read ssp into t0  */
>+        ssrdp t0
>+	REG_S t0, SSP_OFFSET(a0)
>+#endif
>+
> #if !IS_IN (libc) && IS_IN (rtld)
>   /* In ld.so we never save the signal mask.  */
>   li a0, 0
>diff --git a/sysdeps/unix/sysv/linux/riscv/jmp_buf-ssp.sym b/sysdeps/unix/sysv/linux/riscv/jmp_buf-ssp.sym
>new file mode 100644
>index 0000000000..bf944969f7
>--- /dev/null
>+++ b/sysdeps/unix/sysv/linux/riscv/jmp_buf-ssp.sym
>@@ -0,0 +1,7 @@
>+#include <setjmpP.h>
>+#include <stddef.h>
>+#undef __saved_mask
>+
>+--
>+SSP_OFFSET offsetof(struct __jmp_buf_tag, __saved_mask.__saved.__ssp)
>+SSP_BASE_OFFSET offsetof(struct __jmp_buf_tag, __saved_mask.__saved.__ssp_base)
>diff --git a/sysdeps/unix/sysv/linux/riscv/setjmpP.h b/sysdeps/unix/sysv/linux/riscv/setjmpP.h
>new file mode 100644
>index 0000000000..3c3d1d4c03
>--- /dev/null
>+++ b/sysdeps/unix/sysv/linux/riscv/setjmpP.h
>@@ -0,0 +1,78 @@
>+/* Internal header file for <setjmp.h>.  Linux/risc-v version.
>+   Copyright (C) 2025 Free Software Foundation, Inc.
>+   This file is part of the GNU C Library.
>+
>+   The GNU C Library is free software; you can redistribute it and/or
>+   modify it under the terms of the GNU Lesser General Public
>+   License as published by the Free Software Foundation; either
>+   version 2.1 of the License, or (at your option) any later version.
>+
>+   The GNU C Library is distributed in the hope that it will be useful,
>+   but WITHOUT ANY WARRANTY; without even the implied warranty of
>+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
>+   Lesser General Public License for more details.
>+
>+   You should have received a copy of the GNU Lesser General Public
>+   License along with the GNU C Library; if not, see
>+   <https://www.gnu.org/licenses/>.  */
>+
>+#ifndef	_SETJMPP_H
>+#define	_SETJMPP_H	1
>+
>+#include <bits/types/__sigset_t.h>
>+#include <libc-pointer-arith.h>
>+#include <sigsetops.h>
>+
>+/* Number of bits per long.  */
>+#define _JUMP_BUF_SIGSET_BITS_PER_WORD (8 * sizeof (unsigned long int))
>+/* This holds the number of signals, 512 should be sufficient for future.
>+   expansion  */
>+#define _JUMP_BUF_SIGSET_NSIG	512
>+/* Number of longs to hold all signals.  */
>+#define _JUMP_BUF_SIGSET_NWORDS \
>+  (ALIGN_UP (_JUMP_BUF_SIGSET_NSIG, _JUMP_BUF_SIGSET_BITS_PER_WORD) \
>+   / _JUMP_BUF_SIGSET_BITS_PER_WORD)
>+
>+typedef struct
>+  {
>+    unsigned long int __val[_JUMP_BUF_SIGSET_NWORDS];
>+  } __jmp_buf_sigset_t;
>+
>+typedef union
>+  {
>+    __sigset_t __saved_mask_compat;
>+    struct
>+      {
>+	__jmp_buf_sigset_t __saved_mask;
>+	/* Used for shadow stack pointer.  NB: Shadow stack pointer
>+	   must have the same alignment as __saved_mask.  Otherwise
>+	   offset of __saved_mask will be changed.  */
>+	unsigned long int __ssp;
>+	unsigned long int __ssp_base;
>+      } __saved;
>+  } __jmpbuf_arch_t;
>+
>+/* <setjmp/setjmp.h> has
>+
>+   NB: We use setjmp in thread cancellation and this saves the shadow
>+   stack register, but __libc_unwind_longjmp doesn't restore the shadow
>+   stack register since cancellation never returns after longjmp.  */
>+#undef __sigset_t
>+#define __sigset_t __jmpbuf_arch_t
>+#include <setjmp.h>
>+#undef __saved_mask
>+#define __saved_mask __saved_mask.__saved.__saved_mask
>+
>+#include <signal.h>
>+
>+typedef struct
>+  {
>+    unsigned long int __val[__NSIG_WORDS];
>+  } __sigprocmask_sigset_t;
>+
>+extern jmp_buf ___buf;
>+extern  __typeof (___buf[0].__saved_mask) ___saved_mask;
>+_Static_assert (sizeof (___saved_mask) >= sizeof (__sigprocmask_sigset_t),
>+		"size of ___saved_mask < size of __sigprocmask_sigset_t");
>+
>+#endif /* setjmpP.h  */
>-- 
>2.39.3
>


More information about the Libc-alpha mailing list