[PATCH v2 00/14] Support RISC-V Control Flow Integrifty (CFI)

Jesse Huang jesse.huang@sifive.com
Tue Jul 29 06:15:54 GMT 2025


On Sat, Jul 26, 2025 at 8:54 PM Vivian Wang <wangruikang@iscas.ac.cn> wrote:

> On 7/23/25 17:12, Jesse Huang wrote:
> > Hi Henrich,
> >
> > Thanks for your question.
> > For dynamic binaries, the loader performs a check on all dependencies
> > listed in link_map, and CFI features (SS, LP) will only be enabled
> > respectively only if property notes present in all of the dependencies.
> > I have summarized the possible combinations and their behavior here.
> >
> > 1) Binary w/o CFI
> > No compatibility problem
> >
> > 2) Binary w/ CFI + dependency w/ CFI
> > CFI will be enabled and should be no problem
> >
> > 3) Binary w/ CFI + dynamic loading library w/o CFI
> > a. If the mode is always-on (default), dlopen will fail.
> > b. If the mode is permissive (can be set by glibc tunables),
> >    CFI will be disabled
> >
> > 4) Binary w/ CFI + dependency w/o CFI
> > CFI will not be enabled because of the dependency check won't pass
> >
> > The only case that causes problem is attempting to execute shadow stack
> > instructions on a RVA20 machine, in this case they will be illegal
> > instructions due to lacking the support of Zimop/Zcmop, but this is
> > unlikely to happen.
>
> The problems is that, AFAICT, now the size of jmp_buf depends on
> __riscv_shadow_stack. You have mentioned this in passing in the cover
> letter, but this is a huge problem for the software ecosystem because
> there will always be binary code that depends on the size of jmp_buf by
> e.g. using it in a struct.
>
> We did that to s390 [1] back in 2014 once. It was bad. Let's not do that
> again.
>
> Can we learn from what x86 and aarch64 did that did not cause this
> problem? My understanding is that they used some otherwise unused
> storage in the full jmp_buf, but I'm not sure about the details.
>
>
Thanks Vivian, this is definitely something I didn't consider.
x86 implemented this by making sigset_t a union that contains the original
sigset_t, or a
shrinked sigset_t and a shadow stack pointer. To leverage the unused space
in sigmask
without affecting the sigset_t's size

/* Number of bits per long.  */
#define _JUMP_BUF_SIGSET_BITS_PER_WORD (8 * sizeof (unsigned long int))
/* The biggest signal number.  As of kernel 4.14, x86 _NSIG is 64. The
   common maximum sigset for i386, x32 and x86-64 is 12 bytes (96 bits).
   Define it to 96 to leave some rooms for future use.  */
#define _JUMP_BUF_SIGSET_NSIG   96
/* 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 __shadow_stack_pointer;
      } __saved;
  } __jmpbuf_arch_t;

#undef __sigset_t
#define __sigset_t __jmpbuf_arch_t

This is similar to our implementation for ucontext in that we shrunk the
sigset_t for extra space,
and x86's way is apparently better because our approach also affects the
size of sigset_t (thus jmp_buf)
which I didn't notice. I will change to x86's approach in the next version.

Deepak, do you have any concern with this approach?


Thanks,
Jesse Huang



> Thanks,
> Vivian "dramforever" Wang
>
> [1]: https://lwn.net/Articles/606377/
>
>
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://sourceware.org/pipermail/libc-alpha/attachments/20250729/6c404693/attachment.htm>


More information about the Libc-alpha mailing list