[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