Bug 21943

Summary: pthreads: longjmp from signal handler jumps to the wrong location
Product: Base System Reporter: Mike Hibler <mike>
Component: binAssignee: Jason Evans <jasone>
Status: Closed FIXED    
Severity: Affects Only Me    
Priority: Normal    
Version: 4.1.1-RELEASE   
Hardware: Any   
OS: Any   

Description Mike Hibler 2000-10-13 03:10:01 UTC
When using pthreads (gcc -pthread), performing a longjmp out of a
signal handler can sometimes return to the point at which the signal
occured rather than to the location indicated by the jmpbuf.

Specifically, when the signal is delivered from pthread_sigmask due
to unblocking a pending signal, a longjmp returns to the end of
pthread_sigmask instead of the corresponding setjmp location.

This appears to have been introduced 1/19/2000 when "continuations"
were added to "correctly handle [sig|_]longjmp() inside of a
signal handler".  The user's setjmp buf is saved in nested_jmp buf
the jmpflags are not checked after delivering signals in pthread_sigmask.

Fix: 

Suggested above, but haven't tried.
How-To-Repeat: With the following program:

#include <stdio.h>
#include <pthread.h>
#include <assert.h>
#include <setjmp.h>
#include <signal.h>

#if 1
#define JMPBUF          sigjmp_buf
#define SETJMP(b)       sigsetjmp((b), 0)
#define LONGJMP(b,v)    siglongjmp((b), (v))
#else
#define JMPBUF          jmp_buf
#define SETJMP(b)       setjmp((b))
#define LONGJMP(b,v)    longjmp((b), (v))
#endif

static JMPBUF jb;
static pthread_mutex_t lock;
static pthread_cond_t cond;
static volatile int started, signaled;
static sigset_t mask;

static void
handler(int sig, siginfo_t *si, void *sc)
{
        printf("Got signal, longjmping...\n");
        signaled = 1;
        LONGJMP(jb, 1);
        assert(0);
}

static void *
cwloop(void *arg)
{
        struct sigaction act;
        int err;

        err = pthread_mutex_lock(&lock); assert(err == 0);

#ifdef SA_SIGINFO
        act.sa_flags = SA_SIGINFO;
        act.sa_sigaction = handler;
#else
        act.sa_handler = handler;
#endif
        sigemptyset(&act.sa_mask);
        sigaction(SIGUSR1, &act, 0);
        err = pthread_sigmask(SIG_BLOCK, &mask, 0); assert(err == 0);
        if (SETJMP(jb) != 0) {
                assert(signaled == 1);
                printf("WORKED!\n");
                pthread_exit(0);
        }

        printf("Child thread running, waiting for signal...\n");

        started = 1;
        err = pthread_cond_signal(&cond); assert(err == 0);
        err = pthread_mutex_unlock(&lock); assert(err == 0);
        while (!signaled) {
                err = pthread_sigmask(SIG_UNBLOCK, &mask, 0); assert(err == 0);
                err = pthread_sigmask(SIG_BLOCK, &mask, 0); assert(err == 0);
        }

        printf("FAILED!\n");
        pthread_exit((void *)1);

        return 0;
}

main()
{
        pthread_t th;
        int err;

        sigemptyset(&mask);
        sigaddset(&mask, SIGUSR1);

        err = pthread_mutex_init(&lock, 0); assert(err == 0);
        err = pthread_cond_init(&cond, 0); assert(err == 0);

        err = pthread_mutex_lock(&lock); assert(err == 0);
        err = pthread_create(&th, 0, cwloop, 0); assert(err == 0);
        while (!started) {
                err = pthread_cond_wait(&cond, &lock); assert(err == 0);
        }

        printf("Started child, sending SIGUSR1...\n");
        err = pthread_mutex_unlock(&lock); assert(err == 0);
        err = pthread_kill(th, SIGUSR1); assert(err == 0);
        err = pthread_join(th, (void **)&err); assert(err == 0);
        printf("Child died with exit code %d\n", err);
        exit(0);
}
Comment 1 Jason Evans freebsd_committer freebsd_triage 2000-10-23 22:36:29 UTC
Responsible Changed
From-To: freebsd-bugs->jasone

Over to maintainer. 

Comment 2 Jason Evans 2000-11-07 02:48:40 UTC
On Thu, Oct 12, 2000 at 07:02:58PM -0700, mike@cs.utah.edu wrote:
> >Description:
> When using pthreads (gcc -pthread), performing a longjmp out of a
> signal handler can sometimes return to the point at which the signal
> occured rather than to the location indicated by the jmpbuf.

Dan Eischen has some patches that appear to fix this, and he'll probably be
committing them to -current within the next few days.  It's still not
certain whether the libc_r changes are going to be MFCed for FreeBSD 4.2,
but there's some chance it will happen.

Jason
Comment 3 Daniel Eischen freebsd_committer freebsd_triage 2000-11-18 00:15:01 UTC
State Changed
From-To: open->closed

Fixed in both -stable and -current.