<div dir="ltr">After a little bit of thought, I ran into a problem.<div><br><div>Saving a restore token on top of the shadow stack indicates the shadow stack is</div><div>no longer active and usable, because any subsequent use would just overwrite</div><div>the restore token, or, if we keep the token by decreasing the ssp by a slot, then it</div><div>simply crashes on some normal return that pops the restore token.</div><div><br></div><div>I checked the longjmp implementation of x86, they are still using unwinding for this</div><div>case (where target and source share the same shadow stack). But we get back to the</div><div>problem that we can't distinguish both without saving the base in ucontext.</div></div><div><br></div><div>Another thing I don't understand is that x86 try to search for a restore token on the jmp_buf</div><div>in longjmp, then unwind if it fails to find one, while setjmp should be the only possible producer</div><div>of the jmp_buf and they don't save restore token in setjmp. So what could be the other sources</div><div>of jmp_buf that holds a restore token, or are they trying to support some hand-crafted jmp_buf?</div></div><br><div class="gmail_quote gmail_quote_container"><div dir="ltr" class="gmail_attr">Deepak Gupta <<a href="mailto:debug@rivosinc.com">debug@rivosinc.com</a>> 於 2025年6月19日 週四 上午4:18寫道:<br></div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex">Hi Jesse,<br>
<br>
Thanks for taking the lead.<br>
<br>
Can we do save and restore a bit differently using a token mechanism.<br>
<br>
On Wed, Jun 18, 2025 at 01:42:56AM -0700, Jesse Huang wrote:<br>
>Since longjmp to a previous setjmp'ed state could change the stack<br>
>frame and involves stack frame unwinding, shadow stacks is also required<br>
>to be unwinded.<br>
><br>
>The unwinding is implemented according to the zicfiss spec by increasing<br>
>the ssp by a page size (4K) at most, to prevent from accidentally point<br>
>to another legal shadow stack page after the adjustment.<br>
>---<br>
> sysdeps/riscv/__longjmp.S   | 30 ++++++++++++++++++++++++++++++<br>
> sysdeps/riscv/bits/setjmp.h |  4 ++++<br>
> sysdeps/riscv/setjmp.S      | 10 ++++++++++<br>
> 3 files changed, 44 insertions(+)<br>
><br>
>diff --git a/sysdeps/riscv/__longjmp.S b/sysdeps/riscv/__longjmp.S<br>
>index 47ff3aa09e..3f1fc3148d 100644<br>
>--- a/sysdeps/riscv/__longjmp.S<br>
>+++ b/sysdeps/riscv/__longjmp.S<br>
>@@ -51,6 +51,36 @@ ENTRY (__longjmp)<br>
>       FREG_L fs11,14*SZREG+11*SZFREG(a0)<br>
> #endif<br>
><br>
>+#ifdef __riscv_shadow_stack<br>
>+        /* read ssp into t0  */<br>
>+        ssrdp t0<br>
>+        /* skip unwinding if ss is not enabled  */<br>
>+        beqz  t0, .Lunwind_fin<br>
>+# ifndef __riscv_float_abi_soft<br>
>+      REG_L t1, 14*SZREG+12*SZFREG(a0)<br>
>+# else<br>
>+      REG_L t1, 14*SZREG(a0)<br>
>+# endif<br>
>+.Lunwind:<br>
>+        /* should not be taken in normal condition  */<br>
>+        bleu  t1, t0, .Lunwind_fin<br>
>+        /* The unwinding algorithm came from the shadow_stack spec, increase ssp<br>
>+           by at most a page size to ensure always run into a guard page<br>
>+           before accidentally point to another legal shadow stack page  */<br>
>+        /* t0 = (t1 - t0 >= 4096) ? t0 + 4096 : t1  */<br>
>+        lui   a0, 1<br>
>+        add   t0, t0, a0<br>
>+        bleu  t0, t1, 1f<br>
>+        mv    t0, t1<br>
>+1:<br>
>+        csrw  ssp, t0<br>
>+        /* Test if the location pointed by ssp is legal  */<br>
>+        sspush x5<br>
>+        sspopchk x5<br>
<br>
Above is useful in C++ exception unwinding where a lot of other stuff gets<br>
unwinded too (registers, etc) as per static information in DWARF (I think its<br>
in dwarf)<br>
<br>
In case of setjmp/longjmp, we can use shadow stack token mechainsm.<br>
We can do below instead to restore shadow stack pointer (see save token<br>
in my comments in setjmp)<br>
<br>
        ssrdp t0<br>
        beqz t0, .Lskip_ss_token_rstor<br>
<br>
        /*<br>
         * non-zero t0 means shadow stack is enabled. thus ssamoswap/csrw to<br>
         * ssp won't fault.<br>
         */<br>
<br>
        REG_L t0, 14*SZREG(a0)  /* get the saved ss pointer */<br>
        ssamoswap t1, x0, (t0)  /* Get the saved token, and store 0 */<br>
        addi t0, t0, 8          /* increment ss pointer */<br>
        bneq t0, t1, fatal      /* ss ptr and token must match */<br>
        csrw ssp, t0            /* we did validation and restoring ssp correctly */<br>
<br>
.Lskip_ss_token_rstor:<br>
<br>
<br>
fatal:<br>
        terminate or recovery action<br>
<br>
>+        j .Lunwind<br>
>+.Lunwind_fin:<br>
>+#endif<br>
>+<br>
>       seqz a0, a1<br>
>       add  a0, a0, a1   # a0 = (a1 == 0) ? 1 : a1<br>
>       ret<br>
>diff --git a/sysdeps/riscv/bits/setjmp.h b/sysdeps/riscv/bits/setjmp.h<br>
>index 5ebbec393a..5cbc12fa3d 100644<br>
>--- a/sysdeps/riscv/bits/setjmp.h<br>
>+++ b/sysdeps/riscv/bits/setjmp.h<br>
>@@ -33,6 +33,10 @@ typedef struct __jmp_buf_internal_tag<br>
>    double __fpregs[12];<br>
> #elif !defined __riscv_float_abi_soft<br>
> # error unsupported FLEN<br>
>+#endif<br>
>+#ifdef __riscv_shadow_stack<br>
>+    /* Shadow stack pointer.  */<br>
>+    long int __ssp;<br>
> #endif<br>
>   } __jmp_buf[1];<br>
><br>
>diff --git a/sysdeps/riscv/setjmp.S b/sysdeps/riscv/setjmp.S<br>
>index df048cb544..f547bc0238 100644<br>
>--- a/sysdeps/riscv/setjmp.S<br>
>+++ b/sysdeps/riscv/setjmp.S<br>
>@@ -61,6 +61,16 @@ ENTRY (__sigsetjmp)<br>
>       FREG_S fs11,14*SZREG+11*SZFREG(a0)<br>
> #endif<br>
><br>
>+#ifdef __riscv_shadow_stack<br>
>+        /* read ssp into t0  */<br>
>+        ssrdp t0<br>
<br>
It will be better if we do something like below to save shadow stack pointer<br>
as token on shadow stack itself.<br>
<br>
        ssrdp t0<br>
        beqz t0, .Lskip_ss_token_prep<br>
<br>
        mv t1, t0               /* record current ss pointer in t1 */<br>
        addi t0, t0, -8         /* decrement current ss pointer by 8 */<br>
        ssamoswap x0, t1, (t0)  /* save away current ss pointer on ss itself */<br>
<br>
.Lskip_ss_token_prep:<br>
>+# ifndef __riscv_float_abi_soft<br>
>+      REG_S t0, 14*SZREG+12*SZFREG(a0)<br>
>+# else<br>
>+      REG_S t0, 14*SZREG(a0)<br>
>+# endif<br>
>+#endif<br>
>+<br>
> #if !IS_IN (libc) && IS_IN (rtld)<br>
>   /* In ld.so we never save the signal mask.  */<br>
>   li a0, 0<br>
>-- <br>
>2.39.3<br>
><br>
</blockquote></div>