#include "core/types.h" #include "core/defs.h" #include "core/param.h" #include "core/proc.h" #include "arch/x86.h" #include "core/syscall.h" #include "core/uaccess.h" #include "uapi/errno.h" #include "uapi/posix.h" /* SIG_BLOCK, SIG_UNBLOCK, SIG_SETMASK, signal numbers */ // Linux sigset_t puts signal N at bit N-1; kernel-internal masks // (pending_signals / signal_mask / deliver_signals) put signal N at bit N. // Convert at the user boundary — storing user sets raw silently shifted // every mask by one signal (blocking SIGCHLD actually blocked SIGCONT), // which let mksh's SIGCHLD handler run inside the very critical sections // sigprocmask() was guarding, wrecking its job table. static uint64 sigset_from_user(uint64 uset) { return uset << 1; } static uint64 sigset_to_user(uint64 kset) { return kset >> 1; } addr_t sys_sigfillset(void) { uint64 *set; if (argptr(0, (char**)&set, sizeof(uint64)) < 0) return -1; if (set) *set = ~0ULL; return 0; } addr_t sys_sigprocmask(void) { int how; addr_t setp, oldsetp; argint(0, &how); argaddr(1, &setp); argaddr(2, &oldsetp); // oldset is optional (NULL = don't return previous mask) if (oldsetp) { uint64 old = sigset_to_user(myproc()->signal_mask); put_user(old, oldsetp); } // set is optional (NULL = just query) if (setp) { uint64 uset; if (copy_from_user(&uset, setp, sizeof(uset)) == 0) { uint64 set = sigset_from_user(uset); if (how == SIG_BLOCK) myproc()->signal_mask |= set; else if (how == SIG_UNBLOCK) myproc()->signal_mask &= ~set; else myproc()->signal_mask = set; } } return 0; } addr_t sys_sigwait(void) { uint64 *set_ptr; int *sig_ptr; if (argptr(0, (char**)&set_ptr, sizeof(uint64)) < 0 || argptr(1, (char**)&sig_ptr, sizeof(int)) < 0) return -1; uint64 set = sigset_from_user(*set_ptr); acquire(&tickslock); while (!(myproc()->pending_signals & set)) { if (myproc()->killed) { release(&tickslock); return -1; } sleep(myproc(), &tickslock); } for (int i = 0; i < 64; i++) { if ((myproc()->pending_signals & set) & (1ULL << i)) { myproc()->pending_signals &= ~(1ULL << i); *sig_ptr = i; break; } } release(&tickslock); return 0; } // The native musl hands over its own struct sigaction (musl 1.2.5 signal.h): // sa_handler at 0, a 128-byte sigset_t, sa_flags (4 bytes) at 136, then // sa_restorer at 144. // // A Linux-ABI process does not. Its musl converts to the kernel's // k_sigaction first -- handler at 0, flags at 8, restorer at 16, mask at 24, // thirty-two bytes in all -- and this used the native offsets for both, so // every Alpine program that asked for the old action had 8 bytes written 112 // bytes past the end of the struct, into whatever its stack held there. #define SA_HANDLER_OFF 0 #define SA_FLAGS_OFF(p) ((p)->syscall_map ? 8 : 136) #define SA_RESTORER_OFF(p) ((p)->syscall_map ? 16 : 144) // rt_sigaction: store the handler + restorer so deliver_signals() (called // from syscall()'s tail, kernel/sys/syscall.c) can actually jump to user // code instead of silently doing nothing. sig 0 is invalid (matches // sigaction(2)); we don't validate against NSIG beyond our own array size. // // User accesses go through copy_from_user/put_user (uaccess.h), which are // page-safe. An earlier version bulk-copied the ~152-byte struct through a // single uva2ka() translation of its start address; when the user's // act/oldact pointer landed close enough to a page boundary, the write ran // past the mapped page into an unrelated physical page, silently crashing // every freshly exec'd /bin/sh (mksh calls sigaction() during startup). addr_t sys_rt_sigaction(void) { int sig; addr_t actp, oldactp; argint(0, &sig); argaddr(1, &actp); argaddr(2, &oldactp); if (sig <= 0 || sig >= 32) return -1; struct proc *p = myproc(); if (oldactp) { put_user(p->sighand->handler[sig], oldactp + SA_HANDLER_OFF); put_user(p->sighand->restorer[sig], oldactp + SA_RESTORER_OFF(p)); if (p->syscall_map) { put_user(p->sighand->flags[sig], oldactp + SA_FLAGS_OFF(p)); } else { uint32 fl = (uint32)p->sighand->flags[sig]; put_user(fl, oldactp + SA_FLAGS_OFF(p)); } } if (actp) { uint64 h, r, fl = 0; if (copy_from_user(&h, actp + SA_HANDLER_OFF, sizeof(h)) < 0 || copy_from_user(&r, actp + SA_RESTORER_OFF(p), sizeof(r)) < 0 || copy_from_user(&fl, actp + SA_FLAGS_OFF(p), p->syscall_map ? 8 : 4) < 0) return -EFAULT; // SIGKILL and SIGSTOP cannot be caught or ignored. if (sig == SIGKILL || sig == SIGSTOP) return -EINVAL; p->sighand->handler[sig] = h; p->sighand->restorer[sig] = r; p->sighand->flags[sig] = fl; } return 0; } // rt_sigreturn: entered via `syscall` (rax=15) from musl's __restore_rt // trampoline, which is exactly what we set as the manufactured return // address in deliver_signals() below — the handler function's own `ret` // lands here once it's done. Restore the trapframe deliver_signals() // stashed before redirecting to the handler, so syscall()'s normal // sysretq path resumes the interrupted code exactly where it left off. addr_t sys_rt_sigreturn(void) { addr_t saved_rax = myproc()->saved_sigtf.rax; *myproc()->tf = myproc()->saved_sigtf; fxrstor(myproc()->sigfpu); // what the interrupted code had myproc()->in_signal = 0; // If this handler interrupted a sigsuspend(), put its pre-suspend // signal mask back now that the handler has finished. if (myproc()->sigsusp_active) { myproc()->signal_mask = myproc()->sigsusp_oldmask; myproc()->sigsusp_active = 0; } // syscall() does `tf->rax = ` right after this // returns — hand back the already-restored rax so that assignment is // a harmless no-op instead of clobbering what we just restored. return saved_rax; } // Called from syscall()'s tail (kernel/sys/syscall.c), right before // returning to user mode, for every syscall a process makes (this is the // only "check point" we have without also hooking the interrupt-return // path in trap.c — sufficient for what actually needed this: a process // blocked in a syscall, like sigsuspend(), waiting for its own next // syscall return to notice a signal arrived). // // If there's a pending, unblocked signal with a registered handler, // redirect tf to it: rdi = signal number (SysV ABI first arg), rip = // handler. // // The return address (what the handler's own `ret` lands on) can't be // sig_restorer[sig] — this musl build has no SA_RESTORER defined at all // (checked: not defined anywhere in musl 1.2.5), so sigaction() never // fills it in; it's always 0. Pushing 0 as a return address is exactly // what crashed every mksh instance the first time a signal was ever // actually delivered (handler ran, `ret` jumped to address 0). Instead, // inject a tiny trampoline directly onto the user's own stack — 7 bytes // of machine code for `mov eax, 15; syscall` (rt_sigreturn) — below the // current stack pointer (clear of the SysV red zone) and point the // return address at that, the standard way a kernel supplies sigreturn // when the C library doesn't hand it one itself. void deliver_signals(struct trapframe *tf) { if (myproc()->in_signal) return; // no nested delivery uint64 deliverable = myproc()->pending_signals & ~myproc()->signal_mask; if (!deliverable) return; for (int sig = 1; sig < 32; sig++) { if (!(deliverable & (1ULL << sig))) continue; // SIG_DFL is 0 and SIG_IGN is 1 -- neither is an address to jump to. // // Only the 0 case was checked here. A process that had asked to // ignore a signal got its handler "called" at address 1, which is a // page fault at rip=0x1 and a dead process. Nothing sent SIGPIPE // until pipes learned to, at which point every shell that ignores // SIGPIPE -- which is every shell -- died the first time a pipeline // ended early. `cat big | head -c 5` was enough. // // The default action for a signal with no handler has already been // applied by sigsend(); all that is left here is to stop it being // pending, which the old `continue` never did, so an ignored signal // was re-examined on the way out of every syscall forever after. // // Except for a signal that was blocked when it arrived: sigsend() // left it pending without acting, and now that it is deliverable its // default action happens here. // SIGSTOP cannot be caught or ignored; the other three stop only by // default. Stopped here and resumed here, by SIGCONT or SIGKILL. if (sig == SIGSTOP || (sig_default_stops(sig) && myproc()->sighand->handler[sig] == 0)) { myproc()->pending_signals &= ~(1ULL << sig); proc_stop(sig); if (myproc()->killed) exit(W_EXITCODE(0, myproc()->killsig ? myproc()->killsig : SIGKILL)); continue; } if (myproc()->sighand->handler[sig] <= 1) { myproc()->pending_signals &= ~(1ULL << sig); if (myproc()->sighand->handler[sig] == 0 && sig_default_kills(sig)) { myproc()->killed = 1; if (myproc()->killsig == 0) myproc()->killsig = sig; } continue; } uint64 tramp_addr = (tf->rsp - 256) & ~0xFULL; uint8 *tramp = (uint8*)uva2ka(myproc()->pgdir, (char*)tramp_addr); if (!tramp) continue; // can't deliver safely; try another pending signal uint64 sp = tramp_addr - 8; uint64 *retaddr = (uint64*)uva2ka(myproc()->pgdir, (char*)sp); if (!retaddr) continue; // The return trampoline: `mov eax, ; syscall`. // // **Which number depends on the process's ABI**, and it used to be // hardcoded to 15. Fifteen is Linux's rt_sigreturn, which is right for // anything musl-linked -- every port, and the shells. It is *native* // syscall 15 for a first-party binary, and native 15 is `open`. So a // handler in any cmd/ program ran, returned into the trampoline, called // open() with the handler's leftover registers as arguments, got // -ENOENT, and carried on executing the bytes after the trampoline -- // which are stack, not code. // // See table.inc: SYSCALL(97, 15, rt_sigreturn). syscall_map is the // thing that says which numbering this process speaks. uint32 sret = myproc()->syscall_map ? 15 : SYS_rt_sigreturn; tramp[0] = 0xb8; // mov eax, imm32 tramp[1] = (uint8)(sret ); tramp[2] = (uint8)(sret >> 8); tramp[3] = (uint8)(sret >> 16); tramp[4] = (uint8)(sret >> 24); tramp[5] = 0x0f; tramp[6] = 0x05; // syscall *retaddr = tramp_addr; myproc()->pending_signals &= ~(1ULL << sig); myproc()->saved_sigtf = *tf; // The handler is free to use %xmm; the code it interrupted had values // there. Put back by rt_sigreturn. fxsave(myproc()->sigfpu); myproc()->in_signal = 1; tf->rsp = sp; tf->rdi = (uint64)sig; // Both return paths must point at the handler: iretq-based returns // use tf->rip, but syscall_trapret exits via sysretq, which takes // the user RIP from RCX and *discards* tf->rip (trapasm.S pops rcx // from the frame, then `addq $40` throws rip/cs/rflags away). With // only tf->rip set, the "delivered" handler never actually ran on // the syscall path: user code resumed at the old rcx with the // shifted rsp, and its ret fell into the sigreturn trampoline, // silently undoing everything. exec() handles this same quirk by // setting tf->rcx = elf.entry. tf->rip = myproc()->sighand->handler[sig]; tf->rcx = myproc()->sighand->handler[sig]; return; } } // sigaltstack: set/get alternate signal stack. // We don't deliver signals, so just store/return zeros. addr_t sys_sigaltstack(void) { return 0; } // rt_sigpending: write the set of pending signals to user buffer. addr_t sys_rt_sigpending(void) { addr_t setp; argaddr(0, &setp); if (setp) { uint64 pend = sigset_to_user(myproc()->pending_signals); put_user(pend, setp); } return 0; } // rt_sigtimedwait: wait for a signal from the set; returns -EAGAIN immediately. addr_t sys_rt_sigtimedwait(void) { return -EAGAIN; } // rt_sigsuspend: atomically replace the signal mask and suspend until a // signal not blocked by the *temporary* mask is pending, then return -EINTR. // // The atomic mask swap is the entire point of sigsuspend(2): mksh blocks // SIGCHLD while it inspects its job table, then calls sigsuspend() with the // old (SIGCHLD-unblocked) mask to wait for a child. The previous stub // ignored the mask argument and just slept one tick — SIGCHLD stayed // blocked, deliver_signals() could never fire the handler, and the shell // hung forever after running any external command (unreaped zombie child, // shell parked in sigsuspend: the "can't type after a command" hang). // // The temporary mask must still be in effect when deliver_signals() runs at // syscall()'s tail (that's what allows the delivery), so the old mask can't // be restored here — sys_rt_sigreturn() restores it once the handler // finishes, via sigsusp_oldmask/sigsusp_active. addr_t sys_rt_sigsuspend(void) { addr_t maskp; uint64 newmask; argaddr(0, &maskp); uint64 oldmask = myproc()->signal_mask; if (maskp && copy_from_user(&newmask, maskp, sizeof(newmask)) == 0) myproc()->signal_mask = sigset_from_user(newmask); acquire(&tickslock); while (!(myproc()->pending_signals & ~myproc()->signal_mask) && !myproc()->killed) sleep(&ticks, &tickslock); release(&tickslock); // Will any pending signal actually reach a handler? If so, the mask is // restored by sigreturn after the handler runs; otherwise (no handler // installed, or we were killed) restore it right now. uint64 deliverable = myproc()->pending_signals & ~myproc()->signal_mask; int will_deliver = 0; for (int sig = 1; sig < 32; sig++) if ((deliverable & (1ULL << sig)) && myproc()->sighand->handler[sig]) { will_deliver = 1; break; } if (will_deliver && !myproc()->in_signal) { myproc()->sigsusp_oldmask = oldmask; myproc()->sigsusp_active = 1; } else { myproc()->signal_mask = oldmask; } return -EINTR; }