[v4,10/17] riscv/cfi: Adjust setjmp/longjmp for shadow stack to work

Message ID 20260526061703.2188042-11-jesse.huang@sifive.com (mailing list archive)
State New
Headers
Series Support RISC-V Control Flow Integrifty (CFI) |

Checks

Context Check Description
redhat-pt-bot/TryBot-apply_patch success Patch applied to master at the time it was sent
linaro-tcwg-bot/tcwg_glibc_build--master-arm success Build passed
linaro-tcwg-bot/tcwg_glibc_build--master-aarch64 success Build passed
linaro-tcwg-bot/tcwg_glibc_check--master-aarch64 success Test passed
linaro-tcwg-bot/tcwg_glibc_check--master-arm success Test passed

Commit Message

Jesse Huang May 26, 2026, 6:16 a.m. UTC
  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                     | 55 ++++++++++++-
 sysdeps/riscv/setjmp.S                        | 22 ++++++
 sysdeps/unix/sysv/linux/riscv/jmp_buf-ssp.sym |  7 ++
 sysdeps/unix/sysv/linux/riscv/setjmpP.h       | 78 +++++++++++++++++++
 5 files changed, 165 insertions(+), 1 deletion(-)
 create mode 100644 sysdeps/unix/sysv/linux/riscv/jmp_buf-ssp.sym
 create mode 100644 sysdeps/unix/sysv/linux/riscv/setjmpP.h
  

Comments

Andreas Schwab May 26, 2026, 8:11 a.m. UTC | #1
On Mai 25 2026, 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
                                                          are
> to be unwinded.
        unwound
  

Patch

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 f43b0f4c32..33d46b777d 100644
--- a/sysdeps/riscv/__longjmp.S
+++ b/sysdeps/riscv/__longjmp.S
@@ -18,10 +18,12 @@ 
 
 #include <sysdep.h>
 #include <sys/asm.h>
+#include <jmp_buf-ssp.h>
+#include <tcb-offsets.h>
 
 ENTRY (__longjmp)
 	LPAD
-	REG_L ra,  0*SZREG(a0)
+	REG_L t1,  0*SZREG(a0)
 	REG_L s0,  1*SZREG(a0)
 	REG_L s1,  2*SZREG(a0)
 	REG_L s2,  3*SZREG(a0)
@@ -51,8 +53,59 @@  ENTRY (__longjmp)
 	FREG_L fs11,14*SZREG+11*SZFREG(a0)
 #endif
 
+#ifdef __riscv_shadow_stack
+	/* skip unwinding if ss is not enabled  */
+	ssrdp	ra
+	beqz	ra, .Lfin
+	REG_L	t0, SSP_OFFSET(a0)
+	REG_L	a0, SSP_BASE_OFFSET(a0)
+	REG_L	t2, TLS_SSP_BASE_OFFSET(tp)
+	bne	a0, t2, .Ldifferent_stack
+.Lunwind:
+	bleu  t0, ra, .Lfin
+	/* 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  */
+	/* ra = (t0 - ra >= 4096) ? ra + 4096 : t0  */
+	lui   a0, 1
+	add   ra, ra, a0
+	bleu  ra, t0, 1f
+	mv    ra, t0
+1:
+	csrw  ssp, ra
+	/* Test if the location pointed by ssp is legal  */
+	sspush x5
+	sspopchk x5
+	j .Lunwind
+.Ldifferent_stack:
+	/* Create restore token  */
+	sspush  ra
+	mv	a4, t0
+
+.Lfind_rstor_token:
+	/* Probe and validate target restore token  */
+	ssamoswap.d a3, x0, (a4)
+	addi    a2, a4, 8
+	beq     a3, a2, .Lswitch_stack
+	/* Restore the shadow stack and try the next slot  */
+	ssamoswap.d x0, a3, (a4)
+	addi    a4, a4, -8
+	j	.Lfind_rstor_token
+
+.Lswitch_stack:
+	/* Switch stack: update ssp and base  */
+	csrw    ssp, t0
+	REG_S   a0, TLS_SSP_BASE_OFFSET(tp)
+.Lfin:
+#endif
 	seqz a0, a1
 	add  a0, a0, a1   # a0 = (a1 == 0) ? 1 : a1
+#ifdef __riscv_landing_pad
+	/* Use indirect branch if CFI is enabled  */
+	jr   t1
+#else
+	mv   ra, t1
 	ret
+#endif
 
 END (__longjmp)
diff --git a/sysdeps/riscv/setjmp.S b/sysdeps/riscv/setjmp.S
index aecf2d2bab..4d5a5f0aff 100644
--- a/sysdeps/riscv/setjmp.S
+++ b/sysdeps/riscv/setjmp.S
@@ -18,6 +18,8 @@ 
 
 #include <sysdep.h>
 #include <sys/asm.h>
+#include <jmp_buf-ssp.h>
+#include <tcb-offsets.h>
 
 ENTRY (_setjmp)
   LPAD
@@ -61,6 +63,26 @@  ENTRY (__sigsetjmp)
 	FREG_S fs11,14*SZREG+11*SZFREG(a0)
 #endif
 
+#ifdef __riscv_shadow_stack
+	/* Skip if shadow stack is not enabled */
+	ssrdp	t0
+	beqz	t0, .Lfin
+
+	/* Read ssp_base from TLS  */
+	REG_L	t2, TLS_SSP_BASE_OFFSET(tp)
+	bnez	t2, .Lbase_saved
+
+	/* if not found, use current ssp as the marker  */
+	mv	t2, t0
+	REG_S	t2, TLS_SSP_BASE_OFFSET(tp)
+
+.Lbase_saved:
+	/* Save caller's ssp and base marker to jmp_buf  */
+	REG_S	t0, SSP_OFFSET(a0)
+	REG_S	t2, SSP_BASE_OFFSET(a0)
+.Lfin:
+#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..43cb28e2d1
--- /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) 2026 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  */