1
0
Fork 0
mirror of https://gitlab.com/sortix/sortix.git synced 2023-02-13 20:55:38 -05:00
sortix--sortix/kernel/signal.cpp
2018-12-08 22:54:28 +01:00

1003 lines
31 KiB
C++

/*
* Copyright (c) 2011-2016, 2018 Jonas 'Sortie' Termansen.
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*
* signal.cpp
* Asynchronous user-space thread interruption.
*/
#include <sys/types.h>
#include <assert.h>
#include <errno.h>
#include <string.h>
#include <stdint.h>
#include <stdlib.h>
#include <signal.h>
#include <sortix/sigaction.h>
#include <sortix/signal.h>
#include <sortix/sigset.h>
#include <sortix/stack.h>
#include <sortix/ucontext.h>
#include <sortix/kernel/copy.h>
#include <sortix/kernel/interrupt.h>
#include <sortix/kernel/kernel.h>
#include <sortix/kernel/process.h>
#include <sortix/kernel/ptable.h>
#include <sortix/kernel/signal.h>
#include <sortix/kernel/syscall.h>
#include <sortix/kernel/thread.h>
namespace Sortix {
sigset_t default_ignored_signals;
sigset_t default_stop_signals;
sigset_t unblockable_signals;
// A per-cpu value whether a signal is pending in the running task.
extern "C" { volatile unsigned long asm_signal_is_pending = 0; }
static
void UpdatePendingSignals(Thread* thread) // thread->process->signal_lock held
{
struct sigaction* signal_actions = thread->process->signal_actions;
// Determine which signals wouldn't be ignored if received.
sigset_t handled_signals;
sigemptyset(&handled_signals);
for ( int i = 1; i < SIG_MAX_NUM; i++ )
{
if ( signal_actions[i].sa_handler == SIG_IGN )
continue;
if ( signal_actions[i].sa_handler == SIG_DFL &&
sigismember(&default_ignored_signals, i) )
continue;
// TODO: A process that is a member of an orphaned process group shall
// not be allowed to stop in response to the SIGTSTP, SIGTTIN, or
// SIGTTOU signals. In cases where delivery of one of these
// signals would stop such a process, the signal shall be
// discarded.
if ( /* is member of an orphaned process group */ false &&
signal_actions[i].sa_handler == SIG_DFL &&
sigismember(&default_stop_signals, i) )
continue;
sigaddset(&handled_signals, i);
}
// TODO: Handle that signals can be pending process-wide!
// Discard all requested signals that would be ignored if delivered.
sigandset(&thread->signal_pending, &thread->signal_pending, &handled_signals);
// Determine which signals are not blocked.
sigset_t permitted_signals;
signotset(&permitted_signals, &thread->signal_mask);
sigorset(&permitted_signals, &permitted_signals, &unblockable_signals);
// Determine which signals can currently be delivered to this thread.
sigset_t deliverable_signals;
sigandset(&deliverable_signals, &permitted_signals, &thread->signal_pending);
// Determine whether any signals can be delivered.
unsigned long is_pending = !sigisemptyset(&deliverable_signals) ? 1 : 0;
if ( thread->force_no_signals )
is_pending = 0;
// Store whether a signal is pending in the virtual register.
if ( thread == CurrentThread() )
asm_signal_is_pending = is_pending;
else
thread->registers.signal_pending = is_pending;
}
void Thread::DoUpdatePendingSignal()
{
ScopedLock lock(&process->signal_lock);
UpdatePendingSignals(this);
}
int sys_sigaction(int signum,
const struct sigaction* user_newact,
struct sigaction* user_oldact)
{
if ( signum < 0 || signum == 0 /* null signal */ || SIG_MAX_NUM <= signum )
return errno = EINVAL;
Process* process = CurrentProcess();
ScopedLock lock(&process->signal_lock);
struct sigaction* kact = &process->signal_actions[signum];
// Let the caller know the previous action.
if ( user_oldact )
{
if ( !CopyToUser(user_oldact, kact, sizeof(struct sigaction)) )
return -1;
}
// Retrieve and validate the new signal action.
if ( user_newact )
{
struct sigaction newact;
if ( !CopyFromUser(&newact, user_newact, sizeof(struct sigaction)) )
return -1;
if ( newact.sa_flags & ~__SA_SUPPORTED_FLAGS )
return errno = EINVAL, -1;
if ( newact.sa_handler == SIG_ERR )
return errno = EINVAL, -1;
memcpy(kact, &newact, sizeof(struct sigaction));
// Signals may become discarded because of the new handler.
ScopedLock threads_lock(&process->threadlock);
for ( Thread* t = process->firstthread; t; t = t->nextsibling )
UpdatePendingSignals(t);
}
return 0;
}
int sys_sigaltstack(const stack_t* user_newstack, stack_t* user_oldstack)
{
Thread* thread = CurrentThread();
if ( user_oldstack )
{
if ( !CopyToUser(user_oldstack, &thread->signal_stack, sizeof(stack_t)) )
return -1;
}
if ( user_newstack )
{
stack_t newstack;
if ( !CopyFromUser(&newstack, user_newstack, sizeof(stack_t)) )
return -1;
if ( newstack.ss_flags & ~__SS_SUPPORTED_FLAGS )
return errno = EINVAL, -1;
memcpy(&thread->signal_stack, &newstack, sizeof(stack_t));
}
return 0;
}
int sys_sigpending(sigset_t* set)
{
Process* process = CurrentProcess();
Thread* thread = CurrentThread();
ScopedLock lock(&process->signal_lock);
// TODO: What about process-wide signals?
return CopyToUser(set, &thread->signal_pending, sizeof(sigset_t)) ? 0 : -1;
}
namespace Signal {
void UpdateMask(int how, const sigset_t* set, sigset_t* oldset)
{
Process* process = CurrentProcess();
Thread* thread = CurrentThread();
// TODO: Signal masks are a per-thread property, perhaps this should be
// locked in another manner?
ScopedLock lock(&process->signal_lock);
// Let the caller know the previous signal mask.
if ( oldset )
memcpy(oldset, &thread->signal_mask, sizeof(sigset_t));
// Update the current signal mask according to how.
if ( set )
{
switch ( how )
{
case SIG_BLOCK:
sigorset(&thread->signal_mask, &thread->signal_mask, set);
break;
case SIG_UNBLOCK:
{
sigset_t notset;
signotset(&notset, set);
sigandset(&thread->signal_mask, &thread->signal_mask, &notset);
break;
}
case SIG_SETMASK:
memcpy(&thread->signal_mask, set, sizeof(sigset_t));
break;
};
UpdatePendingSignals(thread);
}
}
} // namespace Signal
int sys_sigprocmask(int how, const sigset_t* user_set, sigset_t* user_oldset)
{
if ( how != SIG_BLOCK && how != SIG_UNBLOCK && how != SIG_SETMASK )
return errno = EINVAL, -1;
sigset_t set, oldset;
if ( user_set && !CopyFromUser(&set, user_set, sizeof(sigset_t)) )
return -1;
Signal::UpdateMask(
how, user_set ? &set : NULL, user_oldset ? &oldset : NULL);
if ( user_oldset && !CopyToUser(user_oldset, &oldset, sizeof(sigset_t)) )
return -1;
return 0;
}
int sys_sigsuspend(const sigset_t* set)
{
Process* process = CurrentProcess();
Thread* thread = CurrentThread();
sigset_t old_signal_mask; sigemptyset(&old_signal_mask);
sigset_t new_signal_mask;
ScopedLock lock(&process->signal_lock);
// Only accept signals from the user-provided set if given.
if ( set )
{
if ( !CopyFromUser(&new_signal_mask, set, sizeof(sigset_t)) )
return -1;
memcpy(&old_signal_mask, &thread->signal_mask, sizeof(sigset_t));
memcpy(&thread->signal_mask, &new_signal_mask, sizeof(sigset_t));
UpdatePendingSignals(thread);
}
// Wait for a signal to happen or otherwise never halt.
kthread_cond_t never_triggered = KTHREAD_COND_INITIALIZER;
while ( !Signal::IsPending() )
kthread_cond_wait_signal(&never_triggered, &process->signal_lock);
// Restore the previous signal mask if the user gave its own set to wait on.
if ( set )
{
memcpy(&thread->signal_mask, &old_signal_mask, sizeof(sigset_t));
UpdatePendingSignals(thread);
}
// The system call never halts or it halts because a signal interrupted it.
return errno = EINTR, -1;
}
int sys_kill(pid_t pid, int signum)
{
// Protect the kernel process.
if ( !pid )
return errno = EPERM, -1;
// TODO: Implement that pid == -1 means all processes!
bool process_group = pid < 0 ? (pid = -pid, true) : false;
ScopedLock lock(&process_family_lock);
Process* process = CurrentProcess()->GetPTable()->Get(pid);
if ( !process )
return errno = ESRCH, -1;
// TODO: Protect init?
// TODO: Check for permission.
// TODO: Check for zombies.
if ( process_group )
{
if ( !process->DeliverGroupSignal(signum) && errno != ESIGPENDING )
return -1;
return errno = 0, 0;
}
if ( !process->DeliverSignal(signum) && errno != ESIGPENDING )
return -1;
return errno = 0, 0;
}
bool Process::DeliverGroupSignal(int signum) // process_family_lock held
{
if ( !groupfirst )
return errno = ESRCH, false;
for ( Process* iter = groupfirst; iter; iter = iter->groupnext )
{
int saved_errno = errno;
if ( !iter->DeliverSignal(signum) && errno != ESIGPENDING )
{
// This is not currently an error condition.
}
errno = saved_errno;
}
return true;
}
bool Process::DeliverSessionSignal(int signum) // process_family_lock held
{
if ( !sessionfirst )
return errno = ESRCH, false;
for ( Process* iter = sessionfirst; iter; iter = iter->sessionnext )
{
int saved_errno = errno;
if ( !iter->DeliverSignal(signum) && errno != ESIGPENDING )
{
// This is not currently an error condition.
}
errno = saved_errno;
}
return true;
}
bool Process::DeliverSignal(int signum)
{
ScopedLock lock(&threadlock);
if ( !firstthread )
return errno = EINIT, false;
// Broadcast particular signals to all the threads in the process.
if ( signum == SIGCONT || signum == SIGSTOP || signum == SIGKILL )
{
int saved_errno = errno;
for ( Thread* t = firstthread; t; t = t->nextsibling )
{
if ( !t->DeliverSignal(signum) && errno != ESIGPENDING )
{
// This is not currently an error condition.
}
}
errno = saved_errno;
return true;
}
// Route the signal to a suitable thread that accepts it.
// TODO: This isn't how signals should be routed to a particular thread.
if ( CurrentThread()->process == this )
return CurrentThread()->DeliverSignal(signum);
return firstthread->DeliverSignal(signum);
}
int sys_raise(int signum)
{
if ( !CurrentThread()->DeliverSignal(signum) && errno != ESIGPENDING )
return -1;
return errno = 0, 0;
}
bool Thread::DeliverSignal(int signum)
{
ScopedLock lock(&process->signal_lock);
return DeliverSignalUnlocked(signum);
}
bool Thread::DeliverSignalUnlocked(int signum) // thread->process->signal_lock held
{
if ( signum <= 0 || SIG_MAX_NUM <= signum )
return errno = EINVAL, false;
// Discard the null signal, which does error checking, but doesn't actually
// deliver a signal to the process or thread.
if ( signum == 0 )
return true;
if ( sigismember(&signal_pending, signum) )
return errno = ESIGPENDING, false;
sigaddset(&signal_pending, signum);
if ( signum == SIGSTOP || signum == SIGTSTP ||
signum == SIGTTIN || signum == SIGTTOU )
sigdelset(&signal_pending, SIGCONT);
if ( signum == SIGCONT )
{
sigdelset(&signal_pending, SIGSTOP);
sigdelset(&signal_pending, SIGTSTP);
sigdelset(&signal_pending, SIGTTIN);
sigdelset(&signal_pending, SIGTTOU);
}
UpdatePendingSignals(this);
return true;
}
static int PickImportantSignal(const sigset_t* set)
{
if ( sigismember(set, SIGKILL) )
return SIGKILL;
if ( sigismember(set, SIGSTOP) )
return SIGSTOP;
for ( int i = 1; i < SIG_MAX_NUM; i++ )
if ( sigismember(set, i) )
return i;
return 0;
}
static void EncodeMachineContext(mcontext_t* mctx,
const struct thread_registers* regs,
const struct interrupt_context* intctx)
{
memset(mctx, 0, sizeof(*mctx));
#if defined(__i386__)
// TODO: REG_GS
// TODO: REG_FS
// TODO: REG_ES
// TODO: REG_DS
mctx->gregs[REG_EDI] = regs->edi;
mctx->gregs[REG_ESI] = regs->esi;
mctx->gregs[REG_EBP] = regs->ebp;
mctx->gregs[REG_ESP] = regs->esp;
mctx->gregs[REG_EBX] = regs->ebx;
mctx->gregs[REG_EDX] = regs->edx;
mctx->gregs[REG_ECX] = regs->ecx;
mctx->gregs[REG_EAX] = regs->eax;
mctx->gregs[REG_EIP] = regs->eip;
// TODO: REG_CS
mctx->gregs[REG_EFL] = regs->eflags & 0x0000FFFF;
mctx->gregs[REG_CR2] = intctx->cr2;
// TODO: REG_SS
memcpy(mctx->fpuenv, regs->fpuenv, 512);
#elif defined(__x86_64__)
mctx->gregs[REG_R8] = regs->r8;
mctx->gregs[REG_R9] = regs->r9;
mctx->gregs[REG_R10] = regs->r10;
mctx->gregs[REG_R11] = regs->r11;
mctx->gregs[REG_R12] = regs->r12;
mctx->gregs[REG_R13] = regs->r13;
mctx->gregs[REG_R14] = regs->r14;
mctx->gregs[REG_R15] = regs->r15;
mctx->gregs[REG_RDI] = regs->rdi;
mctx->gregs[REG_RSI] = regs->rsi;
mctx->gregs[REG_RBP] = regs->rbp;
mctx->gregs[REG_RBX] = regs->rbx;
mctx->gregs[REG_RDX] = regs->rdx;
mctx->gregs[REG_RAX] = regs->rax;
mctx->gregs[REG_RCX] = regs->rcx;
mctx->gregs[REG_RSP] = regs->rsp;
mctx->gregs[REG_RIP] = regs->rip;
mctx->gregs[REG_EFL] = regs->rflags & 0x000000000000FFFF;
// TODO: REG_CSGSFS.
mctx->gregs[REG_CR2] = intctx->cr2;
mctx->gregs[REG_FSBASE] = 0x0;
mctx->gregs[REG_GSBASE] = 0x0;
memcpy(mctx->fpuenv, regs->fpuenv, 512);
#else
#error "You need to implement conversion to mcontext"
#endif
}
static void DecodeMachineContext(const mcontext_t* mctx,
struct thread_registers* regs)
{
#if defined(__i386__) || defined(__x86_64__)
unsigned long user_flags = FLAGS_CARRY | FLAGS_PARITY | FLAGS_AUX
| FLAGS_ZERO | FLAGS_SIGN | FLAGS_DIRECTION
| FLAGS_OVERFLOW;
#endif
#if defined(__i386__)
regs->edi = mctx->gregs[REG_EDI];
regs->esi = mctx->gregs[REG_ESI];
regs->ebp = mctx->gregs[REG_EBP];
regs->esp = mctx->gregs[REG_ESP];
regs->ebx = mctx->gregs[REG_EBX];
regs->edx = mctx->gregs[REG_EDX];
regs->ecx = mctx->gregs[REG_ECX];
regs->eax = mctx->gregs[REG_EAX];
regs->eip = mctx->gregs[REG_EIP];
regs->eflags &= ~user_flags;
regs->eflags |= mctx->gregs[REG_EFL] & user_flags;
memcpy(regs->fpuenv, mctx->fpuenv, 512);
#elif defined(__x86_64__)
regs->r8 = mctx->gregs[REG_R8];
regs->r9 = mctx->gregs[REG_R9];
regs->r10 = mctx->gregs[REG_R10];
regs->r11 = mctx->gregs[REG_R11];
regs->r12 = mctx->gregs[REG_R12];
regs->r13 = mctx->gregs[REG_R13];
regs->r14 = mctx->gregs[REG_R14];
regs->r15 = mctx->gregs[REG_R15];
regs->rdi = mctx->gregs[REG_RDI];
regs->rsi = mctx->gregs[REG_RSI];
regs->rbp = mctx->gregs[REG_RBP];
regs->rbx = mctx->gregs[REG_RBX];
regs->rdx = mctx->gregs[REG_RDX];
regs->rax = mctx->gregs[REG_RAX];
regs->rcx = mctx->gregs[REG_RCX];
regs->rsp = mctx->gregs[REG_RSP];
regs->rip = mctx->gregs[REG_RIP];
regs->rflags &= ~user_flags;
regs->rflags |= mctx->gregs[REG_EFL] & user_flags;
memcpy(regs->fpuenv, mctx->fpuenv, 512);
#else
#error "You need to implement conversion to mcontext"
#endif
}
#if defined(__i386__)
struct stack_frame
{
uintptr_t misalignment[3];
uintptr_t sigreturn;
int signum_param;
siginfo_t* siginfo_param;
ucontext_t* ucontext_param;
void* cookie_param;
uintptr_t canary;
ucontext_t ucontext;
siginfo_t siginfo;
};
#elif defined(__x86_64__)
struct stack_frame
{
uintptr_t misalignment[1];
uintptr_t sigreturn;
uintptr_t canary;
ucontext_t ucontext;
siginfo_t siginfo;
};
#else
#error "You need to implement struct stack_frame"
#endif
void Thread::HandleSignal(struct interrupt_context* intctx)
{
assert(Interrupt::IsEnabled());
assert(this == CurrentThread());
ScopedLock lock(&process->signal_lock);
retry_another_signal:
// Determine which signals are not blocked.
sigset_t permitted_signals;
signotset(&permitted_signals, &signal_mask);
sigorset(&permitted_signals, &permitted_signals, &unblockable_signals);
// Determine which signals can currently be delivered to this thread.
sigset_t deliverable_signals;
sigandset(&deliverable_signals, &permitted_signals, &signal_pending);
// Decide which signal to deliver to the thread.
int signum = PickImportantSignal(&deliverable_signals);
if ( !signum )
{
if ( has_saved_signal_mask )
{
memcpy(&signal_mask, &saved_signal_mask, sizeof(sigset_t));
has_saved_signal_mask = false;
UpdatePendingSignals(this);
goto retry_another_signal;
}
return;
}
// Unmark the selected signal as pending.
sigdelset(&signal_pending, signum);
UpdatePendingSignals(this);
intctx->signal_pending = asm_signal_is_pending;
// Destroy the current thread if the signal is critical.
if ( signum == SIGKILL )
{
lock.Reset();
kthread_exit();
}
struct sigaction* action = &process->signal_actions[signum];
// Stop the current thread upon receipt of a stop signal that isn't handled
// or cannot be handled (SIGSTOP).
if ( (action->sa_handler == SIG_DFL &&
sigismember(&default_stop_signals, signum) ) ||
signum == SIGSTOP )
{
Log::PrintF("%s:%u: `%s' FIXME SIGSTOP\n", __FILE__, __LINE__, __PRETTY_FUNCTION__);
// TODO: Stop the current process.
// TODO: Deliver SIGCHLD to the parent except if SA_NOCLDSTOP is set in
// the parent's SIGCHLD sigaction.
// TODO: SIGCHLD should not be delivered until all the threads in the
// process has received SIGSTOP and stopped?
// TODO: SIGKILL must still be deliverable to a stopped process.
}
// Resume the current thread upon receipt of SIGCONT.
if ( signum == SIGCONT )
{
Log::PrintF("%s:%u: `%s' FIXME SIGCONT\n", __FILE__, __LINE__, __PRETTY_FUNCTION__);
// TODO: Resume the current process.
// TODO: Can SIGCONT be masked?
// TODO: Can SIGCONT be handled?
// TODO: Can SIGCONT be ignored?
// TODO: Deliver SIGCHLD to the parent except if SA_NOCLDSTOP is set in
// the parent's SIGCHLD sigaction.
}
// Signals that would be ignored are already filtered away at this point.
assert(action->sa_handler != SIG_IGN);
assert(action->sa_handler != SIG_DFL || !sigismember(&default_ignored_signals, signum));
// The default action must be to terminate the process. Signals that are
// ignored by default got discarded earlier. If execve() failed, sigreturn
// may be NULL and the process isn't able to properly process signals.
if ( action->sa_handler == SIG_DFL || !process->sigreturn )
{
kthread_mutex_unlock(&process->signal_lock);
process->ExitThroughSignal(signum);
kthread_mutex_lock(&process->signal_lock);
goto retry_another_signal;
}
// At this point we have to attempt to invoke the user-space signal handler,
// which will then return control to us through sigreturn. However, we can't
// save the kernel state because 1) we can't trust the user-space stack 2)
// we can't rely on the kernel stack being intact as the signal handler may
// invoke system calls. For those reasons, we'll have to modify the saved
// registers so they restore a user-space state. We can do this because
// threads in the kernel cannot be delivered signals except when returning
// from a system call, so we'll simply save the state that would have been
// returned to user-space had no signal occured.
if ( !InUserspace(intctx) )
{
#if defined(__i386__)
uint32_t* params = (uint32_t*) intctx->ebx;
intctx->eip = params[0];
intctx->eflags = params[2];
intctx->esp = params[3];
intctx->cs = UCS | URPL;
intctx->ds = UDS | URPL;
intctx->ss = UDS | URPL;
intctx->ebx = 0;
#elif defined(__x86_64__)
intctx->rip = intctx->rdi;
intctx->rflags = intctx->rsi;
intctx->rsp = intctx->r8;
intctx->cs = UCS | URPL;
intctx->ds = UDS | URPL;
intctx->ss = UDS | URPL;
intctx->rdi = 0;
intctx->rsi = 0;
intctx->r8 = 0;
#else
#error "You may need to fix the registers"
#endif
}
struct thread_registers stopped_regs;
Scheduler::SaveInterruptedContext(intctx, &stopped_regs);
sigset_t new_signal_mask;
memcpy(&new_signal_mask, &action->sa_mask, sizeof(sigset_t));
sigorset(&new_signal_mask, &new_signal_mask, &signal_mask);
// Prevent signals from interrupting themselves by default.
if ( !(action->sa_flags & SA_NODEFER) )
sigaddset(&new_signal_mask, signum);
// Determine whether we use an alternate signal stack.
bool signal_uses_altstack = action->sa_flags & SA_ONSTACK;
bool usable_altstack = !(signal_stack.ss_flags & (SS_DISABLE | SS_ONSTACK));
bool use_altstack = signal_uses_altstack && usable_altstack;
// Determine which signal stack to use and what to save.
stack_t old_signal_stack, new_signal_stack;
uintptr_t stack_location;
if ( use_altstack )
{
old_signal_stack = signal_stack;
new_signal_stack = signal_stack;
new_signal_stack.ss_flags |= SS_ONSTACK;
#if defined(__i386__) || defined(__x86_64__)
stack_location = (uintptr_t) signal_stack.ss_sp + signal_stack.ss_size;
#else
#error "You need to implement getting the alternate stack pointer"
#endif
}
else
{
old_signal_stack.ss_sp = NULL;
old_signal_stack.ss_flags = SS_DISABLE;
old_signal_stack.ss_size = 0;
new_signal_stack = signal_stack;
#if defined(__i386__)
stack_location = (uintptr_t) stopped_regs.esp;
#elif defined(__x86_64__)
stack_location = (uintptr_t) stopped_regs.rsp;
#else
#error "You need to implement getting the user-space stack pointer"
#endif
}
struct thread_registers handler_regs;
memcpy(&handler_regs, &stopped_regs, sizeof(handler_regs));
struct stack_frame stack_frame;
memset(&stack_frame, 0, sizeof(stack_frame));
void* handler_ptr = action->sa_flags & SA_COOKIE ?
(void*) action->sa_sigaction_cookie :
action->sa_flags & SA_SIGINFO ?
(void*) action->sa_sigaction :
(void*) action->sa_handler;
#if defined(__i386__)
stack_location -= sizeof(stack_frame);
stack_location &= ~(16UL-1UL); /* 16-byte align */
struct stack_frame* stack = (struct stack_frame*) stack_location;
stack_frame.sigreturn = (uintptr_t) process->sigreturn;
stack_frame.signum_param = signum;
stack_frame.siginfo_param = &stack->siginfo;
stack_frame.ucontext_param = &stack->ucontext;
stack_frame.cookie_param = action->sa_cookie;
handler_regs.esp = (unsigned long) stack + sizeof(stack->misalignment);
handler_regs.eip = (unsigned long) handler_ptr;
handler_regs.eflags &= ~FLAGS_DIRECTION;
#elif defined(__x86_64__)
stack_location -= 128; /* Red zone. */
stack_location -= sizeof(stack_frame);
stack_location &= ~(16UL-1UL); /* 16-byte align */
struct stack_frame* stack = (struct stack_frame*) stack_location;
stack_frame.sigreturn = (uintptr_t) process->sigreturn;
handler_regs.rdi = (unsigned long) signum;
handler_regs.rsi = (unsigned long) &stack->siginfo;
handler_regs.rdx = (unsigned long) &stack->ucontext;
handler_regs.rcx = (unsigned long) action->sa_cookie;
handler_regs.rsp = (unsigned long) stack + sizeof(stack->misalignment);
handler_regs.rip = (unsigned long) handler_ptr;
handler_regs.rflags &= ~FLAGS_DIRECTION;
#else
#error "You need to format the stack frame"
#endif
// Store a canary so it can later be verified this is a real signal being
// returned from.
if ( signal_count == 0 )
arc4random_buf(&signal_canary, sizeof(signal_canary));
stack_frame.canary = signal_canary ^ (uintptr_t) stack;
// Format the siginfo into the stack frame.
stack_frame.siginfo.si_signo = signum;
#if defined(__i386__) || defined(__x86_64__)
// TODO: Is this cr2 value trustworthy? I don't think it is.
if ( signum == SIGSEGV )
stack_frame.siginfo.si_addr = (void*) intctx->cr2;
#else
#warning "You need to tell user-space where it crashed"
#endif
// Format the ucontext into the stack frame.
stack_frame.ucontext.uc_link = NULL;
if ( has_saved_signal_mask )
{
// If a system call temporarily set another signal mask, it wants us to
// deliver signals for that temporary signal masks, however we must
// restore the original saved signal mask in that case.
memcpy(&stack_frame.ucontext.uc_sigmask, &saved_signal_mask,
sizeof(saved_signal_mask));
// 'has_saved_signal_mask' is set to false below when it's actually
// delivered. This handles the case where the signal delivery fails and
// and instead turns into another (we still want to restore the right
// saved mask in that case).
}
else
memcpy(&stack_frame.ucontext.uc_sigmask, &signal_mask, sizeof(signal_mask));
memcpy(&stack_frame.ucontext.uc_stack, &signal_stack, sizeof(signal_stack));
EncodeMachineContext(&stack_frame.ucontext.uc_mcontext, &stopped_regs, intctx);
if ( !CopyToUser(stack, &stack_frame, sizeof(stack_frame)) )
{
// Self-destruct if we crashed during delivering the crash signal.
if ( signum == SIGSEGV )
{
kthread_mutex_unlock(&process->signal_lock);
process->ExitThroughSignal(signum);
kthread_mutex_lock(&process->signal_lock);
goto retry_another_signal;
}
// Deliver SIGSEGV if we could not deliver the signal on the stack.
// TODO: Is it possible to block SIGSEGV here?
kthread_mutex_unlock(&process->signal_lock);
DeliverSignal(SIGSEGV);
kthread_mutex_lock(&process->signal_lock);
goto retry_another_signal;
}
// Update the current signal mask. UpdatePendingSignals isn't called because
// sa_mask was or'd onto the current signal mask, only blocking signals, so
// nothing new can be delivered.
memcpy(&signal_mask, &new_signal_mask, sizeof(sigset_t));
// Update the current alternate signal stack.
signal_stack = new_signal_stack;
// Update the current registers.
Scheduler::LoadInterruptedContext(intctx, &handler_regs);
// Reset the signal handler if this signal handler is once only.
if ( action->sa_flags & SA_RESETHAND )
{
// POSIX mandates SA_RESETHAND is silently ignored for these signals.
if ( signum != SIGILL && signum != SIGTRAP )
{
action->sa_flags &= ~SA_RESETHAND;
action->sa_handler = SIG_DFL;
}
}
// Know for sure when there's no signal still being handled. This is an
// over-approximation as programs may do absolutely awful things such as
// longjmp(3)ing out of signal handlers, so each delivered signal does not
// nessesarily mean a sigreturn. This will work correctly in reasonable
// programs though and will harden those programs. Remember the stack frame
// as well for verification if there is no recursive signal handling.
if ( signal_count != SIZE_MAX )
signal_count++;
if ( (signal_single = signal_count == 1) )
signal_single_frame = (uintptr_t) stack;
// If there was a saved signal mask, it will be restored once the signal
// handler complete, so forget it was ever saved.
has_saved_signal_mask = false;
// Run the signal handler by returning to user-space.
return;
}
void Thread::HandleSigreturn(struct interrupt_context* intctx)
{
assert(Interrupt::IsEnabled());
assert(this == CurrentThread());
struct stack_frame stack_frame;
struct stack_frame* user_stack_frame;
#if defined(__i386__)
user_stack_frame = (struct stack_frame*)
(intctx->esp - offsetof(struct stack_frame, sigreturn) - 4);
bool wrong_ip = intctx->eip != (uintptr_t) process->sigreturn + 2;
#elif defined(__x86_64__)
user_stack_frame = (struct stack_frame*)
(intctx->rsp - offsetof(struct stack_frame, sigreturn) - 8);
bool wrong_ip = intctx->rip != (uintptr_t) process->sigreturn + 2;
#else
#error "You need to locate the stack we passed the signal handler"
#endif
// Protect against sigreturn oriented programming (SROP) as described in
// "Framing Signals - A Return to Portable Shellcode" (Bosman and Bos 2014).
// If the we're not called from the kernel sigreturn page, it is not a valid
// sigreturn.
if ( wrong_ip )
{
process->ExitThroughSignal(SIGABRT);
Log::PrintF("%s[%ji]: sigreturn smashing detected: Bypassed kernel sigreturn page\n",
process->program_image_path,
(intmax_t) process->pid);
// TODO: Allow debugging this event (see scram(2)).
kthread_exit();
}
// If no signals are being serviced by this thread at the moment, it is not
// a valid sigreturn.
if ( signal_count == 0 )
{
process->ExitThroughSignal(SIGABRT);
Log::PrintF("%s[%ji]: sigreturn smashing detected: Thread wasn't servicing a signal\n",
process->program_image_path,
(intmax_t) process->pid);
// TODO: Allow debugging this event (see scram(2)).
kthread_exit();
}
// If a single signal is being serviced by this thread at the moment, the
// stack pointer must be what we expect it to be. If there's multiple, we
// don't know which one is the correct. (We could keep track of them and
// ensure it's one of them, but that's not really worth it. The list of such
// delivered signals could grow without bound because it's valid to longjmp
// out of a signal handler)
if ( signal_single && (uintptr_t) user_stack_frame != signal_single_frame )
{
process->ExitThroughSignal(SIGABRT);
Log::PrintF("%s[%ji]: sigreturn smashing detected: Stack pointer was wrong\n",
process->program_image_path,
(intmax_t) process->pid);
// TODO: Allow debugging this event (see scram(2)).
kthread_exit();
}
// If we couldn't read the frame, the sigreturn is certainly bad.
if ( !CopyFromUser(&stack_frame, user_stack_frame, sizeof(stack_frame)) )
{
process->ExitThroughSignal(SIGABRT);
Log::PrintF("%s[%ji]: sigreturn smashing detected: Couldn't read stack frame: %m\n",
process->program_image_path,
(intmax_t) process->pid);
// TODO: Allow debugging this event (see scram(2)).
kthread_exit();
}
ZeroUser(user_stack_frame, sizeof(*user_stack_frame));
// If the random canary isn't correct, the sigreturn is certianly bad.
if ( stack_frame.canary != (signal_canary ^ (uintptr_t) user_stack_frame) )
{
process->ExitThroughSignal(SIGABRT);
Log::PrintF("%s[%ji]: sigreturn smashing detected: Verification value was incorrect\n",
process->program_image_path,
(intmax_t) process->pid);
// TODO: Allow debugging this event (see scram(2)).
kthread_exit();
}
ScopedLock lock(&process->signal_lock);
memcpy(&signal_mask, &stack_frame.ucontext.uc_sigmask, sizeof(signal_mask));
memcpy(&signal_stack, &stack_frame.ucontext.uc_stack, sizeof(signal_stack));
signal_stack.ss_flags &= __SS_SUPPORTED_FLAGS;
struct thread_registers resume_regs;
Scheduler::SaveInterruptedContext(intctx, &resume_regs);
DecodeMachineContext(&stack_frame.ucontext.uc_mcontext, &resume_regs);
Scheduler::LoadInterruptedContext(intctx, &resume_regs);
if ( signal_count != SIZE_MAX )
signal_count--;
signal_single = false;
UpdatePendingSignals(this);
intctx->signal_pending = asm_signal_is_pending;
lock.Reset();
assert(Interrupt::IsEnabled());
HandleSignal(intctx);
}
namespace Signal {
void DispatchHandler(struct interrupt_context* intctx, void* /*user*/)
{
assert(Interrupt::IsEnabled());
return CurrentThread()->HandleSignal(intctx);
}
void ReturnHandler(struct interrupt_context* intctx, void* /*user*/)
{
assert(Interrupt::IsEnabled());
return CurrentThread()->HandleSigreturn(intctx);
}
void Init()
{
sigemptyset(&default_ignored_signals);
sigaddset(&default_ignored_signals, SIGCHLD);
sigaddset(&default_ignored_signals, SIGURG);
sigaddset(&default_ignored_signals, SIGPWR);
sigaddset(&default_ignored_signals, SIGWINCH);
sigemptyset(&default_stop_signals);
sigaddset(&default_stop_signals, SIGTSTP);
sigaddset(&default_stop_signals, SIGTTIN);
sigaddset(&default_stop_signals, SIGTTOU);
sigemptyset(&unblockable_signals);
sigaddset(&unblockable_signals, SIGKILL);
sigaddset(&unblockable_signals, SIGSTOP);
}
} // namespace Signal
} // namespace Sortix