PATCH for signal stacks, sigaltstack() call #1

Melvin Smith (msmith@quix.robins.af.mil)
Fri, 24 May 1996 23:31:27 -0400 (EDT)


I implemented signal stacks for pre2.0.6, and have tested
successfully with single threaded, single process. This
is the beginning of reliable sigs, SA_SIGINFO, etc.
For example, with the patch, you can now handle a stack
overflow correctly with SIGSEGV. The patch is included
the next mailing will be a test program if you want to play
with it. Some of the code is #ifdef 0 such as the fork() and
clone() related because it is untested. This should go into
pre2.0 because IMHO it is not a NEW feature but plugging
a hole, and Linux is about the only flavor without support.

I would appreciate if someone would play with the test program
and give me some report.

-Melvin Smith

diff -urN /usr/src/linux-pre2.0.6/arch/i386/kernel/entry.S /usr/src/linux/arch/i386/kernel/entry.S
--- /usr/src/linux-pre2.0.6/arch/i386/kernel/entry.S Mon May 13 16:23:08 1996
+++ /usr/src/linux/arch/i386/kernel/entry.S Mon May 20 23:24:10 1996
@@ -674,4 +674,5 @@
.long SYMBOL_NAME(sys_sched_rr_get_interval)
.long SYMBOL_NAME(sys_nanosleep)
.long SYMBOL_NAME(sys_mremap)
- .space (NR_syscalls-163)*4
+ .long SYMBOL_NAME(sys_sigaltstack)
+ .space (NR_syscalls-164)*4
diff -urN /usr/src/linux-pre2.0.6/arch/i386/kernel/process.c /usr/src/linux/arch/i386/kernel/process.c
--- /usr/src/linux-pre2.0.6/arch/i386/kernel/process.c Fri Apr 19 11:35:15 1996
+++ /usr/src/linux/arch/i386/kernel/process.c Fri May 24 23:08:47 1996
@@ -267,6 +267,12 @@
for (i=0 ; i<8 ; i++)
current->debugreg[i] = 0;

+ /* zero sigaltstack */
+ if( current->sa_info ) {
+ kfree( current->sa_info );
+ current->sa_info = 0;
+ }
+
/*
* Forget coprocessor state..
*/
@@ -309,6 +315,25 @@
*childregs = *regs;
childregs->eax = 0;
childregs->esp = esp;
+
+#ifdef 0
+ /* UNTESTED!! - Melvin */
+ /* Here we need to check if parent was on sigstack
+ * If so, start child off on its sigstack which has already
+ * been cloned by copy_sighand()
+ */
+
+#define CUR_ALT_STACK_BOT (unsigned long)current->sa_info->ss_sp
+#define CUR_ALT_STACK_TOP CUR_ALT_STACK_BOT - ( current->sa_info->ss_size - 1 )
+
+ if( current->sa_info && current->tss.esp < CUR_ALT_STACK_BOT
+ && current->tss.esp > CUR_ALT_STACK_TOP ) {
+
+ /* Duplicate sig-stack frame position */
+ /* set SA_ONSTACK bit */
+ }
+#endif
+
p->tss.back_link = 0;
p->tss.eflags = regs->eflags & 0xffffcfff; /* iopl is always 0 for a new process */
p->tss.ldt = _LDT(nr);
diff -urN /usr/src/linux-pre2.0.6/arch/i386/kernel/signal.c /usr/src/linux/arch/i386/kernel/signal.c
--- /usr/src/linux-pre2.0.6/arch/i386/kernel/signal.c Mon May 6 09:31:18 1996
+++ /usr/src/linux/arch/i386/kernel/signal.c Fri May 24 23:07:10 1996
@@ -2,6 +2,10 @@
* linux/arch/i386/kernel/signal.c
*
* Copyright (C) 1991, 1992 Linus Torvalds
+ *
+ * 05.25.96 Melvin Smith
+ * Started reliable signal handling. Signal stacks implemented.
+ * Hopefully SA_SIGINFO is next.
*/

#include <linux/config.h>
@@ -11,6 +15,7 @@
#include <linux/kernel.h>
#include <linux/signal.h>
#include <linux/errno.h>
+#include <linux/malloc.h>
#include <linux/wait.h>
#include <linux/ptrace.h>
#include <linux/unistd.h>
@@ -72,10 +77,83 @@
restore_i387_soft(buf);
#endif
}
-
+
+/* Right now I'm checking not only instack but instack->ss_sp, maybe wrong
+ * but I think it is safe. System shouldn't let user install stack if it
+ * is too small anyway
+ */
+asmlinkage int sys_sigaltstack( const struct sigaltstack * sa_in,
+ struct sigaltstack *sa_out )
+{
+ /* In case sa_in == sa_out */
+ struct sigaltstack sa_temp, sa_new;
+
+ /* If old sigstack pointer then save old info in that pointer */
+ if( sa_out ) {
+ if( verify_area( VERIFY_WRITE, sa_out, sizeof( struct sigaltstack ) ) )
+ return -EINVAL;
+
+ if( !current->sa_info )
+ memset( &sa_temp, 0, sizeof( struct sigaltstack ) );
+ else
+ memcpy( &sa_temp, current->sa_info, sizeof( struct sigaltstack ) );
+ }
+
+ /* If new sigstack is passed then update current */
+ sa_new.ss_sp = 0;
+ if( sa_in ) {
+ if( verify_area( VERIFY_READ, sa_in, sizeof( struct sigaltstack ) ) )
+ return -EINVAL;
+ memcpy_fromfs( &sa_new, sa_in, sizeof( *sa_in ) );
+ }
+
+ if( sa_new.ss_sp ) {
+ /* If SS_DISABLE, ignore stack info */
+ if( !( sa_new.ss_flags & SS_DISABLE ) ) {
+ /* Can't change stack while executing on that stack */
+ if( current->sa_info && (current->sa_info->ss_flags & SA_ONSTACK) )
+ return -EINVAL;
+
+ /* Make sure the stack is as big as the user says it is, either
+ * way it must be at least MINSIGSTKSZ bytes */
+ if( sa_new.ss_size < MINSIGSTKSZ )
+ return -EINVAL;
+ else if( verify_area( VERIFY_WRITE, sa_new.ss_sp, sa_new.ss_size ) )
+ return -EINVAL;
+ }
+
+ if( !current->sa_info ) {
+ current->sa_info = (struct sigaltstack *)kmalloc( sizeof( struct sigaltstack ),
+ GFP_KERNEL );
+ if( !current->sa_info )
+ return -ENOMEM;
+ }
+
+ memcpy( current->sa_info, &sa_new, sizeof( struct sigaltstack ) );
+ /* i386 stack starts at high mem.
+ * This is transparent to caller, reset if sigaltstack queried.
+ */
+ (char *)current->sa_info->ss_sp += ( current->sa_info->ss_size - 1 );
+ }
+ else if( current->sa_info ) {
+ /* sa_in.ss_sp = 0 so we remove sig-stack from task struct */
+ kfree( current->sa_info );
+ current->sa_info = 0;
+ }
+
+ if( sa_out ) {
+ /* Set back to low memory so user can handle it like normal. */
+ if( sa_temp.ss_sp )
+ (char *)sa_temp.ss_sp -= ( sa_temp.ss_size - 1 );
+ memcpy_tofs( sa_out, &sa_temp, sizeof( struct sigaltstack ) );
+ }
+
+ return 0;
+}
+

/*
- * This sets regs->esp even though we don't actually use sigstacks yet..
+ * 5.25.96 Added sigaltstack restore
*/
asmlinkage int sys_sigreturn(unsigned long __unused)
{
@@ -92,6 +170,7 @@
goto badframe;
memcpy_fromfs(&context,(void *) regs->esp, sizeof(context));
current->blocked = context.oldmask & _BLOCKABLE;
+
COPY_SEG(ds);
COPY_SEG(es);
COPY_SEG(fs);
@@ -106,6 +185,18 @@
regs->eflags &= ~0x40DD5;
regs->eflags |= context.eflags & 0x40DD5;
regs->orig_eax = -1; /* disable syscall checks */
+
+#define CUR_ALT_STACK_BOT (unsigned long)current->sa_info->ss_sp
+#define CUR_ALT_STACK_TOP CUR_ALT_STACK_BOT - ((unsigned long)current->sa_info->ss_sp - 1 )
+
+ /* Before clearing altstack bit, test if esp still within altstack
+ * space. A nested signal handler on alt stack maybe */
+ if( current->sa_info && ( current->sa_info->ss_flags & SS_ONSTACK ) ) {
+ if( regs->esp <= CUR_ALT_STACK_BOT
+ || regs->esp >= CUR_ALT_STACK_TOP )
+ current->sa_info->ss_flags &= ~SS_ONSTACK;
+ }
+
if (context.fpstate) {
struct _fpstate * buf = context.fpstate;
if (verify_area(VERIFY_READ, buf, sizeof(*buf)))
@@ -156,6 +247,9 @@
/*
* Set up a signal frame... Make the stack look the way iBCS2 expects
* it to look.
+ * 5.17.96 Added sigstack support. DEC UNIX manpage says if longjmp() is
+ * called from a -nested- signal on an alternate stack then the effect is
+ * undefined. I try to handle it here.
*/
static void setup_frame(struct sigaction * sa,
struct pt_regs * regs, int signr,
@@ -164,10 +258,19 @@
unsigned long * frame;

frame = (unsigned long *) regs->esp;
- if (regs->ss != USER_DS && sa->sa_restorer)
- frame = (unsigned long *) sa->sa_restorer;
+
+ /* See if sigaction requests alternate stack...
+ * Now see if we are already on that stack, or the stack is disabled.
+ */
+ if (regs->ss == USER_DS && sa->sa_flags & SA_ONSTACK
+ && current->sa_info
+ && !( current->sa_info->ss_flags & (SS_DISABLE | SS_ONSTACK) ) ) {
+ frame = (unsigned long *) current->sa_info->ss_sp;
+ current->sa_info->ss_flags = SS_ONSTACK;
+ }
+
frame -= 64;
- if (verify_area(VERIFY_WRITE,frame,64*4))
+ if (verify_area(VERIFY_WRITE,frame,64*sizeof(*frame)))
do_exit(SIGSEGV);

/* set up the "normal" stack seen by the signal handler (iBCS2) */
diff -urN /usr/src/linux-pre2.0.6/include/asm-i386/signal.h /usr/src/linux/include/asm-i386/signal.h
--- /usr/src/linux-pre2.0.6/include/asm-i386/signal.h Fri Mar 1 00:50:56 1996
+++ /usr/src/linux/include/asm-i386/signal.h Thu May 23 23:46:29 1996
@@ -44,17 +44,17 @@
#define SIGUNUSED 31

/*
- * sa_flags values: SA_STACK is not currently supported, but will allow the
- * usage of signal stacks by using the (now obsolete) sa_restorer field in
- * the sigaction structure as a stack pointer. This is now possible due to
- * the changes in signal handling. LBT 010493.
+ * SA_STACK changed to SA_ONSTACK and implementation started.
+ * The stack pointer is not on the sigact struct however, I don't think it
+ * belongs there but I may be wrong. I added sigaltstack struct and added
+ * a sigaltstack member to task_struct - 5.17.96 Melvin Smith
* SA_INTERRUPT is a no-op, but left due to historical reasons. Use the
* SA_RESTART flag to get restarting signals (which were the default long ago)
* SA_SHIRQ flag is for shared interrupt support on PCI and EISA.
*/
#define SA_NOCLDSTOP 1
#define SA_SHIRQ 0x04000000
-#define SA_STACK 0x08000000
+#define SA_ONSTACK 0x08000000
#define SA_RESTART 0x10000000
#define SA_INTERRUPT 0x20000000
#define SA_NOMASK 0x40000000
@@ -89,6 +89,21 @@
unsigned long sa_flags;
void (*sa_restorer)(void);
};
+
+#define MINSIGSTKSZ 4096 /* minimum signal handler alternate stack size */
+#define SIGSTKSZ 16384 /* average or default alternate stack size */
+
+#define SS_DISABLE 1 /* alternate stack disabled */
+#define SS_ONSTACK 2 /* process is currently executing on the alternate stack */
+
+/* Used by sigaltstack() call */
+struct sigaltstack {
+ char * ss_sp;
+ unsigned long ss_flags;
+ long ss_size;
+};
+
+typedef struct sigaltstack stack_t;

#ifdef __KERNEL__
#include <asm/sigcontext.h>
diff -urN /usr/src/linux-pre2.0.6/include/asm-i386/unistd.h /usr/src/linux/include/asm-i386/unistd.h
--- /usr/src/linux-pre2.0.6/include/asm-i386/unistd.h Fri Mar 22 01:34:02 1996
+++ /usr/src/linux/include/asm-i386/unistd.h Mon May 20 21:07:46 1996
@@ -169,6 +169,7 @@
#define __NR_sched_rr_get_interval 161
#define __NR_nanosleep 162
#define __NR_mremap 163
+#define __NR_sigaltstack 164

/* XXX - _foo needs to be __foo, while __NR_bar could be _NR_bar. */
#define _syscall0(type,name) \
diff -urN /usr/src/linux-pre2.0.6/include/linux/sched.h /usr/src/linux/include/linux/sched.h
--- /usr/src/linux-pre2.0.6/include/linux/sched.h Sun May 19 09:27:11 1996
+++ /usr/src/linux/include/linux/sched.h Fri May 24 19:43:55 1996
@@ -244,6 +244,8 @@
struct mm_struct *mm;
/* signal handlers */
struct signal_struct *sig;
+/* signal stack */
+ struct sigaltstack *sa_info;
#ifdef __SMP__
int processor;
int last_processor;
@@ -309,6 +311,7 @@
/* files */ &init_files, \
/* mm */ &init_mm, \
/* signals */ &init_signals, \
+/* sig stack */ 0, \
}

extern struct mm_struct init_mm;
diff -urN /usr/src/linux-pre2.0.6/kernel/fork.c /usr/src/linux/kernel/fork.c
--- /usr/src/linux-pre2.0.6/kernel/fork.c Mon Apr 22 06:08:47 1996
+++ /usr/src/linux/kernel/fork.c Fri May 24 23:10:37 1996
@@ -182,6 +182,9 @@
static inline int copy_sighand(unsigned long clone_flags, struct task_struct * tsk)
{
if (clone_flags & CLONE_SIGHAND) {
+ /* This goes for sig-stack as well. It doesn't make sense to
+ * me to clone the sig-stack but what else to do? -Melvin
+ */
current->sig->count++;
return 0;
}
@@ -190,6 +193,29 @@
return -1;
tsk->sig->count = 1;
memcpy(tsk->sig->action, current->sig->action, sizeof(tsk->sig->action));
+
+#ifdef 0
+ /* UNTESTED!! -Melvin */
+ /* Needs more work so right now a fork() won't copy sig-stack */
+
+ /* Create an alternate sig-stack for new process */
+ if( current->sa_info ) {
+ tsk->sa_info = (struct sigaltstack *)kmalloc( sizeof( struct sigaltstack ), GFP_KERNEL );
+ if( !tsk->sa_info )
+ return -ENOMEM;
+ tsk->sa_info->ss_size = current->sa_info->ss_size;
+ tsk->sa_info->ss_sp = (struct sigaltstack *)kmalloc( current->sa_info->ss_size, GFP_KERNEL );
+ if( !tsk->sa_info->ss_sp ) {
+ kfree( tsk->sa_info );
+ tsk->sa_info = 0;
+ return -ENOMEM;
+ }
+
+ /* What if fork() is called while on sig-stack ? */
+ tsk->sa_info->ss_flags = 0;
+ }
+#endif
+
return 0;
}