Bug: sigaction w/ SA_SIGINFO not passing the correct context

Hi everybody,

I’m trying to catch the SIGSEGV signal, do something, and then return control back to the application. I use “sigaction” with the SA_SIGINFO flag. This flags allows a handler to be called upon a signal, and it should (POSIX defined) also provide the context of the thread when the signal was issued. The problem is that the right context does NOT work on OpenSuse, but it works on Ubuntu.

So I was wondering if there is a specific action that one needs to take on OpenSuse when dealing with this issue, or if there is a bug in the kernel/opensuse ?

On Ubuntu (kernel 2.6.24-19-generic), sigaction correctly provides the context of the thread that raised the signal. However, on two other installations of OpenSuse (kernels 2.6.16.21-0.8-smp and 2.6.25.11-0.1-default), sigaction provides a different context.

Here is the code example, with its summary. Four pages get allocated in heap. The first two pages with read+write protection, and the last two with “not to be accessed”. A read or write to the last two pages will raise a SIGSEGV signal. What I do is catch this signal when the last two pages are accessed, change their protection from “not to be accessed” to read+write, and then give the control back to the instruction that raised this signal. The code works on Ubuntu, but on the both OpenSuse installations that I tried (see above for kernels) the context that is passed by sigaction is the context of the handler instead of the instruction that raised the signal.

Here is the code:

#include <unistd.h>
#include <signal.h>
#include <bits/siginfo.h>
#include <stdio.h>
#include <malloc.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/mman.h>
#include <ucontext.h>

#define handle_error(msg)
do { perror(msg); exit(EXIT_FAILURE); } while (0)

char *buffer;
ucontext_t context_sig, context_trigger, context_test;
struct sigaction sa_new, sa_old;

static void
handler(int sig, siginfo_t *si, void *context)
{
if ((sig == SIGSEGV) || (sig == SIGBUS) )
{
printf("
Got SIGSEGV or SIGBUS at address: 0x%lx and context =%lx
",(long) si->si_addr, context);

if (mprotect(si->si_addr, sysconf(_SC_PAGE_SIZE) ,PROT_READ | PROT_WRITE) == -1)
handle_error(“mprotect failed from the handler”);
else
printf("
mprotect set the protection to RW
");

#if 1
// The intention here is to switch back to the context that signaled the SIGSEGV. Unfortunately, it does not
// do that. Instead, it switches the context back to the handler function, in a recursive call.
swapcontext(&context_sig, (ucontext_t*)context);
#else
// Manually set the context to the point previous to accessing the memory. This works !
swapcontext(&context_sig, &context_trigger);
#endif
}

}

int
main(int argc, char *argv])
{
char *p;

int pagesize=sysconf(_SC_PAGE_SIZE);

if (pagesize == -1)
handle_error(“sysconf”);

/* Set the handler */
sa_new.sa_flags = SA_SIGINFO;
sigemptyset(&sa_new.sa_mask);
sa_new.sa_sigaction = handler;
if (sigaction(SIGSEGV, &sa_new, &sa_old) == -1)
handle_error(“sigaction”);

/* Allocate a buffer aligned on a page boundary;
initial protection is PROT_READ | PROT_WRITE */

buffer = (char*)memalign(pagesize, 4 * pagesize);
if (buffer == NULL)
handle_error(“memalign”);

printf("Start of region: 0x%lx
", (long) buffer);

if (mprotect(buffer + pagesize * 2, pagesize, PROT_NONE) == -1) handle_error(“mprotect”);

int i=0;

for ( p = buffer ; p< (buffer+ 4*pagesize) ; ++i)
{
getcontext(&context_trigger);

if ((i%1000) == 0) printf("
iteration %d",i);

*(p++) = ‘a’;

}

printf("
Loop completed
");
exit(EXIT_SUCCESS);
}

Any help/pointer would be greatly appreciated.

Thanks,
Alin Jula