diff --git a/regression/apps/signal_c/signal_test.c b/regression/apps/signal_c/signal_test.c index 305eae681..a885e1398 100644 --- a/regression/apps/signal_c/signal_test.c +++ b/regression/apps/signal_c/signal_test.c @@ -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; } \ No newline at end of file diff --git a/services/libs/jinux-std/src/process/posix_thread/builder.rs b/services/libs/jinux-std/src/process/posix_thread/builder.rs index 0f59d06df..ead10779d 100644 --- a/services/libs/jinux-std/src/process/posix_thread/builder.rs +++ b/services/libs/jinux-std/src/process/posix_thread/builder.rs @@ -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), }; diff --git a/services/libs/jinux-std/src/process/posix_thread/mod.rs b/services/libs/jinux-std/src/process/posix_thread/mod.rs index 24528b111..414ab260e 100644 --- a/services/libs/jinux-std/src/process/posix_thread/mod.rs +++ b/services/libs/jinux-std/src/process/posix_thread/mod.rs @@ -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>, + sig_stack: Mutex>, } impl PosixThread { @@ -152,6 +153,10 @@ impl PosixThread { &self.sig_context } + pub fn sig_stack(&self) -> &Mutex> { + &self.sig_stack + } + pub fn robust_list(&self) -> &Mutex> { &self.robust_list } diff --git a/services/libs/jinux-std/src/process/signal/mod.rs b/services/libs/jinux-std/src/process/signal/mod.rs index e74326a44..8ba2e74da 100644 --- a/services/libs/jinux-std/src/process/signal/mod.rs +++ b/services/libs/jinux-std/src/process/signal/mod.rs @@ -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::() 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::() 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::(), 16)?; + stack_pointer = alloc_aligned_in_user_stack(stack_pointer, mem::size_of::(), 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 { + 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 { let rsp = rsp - 8; write_val_to_user(rsp as Vaddr, &value)?; diff --git a/services/libs/jinux-std/src/process/signal/sig_action.rs b/services/libs/jinux-std/src/process/signal/sig_action.rs index d7dc97996..937f2e75a 100644 --- a/services/libs/jinux-std/src/process/signal/sig_action.rs +++ b/services/libs/jinux-std/src/process/signal/sig_action.rs @@ -103,7 +103,6 @@ impl SigActionFlags { self.intersects( SigActionFlags::SA_NOCLDSTOP | SigActionFlags::SA_NOCLDWAIT - | SigActionFlags::SA_ONSTACK | SigActionFlags::SA_RESETHAND, ) } diff --git a/services/libs/jinux-std/src/process/signal/sig_stack.rs b/services/libs/jinux-std/src/process/signal/sig_stack.rs new file mode 100644 index 000000000..b7ce81679 --- /dev/null +++ b/services/libs/jinux-std/src/process/signal/sig_stack.rs @@ -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) + } +} diff --git a/services/libs/jinux-std/src/syscall/mod.rs b/services/libs/jinux-std/src/syscall/mod.rs index f35d98367..dcd62aabb 100644 --- a/services/libs/jinux-std/src/syscall/mod.rs +++ b/services/libs/jinux-std/src/syscall/mod.rs @@ -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), diff --git a/services/libs/jinux-std/src/syscall/rt_sigreturn.rs b/services/libs/jinux-std/src/syscall/rt_sigreturn.rs index 8774ffac9..4132b5ab7 100644 --- a/services/libs/jinux-std/src/syscall/rt_sigreturn.rs +++ b/services/libs/jinux-std/src/syscall/rt_sigreturn.rs @@ -23,6 +23,15 @@ pub fn sys_rt_sigreturn(context: &mut UserContext) -> Result { debug_assert!(sig_context_addr == context.rsp() as Vaddr); let ucontext = read_val_from_user::(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 { // unblock sig mask let sig_mask = ucontext.uc_sigmask; posix_thread.sig_mask().lock().unblock(sig_mask); + Ok(SyscallReturn::NoReturn) } diff --git a/services/libs/jinux-std/src/syscall/sigaltstack.rs b/services/libs/jinux-std/src/syscall/sigaltstack.rs new file mode 100644 index 000000000..22f291f1a --- /dev/null +++ b/services/libs/jinux-std/src/syscall/sigaltstack.rs @@ -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 { + 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::(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 for SigStack { + type Error = Error; + + fn try_from(stack: stack_t) -> Result { + 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 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;