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

Jesse Huang jesse.huang@sifive.com
Mon Oct 27 04:26:28 GMT 2025


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.
---
 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