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

Gabriel Ravier gabravier@gmail.com
Sun Sep 13 23:06:33 GMT 2026


On 6/28/26 9:02 AM, Jesse Huang wrote:
> Since longjmp to a previous setjmp state can change the stack frame and
> involves stack frame unwinding, the shadow stack is also required to be
> unwound.
>
> The unwinding is implemented according to the Zicfiss specification by
> increasing the SSP by at most one page size (4K), to avoid accidentally
> pointing to another legal shadow stack page after the adjustment.
>
> The shadow stack pointer is stored in a wrapped sigset_t.  By defining it
> inside a union, we can avoid changing the size of sigset_t and therefore
> jmp_buf.
> ---
>   sysdeps/riscv/Makefile                        |  4 +
>   sysdeps/riscv/__longjmp.S                     | 54 +++++++++++++
>   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(+)
>   create mode 100644 sysdeps/unix/sysv/linux/riscv/jmp_buf-ssp.sym
>   create mode 100644 sysdeps/unix/sysv/linux/riscv/setjmpP.h
>
> [snip]
> diff --git a/sysdeps/riscv/__longjmp.S b/sysdeps/riscv/__longjmp.S
> index bea854199c..8ee0662b81 100644
> --- a/sysdeps/riscv/__longjmp.S
> +++ b/sysdeps/riscv/__longjmp.S
> @@ -18,9 +18,12 @@
>   
>   #include <sysdep.h>
>   #include <sys/asm.h>
> +#include <jmp_buf-ssp.h>
> +#include <tcb-offsets.h>
>   
>   ENTRY (__longjmp)
>   	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)
> @@ -50,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 one page size to ensure the adjustment
> +	   always runs into a guard page before accidentally pointing 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

Actually, t1 seems just flatly wrong - the RISC-V spec (4.17.1.1. 
Landing Pad Enforcement) appears to specify solely x7 as the register 
for a software-guarded branch, and t1 is x6 (x7 is t2). Shouldn't this 
(and the above load) use t2 instead of t1?

> +#else
> +	mv   ra, t1
>   	ret
> +#endif
>   
>   END (__longjmp)
> [snip]




More information about the Libc-alpha mailing list