[PATCH] signal stacks

Alain Knaff (msmith@quix.robins.af.mil)
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();
}