Support alternate signal stack

This commit is contained in:
Jianfeng Jiang
2023-09-27 10:06:02 +08:00
committed by Tate, Hongliang Tian
parent 3734306398
commit a91a35ebce
9 changed files with 362 additions and 38 deletions

View File

@ -265,6 +265,7 @@ int test_sigchld() {
// child process
printf("create a new proces successfully (pid = %d)\n", getpid());
fflush(stdout);
exit(0);
} else {
// parent process
wait(NULL);
@ -274,11 +275,82 @@ int test_sigchld() {
return 0;
}
// ============================================================================
// Test handle signal on alternate signal stack
// ============================================================================
#define MAX_ALTSTACK_RECURSION_LEVEL 2
stack_t g_old_ss;
static void handle_sigpipe(int num, siginfo_t *info, void *context) {
static volatile int recursion_level = 0;
printf("Hello from SIGPIPE signal handler on the alternate signal stack (recursion_level = %d)\n",
recursion_level);
// save old_ss to check if we are on stack
stack_t old_ss;
sigaltstack(NULL, &old_ss);
g_old_ss = old_ss;
recursion_level++;
if (recursion_level <= MAX_ALTSTACK_RECURSION_LEVEL) {
raise(SIGPIPE);
}
recursion_level--;
}
#define SIGSTACKSIZE (4*4096)
int test_sigaltstack() {
static char stack[SIGSTACKSIZE];
stack_t expected_ss = {
.ss_size = SIGSTACKSIZE,
.ss_sp = stack,
.ss_flags = 0,
};
if (sigaltstack(&expected_ss, NULL) < 0) {
THROW_ERROR("failed to call sigaltstack");
}
stack_t actual_ss;
if (sigaltstack(NULL, &actual_ss) < 0) {
THROW_ERROR("failed to call sigaltstack");
}
if (actual_ss.ss_size != expected_ss.ss_size
|| actual_ss.ss_sp != expected_ss.ss_sp
|| actual_ss.ss_flags != expected_ss.ss_flags) {
THROW_ERROR("failed to check the signal stack after set");
}
struct sigaction new_action, old_action;
memset(&new_action, 0, sizeof(struct sigaction));
memset(&old_action, 0, sizeof(struct sigaction));
new_action.sa_sigaction = handle_sigpipe;
new_action.sa_flags = SA_SIGINFO | SA_NODEFER | SA_ONSTACK;
if (sigaction(SIGPIPE, &new_action, &old_action) < 0) {
THROW_ERROR("registering new signal handler failed");
}
if (old_action.sa_handler != SIG_DFL) {
THROW_ERROR("unexpected old sig handler");
}
raise(SIGPIPE);
if (g_old_ss.ss_flags != SS_ONSTACK) {
THROW_ERROR("check stack flags failed");
}
if (sigaction(SIGPIPE, &old_action, NULL) < 0) {
THROW_ERROR("restoring old signal handler failed");
}
return 0;
}
int main() {
test_sigprocmask();
test_raise();
test_handle_sigfpe();
test_handle_sigsegv();
test_sigchld();
test_sigaltstack();
return 0;
}

View File

@ -92,7 +92,6 @@ impl PosixThreadBuilder {
let thread = Arc::new_cyclic(|thread_ref| {
let task = create_new_user_task(user_space, thread_ref.clone());
let status = ThreadStatus::Init;
let sig_context = Mutex::new(None);
let posix_thread = PosixThread {
process,
is_main_thread,
@ -102,7 +101,8 @@ impl PosixThreadBuilder {
credentials,
sig_mask: Mutex::new(sig_mask),
sig_queues: Mutex::new(sig_queues),
sig_context,
sig_context: Mutex::new(None),
sig_stack: Mutex::new(None),
robust_list: Mutex::new(None),
};

View File

@ -3,7 +3,7 @@ use super::signal::sig_mask::SigMask;
use super::signal::sig_num::SigNum;
use super::signal::sig_queues::SigQueues;
use super::signal::signals::Signal;
use super::signal::{SigEvents, SigEventsFilter};
use super::signal::{SigEvents, SigEventsFilter, SigStack};
use super::{do_exit_group, Credentials, Process, TermStatus};
use crate::events::Observer;
use crate::prelude::*;
@ -51,6 +51,7 @@ pub struct PosixThread {
/// Signal handler ucontext address
/// FIXME: This field may be removed. For glibc applications with RESTORER flag set, the sig_context is always equals with rsp.
sig_context: Mutex<Option<Vaddr>>,
sig_stack: Mutex<Option<SigStack>>,
}
impl PosixThread {
@ -152,6 +153,10 @@ impl PosixThread {
&self.sig_context
}
pub fn sig_stack(&self) -> &Mutex<Option<SigStack>> {
&self.sig_stack
}
pub fn robust_list(&self) -> &Mutex<Option<RobustListHead>> {
&self.robust_list
}

View File

@ -8,30 +8,27 @@ pub mod sig_disposition;
pub mod sig_mask;
pub mod sig_num;
pub mod sig_queues;
mod sig_stack;
pub mod signals;
pub use events::{SigEvents, SigEventsFilter};
pub use pauser::Pauser;
pub use poll::{Pollee, Poller};
use core::mem;
pub use sig_stack::{SigStack, SigStackFlags, SigStackStatus};
use align_ext::AlignExt;
use jinux_frame::{cpu::UserContext, task::Task};
use core::mem;
use jinux_frame::cpu::UserContext;
use jinux_frame::task::Task;
use self::c_types::siginfo_t;
use self::sig_mask::SigMask;
use self::sig_num::SigNum;
use crate::current_thread;
use crate::process::posix_thread::PosixThreadExt;
use crate::process::signal::c_types::ucontext_t;
use crate::process::signal::sig_action::SigActionFlags;
use super::posix_thread::{PosixThread, PosixThreadExt};
use crate::prelude::*;
use crate::process::{do_exit_group, TermStatus};
use crate::util::{write_bytes_to_user, write_val_to_user};
use crate::{
prelude::*,
process::signal::sig_action::{SigAction, SigDefaultAction},
};
use c_types::{siginfo_t, ucontext_t};
use sig_action::{SigAction, SigActionFlags, SigDefaultAction};
use sig_mask::SigMask;
use sig_num::SigNum;
/// Handle pending signal for current process
pub fn handle_pending_signal(context: &mut UserContext) -> Result<()> {
@ -134,19 +131,24 @@ pub fn handle_user_signal(
// block signals in sigmask when running signal handler
posix_thread.sig_mask().lock().block(mask.as_u64());
// Set up signal stack in user stack,
// to avoid corrupting user stack, we minus 128 first.
let mut user_rsp = context.rsp() as u64;
user_rsp -= 128;
// Set up signal stack.
let mut stack_pointer = if let Some(sp) = use_alternate_signal_stack(posix_thread) {
sp as u64
} else {
// just use user stack
context.rsp() as u64
};
// To avoid corrupting signal stack, we minus 128 first.
stack_pointer -= 128;
// 1. write siginfo_t
user_rsp -= mem::size_of::<siginfo_t>() as u64;
write_val_to_user(user_rsp as _, &sig_info)?;
let siginfo_addr = user_rsp;
// debug!("siginfo_addr = 0x{:x}", siginfo_addr);
stack_pointer -= mem::size_of::<siginfo_t>() as u64;
write_val_to_user(stack_pointer as _, &sig_info)?;
let siginfo_addr = stack_pointer;
// 2. write ucontext_t.
user_rsp = alloc_aligned_in_user_stack(user_rsp, mem::size_of::<ucontext_t>(), 16)?;
stack_pointer = alloc_aligned_in_user_stack(stack_pointer, mem::size_of::<ucontext_t>(), 16)?;
let mut ucontext = ucontext_t {
uc_sigmask: mask.as_u64(),
..Default::default()
@ -159,18 +161,17 @@ pub fn handle_user_signal(
ucontext.uc_link = 0;
}
// TODO: store fp regs in ucontext
write_val_to_user(user_rsp as _, &ucontext)?;
let ucontext_addr = user_rsp;
// Store the ucontext addr in sig context of current process.
write_val_to_user(stack_pointer as _, &ucontext)?;
let ucontext_addr = stack_pointer;
// Store the ucontext addr in sig context of current thread.
*sig_context = Some(ucontext_addr as Vaddr);
// current.sig_context().lock().push_back(ucontext_addr as _);
// 3. Set the address of the trampoline code.
if flags.contains(SigActionFlags::SA_RESTORER) {
// If contains SA_RESTORER flag, trampoline code is provided by libc in restorer_addr.
// We just store restorer_addr on user stack to allow user code just to trampoline code.
user_rsp = write_u64_to_user_stack(user_rsp, restorer_addr as u64)?;
trace!("After set restorer addr: user_rsp = 0x{:x}", user_rsp);
stack_pointer = write_u64_to_user_stack(stack_pointer, restorer_addr as u64)?;
trace!("After set restorer addr: user_rsp = 0x{:x}", stack_pointer);
} else {
// Otherwise we create a trampoline.
// FIXME: This may cause problems if we read old_context from rsp.
@ -179,14 +180,14 @@ pub fn handle_user_signal(
0x0f, 0x05, // syscall (call rt_sigreturn)
0x90, // nop (for alignment)
];
user_rsp -= TRAMPOLINE.len() as u64;
let trampoline_rip = user_rsp;
write_bytes_to_user(user_rsp as Vaddr, TRAMPOLINE)?;
user_rsp = write_u64_to_user_stack(user_rsp, trampoline_rip)?;
stack_pointer -= TRAMPOLINE.len() as u64;
let trampoline_rip = stack_pointer;
write_bytes_to_user(stack_pointer as Vaddr, TRAMPOLINE)?;
stack_pointer = write_u64_to_user_stack(stack_pointer, trampoline_rip)?;
}
// 4. Set correct register values
context.set_rip(handler_addr as _);
context.set_rsp(user_rsp as usize);
context.set_rsp(stack_pointer as usize);
// parameters of signal handler
context.set_rdi(sig_num.as_u8() as usize); // signal number
if flags.contains(SigActionFlags::SA_SIGINFO) {
@ -200,6 +201,34 @@ pub fn handle_user_signal(
Ok(())
}
/// Use an alternate signal stack, which was installed by sigaltstack.
/// It the stack is already active, we just increase the handler counter and return None, since
/// the stack pointer can be read from context.
/// It the stack is not used by any handler, we will return the new sp in alternate signal stack.
fn use_alternate_signal_stack(posix_thread: &PosixThread) -> Option<usize> {
let mut sig_stack = posix_thread.sig_stack().lock();
let Some(sig_stack) = &mut *sig_stack else {
return None;
};
if sig_stack.is_disabled() {
return None;
}
if sig_stack.is_active() {
// The stack is already active, so we just use sp in context.
sig_stack.increase_handler_counter();
return None;
}
sig_stack.increase_handler_counter();
// Make sp align at 16. FIXME: is this required?
let stack_pointer = (sig_stack.base() + sig_stack.size()).align_down(16);
Some(stack_pointer)
}
fn write_u64_to_user_stack(rsp: u64, value: u64) -> Result<u64> {
let rsp = rsp - 8;
write_val_to_user(rsp as Vaddr, &value)?;

View File

@ -103,7 +103,6 @@ impl SigActionFlags {
self.intersects(
SigActionFlags::SA_NOCLDSTOP
| SigActionFlags::SA_NOCLDWAIT
| SigActionFlags::SA_ONSTACK
| SigActionFlags::SA_RESETHAND,
)
}

View File

@ -0,0 +1,93 @@
use crate::prelude::*;
/// User-provided signal stack. `SigStack` is per-thread, and each thread can have
/// at most one `SigStack`. If one signal handler specifying the `SA_ONSTACK` flag,
/// the handler should be executed on the `SigStack`, instead of on the default stack.
///
/// SigStack can be registered and unregistered by syscall `sigaltstack`.
#[derive(Debug, Clone)]
pub struct SigStack {
base: Vaddr,
flags: SigStackFlags,
size: usize,
/// The number of handlers that are currently using the stack
handler_counter: usize,
}
bitflags! {
pub struct SigStackFlags: u32 {
const SS_AUTODISARM = 1 << 31;
}
}
#[repr(u8)]
#[allow(non_camel_case_types)]
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub enum SigStackStatus {
#[default]
SS_INACTIVE = 0,
// The thread is currently executing on the alternate signal stack
SS_ONSTACK = 1,
// The stack is currently disabled.
SS_DISABLE = 2,
}
impl SigStack {
pub fn new(base: Vaddr, flags: SigStackFlags, size: usize) -> Self {
Self {
base,
flags,
size,
handler_counter: 0,
}
}
pub fn base(&self) -> Vaddr {
self.base
}
pub fn flags(&self) -> SigStackFlags {
self.flags
}
pub fn size(&self) -> usize {
self.size
}
pub fn status(&self) -> SigStackStatus {
if self.handler_counter == 0 {
return SigStackStatus::SS_INACTIVE;
}
// Learning From [sigaltstack doc](https://man7.org/linux/man-pages/man2/sigaltstack.2.html):
// If the stack is currently executed on,
// 1. If the stack was established with flag SS_AUTODISARM, the stack status is DISABLE,
// 2. otherwise, the stack status is ONSTACK
if self.flags.contains(SigStackFlags::SS_AUTODISARM) {
SigStackStatus::SS_DISABLE
} else {
SigStackStatus::SS_ONSTACK
}
}
/// Mark the stack is currently used by a signal handler.
pub fn increase_handler_counter(&mut self) {
self.handler_counter += 1;
}
// Mark the stack is freed by current handler.
pub fn decrease_handler_counter(&mut self) {
// FIXME: deal with SS_AUTODISARM flag
self.handler_counter -= 1
}
/// Determins whether the stack is executed on by any signal handler
pub fn is_active(&self) -> bool {
// FIXME: can DISABLE stack be used?
self.handler_counter != 0 && !self.flags.contains(SigStackFlags::SS_AUTODISARM)
}
pub fn is_disabled(&self) -> bool {
self.handler_counter != 0 && self.flags.contains(SigStackFlags::SS_AUTODISARM)
}
}

View File

@ -99,6 +99,7 @@ use self::setsid::sys_setsid;
use self::setsockopt::sys_setsockopt;
use self::setuid::sys_setuid;
use self::shutdown::sys_shutdown;
use self::sigaltstack::sys_sigaltstack;
use self::socket::sys_socket;
use self::socketpair::sys_socketpair;
@ -185,6 +186,7 @@ mod setsid;
mod setsockopt;
mod setuid;
mod shutdown;
mod sigaltstack;
mod socket;
mod socketpair;
mod stat;
@ -316,6 +318,7 @@ define_syscall_nums!(
SYS_SETFSUID = 122,
SYS_SETFSGID = 123,
SYS_GETSID = 124,
SYS_SIGALTSTACK = 131,
SYS_STATFS = 137,
SYS_FSTATFS = 138,
SYS_PRCTL = 157,
@ -491,6 +494,7 @@ pub fn syscall_dispatch(
SYS_SETFSUID => syscall_handler!(1, sys_setfsuid, args),
SYS_SETFSGID => syscall_handler!(1, sys_setfsgid, args),
SYS_GETSID => syscall_handler!(1, sys_getsid, args),
SYS_SIGALTSTACK => syscall_handler!(2, sys_sigaltstack, args),
SYS_STATFS => syscall_handler!(2, sys_statfs, args),
SYS_FSTATFS => syscall_handler!(2, sys_fstatfs, args),
SYS_PRCTL => syscall_handler!(5, sys_prctl, args),

View File

@ -23,6 +23,15 @@ pub fn sys_rt_sigreturn(context: &mut UserContext) -> Result<SyscallReturn> {
debug_assert!(sig_context_addr == context.rsp() as Vaddr);
let ucontext = read_val_from_user::<ucontext_t>(sig_context_addr)?;
// If the sig stack is active and used by current handler, decrease handler counter.
if let Some(sig_stack) = posix_thread.sig_stack().lock().as_mut() {
let rsp = context.rsp();
if rsp >= sig_stack.base() && rsp <= sig_stack.base() + sig_stack.size() {
sig_stack.decrease_handler_counter();
}
}
// Set previous ucontext address
if ucontext.uc_link == 0 {
*sig_context = None;
@ -33,5 +42,6 @@ pub fn sys_rt_sigreturn(context: &mut UserContext) -> Result<SyscallReturn> {
// unblock sig mask
let sig_mask = ucontext.uc_sigmask;
posix_thread.sig_mask().lock().unblock(sig_mask);
Ok(SyscallReturn::NoReturn)
}

View File

@ -0,0 +1,112 @@
use crate::log_syscall_entry;
use crate::prelude::*;
use crate::process::posix_thread::PosixThreadExt;
use crate::process::signal::SigStack;
use crate::process::signal::SigStackFlags;
use crate::util::read_val_from_user;
use crate::util::write_val_to_user;
use super::{SyscallReturn, SYS_SIGALTSTACK};
pub fn sys_sigaltstack(sig_stack_addr: Vaddr, old_sig_stack_addr: Vaddr) -> Result<SyscallReturn> {
log_syscall_entry!(SYS_SIGALTSTACK);
debug!(
"sig_stack_addr = 0x{:x}, old_sig_stack_addr: 0x{:x}",
sig_stack_addr, old_sig_stack_addr
);
let old_stack = {
let current_thread = current_thread!();
let posix_thread = current_thread.as_posix_thread().unwrap();
let sig_stack = posix_thread.sig_stack().lock();
sig_stack.clone()
};
get_old_stack(old_sig_stack_addr, old_stack.as_ref())?;
set_new_stack(sig_stack_addr, old_stack.as_ref())?;
Ok(SyscallReturn::Return(0))
}
fn get_old_stack(old_sig_stack_addr: Vaddr, old_stack: Option<&SigStack>) -> Result<()> {
if old_sig_stack_addr == 0 {
return Ok(());
}
let Some(old_stack) = old_stack else {
return Ok(());
};
debug!("old stack = {:?}", old_stack);
let stack = stack_t::from(old_stack.clone());
write_val_to_user(old_sig_stack_addr, &stack)
}
fn set_new_stack(sig_stack_addr: Vaddr, old_stack: Option<&SigStack>) -> Result<()> {
if sig_stack_addr == 0 {
return Ok(());
}
if let Some(old_stack) = old_stack && old_stack.is_active() {
return_errno_with_message!(Errno::EPERM, "the old stack is active now");
}
let new_stack = {
let stack = read_val_from_user::<stack_t>(sig_stack_addr)?;
SigStack::try_from(stack)?
};
debug!("new_stack = {:?}", new_stack);
let current_thread = current_thread!();
let posix_thread = current_thread.as_posix_thread().unwrap();
*posix_thread.sig_stack().lock() = Some(new_stack);
Ok(())
}
#[allow(non_camel_case_types)]
#[derive(Debug, Clone, Copy, Pod)]
#[repr(C)]
struct stack_t {
// Base address of stack
sp: Vaddr,
flags: i32,
// Number of bytes in stack
size: usize,
}
impl TryFrom<stack_t> for SigStack {
type Error = Error;
fn try_from(stack: stack_t) -> Result<Self> {
if stack.flags < 0 {
return_errno_with_message!(Errno::EINVAL, "negative flags");
}
let flags = SigStackFlags::from_bits(stack.flags as u32)
.ok_or_else(|| Error::with_message(Errno::EINVAL, "invalid flags"))?;
if stack.size < MINSTKSZ {
return_errno_with_message!(Errno::ENOMEM, "stack size is less than MINSTKSZ");
}
Ok(Self::new(stack.sp, flags, stack.size))
}
}
impl From<SigStack> for stack_t {
fn from(stack: SigStack) -> Self {
let flags = stack.flags().bits() as i32 | stack.status() as i32;
Self {
sp: stack.base(),
flags,
size: stack.size(),
}
}
}
const SIGSTKSZ: usize = 8192;
const MINSTKSZ: usize = 2048;