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
139 changes: 139 additions & 0 deletions kernel/arch/aarch64/signal/delivery.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
#include "signal/delivery.h"
#include "sched/fpu.h"
#include "sched/sched.h"
#include "sched/task.h"
#include "signals/signal.h"
#include "mm/uaccess.h"
#include "mm/heap.h"

namespace aarch64 {

// PSTATE condition flags (NZCV). Everything else is forced so a restored
// context returns to EL0t (mode bits 0) with interrupts unmasked (DAIF 0).
constexpr uint64_t SPSR_NZCV_MASK = 0xF0000000ULL;

static inline uint64_t align_down(uint64_t v, uint64_t a) {
return v & ~(a - 1);
}

static inline void copy_vregs(uint8_t dst[32][16], const uint8_t src[32][16]) {
for (uint32_t i = 0; i < 32; i++) {
for (uint32_t j = 0; j < 16; j++) {
dst[i][j] = src[i][j];
}
}
}

__PRIVILEGED_CODE void pack_sigframe(rt_sigframe* frame, const trap_frame* tf,
int64_t saved_result, uint32_t sig,
signals::sig_set_t old_blocked,
const sched::fpu_state* fp) {
sigcontext& sc = frame->uc.uc_mcontext;
for (uint32_t i = 0; i < 31; i++) {
sc.regs[i] = tf->x[i];
}

sc.regs[0] = static_cast<uint64_t>(saved_result);
sc.sp = tf->sp;
sc.pc = tf->elr;
sc.pstate = tf->spsr;
sc.fault_address = tf->far;

fpsimd_context* fc = reinterpret_cast<fpsimd_context*>(sc.__reserved);
fc->magic = FPSIMD_MAGIC;
fc->size = sizeof(fpsimd_context);
fc->fpsr = fp->fpsr;
fc->fpcr = fp->fpcr;
copy_vregs(fc->vregs, fp->vregs);

frame->uc.uc_sigmask = old_blocked;
frame->info.si_signo = static_cast<int32_t>(sig);
frame->info.si_code = SI_USER;
}

__PRIVILEGED_CODE bool unpack_sigframe(const rt_sigframe* frame, trap_frame* tf,
sched::fpu_state* fp,
signals::sig_set_t* mask) {
const sigcontext& sc = frame->uc.uc_mcontext;
const fpsimd_context* fc = reinterpret_cast<const fpsimd_context*>(sc.__reserved);
if (fc->magic != FPSIMD_MAGIC) {
return false;
}

for (uint32_t i = 0; i < 31; i++) {
tf->x[i] = sc.regs[i];
}

tf->sp = sc.sp;
tf->elr = sc.pc;
tf->spsr = sc.pstate & SPSR_NZCV_MASK;

fp->fpsr = fc->fpsr;
fp->fpcr = fc->fpcr;
copy_vregs(fp->vregs, fc->vregs);

*mask = frame->uc.uc_sigmask;
return true;
}

__PRIVILEGED_CODE int32_t build_signal_frame(trap_frame* tf, uint32_t sig,
const signals::k_sigaction* act,
signals::sig_set_t old_blocked,
int64_t saved_result) {
uint64_t frame_addr = align_down(tf->sp - sizeof(rt_sigframe), 16);

rt_sigframe* frame = heap::kalloc_new<rt_sigframe>();
if (!frame) {
return -1;
}
sched::fpu_state fp;
fpu::save(&fp);
pack_sigframe(frame, tf, saved_result, sig, old_blocked, &fp);

int32_t rc = mm::uaccess::copy_to_user(
reinterpret_cast<void*>(frame_addr), frame, sizeof(*frame));
heap::kfree_delete(frame);

if (rc != mm::uaccess::OK) {
return rc;
}

tf->sp = frame_addr;
tf->elr = act->handler;
tf->x[0] = sig;
tf->x[1] = frame_addr + __builtin_offsetof(rt_sigframe, info);
tf->x[2] = frame_addr + __builtin_offsetof(rt_sigframe, uc);
tf->x[30] = act->restorer; // LR, the handler returns into the restorer
return 0;
}

__PRIVILEGED_CODE int64_t restore_signal_frame(trap_frame* tf) {
uint64_t frame_addr = tf->sp;

rt_sigframe* frame = heap::kalloc_new<rt_sigframe>();
if (!frame) {
signals::die_from_signal(signals::SIGSEGV);
}
if (mm::uaccess::copy_from_user(
frame, reinterpret_cast<void*>(frame_addr), sizeof(*frame)) != mm::uaccess::OK) {
heap::kfree_delete(frame);
signals::die_from_signal(signals::SIGSEGV);
}

sched::fpu_state fp;
signals::sig_set_t mask = 0;
if (!unpack_sigframe(frame, tf, &fp, &mask)) {
heap::kfree_delete(frame);
signals::die_from_signal(signals::SIGSEGV);
}

signals::set_blocked(sched::current(), signals::SIG_SETMASK, &mask, nullptr);
fpu::restore(&fp);

int64_t resume = static_cast<int64_t>(tf->x[0]);
heap::kfree_delete(frame);

return resume;
}

} // namespace aarch64
52 changes: 52 additions & 0 deletions kernel/arch/aarch64/signal/delivery.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
#ifndef STELLUX_ARCH_AARCH64_SIGNAL_DELIVERY_H
#define STELLUX_ARCH_AARCH64_SIGNAL_DELIVERY_H

#include "signal/sigframe.h"
#include "trap/trap_frame.h"
#include "sched/fpu_state.h"
#include "signals/signal_types.h"

namespace aarch64 {

/**
* @brief Fill a zeroed kernel-local signal frame from interrupted state.
* Pure marshaling with no user access, so it is unit-testable. The FP block
* is written in the kernel ABI field order, converting from fpu_state.
* @note Privilege: **required**
*/
__PRIVILEGED_CODE void pack_sigframe(rt_sigframe* frame, const trap_frame* tf,
int64_t saved_result, uint32_t sig,
signals::sig_set_t old_blocked,
const sched::fpu_state* fp);

/**
* @brief Apply a restored frame onto interrupted state (rt_sigreturn core).
* Forces PSTATE back to EL0 with unmasked interrupts so a forged frame can
* never return to EL1, and recovers the saved mask and FP. Returns false and
* leaves tf untouched on a corrupt FP record. Pure, unit-testable.
* @note Privilege: **required**
*/
__PRIVILEGED_CODE bool unpack_sigframe(const rt_sigframe* frame, trap_frame* tf,
sched::fpu_state* fp,
signals::sig_set_t* mask);

/**
* @brief Build a signal frame on the user stack and redirect tf to the
* handler. Returns 0 on success, negative when the user stack is unwritable.
* @note Privilege: **required**
*/
__PRIVILEGED_CODE int32_t build_signal_frame(trap_frame* tf, uint32_t sig,
const signals::k_sigaction* act,
signals::sig_set_t old_blocked,
int64_t saved_result);

/**
* @brief Restore interrupted state from the user signal frame and return
* the value to resume in x0. Kills the task with SIGSEGV on a bad frame.
* @note Privilege: **required**
*/
__PRIVILEGED_CODE int64_t restore_signal_frame(trap_frame* tf);

} // namespace aarch64

#endif // STELLUX_ARCH_AARCH64_SIGNAL_DELIVERY_H
34 changes: 30 additions & 4 deletions kernel/arch/x86_64/sched/fpu.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,13 @@

namespace fpu {

// FXSAVE area layout: MXCSR and the CPU-reported mask of its usable bits
constexpr size_t MXCSR_OFFSET = 24;
constexpr size_t MXCSR_MASK_OFFSET = 28;

// Architectural fallback when the CPU reports no mask, DAZ excluded
constexpr uint32_t MXCSR_DEFAULT_MASK = 0xFFBF;

/**
* @note Privilege: **required**
*/
Expand All @@ -27,12 +34,31 @@ __PRIVILEGED_CODE inline void init_state(sched::fpu_state* state) {
for (size_t i = 0; i < 512; i++) {
area[i] = 0;
}
// FCW (offset 0): 0x037F mask all x87 exceptions, 64-bit precision, round-to-nearest
// FCW (offset 0): 0x037F - mask all x87 exceptions, 64-bit precision, round-to-nearest
area[0] = 0x7F;
area[1] = 0x03;
// MXCSR (offset 24): 0x1F80 — mask all SSE exceptions, round-to-nearest
area[24] = 0x80;
area[25] = 0x1F;
// MXCSR: 0x1F80 - mask all SSE exceptions, round-to-nearest
area[MXCSR_OFFSET] = 0x80;
area[MXCSR_OFFSET + 1] = 0x1F;
}

/**
* @brief Clear MXCSR bits the CPU rejects from an untrusted FP image.
* FXRSTOR raises #GP in Ring 0 on a reserved bit, so an image that crossed
* the user boundary must pass through here before restore.
* @note Privilege: **required**
*/
__PRIVILEGED_CODE inline void sanitize_user_mxcsr(sched::fpu_state* state) {
sched::fpu_state probe;
save(&probe);

uint32_t mask =
*reinterpret_cast<const uint32_t*>(&probe.fxsave_area[MXCSR_MASK_OFFSET]);
if (!mask) {
mask = MXCSR_DEFAULT_MASK;
}

*reinterpret_cast<uint32_t*>(&state->fxsave_area[MXCSR_OFFSET]) &= mask;
}

} // namespace fpu
Expand Down
Loading
Loading