Questions about signals implementation
Posted: Mon Dec 14, 2020 10:08 pm
I'm in the process of adding support for signals in my kernel, and I have a couple of questions about the design implications of the POSIX API.
According to spec, the extended signature for a signal handler is: where context is actually a pointer to type ucontext_t, which has at least the following members:
The uc_mcontext member is a machine-specific representation of the interrupted context, and the signal handler can examine and modify its contents (at the cost of portability). If any changes to the context are applied on resuming, then we have a potential security and stability risk: if the interrupted context is in the kernel (e.g., in a system call), the signal handler can cause arbitrary kernel code to be executed. So either we treat the uc_mcontext member as read-only (i.e., just pass in a copy and ignore any changes), or we ensure that the interrupted context is always in user space and validate that is still the case before resuming. Treating the context as read-only prevents the execution of random kernel code, but it still exposes addresses and register contents from the kernel, which seems undesirable. So we have to pass in the interrupted context from user space, which pretty much necessitates that signals are delivered at the point where the kernel would return to user space (there are other reasons for this as well). However, this seems to complicate restarting of system calls.
Consider a scenario where a thread is waiting inside the kernel in some system call, and it gets interrupted because of a signal. In order to deliver the signal, we need to save the user context, which requires exiting the system call and returning up the kernel call stack until all the user space register values are restored. At this point, we can save the user context and invoke the signal handler. When the signal handler returns back to the kernel, if SA_RESTART was specified in sigaction() flags when the handler was specified, then we need to restart the interrupted system call. However, since we unwound the kernel stack in order to deliver the signal, there is no generic way to restart properly (each system call will require its own restart logic). So how can we restart the system call within the kernel? Seems like we have to return to user space and have the user space system call wrapper handle the restart. Am I overlooking something?
According to spec, the extended signature for a signal handler is:
Code: Select all
void handler(int signo, siginfo_t* info, void* context);
Code: Select all
typedef struct ucontext_t
{
struct ucontext_t* uc_link;
sigset_t uc_sigmask;
stack_t uc_stack;
mcontext_t uc_mcontext;
} ucontext_t;
Consider a scenario where a thread is waiting inside the kernel in some system call, and it gets interrupted because of a signal. In order to deliver the signal, we need to save the user context, which requires exiting the system call and returning up the kernel call stack until all the user space register values are restored. At this point, we can save the user context and invoke the signal handler. When the signal handler returns back to the kernel, if SA_RESTART was specified in sigaction() flags when the handler was specified, then we need to restart the interrupted system call. However, since we unwound the kernel stack in order to deliver the signal, there is no generic way to restart properly (each system call will require its own restart logic). So how can we restart the system call within the kernel? Seems like we have to return to user space and have the user space system call wrapper handle the restart. Am I overlooking something?