Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions kernel/arch/aarch64/signal/delivery.cpp
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#include "signal/delivery.h"
#include "arch/arch_signal.h"
#include "sched/fpu.h"
#include "sched/sched.h"
#include "sched/task.h"
Expand Down Expand Up @@ -137,3 +138,30 @@ __PRIVILEGED_CODE int64_t restore_signal_frame(trap_frame* tf) {
}

} // namespace aarch64

__PRIVILEGED_CODE int64_t arch::deliver_pending_signal(sched::task* self,
int64_t result) {
uint32_t sig = 0;
signals::k_sigaction act{};
signals::sig_set_t old_blocked = 0;
if (!signals::take_deliverable(self, &sig, &act, &old_blocked)) {
return result;
}

// Same restorer contract as x86_64, the bundled musl always passes one
if (!(act.flags & signals::SA_RESTORER) || !act.restorer) {
signals::die_from_signal(signals::SIGSEGV);
}

if (aarch64::build_signal_frame(aarch64::current_trap_frame(), sig, &act,
old_blocked, result) != 0) {
signals::die_from_signal(signals::SIGSEGV);
}

// The dispatcher writes this into x0, matching the frame's sig argument
return static_cast<int64_t>(sig);
}

__PRIVILEGED_CODE int64_t arch::restore_signal_context() {
return aarch64::restore_signal_frame(aarch64::current_trap_frame());
}
1 change: 1 addition & 0 deletions kernel/arch/aarch64/syscall/linux_syscalls.h
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ constexpr uint64_t TGKILL = 131;
constexpr uint64_t RT_SIGACTION = 134;
constexpr uint64_t RT_SIGPROCMASK = 135;
constexpr uint64_t RT_SIGPENDING = 136;
constexpr uint64_t RT_SIGRETURN = 139;
constexpr uint64_t SETPGID = 154;
constexpr uint64_t GETPGID = 155;
constexpr uint64_t UNAME = 160;
Expand Down
13 changes: 13 additions & 0 deletions kernel/arch/aarch64/trap/trap_frame.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
#define STELLUX_ARCH_AARCH64_TRAP_TRAP_FRAME_H

#include "types.h"
#include "sched/task_exec_core.h"

namespace aarch64 {

Expand Down Expand Up @@ -46,6 +47,18 @@ inline uint64_t get_far(const trap_frame* tf) {
return tf->far;
}

/**
* @brief The current task's saved trap frame.
* Valid only while handling an EL0/EL1t-origin trap, when the frame sits
* immediately below the task's exception stack top (the scheduler pins
* SP_EL1 to system_stack_top on every return to a task-mode context).
* @note Privilege: **required**
*/
__PRIVILEGED_CODE inline trap_frame* current_trap_frame() {
return reinterpret_cast<trap_frame*>(
this_cpu(current_task_exec)->system_stack_top - sizeof(trap_frame));
}

} // namespace aarch64

#endif // STELLUX_ARCH_AARCH64_TRAP_TRAP_FRAME_H
Expand Down
32 changes: 32 additions & 0 deletions kernel/arch/arch_signal.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
#ifndef STELLUX_ARCH_ARCH_SIGNAL_H
#define STELLUX_ARCH_ARCH_SIGNAL_H

#include "common/types.h"

namespace sched {
struct task;
}

namespace arch {

/**
* @brief Deliver one pending handler-bound signal at the syscall-return
* boundary by redirecting the saved user context to the handler.
* An action without a restorer or an unwritable user stack kills the task
* with SIGSEGV. Returns the value for the user's result register, so a
* built frame keeps the handler's first argument intact.
* @note Privilege: **required**
*/
__PRIVILEGED_CODE int64_t deliver_pending_signal(sched::task* self,
int64_t result);

/**
* @brief rt_sigreturn core: restore the interrupted context from the user
* signal frame and return the value to resume in the result register.
* @note Privilege: **required**
*/
__PRIVILEGED_CODE int64_t restore_signal_context();

} // namespace arch

#endif // STELLUX_ARCH_ARCH_SIGNAL_H
27 changes: 27 additions & 0 deletions kernel/arch/x86_64/signal/delivery.cpp
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#include "signal/delivery.h"
#include "arch/arch_signal.h"
#include "defs/segments.h"
#include "sched/fpu.h"
#include "sched/sched.h"
Expand Down Expand Up @@ -180,3 +181,29 @@ __PRIVILEGED_CODE int64_t restore_signal_frame(syscall_frame* ctx) {
}

} // namespace x86

__PRIVILEGED_CODE int64_t arch::deliver_pending_signal(sched::task* self,
int64_t result) {
uint32_t sig = 0;
signals::k_sigaction act{};
signals::sig_set_t old_blocked = 0;
if (!signals::take_deliverable(self, &sig, &act, &old_blocked)) {
return result;
}

// The handler returns through the restorer's rt_sigreturn,
// an action installed without one is undeliverable.
if (!(act.flags & signals::SA_RESTORER) || !act.restorer) {
signals::die_from_signal(signals::SIGSEGV);
}

if (x86::build_signal_frame(x86::current_syscall_frame(), sig, &act,
old_blocked, result) != 0) {
signals::die_from_signal(signals::SIGSEGV);
}
return result;
}

__PRIVILEGED_CODE int64_t arch::restore_signal_context() {
return x86::restore_signal_frame(x86::current_syscall_frame());
}
1 change: 1 addition & 0 deletions kernel/arch/x86_64/syscall/linux_syscalls.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ constexpr uint64_t MUNMAP = 11;
constexpr uint64_t BRK = 12;
constexpr uint64_t RT_SIGACTION = 13;
constexpr uint64_t RT_SIGPROCMASK = 14;
constexpr uint64_t RT_SIGRETURN = 15;
constexpr uint64_t IOCTL = 16;
constexpr uint64_t WRITEV = 20;
constexpr uint64_t ACCESS = 21;
Expand Down
78 changes: 74 additions & 4 deletions kernel/signals/signal.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ namespace signals {
enum class send_verdict : uint8_t {
FATAL, // default-terminate, wake the target so it can die
IGNORABLE, // droppable unless the target blocks it
HANDLED, // a user handler is installed, leave it pending
HANDLED, // a user handler is installed, wake the target to deliver
};

/**
Expand Down Expand Up @@ -175,7 +175,7 @@ __PRIVILEGED_CODE int32_t send_to_task(sched::task* t, uint32_t sig) {
}

__atomic_fetch_or(&t->sig.pending, sig_bit(sig), __ATOMIC_ACQ_REL);
if (verdict == send_verdict::FATAL && !is_blocked) {
if (verdict != send_verdict::IGNORABLE && !is_blocked) {
wake_for_signal(t);
}
return OK;
Expand Down Expand Up @@ -227,7 +227,7 @@ __PRIVILEGED_CODE int32_t send_to_group(sched::thread_group* tg, uint32_t sig) {

__atomic_fetch_or(&tg->sig.shared_pending, bit, __ATOMIC_ACQ_REL);

if (verdict == send_verdict::FATAL) {
if (verdict != send_verdict::IGNORABLE) {
// Wake one thread with the signal unblocked, leader preferred
sched::task* target = nullptr;
if (tg->leader &&
Expand Down Expand Up @@ -291,7 +291,7 @@ __PRIVILEGED_CODE uint32_t fatal_pending(sched::task* t) {
}

__PRIVILEGED_CODE bool interrupt_pending(sched::task* t) {
return t && fatal_pending(t) != 0;
return t && (fatal_pending(t) != 0 || next_deliverable(t) != 0);
}

__PRIVILEGED_CODE uint32_t next_deliverable(sched::task* t) {
Expand Down Expand Up @@ -325,6 +325,76 @@ __PRIVILEGED_CODE uint32_t next_deliverable(sched::task* t) {
return result;
}

__PRIVILEGED_CODE bool take_deliverable(sched::task* t, uint32_t* sig,
k_sigaction* act,
sig_set_t* old_blocked) {
if (!t || !t->group) {
return false;
}

// Lock-free fast path, the boundary check must stay cheap
sig_set_t blocked = __atomic_load_n(&t->sig.blocked, __ATOMIC_ACQUIRE);
sig_set_t deliverable = (__atomic_load_n(&t->sig.pending, __ATOMIC_ACQUIRE)
| __atomic_load_n(&t->group->sig.shared_pending, __ATOMIC_ACQUIRE))
& ~blocked;
if (!deliverable) {
return false;
}

sync::irq_state irq = sync::spin_lock_irqsave(t->group->sig.lock);

// Re-read under the lock: bits are only cleared by sig.lock holders,
// so the selection cannot race with a discard or another consumer
deliverable = (__atomic_load_n(&t->sig.pending, __ATOMIC_ACQUIRE)
| __atomic_load_n(&t->group->sig.shared_pending, __ATOMIC_ACQUIRE))
& ~blocked;

uint32_t selected = 0;
while (deliverable) {
uint32_t s = static_cast<uint32_t>(__builtin_ctzll(deliverable)) + 1;
deliverable &= deliverable - 1;

uintptr_t handler = t->group->sig.actions[s - 1].handler;
if (handler != SIG_DFL && handler != SIG_IGN) {
selected = s;
break;
}
}

if (!selected) {
sync::spin_unlock_irqrestore(t->group->sig.lock, irq);
return false;
}

// Consume one pending instance, thread set before the shared set
const sig_set_t keep = ~sig_bit(selected);
if (!(__atomic_fetch_and(&t->sig.pending, keep, __ATOMIC_ACQ_REL)
& sig_bit(selected))) {
__atomic_fetch_and(&t->group->sig.shared_pending, keep, __ATOMIC_ACQ_REL);
}

*act = t->group->sig.actions[selected - 1];

// POSIX: SA_RESETHAND restores the default disposition on delivery
if (act->flags & SA_RESETHAND) {
t->group->sig.actions[selected - 1].handler = SIG_DFL;
}

sync::spin_unlock_irqrestore(t->group->sig.lock, irq);

// The handler runs with its sa_mask plus its own signal blocked,
// the frame carries old_blocked for rt_sigreturn to restore
sig_set_t next = blocked | act->mask;
if (!(act->flags & SA_NODEFER)) {
next |= sig_bit(selected);
}
set_blocked(t, SIG_SETMASK, &next, nullptr);

*sig = selected;
*old_blocked = blocked;
return true;
}

__PRIVILEGED_CODE void die_from_signal(uint32_t sig) {
sched::task* self = sched::current();
sched::thread_group* tg = self->group;
Expand Down
18 changes: 15 additions & 3 deletions kernel/signals/signal.h
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,7 @@ __PRIVILEGED_CODE sig_set_t pending_blocked_set(sched::task* t);
* @brief Send a thread-directed signal (tkill semantics).
* SIGKILL terminates the whole process via the kill machinery. Signals
* resolving to ignore are dropped unless the target blocks them. Fatal
* signals wake a blocked target so it can act promptly. Signals with a
* handler installed are left pending for delivery.
* and handler-bound signals wake a blocked target so it can act promptly.
* @return OK, ERR_INVAL for a bad signal, ERR_PERM for kernel/idle tasks.
* @note Privilege: **required**
*/
Expand All @@ -80,7 +79,7 @@ __PRIVILEGED_CODE uint32_t fatal_pending(sched::task* t);

/**
* @brief True if a blocking wait must unwind and return EINTR.
* Covers kills and fatal signals (handler delivery extends this later).
* Covers kills, fatal signals, and handler-bound deliveries.
* @note Privilege: **required**
*/
__PRIVILEGED_CODE bool interrupt_pending(sched::task* t);
Expand All @@ -94,6 +93,19 @@ __PRIVILEGED_CODE bool interrupt_pending(sched::task* t);
*/
__PRIVILEGED_CODE uint32_t next_deliverable(sched::task* t);

/**
* @brief Consume the lowest-numbered signal ready for handler delivery.
* Clears one pending instance (thread set before shared), snapshots the
* action, applies SA_RESETHAND, and blocks the handler's sa_mask plus the
* signal itself (skipped under SA_NODEFER). old_blocked receives the
* pre-delivery mask the signal frame must carry for rt_sigreturn.
* @return true when a signal was taken and the outputs are valid.
* @note Privilege: **required**
*/
__PRIVILEGED_CODE bool take_deliverable(sched::task* t, uint32_t* sig,
k_sigaction* act,
sig_set_t* old_blocked);

/**
* @brief Terminate the current task because of signal sig.
* Fatal signals kill the whole process: a non-leader records sig as the
Expand Down
7 changes: 7 additions & 0 deletions kernel/syscall/handlers/sys_signal.cpp
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#include "syscall/handlers/sys_signal.h"
#include "arch/arch_signal.h"
#include "signals/signal.h"
#include "sched/sched.h"
#include "sched/task.h"
Expand Down Expand Up @@ -173,6 +174,12 @@ DEFINE_SYSCALL2(rt_sigpending, u_set, sigsetsize) {
return 0;
}

DEFINE_SYSCALL0(rt_sigreturn) {
// Returns the restored context's saved result value, so the normal
// result write completes the restore instead of clobbering it
return arch::restore_signal_context();
}

DEFINE_SYSCALL2(kill, u_pid, u_sig) {
if (u_sig > signals::NSIG) {
return syscall::EINVAL;
Expand Down
1 change: 1 addition & 0 deletions kernel/syscall/handlers/sys_signal.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
DECLARE_SYSCALL(rt_sigaction);
DECLARE_SYSCALL(rt_sigprocmask);
DECLARE_SYSCALL(rt_sigpending);
DECLARE_SYSCALL(rt_sigreturn);
DECLARE_SYSCALL(kill);
DECLARE_SYSCALL(tkill);
DECLARE_SYSCALL(tgkill);
Expand Down
7 changes: 7 additions & 0 deletions kernel/syscall/syscall.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#include "syscall/syscall.h"
#include "syscall/syscall_table.h"
#include "arch/arch_signal.h"
#include "sched/task_exec_core.h"
#include "sched/sched.h"
#include "sched/task.h"
Expand Down Expand Up @@ -54,6 +55,12 @@ extern "C" __PRIVILEGED_CODE int64_t stlx_syscall_handler(
if (fsig) {
signals::die_from_signal(fsig);
}

// Handler delivery only when returning to user mode, an elevated
// task keeps its signals pending until it lowers
if (!(self->exec.flags & sched::TASK_FLAG_ELEVATED)) {
result = arch::deliver_pending_signal(self, result);
}
}

// Return-boundary restore: dynamic runtime elevation follows the selected
Expand Down
1 change: 1 addition & 0 deletions kernel/syscall/syscall_table.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ __PRIVILEGED_CODE void init_syscall_table() {
REGISTER_SYSCALL(linux_nr::RT_SIGACTION, rt_sigaction);
REGISTER_SYSCALL(linux_nr::RT_SIGPROCMASK, rt_sigprocmask);
REGISTER_SYSCALL(linux_nr::RT_SIGPENDING, rt_sigpending);
REGISTER_SYSCALL(linux_nr::RT_SIGRETURN, rt_sigreturn);
REGISTER_SYSCALL(linux_nr::KILL, kill);
REGISTER_SYSCALL(linux_nr::TKILL, tkill);
REGISTER_SYSCALL(linux_nr::TGKILL, tgkill);
Expand Down
18 changes: 18 additions & 0 deletions kernel/tests/signals/signal_interrupt.test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -121,3 +121,21 @@ TEST(signal_interrupt, interrupt_pending_truth) {
RUN_ELEVATED({ intr = signals::interrupt_pending(nullptr); });
EXPECT_FALSE(intr);
}

TEST(signal_interrupt, interrupt_pending_covers_handled_signals) {
signals::k_sigaction act = {};
act.handler = 0x400000;
RUN_ELEVATED({
signals::set_action(g_tg, signals::SIGUSR1, &act, nullptr);
});

bool intr = false;
g_leader->sig.pending = signals::sig_bit(signals::SIGUSR1);
RUN_ELEVATED({ intr = signals::interrupt_pending(g_leader); });
EXPECT_TRUE(intr);

// Blocking the signal removes the interrupt reason
g_leader->sig.blocked = signals::sig_bit(signals::SIGUSR1);
RUN_ELEVATED({ intr = signals::interrupt_pending(g_leader); });
EXPECT_FALSE(intr);
}
2 changes: 1 addition & 1 deletion kernel/tests/signals/signal_send.test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ TEST(signal_send, ignored_blocked_send_pends) {
EXPECT_EQ(g_thread->sig.pending, signals::sig_bit(signals::SIGCHLD));
}

TEST(signal_send, handled_send_pends_only) {
TEST(signal_send, handled_send_pends) {
signals::k_sigaction act = {};
act.handler = 0x400000;
int32_t rc = 0;
Expand Down
Loading
Loading