Getting rid of stack guard handling: an alternative way to detect stack overflow

We currently detect the address of the stack guard page and even try to create one if it’s not created by the operating system. However, it has been almost impossible to get things right because stack guards can exist or not exist depending on OS, kernel version, build configuration and the methods differs between different implementation of pthread library.

The main purpose of the stack guard is to ensure and allow us to detect stack overflow. Here I propose an alternative approach that solely relies on what a signal handler provides.

In short, the idea is to determine both the fault address and the stack pointer when the memory access violation occur. Then we can compare them, check if they’re in single page (assuming stack probe is supported) and we can now detect stack overflow.

From sigaction(3p):

                 If SA_SIGINFO is set and the signal is caught, the signal-catching function shall be entered as:

                     void func(int signo, siginfo_t *info, void *context);

                 where two additional arguments are passed to the signal-catching function. The second argument shall point to an object of type sig‐
                 info_t explaining the reason why the signal was generated; the third argument can be cast to a pointer to an object  of  type  ucon‐
                 text_t  to refer to the receiving thread's context that was interrupted when the signal was delivered.

While the type of context is opaque for the POSIX spec, we can expect it to be properly defined in kernel headers for each platform, unlike stack guard which is merely a security feature of the OS.

cc @oli-obk @pnkfelix @nagisa @nikomatsakis who was on a (somewhat) related Zulip thread.

I do not see it being possible to get rid of concept of the stack guard to achieve what we want to achieve. In the first place, there is no guarantee whatsoever that the pages immediately outside the stack will not be mapped. Stack guard ensures that we do get a fault when those pages are accessed and ensures that they won’t be mapped for any other use. The fact that we check if address falls into the stack guard memory area is more a convenience than anything else, honestly.

If the stack guard pages are jettisoned, we open ourselves up for a plethora of very nasty soundness bugs.

Now, wether the way we handle stack guards is correct or not is something I would be open to debating. Handling of stack guards for threads is just fine, there is no doubt about it. However, that is only the case because we ourselves are responsible for allocating the stack and know everything about it there is to know. Handing of the stack guard for the main thread is a different matter. There the stack size may or may not be variable, and OS-provided guard page may or may not exist depending, as you say, various factors. As practice shows, we don’t exactly get it right every time, and ideas on how to fix that would be most welcome.

3 Likes

I’m assuming your proposal is to keep the stack guard, but just to avoid looking at it in the signal handler? Or at least replacing the unwrap_or(0..0) behavior? That seems fine with me, the signal handler is only there anyway to keep the people who want safe Rust to “never segfault” anyway.

Is your proposal going to work? I don’t know for sure how stack probes work but I can imagine implementations where the stack pointer is not actually increased, instead just doing an indexed memory load.

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.