diff --git a/apps/CMakeLists.txt b/apps/CMakeLists.txt index be45f1c8..d9d89610 100644 --- a/apps/CMakeLists.txt +++ b/apps/CMakeLists.txt @@ -30,6 +30,7 @@ luna_app(uname.cpp uname) luna_app(base64.cpp base64) luna_app(login.cpp login) luna_app(ipc-test.cpp ipc-test) +luna_app(signal-test.cpp signal-test) luna_app(mount.cpp mount) luna_app(umount.cpp umount) luna_app(ps.cpp ps) diff --git a/apps/signal-test.cpp b/apps/signal-test.cpp new file mode 100644 index 00000000..3d27491a --- /dev/null +++ b/apps/signal-test.cpp @@ -0,0 +1,20 @@ +#include +#include + +void handler(int) +{ + puts("I'm a signal handler"); +} + +int main() +{ + struct sigaction sa; + sa.sa_handler = handler; + sigaction(SIGABRT, &sa, NULL); + + raise(SIGABRT); + + puts("I'm outside the signal handler!"); + + return 0; +} diff --git a/kernel/CMakeLists.txt b/kernel/CMakeLists.txt index cc3d5c82..10c3b425 100644 --- a/kernel/CMakeLists.txt +++ b/kernel/CMakeLists.txt @@ -40,6 +40,7 @@ set(SOURCES src/sys/uname.cpp src/sys/mount.cpp src/sys/resource.cpp + src/sys/signal.cpp src/fs/VFS.cpp src/fs/Pipe.cpp src/fs/Mount.cpp diff --git a/kernel/src/arch/x86_64/Thread.cpp b/kernel/src/arch/x86_64/Thread.cpp index 671e9eca..2934e68a 100644 --- a/kernel/src/arch/x86_64/Thread.cpp +++ b/kernel/src/arch/x86_64/Thread.cpp @@ -1,5 +1,9 @@ #include "thread/Thread.h" +#include "Log.h" +#include "memory/MemoryManager.h" +#include #include +#include bool is_in_kernel(Registers* regs) { @@ -31,6 +35,11 @@ void Thread::set_return(u64 ret) regs.rax = ret; } +u64 Thread::return_register() +{ + return regs.rax; +} + void Thread::init_regs_kernel() { memset(®s, 0, sizeof(Registers)); @@ -61,3 +70,87 @@ void switch_context(Thread* old_thread, Thread* new_thread, Registers* regs) memcpy(regs, &new_thread->regs, sizeof(Registers)); } + +// FIXME: Move this function to a common location (also used in ThreadImage) +Result Thread::push_mem_on_stack(const u8* mem, usize size) +{ + if ((regs.rsp - size) < stack.bottom()) return err(E2BIG); + + if (!MemoryManager::validate_user_write((void*)(regs.rsp - size), size)) return err(EFAULT); + + regs.rsp -= size; + + memcpy((void*)regs.rsp, mem, size); + + return regs.rsp; +} + +Result Thread::pop_mem_from_stack(u8* mem, usize size) +{ + if ((regs.rsp + size) > stack.top()) return err(E2BIG); + + if (!MemoryManager::validate_user_read((void*)regs.rsp, size)) return err(EFAULT); + + memcpy(mem, (void*)regs.rsp, size); + + regs.rsp += size; + + return regs.rsp; +} + +bool Thread::deliver_signal(int signo, Registers* current_regs) +{ + auto handler = signal_handlers[signo - 1]; + check(handler.sa_handler != SIG_IGN); + check(handler.sa_handler != SIG_DFL); + + memcpy(®s, current_regs, sizeof(regs)); + + kinfoln("signal: delivering signal %d for thread %ld, ip=%p, sp=%p, handler=%p, sigreturn=%p", signo, id, + (void*)regs.rip, (void*)regs.rsp, (void*)handler.sa_handler, (void*)handler.__sa_sigreturn); + + regs.rsp -= 128; // Skip the red zone + + if (push_mem_on_stack((u8*)current_regs, sizeof(*current_regs)).has_error()) return false; + if (push_mem_on_stack((u8*)&signal_mask, sizeof(signal_mask)).has_error()) return false; + + u64 rsp = regs.rsp; + + regs.rsp = align_down<16>(regs.rsp); + if (push_mem_on_stack((u8*)&rsp, sizeof(u64)).has_error()) return false; + if (push_mem_on_stack((u8*)&handler.__sa_sigreturn, sizeof(void*)).has_error()) return false; + + // FIXME: Do not add the current signal to the signal mask if SA_NODEFER is set. + signal_mask = handler.sa_mask | (1 << (signo - 1)); + + rsp = regs.rsp; + + init_regs_user(); + regs.rsp = rsp; + regs.rip = (u64)handler.sa_handler; + regs.rdi = signo; + + memcpy(current_regs, ®s, sizeof(regs)); + + return true; +} + +void Thread::sigreturn(Registers* current_regs) +{ + memcpy(®s, current_regs, sizeof(regs)); + + u64 rsp; + pop_mem_from_stack((u8*)&rsp, sizeof(rsp)); + regs.rsp = rsp; + pop_mem_from_stack((u8*)&signal_mask, sizeof(signal_mask)); + pop_mem_from_stack((u8*)current_regs, sizeof(*current_regs)); + memcpy(®s, current_regs, sizeof(regs)); + regs.cs = 0x18 | 3; + regs.ss = 0x20 | 3; + // FIXME: Using this, a program can craft a special RFLAGS that gives them a higher IOPL or other stuff. Find out + // exactly what bits to block from modifying. + + kinfoln("sigreturn: restored program state, sp=%p, ip=%p", (void*)regs.rsp, (void*)regs.rip); + + memcpy(current_regs, ®s, sizeof(regs)); +} diff --git a/kernel/src/sys/Syscall.cpp b/kernel/src/sys/Syscall.cpp index d45552aa..a19a35be 100644 --- a/kernel/src/sys/Syscall.cpp +++ b/kernel/src/sys/Syscall.cpp @@ -1,4 +1,5 @@ #include "sys/Syscall.h" +#include "thread/Scheduler.h" #include syscall_func_t syscalls[] = { @@ -13,6 +14,9 @@ i64 invoke_syscall(Registers* regs, SyscallArgs args, u64 syscall) if (syscall >= Syscalls::__count) { return -ENOSYS; } auto rc = syscalls[syscall](regs, args); + + Scheduler::current()->process_pending_signals(regs); + if (rc.has_error()) return -rc.error(); return (i64)rc.value(); } diff --git a/kernel/src/sys/exec.cpp b/kernel/src/sys/exec.cpp index 5ef59522..fe6a8623 100644 --- a/kernel/src/sys/exec.cpp +++ b/kernel/src/sys/exec.cpp @@ -112,6 +112,11 @@ Result sys_execve(Registers* regs, SyscallArgs args) memcpy(regs, ¤t->regs, sizeof(*regs)); + for (int i = 0; i < NSIG; i++) + { + current->signal_handlers[i] = { .sa_handler = SIG_DFL, .sa_mask = 0, .sa_flags = 0 }; + } + kinfoln("exec: thread %lu was replaced with %s", current->id, path.chars()); return 0; @@ -151,6 +156,13 @@ Result sys_fork(Registers* regs, SyscallArgs) memcpy(&thread->regs, regs, sizeof(*regs)); + for (int i = 0; i < NSIG; i++) + { + auto sighandler = current->signal_handlers[i].sa_handler; + thread->signal_handlers[i] = { .sa_handler = sighandler, .sa_mask = 0, .sa_flags = 0 }; + } + thread->signal_mask = current->signal_mask; + thread->set_return(0); Scheduler::add_thread(thread); diff --git a/kernel/src/sys/signal.cpp b/kernel/src/sys/signal.cpp new file mode 100644 index 00000000..a0bfdd36 --- /dev/null +++ b/kernel/src/sys/signal.cpp @@ -0,0 +1,68 @@ +#include "Log.h" +#include "memory/MemoryManager.h" +#include "sys/Syscall.h" +#include "thread/Scheduler.h" + +Result sys_sigreturn(Registers* regs, SyscallArgs) +{ + auto* current = Scheduler::current(); + + current->sigreturn(regs); + + // Since we could be returning to anywhere in the program, we don't want to be changing the return value register + // (RAX on x86_64). But our syscall framework doesn't allow that, so we just set it to its current value. + return current->return_register(); +} + +Result sys_sigaction(Registers*, SyscallArgs args) +{ + auto* current = Scheduler::current(); + + int signo = (int)args[0]; + const struct sigaction* act = (const struct sigaction*)args[1]; + struct sigaction* oldact = (struct sigaction*)args[2]; + void* sigreturn = (void*)args[3]; + + if (signo > NSIG) return err(EINVAL); + if (signo <= 0) return err(EINVAL); + + if (oldact) + { + if (!MemoryManager::copy_to_user_typed(oldact, ¤t->signal_handlers[signo - 1])) return err(EFAULT); + } + + if (act) + { + struct sigaction kact; + if (!MemoryManager::copy_from_user_typed(act, &kact)) return err(EFAULT); + kact.__sa_sigreturn = sigreturn; + + kinfoln("sigaction: installing signal handler for signo=%d: handler=%p, sigreturn=%p", signo, + (void*)kact.sa_handler, sigreturn); + + current->signal_handlers[signo - 1] = kact; + } + + return 0; +} + +Result sys_kill(Registers*, SyscallArgs args) +{ + auto* current = Scheduler::current(); + + pid_t pid = (pid_t)args[0]; + int signo = (int)args[1]; + + // FIXME: Support this case. + if (pid <= 0) return err(ENOTSUP); + + auto* target = TRY(Result::from_option(Scheduler::find_by_pid(pid), ESRCH)); + if (current->auth.euid != 0 && current->auth.euid != target->auth.euid && current->auth.egid != target->auth.egid) + return err(EPERM); + if (target->is_kernel) return 0; + if (signo == 0) return 0; + + target->pending_signals |= 1 << (signo - 1); + + return 0; +} diff --git a/kernel/src/thread/Scheduler.cpp b/kernel/src/thread/Scheduler.cpp index 77d94238..e033d1b2 100644 --- a/kernel/src/thread/Scheduler.cpp +++ b/kernel/src/thread/Scheduler.cpp @@ -155,6 +155,11 @@ namespace Scheduler image->apply(thread); thread->set_arguments(args.size(), argv, env.size(), envp); + for (int i = 0; i < NSIG; i++) + { + thread->signal_handlers[i] = { .sa_handler = SIG_DFL, .sa_mask = 0, .sa_flags = 0 }; + } + kinfoln("Created userspace thread: id %lu with ip %#.16lx and sp %#.16lx (ksp %#lx)", thread->id, thread->ip(), thread->sp(), thread->kernel_stack.top()); @@ -259,6 +264,7 @@ namespace Scheduler Thread* old_thread = g_current; Thread* new_thread = pick_task(); generic_switch_context(old_thread, new_thread, regs); + if (!is_in_kernel(regs)) new_thread->process_pending_signals(regs); } void invoke(Registers* regs) diff --git a/kernel/src/thread/Thread.cpp b/kernel/src/thread/Thread.cpp index 8c7d1eb4..776d189d 100644 --- a/kernel/src/thread/Thread.cpp +++ b/kernel/src/thread/Thread.cpp @@ -1,4 +1,5 @@ #include "thread/Thread.h" +#include "Log.h" #include "memory/MemoryManager.h" #include "thread/Scheduler.h" #include @@ -97,6 +98,37 @@ Result> Thread::resolve_atfile(int dirfd, const String& pa unreachable(); } +void Thread::process_pending_signals(Registers* current_regs) +{ + for (int i = 0; i < NSIG; i++) + { + if (signal_mask & (1 << i)) continue; + if (pending_signals & (1 << i)) + { + int signo = i + 1; + pending_signals &= ~(1 << i); + kinfoln("signal: executing signal %d for thread %ld", signo, id); + auto handler = signal_handlers[i]; + if (handler.sa_handler == SIG_IGN) + { + kinfoln("signal: ignoring signal (handler=SIG_IGN)"); + return; + } + if (handler.sa_handler == SIG_DFL) + { + default_signal: + kinfoln("signal: using default behavior (handler=SIG_DFL) (terminating)"); + // FIXME: Add different default handlers for different signals and add signal exit codes. + exit_and_signal_parent(255); + } + // If we fail to deliver the signal (usually because there's not enough space on the stack), execute the + // default action. + if (!deliver_signal(signo, current_regs)) goto default_signal; + return; + } + } +} + bool FileDescriptor::should_append() { return flags & O_APPEND; diff --git a/kernel/src/thread/Thread.h b/kernel/src/thread/Thread.h index 6e979095..e568bb44 100644 --- a/kernel/src/thread/Thread.h +++ b/kernel/src/thread/Thread.h @@ -3,6 +3,7 @@ #include "arch/MMU.h" #include "fs/VFS.h" #include "memory/AddressSpace.h" +#include #include #include #include @@ -79,6 +80,11 @@ struct Thread : public LinkedListNode bool follow_last_symlink, SharedPtr* parent_inode = nullptr); + struct sigaction signal_handlers[NSIG]; + sigset_t signal_mask { 0 }; + + int pending_signals { 0 }; + FPData fp_data; ThreadState state = ThreadState::Runnable; @@ -128,6 +134,15 @@ struct Thread : public LinkedListNode u64 sp(); void set_return(u64 ret); + u64 return_register(); + + void process_pending_signals(Registers* current_regs); + + bool deliver_signal(int signo, Registers* current_regs); + void sigreturn(Registers* current_regs); + + Result push_mem_on_stack(const u8* mem, usize size); + Result pop_mem_from_stack(u8* mem, usize size); static void init(); }; diff --git a/libc/CMakeLists.txt b/libc/CMakeLists.txt index 307e1523..5edda97a 100644 --- a/libc/CMakeLists.txt +++ b/libc/CMakeLists.txt @@ -21,6 +21,7 @@ set(SOURCES src/grp.cpp src/locale.cpp src/scanf.cpp + src/signal.cpp src/sys/stat.cpp src/sys/mman.cpp src/sys/wait.cpp @@ -36,6 +37,7 @@ if(${LUNA_ARCH} STREQUAL "x86_64") ${SOURCES} src/arch/x86_64/syscall.S src/arch/x86_64/setjmp.S + src/arch/x86_64/sigreturn.S ) endif() diff --git a/libc/include/bits/signal.h b/libc/include/bits/signal.h new file mode 100644 index 00000000..c562c5a8 --- /dev/null +++ b/libc/include/bits/signal.h @@ -0,0 +1,29 @@ +/* bits/signal.h: Signal-related definitions. */ + +#ifndef _BITS_SIGNAL_H +#define _BITS_SIGNAL_H + +typedef void (*__simple_sighandler_t)(int); + +#define SIG_IGN (__simple_sighandler_t)(-1) +#define SIG_DFL (__simple_sighandler_t)(-2) + +typedef int sigset_t; + +struct sigaction +{ + __simple_sighandler_t sa_handler; + sigset_t sa_mask; + int sa_flags; + void* __sa_sigreturn = nullptr; +}; + +enum __signals +{ + SIGABRT = 1, + __NSIG, +}; + +#define NSIG (__NSIG - 1) + +#endif diff --git a/libc/include/signal.h b/libc/include/signal.h index 0b0b6bf2..b12ccb80 100644 --- a/libc/include/signal.h +++ b/libc/include/signal.h @@ -3,6 +3,30 @@ #ifndef _SIGNAL_H #define _SIGNAL_H +#include +#include + typedef int sig_atomic_t; +#ifdef __cplusplus +extern "C" +{ +#endif + +#pragma GCC push_options +#pragma GCC diagnostic ignored "-Wshadow" + /* Examine/change the current thread's signal disposition for a specific signal. */ + int sigaction(int signo, const struct sigaction* act, struct sigaction* oldact); +#pragma GCC pop_options + + /* Send a signal to a specific process. */ + int kill(pid_t pid, int signo); + + /* Send a signal to the current thread. */ + int raise(int signo); + +#ifdef __cplusplus +} +#endif + #endif diff --git a/libc/src/arch/x86_64/sigreturn.S b/libc/src/arch/x86_64/sigreturn.S new file mode 100644 index 00000000..44aeee30 --- /dev/null +++ b/libc/src/arch/x86_64/sigreturn.S @@ -0,0 +1,5 @@ +.global sigreturn +sigreturn: + mov $47, %rax + int $66 + ud2 diff --git a/libc/src/signal.cpp b/libc/src/signal.cpp new file mode 100644 index 00000000..07455bdd --- /dev/null +++ b/libc/src/signal.cpp @@ -0,0 +1,26 @@ +#include +#include +#include +#include + +extern "C" void sigreturn(); + +extern "C" +{ + int sigaction(int signo, const struct sigaction* act, struct sigaction* oldact) + { + long rc = syscall(SYS_sigaction, signo, act, oldact, sigreturn); + __errno_return(rc, int); + } + + int kill(pid_t pid, int signo) + { + long rc = syscall(SYS_kill, pid, signo); + __errno_return(rc, int); + } + + int raise(int signo) + { + return kill(getpid(), signo); + } +} diff --git a/libluna/include/luna/Syscall.h b/libluna/include/luna/Syscall.h index 5a6d6609..93db68ab 100644 --- a/libluna/include/luna/Syscall.h +++ b/libluna/include/luna/Syscall.h @@ -6,7 +6,7 @@ _e(getgid) _e(getegid) _e(setuid) _e(setgid) _e(seteuid) _e(setegid) _e(fchmodat) _e(fchownat) _e(ioctl) \ _e(fstatat) _e(chdir) _e(getcwd) _e(unlinkat) _e(uname) _e(sethostname) _e(dup2) _e(pipe) _e(mount) \ _e(umount) _e(pstat) _e(getrusage) _e(symlinkat) _e(readlinkat) _e(umask) _e(linkat) _e(faccessat) \ - _e(pivot_root) + _e(pivot_root) _e(sigreturn) _e(sigaction) _e(kill) enum Syscalls {