Messages in this thread Patch in this message |  | | Subject | [PATCH] signal stacks | From | <> | Date | Tue, 17 Dec 1996 15:50:39 -0500 (GMT) |
| |
Hi hackers,
Follow is my first step in getting signal stacks into the kernel. It is for i386 for initial testing. The patch is from a clean 2.1.15 tree and follows is a C program to play with. Notice this includes the sigaltstack() system call so its not in libc so you need to call it directly like in sig-test.c
I am going to play with mapping the sig stack as GROWS_DOWN but I'm not sure what the expected semantics should be in this case. Any suggestions are appreciated.
Please test and give me reports so I can forward it to Linus.
Melvin
diff -ur /usr/src/linux-2.1.15-orig/arch/i386/kernel/entry.S linux/arch/i386/kernel/entry.S --- /usr/src/linux-2.1.15-orig/arch/i386/kernel/entry.S Thu Dec 12 09:51:08 1996 +++ linux/arch/i386/kernel/entry.S Tue Dec 17 13:19:45 1996 @@ -627,8 +627,9 @@ .long SYMBOL_NAME(sys_nanosleep) .long SYMBOL_NAME(sys_mremap) .long SYMBOL_NAME(sys_setresuid) - .long SYMBOL_NAME(sys_getresuid) + .long SYMBOL_NAME(sys_getresuid) /* 165 */ .long SYMBOL_NAME(sys_vm86) - .rept NR_syscalls-166 + .long SYMBOL_NAME(sys_sigaltstack) + .rept NR_syscalls-167 .long SYMBOL_NAME(sys_ni_syscall) .endr diff -ur /usr/src/linux-2.1.15-orig/arch/i386/kernel/process.c linux/arch/i386/kernel/process.c --- /usr/src/linux-2.1.15-orig/arch/i386/kernel/process.c Mon Oct 28 07:41:15 1996 +++ linux/arch/i386/kernel/process.c Tue Dec 17 13:47:38 1996 @@ -277,6 +277,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.. */ diff -ur /usr/src/linux-2.1.15-orig/arch/i386/kernel/signal.c linux/arch/i386/kernel/signal.c --- /usr/src/linux-2.1.15-orig/arch/i386/kernel/signal.c Wed Dec 11 09:41:23 1996 +++ linux/arch/i386/kernel/signal.c Tue Dec 17 15:01:52 1996 @@ -11,6 +11,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 +73,84 @@ restore_i387_soft(buf); #endif } - + +/* Right we check 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; + copy_from_user( &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 ); + copy_to_user( sa_out, &sa_temp, sizeof( struct sigaltstack ) ); + } + + return 0; +} + /* - * This sets regs->esp even though we don't actually use sigstacks yet.. + * 122596 Added sigaltstack restore -MS */ asmlinkage int sys_sigreturn(unsigned long __unused) { @@ -120,6 +195,16 @@ regs->eflags &= ~0x40DD5; regs->eflags |= context->eflags & 0x40DD5; regs->orig_eax = -1; /* disable syscall checks */ + +#define SA_STACK_BOT (unsigned long)current->sa_info->ss_sp +#define SA_STACK_TOP SA_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 <= SA_STACK_BOT || regs->esp >= SA_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))) @@ -170,6 +255,9 @@ /* * Set up a signal frame... Make the stack look the way iBCS2 expects * it to look. + * 121796 -Added sigstack support. The manpages I read 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, @@ -178,8 +266,15 @@ unsigned long * frame; frame = (unsigned long *) regs->esp; - if ((regs->xss & 0xffff) != 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->xss == 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)) do_exit(SIGSEGV); diff -ur /usr/src/linux-2.1.15-orig/include/asm-i386/signal.h linux/include/asm-i386/signal.h --- /usr/src/linux-2.1.15-orig/include/asm-i386/signal.h Mon Sep 30 10:47:39 1996 +++ linux/include/asm-i386/signal.h Tue Dec 17 13:05:38 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 - 121796 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 stack size */ +#define SIGSTKSZ 16384 /* average or default alternate stack size */ + +#define SS_DISABLE 1 /* alternate stack disabled */ +#define SS_ONSTACK 2 /* currently executing on 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 -ur /usr/src/linux-2.1.15-orig/include/asm-i386/unistd.h linux/include/asm-i386/unistd.h --- /usr/src/linux-2.1.15-orig/include/asm-i386/unistd.h Fri Oct 11 01:45:06 1996 +++ linux/include/asm-i386/unistd.h Tue Dec 17 13:21:19 1996 @@ -171,6 +171,7 @@ #define __NR_mremap 163 #define __NR_setresuid 164 #define __NR_getresuid 165 +#define __NR_sigaltstack 167 /* user-visible error numbers are in the range -1 - -122: see <asm-i386/errno.h> */ diff -ur /usr/src/linux-2.1.15-orig/include/linux/sched.h linux/include/linux/sched.h --- /usr/src/linux-2.1.15-orig/include/linux/sched.h Thu Dec 12 10:33:06 1996 +++ linux/include/linux/sched.h Tue Dec 17 13:48:05 1996 @@ -245,6 +245,8 @@ struct mm_struct *mm; /* signal handlers */ struct signal_struct *sig; +/* signal stack */ + struct sigaltstack *sa_info; #ifdef __SMP__ int processor; int last_processor; @@ -310,6 +312,7 @@ /* files */ &init_files, \ /* mm */ &init_mm, \ /* signals */ &init_signals, \ +/* sig stack */ 0, \ } extern struct mm_struct init_mm; diff -ur /usr/src/linux-2.1.15-orig/kernel/fork.c linux/kernel/fork.c --- /usr/src/linux-2.1.15-orig/kernel/fork.c Sat Nov 23 05:29:04 1996 +++ linux/kernel/fork.c Tue Dec 17 13:48:16 1996 @@ -200,6 +200,15 @@ return -1; tsk->sig->count = 1; memcpy(tsk->sig->action, current->sig->action, sizeof(tsk->sig->action)); + /* copy sigstack */ + if( current->sa_info ) { + tsk->sa_info = kmalloc(sizeof(struct sigaltstack), GFP_KERNEL); + if (!tsk->sa_info) { + exit_sighand(tsk); + return -1; + } + memcpy(tsk->sa_info, current->sa_info, sizeof(struct sigaltstack)); + } return 0; } -- END PATCH --
/* * sig-test.c - Melvin Smith * Test program to test sigaltstack() system call. * Does a stack overflow, generating SIGSEGV, which normally linux * will not be able to deliver. There are other ways of demonstrating * but this is the easiest. Of course apply the sigaltstack() patch first * or you wont see diddly. * * Version 2 - Added initial testing of fork() with sig-stack. */ #include <sys/signal.h> #include <asm/unistd.h> #include <stdio.h> #include <errno.h> #include <setjmp.h>
static inline _syscall2(int,sigaltstack, stack_t *, sa_in, stack_t *, sa_out )
struct sigaction act;
#define GET_STACK( x ) \ __asm__ __volatile("movl %%esp, %0\n\t" : "=q" (x) : :"%0" )
pid_t pid; jmp_buf back;
void handler() { unsigned long ss_sp; printf( "[%d] Caught SIGSEGV and handled stack overflow correctly.\n", pid ); GET_STACK( ss_sp ); printf( "[%d] signal handler stack pointer = %p\n", pid, ss_sp ); longjmp( back, 1 ); } void recurse() { char buf[ 4096 ]; recurse(); } int main() { long ret; struct sigaltstack sa, sa_old; unsigned long ss_sp; /* Step 1 - setup your alternate sig-stack */ sa.ss_sp = malloc( MINSIGSTKSZ * 2); if( !sa.ss_sp ) printf( "malloc failed\n" ); sa.ss_size = MINSIGSTKSZ * 2; sa.ss_flags = 0;
if( sigaltstack( &sa, &sa_old ) < 0 ) { printf( "failed to install alt-stack!\n" ); exit(0); } /* Step 2 - setup a sighandler and specify we want it delivered * on the alternate stack */ act.sa_handler = handler; act.sa_flags = SA_ONSTACK; if( sigaction( SIGSEGV, &act, 0 ) < 0 ) { printf( "failed to install sig-handler!\n" ); exit(0); } /* Step 3 - Generate a stack-overflow with recursion. * Without the sigaltstack you will not handle the SIGSEGV * because there will be no more space left on the processes * main stack for the call. * With the patch, you will catch it correctly! Wheee!! */ pid = fork(); if( pid < 0 ) { printf( "fork() failed.\n" ); exit(0); } if( pid == 0 ) pid = getppid(); else sleep(1); /* let child get pid first */ GET_STACK( ss_sp ); printf( "[%d] main stack pointer = %p\n", pid, ss_sp );
if( setjmp( back ) ) { printf( "[%d] recovered from stack-overflow.\n", pid ); GET_STACK( ss_sp ); printf( "[%d] main stack pointer after recover = %p\n", pid, ss_sp ); exit(0); } recurse(); }
|  |