The idle thread is now actually run when the system is idle because it truly goes idle. The idle thread is made power efficient by using the hlt instruction rather than a busy loop. The new futex(2) system call is used to implement fast user-space mutexes, condition variables, and semaphores. The same backend and design is used as kutexes for truly sleeping kernel mutexes and condition variables. The new exit_thread(2) flag EXIT_THREAD_FUTEX_WAKE wakes a futex. Sleeping on clocks in the kernel now uses timers for true sleep. The interrupt worker thread now truly sleeps when idle. Kernel threads are now named. This is a compatible ABI change.
1006 lines
31 KiB
C++
1006 lines
31 KiB
C++
/*
|
|
* Copyright (c) 2011-2016, 2018, 2021 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
|
|
Scheduler::SetSignalPending(thread, 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(¬set, set);
|
|
sigandset(&thread->signal_mask, &thread->signal_mask, ¬set);
|
|
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);
|
|
|
|
ScopedLock lock(&process->signal_lock);
|
|
|
|
// Only accept signals from the user-provided set if given.
|
|
if ( set )
|
|
{
|
|
sigset_t new_signal_mask;
|
|
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);
|
|
|
|
// The pending signal might only be pending with the temporary signal mask,
|
|
// so don't restore it. Instead ask for the real signal mask to be restored
|
|
// after the signal has been processed.
|
|
if ( set )
|
|
{
|
|
thread->has_saved_signal_mask = true;
|
|
memcpy(&thread->saved_signal_mask, &old_signal_mask, sizeof(sigset_t));
|
|
}
|
|
|
|
// 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
|