Page 1 of 1

Questions about signal implementation.

Posted: Mon Mar 18, 2024 1:56 pm
by WinExperements
i am trying to implement the POSIX signal handling for userspace programs. And i have some questions about it. So my current realization successfully calls the corespondending registred handler, but as soon as i call any kind of system call i got #PF because of scheduler stack corruption. So my question is: Did i need to create a separate stack for signal processing? If yes, did i need to push into it the return value for ret instruction where the process is stopped? Or how to correctly fix stack corruption?

Re: Questions about signal implementation.

Posted: Mon Mar 18, 2024 6:32 pm
by thewrongchristian
WinExperements wrote:i am trying to implement the POSIX signal handling for userspace programs. And i have some questions about it. So my current realization successfully calls the corespondending registred handler, but as soon as i call any kind of system call i got #PF because of scheduler stack corruption.
There be dragons!

You want to handle the signal on the user stack, so if you're corrupting your scheduler stack, you have bigger problems.

When you want the user level to handle a signal, you generally do the following:

- Arrange for the user signal handler to run upon return to user mode - This involves changing the return address on the kernel stack to point to the user level signal code.
- Also push extra information on the user stack that the user level signal code will use to restore the previous context.
- Return to user space, which invokes the user level code in the signal handler.

Traditionally, the kernel would push code onto the stack to handle the restoration of user context, then push the return address to the on-stack code, then context information and arguments to the signal handler, then finally arrange to return to the signal handler. The signal handler does its stuff, returns to the on-stack code, which restores the user context using sigreturn system call or similar.

An example of the on-stack code that does this return can be found here:

http://bxr.su/NetBSD/lib/libc/compat/ar ... igtramp1.S

This is complicated (or I would argue simplified) by non-executable stacks in modern systems, which preclude the use of on-stack code, and instead the libc passes the address of a function to replace the on-stack code (search for SA_RESTORER in the sigaction(2) manpage for Linux.) This restorer is responsible for calling sigreturn.

Sigreturn itself takes the context pushed onto the stack when the kernel was setting up the signal, and restores that context. The context would include the general purpose registers, signal mask, basically everything the kernel would preserve and restore on entry/exit from user space.
WinExperements wrote: So my question is: Did i need to create a separate stack for signal processing?
In all of this, there is no requirement for a separate user stack, the signal handling code can use the same user stack so long as there is sufficient space.
WinExperements wrote: If yes, did i need to push into it the return value for ret instruction where the process is stopped?
You need more than just the return address . You also need to save the whole user context. Signals can be delivered asynchronously, interrupting the user code, so you need to restore all the user CPU state.
WinExperements wrote: Or how to correctly fix stack corruption?
No idea. You'd need to describe what this stack is. User scheduler, kernel scheduler?

Re: Questions about signal implementation.

Posted: Mon Mar 18, 2024 8:42 pm
by WinExperements
Thanks for reply! So as I understand we need to save the whole user context before calling the handler and we need to push some additional information onto the stack so when the signal handler will exit we restore the interrupted stack so user process can go work when he is interrupted because of signal? If something wrong please correct me

Re: Questions about signal implementation.

Posted: Tue Mar 19, 2024 9:01 am
by nullplan
Yes, you need to save the entire user context (so all integer registers, as well as all FPU and vector registers). It is typically desirable to also mask out the signal that has been caught (although this is controlled by the SA_NODEFER flag to sigaction(2)), so you typically also want to save the signal mask to the user stack. Then invoke the signal handler by modifying the registers you had incoming. E.g. on x86_64, you would set RIP to the address of the handler, RDI to the signal number, RSI to the address of the siginfo_t you also need to hand to userspace if registered with SA_SIGINFO, and RDX to the address of the user context. Those last two only for SA_SIGINFO, but you can also just do them always. There are really no bad consequences to this.

Also arrange for the handler, when it returns, to run the sigreturn system call, which restores the registers and the signal mask.

Also note that you typically have to do some special handling when the process was in a system call when the signal hit. Although that depends on how you do system calls. Note that there is complex behavior here, and some system calls are interrupted by all signals, and some only if the handler was not registered with SA_RESTART. This is a special case that you are going to hit very frequently, so it does need handling.