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

Jesse Huang jesse.huang@sifive.com
Mon Jun 23 20:02:24 GMT 2025


After a little bit of thought, I ran into a problem.

Saving a restore token on top of the shadow stack indicates the shadow
stack is
no longer active and usable, because any subsequent use would just overwrite
the restore token, or, if we keep the token by decreasing the ssp by a
slot, then it
simply crashes on some normal return that pops the restore token.

I checked the longjmp implementation of x86, they are still using unwinding
for this
case (where target and source share the same shadow stack). But we get back
to the
problem that we can't distinguish both without saving the base in ucontext.

Another thing I don't understand is that x86 try to search for a restore
token on the jmp_buf
in longjmp, then unwind if it fails to find one, while setjmp should be the
only possible producer
of the jmp_buf and they don't save restore token in setjmp. So what could
be the other sources
of jmp_buf that holds a restore token, or are they trying to support some
hand-crafted jmp_buf?

Deepak Gupta <debug@rivosinc.com> 於 2025年6月19日 週四 上午4:18寫道:

> Hi Jesse,
>
> Thanks for taking the lead.
>
> Can we do save and restore a bit differently using a token mechanism.
>
> On Wed, Jun 18, 2025 at 01:42:56AM -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.
> >---
> > sysdeps/riscv/__longjmp.S   | 30 ++++++++++++++++++++++++++++++
> > sysdeps/riscv/bits/setjmp.h |  4 ++++
> > sysdeps/riscv/setjmp.S      | 10 ++++++++++
> > 3 files changed, 44 insertions(+)
> >
> >diff --git a/sysdeps/riscv/__longjmp.S b/sysdeps/riscv/__longjmp.S
> >index 47ff3aa09e..3f1fc3148d 100644
> >--- a/sysdeps/riscv/__longjmp.S
> >+++ b/sysdeps/riscv/__longjmp.S
> >@@ -51,6 +51,36 @@ ENTRY (__longjmp)
> >       FREG_L fs11,14*SZREG+11*SZFREG(a0)
> > #endif
> >
> >+#ifdef __riscv_shadow_stack
> >+        /* read ssp into t0  */
> >+        ssrdp t0
> >+        /* skip unwinding if ss is not enabled  */
> >+        beqz  t0, .Lunwind_fin
> >+# ifndef __riscv_float_abi_soft
> >+      REG_L t1, 14*SZREG+12*SZFREG(a0)
> >+# else
> >+      REG_L t1, 14*SZREG(a0)
> >+# endif
> >+.Lunwind:
> >+        /* should not be taken in normal condition  */
> >+        bleu  t1, t0, .Lunwind_fin
> >+        /* The unwinding algorithm came from the shadow_stack spec,
> 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
>
> Above is useful in C++ exception unwinding where a lot of other stuff gets
> unwinded too (registers, etc) as per static information in DWARF (I think
> its
> in dwarf)
>
> In case of setjmp/longjmp, we can use shadow stack token mechainsm.
> We can do below instead to restore shadow stack pointer (see save token
> in my comments in setjmp)
>
>         ssrdp t0
>         beqz t0, .Lskip_ss_token_rstor
>
>         /*
>          * non-zero t0 means shadow stack is enabled. thus ssamoswap/csrw
> to
>          * ssp won't fault.
>          */
>
>         REG_L t0, 14*SZREG(a0)  /* get the saved ss pointer */
>         ssamoswap t1, x0, (t0)  /* Get the saved token, and store 0 */
>         addi t0, t0, 8          /* increment ss pointer */
>         bneq t0, t1, fatal      /* ss ptr and token must match */
>         csrw ssp, t0            /* we did validation and restoring ssp
> correctly */
>
> .Lskip_ss_token_rstor:
>
>
> fatal:
>         terminate or recovery action
>
> >+        j .Lunwind
> >+.Lunwind_fin:
> >+#endif
> >+
> >       seqz a0, a1
> >       add  a0, a0, a1   # a0 = (a1 == 0) ? 1 : a1
> >       ret
> >diff --git a/sysdeps/riscv/bits/setjmp.h b/sysdeps/riscv/bits/setjmp.h
> >index 5ebbec393a..5cbc12fa3d 100644
> >--- a/sysdeps/riscv/bits/setjmp.h
> >+++ b/sysdeps/riscv/bits/setjmp.h
> >@@ -33,6 +33,10 @@ typedef struct __jmp_buf_internal_tag
> >    double __fpregs[12];
> > #elif !defined __riscv_float_abi_soft
> > # error unsupported FLEN
> >+#endif
> >+#ifdef __riscv_shadow_stack
> >+    /* Shadow stack pointer.  */
> >+    long int __ssp;
> > #endif
> >   } __jmp_buf[1];
> >
> >diff --git a/sysdeps/riscv/setjmp.S b/sysdeps/riscv/setjmp.S
> >index df048cb544..f547bc0238 100644
> >--- a/sysdeps/riscv/setjmp.S
> >+++ b/sysdeps/riscv/setjmp.S
> >@@ -61,6 +61,16 @@ ENTRY (__sigsetjmp)
> >       FREG_S fs11,14*SZREG+11*SZFREG(a0)
> > #endif
> >
> >+#ifdef __riscv_shadow_stack
> >+        /* read ssp into t0  */
> >+        ssrdp t0
>
> It will be better if we do something like below to save shadow stack
> pointer
> as token on shadow stack itself.
>
>         ssrdp t0
>         beqz t0, .Lskip_ss_token_prep
>
>         mv t1, t0               /* record current ss pointer in t1 */
>         addi t0, t0, -8         /* decrement current ss pointer by 8 */
>         ssamoswap x0, t1, (t0)  /* save away current ss pointer on ss
> itself */
>
> .Lskip_ss_token_prep:
> >+# ifndef __riscv_float_abi_soft
> >+      REG_S t0, 14*SZREG+12*SZFREG(a0)
> >+# else
> >+      REG_S t0, 14*SZREG(a0)
> >+# endif
> >+#endif
> >+
> > #if !IS_IN (libc) && IS_IN (rtld)
> >   /* In ld.so we never save the signal mask.  */
> >   li a0, 0
> >--
> >2.39.3
> >
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://sourceware.org/pipermail/libc-alpha/attachments/20250624/08e0bfa7/attachment.htm>


More information about the Libc-alpha mailing list