riscv / riscv-cfi

This repo holds the work area and revisions of the RISC-V CFI (Shadow Stack and Landing Pads) specifications. CFI defines the privileged and unprivileged ISA extensions that can be used by privileged and unprivileged programs to protect the integrity of their control-flow.

Home Page:https://jira.riscv.org/browse/RVG-80

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Lack of consideration for jmp_buf ABI

jrtc27 opened this issue · comments

The current shadow stack spec suggests saving the shadow stack pointer to the jmp_buf in setjmp so that longjmp can unwind it back to where it was. However, jmp_buf is an exposed type that is part of the ABI, and so extending it with such state would be an ABI break. There would be no way to incrementally and conditionally adopt it like on other architectures. Unless there is another way to implement setjmp/longjmp without changing anything about jmp_buf I cannot sign off on the extension with my ABI hat on.

Could the jmp_buf be not adopted as done on other architectures:
https://github.com/lattera/glibc/blob/master/sysdeps/x86_64/setjmp.S#L73

That only worked on x86 in glibc because there was existing padding that could be repurposed bminor/glibc@d6cc182 bminor/glibc@f33632c

Other systems may not be so lucky

Could the same technique of using space from the 1024 entry array in __sigset_t __saved_mask be used? I think _NSIG is still only 64 as of 6.5.

In glibc? Maybe. But not every setjmp implementation is glibc. FreeBSD’s does happen to have space, but for different reasons:

  • On x86, space for sigset_t is allocated in units of long, but the struct is in units of int, so on amd64 (but not i386) it allocates double the space needed for it, giving 2 spare longs
  • On riscv, there’s an absurd amount of padding due to a misunderstanding that predates my involvement

But just because glibc and FreeBSD both happen to have wasted space in their jmp_bufs today does not mean that other libcs have such luxury. For example, musl appears to have no unused space when on lp64d.

I tried to read other libc implementations.
Please see if I got any of this wrong.

commented

If shadow stack is in use, the unmodifed longjmp function would have caused an exception (ra mismatch with SS's ra), and it doesn't seem to make much sense to consider ABI compatibility? Because they're incompatible whether they are the same size or not?
It is possible to calculate the number of pops that need with the unwind, but this seems to lead to greater costs and easy to attack. Plus, unlikely to have that in an embedded environment where size matters.

When shadow stack is in use, the longjmp cannot be an unmodified function. But it is also a point to note that an unmodified longjmp would also not have instructions like sspopchk to check ra mismatch with return address obtained from shadow stack - but that is besides the point. As shown above for the cases of linux glibc, bsd libc, bionic libc, and musl libc we may not need to grow the size of the jump buffer.

The point is not about longjmp needing to be modified. That doesn't matter. What matters is whether jmp_buf needs to be modified, because that's the difference between being able to have an incremental migration where shadow stack objects and non-shadow stack objects are ABI-compatible (just run without a shadow stack) or not.

Agree. For the major implementations I think we may be able to get by without needing to grow the jmp_buf (phew!).

  • bionic, seems to define a 32 entry jmp_buf, and presently to use 30 entries. Maybe 2 entries are available.

(since Android's riscv64 ABI isn't final yet, i doubled the size in https://android-review.googlesource.com/c/platform/bionic/+/2719577 just to give us more room to maneuver in future if necessary.)