blob: 3c049c10e6cf2f7df99c62a43a96fa946d0f4e63 [file] [log] [blame]
// Copyright 2021 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
use crate::arch::registers::RegisterState;
use crate::arch::signal_handling::{
RED_ZONE_SIZE, SIG_STACK_SIZE, SignalStackFrame, align_stack_pointer, restore_registers,
};
use crate::mm::{MemoryAccessor, MemoryAccessorExt};
use crate::signals::{KernelSignal, KernelSignalInfo, SignalDetail, SignalInfo, SignalState};
use crate::task::{
CurrentTask, ExitStatus, StopState, Task, TaskFlags, TaskWriteGuard, ThreadState, Waiter,
};
use extended_pstate::ExtendedPstateState;
use starnix_logging::{log_info, log_trace, log_warn};
use starnix_sync::{LockBefore, Locked, ThreadGroupLimits, Unlocked};
use starnix_syscalls::SyscallResult;
use starnix_types::arch::ArchWidth;
use starnix_uapi::errors::{EINTR, ERESTART_RESTARTBLOCK, Errno};
use starnix_uapi::resource_limits::Resource;
use starnix_uapi::signals::{
SIGABRT, SIGALRM, SIGBUS, SIGCHLD, SIGCONT, SIGFPE, SIGHUP, SIGILL, SIGINT, SIGIO, SIGKILL,
SIGPIPE, SIGPROF, SIGPWR, SIGQUIT, SIGSEGV, SIGSTKFLT, SIGSTOP, SIGSYS, SIGTERM, SIGTRAP,
SIGTSTP, SIGTTIN, SIGTTOU, SIGURG, SIGUSR1, SIGUSR2, SIGVTALRM, SIGWINCH, SIGXCPU, SIGXFSZ,
SigSet, sigaltstack_contains_pointer,
};
use starnix_uapi::user_address::UserAddress;
use starnix_uapi::{
SA_NODEFER, SA_ONSTACK, SA_RESETHAND, SA_SIGINFO, SIG_DFL, SIG_IGN, errno, error, sigaction_t,
};
/// Indicates where in the signal queue a signal should go. Signals
/// can jump the queue when being injected by tools like ptrace.
#[derive(PartialEq)]
enum SignalPriority {
First,
Last,
}
// `send_signal*()` calls below may fail only for real-time signals (with EAGAIN). They are
// expected to succeed for all other signals.
pub fn send_signal_first<L>(
locked: &mut Locked<L>,
task: &Task,
task_state: TaskWriteGuard<'_>,
siginfo: SignalInfo,
) where
L: LockBefore<ThreadGroupLimits>,
{
send_signal_prio(locked, task, task_state, siginfo.into(), SignalPriority::First, true)
.expect("send_signal(SignalPriority::First) is not expected to fail")
}
// Sends `signal` to `task`. The signal must be a standard (i.e. not real-time) signal.
pub fn send_standard_signal<L>(locked: &mut Locked<L>, task: &Task, siginfo: SignalInfo)
where
L: LockBefore<ThreadGroupLimits>,
{
debug_assert!(!siginfo.signal.is_real_time());
let state = task.write();
send_signal_prio(locked, task, state, siginfo.into(), SignalPriority::Last, false)
.expect("send_signal(SignalPriority::Last) is not expected to fail for standard signals.")
}
pub fn send_signal<L>(locked: &mut Locked<L>, task: &Task, siginfo: SignalInfo) -> Result<(), Errno>
where
L: LockBefore<ThreadGroupLimits>,
{
let state = task.write();
send_signal_prio(locked, task, state, siginfo.into(), SignalPriority::Last, false)
}
pub fn send_freeze_signal<L>(
locked: &mut Locked<L>,
task: &Task,
waiter: Waiter,
) -> Result<(), Errno>
where
L: LockBefore<ThreadGroupLimits>,
{
let state = task.write();
send_signal_prio(
locked,
task,
state,
KernelSignalInfo::Freeze(waiter),
SignalPriority::First,
true,
)
}
fn send_signal_prio<L>(
locked: &mut Locked<L>,
task: &Task,
mut task_state: TaskWriteGuard<'_>,
kernel_siginfo: KernelSignalInfo,
prio: SignalPriority,
force_wake: bool,
) -> Result<(), Errno>
where
L: LockBefore<ThreadGroupLimits>,
{
let (siginfo, signal, is_masked, was_masked, is_real_time, sigaction, action) =
match kernel_siginfo {
KernelSignalInfo::User(ref user_siginfo) => {
let signal = user_siginfo.signal;
let is_masked = task_state.is_signal_masked(signal);
let was_masked = task_state.is_signal_masked_by_saved_mask(signal);
let sigaction = task.get_signal_action(signal);
let action = action_for_signal(&user_siginfo, sigaction);
(
Some(user_siginfo.clone()),
Some(signal),
is_masked,
was_masked,
signal.is_real_time(),
Some(sigaction),
Some(action),
)
}
KernelSignalInfo::Freeze(_) => (None, None, false, false, false, None, None),
};
if is_real_time && prio != SignalPriority::First {
if task_state.pending_signal_count()
>= task.thread_group().get_rlimit(locked, Resource::SIGPENDING) as usize
{
return error!(EAGAIN);
}
}
// If the signal is ignored then it doesn't need to be queued, except the following 2 cases:
// 1. The signal is blocked by the current or the original mask. The signal may be unmasked
// later, see `SigtimedwaitTest.IgnoredUnmaskedSignal` gvisor test.
// 2. The task is ptraced. In this case we want to queue the signal for signal-delivery-stop.
let is_queued = action.is_none()
|| action != Some(DeliveryAction::Ignore)
|| is_masked
|| was_masked
|| task_state.is_ptraced();
if is_queued {
match kernel_siginfo {
KernelSignalInfo::User(ref siginfo) => {
if prio == SignalPriority::First {
task_state.enqueue_signal_front(siginfo.clone());
} else {
task_state.enqueue_signal(siginfo.clone());
}
task_state.set_flags(TaskFlags::SIGNALS_AVAILABLE, true);
}
KernelSignalInfo::Freeze(waiter) => {
task_state.enqueue_kernel_signal(KernelSignal::Freeze(waiter))
}
}
}
drop(task_state);
if is_queued
&& !is_masked
&& action.map_or_else(|| true, |action| action.must_interrupt(sigaction))
{
// Wake the task. Note that any potential signal handler will be executed before
// the task returns from the suspend (from the perspective of user space).
task.interrupt();
}
// Unstop the process for SIGCONT. Also unstop for SIGKILL, the only signal that can interrupt
// a stopped process.
if signal == Some(SIGKILL) {
task.write().thaw();
task.thread_group().set_stopped(StopState::ForceWaking, siginfo, false);
task.write().set_stopped(StopState::ForceWaking, None, None, None);
} else if signal == Some(SIGCONT) || force_wake {
task.thread_group().set_stopped(StopState::Waking, siginfo, false);
task.write().set_stopped(StopState::Waking, None, None, None);
}
Ok(())
}
/// Represents the action to take when signal is delivered.
///
/// See https://man7.org/linux/man-pages/man7/signal.7.html.
#[derive(Debug, PartialEq)]
pub enum DeliveryAction {
Ignore,
CallHandler,
Terminate,
CoreDump,
Stop,
Continue,
}
impl DeliveryAction {
/// Returns whether the target task must be interrupted to execute the action.
///
/// The task will not be interrupted if the signal is the action is the Continue action, or if
/// the action is Ignore and the user specifically requested to ignore the signal.
pub fn must_interrupt(&self, sigaction: Option<sigaction_t>) -> bool {
match *self {
Self::Continue => false,
Self::Ignore => sigaction.map_or(false, |sa| sa.sa_handler == SIG_IGN),
_ => true,
}
}
}
pub fn action_for_signal(siginfo: &SignalInfo, sigaction: sigaction_t) -> DeliveryAction {
let handler = if siginfo.force && sigaction.sa_handler == SIG_IGN {
SIG_DFL
} else {
sigaction.sa_handler
};
match handler {
SIG_DFL => match siginfo.signal {
SIGCHLD | SIGURG | SIGWINCH => DeliveryAction::Ignore,
sig if sig.is_real_time() => DeliveryAction::Ignore,
SIGHUP | SIGINT | SIGKILL | SIGPIPE | SIGALRM | SIGTERM | SIGUSR1 | SIGUSR2
| SIGPROF | SIGVTALRM | SIGSTKFLT | SIGIO | SIGPWR => DeliveryAction::Terminate,
SIGQUIT | SIGILL | SIGABRT | SIGFPE | SIGSEGV | SIGBUS | SIGSYS | SIGTRAP | SIGXCPU
| SIGXFSZ => DeliveryAction::CoreDump,
SIGSTOP | SIGTSTP | SIGTTIN | SIGTTOU => DeliveryAction::Stop,
SIGCONT => DeliveryAction::Continue,
_ => panic!("Unknown signal"),
},
SIG_IGN => DeliveryAction::Ignore,
_ => DeliveryAction::CallHandler,
}
}
/// Dequeues and handles a pending signal for `current_task`.
pub fn dequeue_signal(locked: &mut Locked<Unlocked>, current_task: &mut CurrentTask) {
let &mut CurrentTask { ref task, ref mut thread_state, .. } = current_task;
let mut task_state = task.write();
// This code is occasionally executed as the task is stopping. Stopping /
// stopped threads should not get signals.
if task.load_stopped().is_stopping_or_stopped() {
return;
}
// If there is a kernel signal needs to handle, deliver the signal right away.
let kernel_signal = task_state.take_kernel_signal();
let siginfo = if kernel_signal.is_some() { None } else { task_state.take_any_signal() };
prepare_to_restart_syscall(
thread_state,
siginfo.as_ref().map(|siginfo| task.thread_group().signal_actions.get(siginfo.signal)),
);
if let Some(ref siginfo) = siginfo {
if task_state.ptrace_on_signal_consume() && siginfo.signal != SIGKILL {
// Indicate we will be stopping for ptrace at the next opportunity.
// Whether you actually deliver the signal is now up to ptrace, so
// we can return.
task_state.set_stopped(
StopState::SignalDeliveryStopping,
Some(siginfo.clone()),
None,
None,
);
return;
}
}
// A syscall may have been waiting with a temporary mask which should be used to dequeue the
// signal, but after the signal has been dequeued the old mask should be restored.
task_state.restore_signal_mask();
{
let (clear, set) = if task_state.pending_signal_count() == 0 {
(TaskFlags::SIGNALS_AVAILABLE, TaskFlags::empty())
} else {
(TaskFlags::empty(), TaskFlags::SIGNALS_AVAILABLE)
};
task_state.update_flags(clear | TaskFlags::TEMPORARY_SIGNAL_MASK, set);
};
if let Some(kernel_signal) = kernel_signal {
let KernelSignal::Freeze(waiter) = kernel_signal;
drop(task_state);
waiter.freeze(locked, current_task);
} else if let Some(ref siginfo) = siginfo {
if let SignalDetail::Timer { timer } = &siginfo.detail {
timer.on_signal_delivered();
}
if let Some(status) = deliver_signal(
&task,
current_task.thread_state.arch_width,
task_state,
siginfo.clone(),
&mut current_task.thread_state.registers,
&current_task.thread_state.extended_pstate,
None,
) {
current_task.thread_group_exit(locked, status);
}
};
}
pub fn deliver_signal(
task: &Task,
arch_width: ArchWidth,
mut task_state: TaskWriteGuard<'_>,
mut siginfo: SignalInfo,
registers: &mut RegisterState,
extended_pstate: &ExtendedPstateState,
restricted_exception: Option<zx::sys::zx_restricted_exception_t>,
) -> Option<ExitStatus> {
loop {
let sigaction = task.thread_group().signal_actions.get(siginfo.signal);
let action = action_for_signal(&siginfo, sigaction);
log_trace!("handling signal {:?} with action {:?}", siginfo, action);
match action {
DeliveryAction::Ignore => {}
DeliveryAction::CallHandler => {
let sigaction = task.thread_group().signal_actions.get(siginfo.signal);
let signal = siginfo.signal;
match dispatch_signal_handler(
task,
arch_width,
registers,
extended_pstate,
task_state.signals_mut(),
siginfo,
sigaction,
) {
Ok(_) => {
// Reset the signal handler if `SA_RESETHAND` was set.
if sigaction.sa_flags & (SA_RESETHAND as u64) != 0 {
let new_sigaction = sigaction_t {
sa_handler: SIG_DFL,
sa_flags: sigaction.sa_flags & !(SA_RESETHAND as u64),
..sigaction
};
task.thread_group().signal_actions.set(signal, new_sigaction);
}
}
Err(err) => {
log_warn!("failed to deliver signal {:?}: {:?}", signal, err);
siginfo = SignalInfo::default(SIGSEGV);
// The behavior that we want is:
// 1. If we failed to send a SIGSEGV, or SIGSEGV is masked, or SIGSEGV is
// ignored, we reset the signal disposition and unmask SIGSEGV.
// 2. Send a SIGSEGV to the program, with the (possibly) updated signal
// disposition and mask.
let sigaction = task.thread_group().signal_actions.get(siginfo.signal);
let action = action_for_signal(&siginfo, sigaction);
let masked_signals = task_state.signal_mask();
if signal == SIGSEGV
|| masked_signals.has_signal(SIGSEGV)
|| action == DeliveryAction::Ignore
{
task_state.set_signal_mask(masked_signals & !SigSet::from(SIGSEGV));
task.thread_group().signal_actions.set(SIGSEGV, sigaction_t::default());
}
// Try to deliver the SIGSEGV.
// We already checked whether we needed to unmask or reset the signal
// disposition.
// This could not lead to an infinite loop, because if we had a SIGSEGV
// handler, and we failed to send a SIGSEGV, we remove the handler and resend
// the SIGSEGV.
continue;
}
}
}
DeliveryAction::Terminate => {
// Release the signals lock. [`ThreadGroup::exit`] sends signals to threads which
// will include this one and cause a deadlock re-acquiring the signals lock.
drop(task_state);
return Some(ExitStatus::Kill(siginfo));
}
DeliveryAction::CoreDump => {
task_state.set_flags(TaskFlags::DUMP_ON_EXIT, true);
drop(task_state);
if let Some(exception) = restricted_exception {
log_info!(
registers:?=exception.state,
exception:?=exception.exception;
"Restricted mode exception caused core dump",
);
}
return Some(ExitStatus::CoreDump(siginfo));
}
DeliveryAction::Stop => {
drop(task_state);
task.thread_group().set_stopped(StopState::GroupStopping, Some(siginfo), false);
}
DeliveryAction::Continue => {
// Nothing to do. Effect already happened when the signal was raised.
}
};
break;
}
None
}
/// Prepares `current` state to execute the signal handler stored in `action`.
///
/// This function stores the state required to restore after the signal handler on the stack.
fn dispatch_signal_handler(
task: &Task,
arch_width: ArchWidth,
registers: &mut RegisterState,
extended_pstate: &ExtendedPstateState,
signal_state: &mut SignalState,
siginfo: SignalInfo,
action: sigaction_t,
) -> Result<(), Errno> {
let main_stack = registers.stack_pointer_register().checked_sub(RED_ZONE_SIZE);
let stack_bottom = if (action.sa_flags & SA_ONSTACK as u64) != 0 {
match signal_state.alt_stack {
Some(sigaltstack) => {
match main_stack {
// Only install the sigaltstack if the stack pointer is not already in it.
Some(sp) if sigaltstack_contains_pointer(&sigaltstack, sp) => main_stack,
_ => {
// Since the stack grows down, the size is added to the ss_sp when
// calculating the "bottom" of the stack.
// Use the main stack if sigaltstack overflows.
sigaltstack
.ss_sp
.addr
.checked_add(sigaltstack.ss_size)
.map(|sp| sp as u64)
.or(main_stack)
}
}
}
None => main_stack,
}
} else {
main_stack
}
.ok_or_else(|| errno!(EINVAL))?;
let stack_pointer =
align_stack_pointer(stack_bottom.checked_sub(SIG_STACK_SIZE as u64).ok_or_else(|| {
errno!(
EINVAL,
format!(
"Subtracting SIG_STACK_SIZE ({}) from stack bottom ({}) overflowed",
SIG_STACK_SIZE, stack_bottom
)
)
})?);
// Check that if the stack pointer is inside altstack, the entire signal stack is inside
// altstack.
if let Some(alt_stack) = signal_state.alt_stack {
if sigaltstack_contains_pointer(&alt_stack, stack_pointer)
!= sigaltstack_contains_pointer(&alt_stack, stack_bottom)
{
return error!(EINVAL);
}
}
let signal_stack_frame = SignalStackFrame::new(
task,
arch_width,
registers,
extended_pstate,
signal_state,
&siginfo,
action,
UserAddress::from(stack_pointer),
)?;
// Write the signal stack frame at the updated stack pointer.
task.write_memory(UserAddress::from(stack_pointer), signal_stack_frame.as_bytes())?;
let mut mask: SigSet = action.sa_mask.into();
if action.sa_flags & (SA_NODEFER as u64) == 0 {
mask = mask | siginfo.signal.into();
}
// Preserve the existing mask when handling a nested signal
signal_state.set_mask(mask | signal_state.mask());
registers.set_stack_pointer_register(stack_pointer);
registers.set_arg0_register(siginfo.signal.number() as u64);
if (action.sa_flags & SA_SIGINFO as u64) != 0 {
registers.set_arg1_register(
stack_pointer + memoffset::offset_of!(SignalStackFrame, siginfo_bytes) as u64,
);
registers.set_arg2_register(
stack_pointer + memoffset::offset_of!(SignalStackFrame, context) as u64,
);
}
registers.set_instruction_pointer_register(action.sa_handler.addr);
registers.reset_flags(); // TODO(https://fxbug.dev/413070731): Verify and update the logic in resetting the flags.
Ok(())
}
pub fn restore_from_signal_handler(current_task: &mut CurrentTask) -> Result<(), Errno> {
// Read the signal stack frame from memory.
let signal_frame_address = UserAddress::from(align_stack_pointer(
current_task.thread_state.registers.stack_pointer_register(),
));
let signal_stack_bytes =
current_task.read_memory_to_array::<SIG_STACK_SIZE>(signal_frame_address)?;
// Grab the registers state from the stack frame.
let signal_stack_frame = SignalStackFrame::from_bytes(signal_stack_bytes);
restore_registers(current_task, &signal_stack_frame, signal_frame_address)?;
// Restore the stored signal mask.
current_task.write().set_signal_mask(signal_stack_frame.get_signal_mask());
Ok(())
}
/// Maybe adjust a task's registers to restart a syscall once the task switches back to userspace,
/// based on whether the task previously had a syscall return with an error code indicating that a
/// restart was required.
pub fn prepare_to_restart_syscall(thread_state: &mut ThreadState, sigaction: Option<sigaction_t>) {
// Taking the value ensures each syscall is only considered for restart once.
let Some(err) = thread_state.restart_code.take() else {
// Don't interact with register state at all unless other kernel code indicates that we may
// need to restart.
return;
};
if err.should_restart(sigaction) {
// This error code is returned for system calls that need restart_syscall() to adjust time
// related arguments when the syscall is restarted. Other syscall restarts can be dispatched
// directly to the original syscall implementation.
if err == ERESTART_RESTARTBLOCK {
thread_state.registers.prepare_for_custom_restart();
} else {
thread_state.registers.restore_original_return_register();
}
// TODO(https://fxbug.dev/388051291) figure out whether Linux relies on registers here
thread_state.registers.rewind_syscall_instruction();
} else {
thread_state.registers.set_return_register(EINTR.return_value());
}
}
pub fn sys_restart_syscall(
locked: &mut Locked<Unlocked>,
current_task: &mut CurrentTask,
) -> Result<SyscallResult, Errno> {
match current_task.thread_state.syscall_restart_func.take() {
Some(f) => f(locked, current_task),
None => {
// This may indicate a bug where a syscall returns ERESTART_RESTARTBLOCK without
// setting a restart func. But it can also be triggered by userspace, e.g. by directly
// calling restart_syscall or injecting an ERESTART_RESTARTBLOCK error through ptrace.
log_warn!("restart_syscall called, but nothing to restart");
error!(EINTR)
}
}
}
/// Test utilities for signal handling.
#[cfg(test)]
pub(crate) mod testing {
use super::*;
use crate::testing::AutoReleasableTask;
use std::ops::DerefMut as _;
pub(crate) fn dequeue_signal_for_test(
locked: &mut Locked<Unlocked>,
current_task: &mut AutoReleasableTask,
) {
dequeue_signal(locked, current_task.deref_mut());
}
}