On Tue, Mar 13, 2012 at 11:09:54AM +0100, FeepingCreature wrote: [...] > I've seen it argued a lot over the years (even argued it myself) that > it's impossible to throw from Linux signal handlers. This is basically > correct, because they constitute an interruption in the stack that > breaks exceptions' ability to unroll properly. > > However, there is a method to turn a signal handler into a regular > function call that you can throw from. > > Basically, what we need to do is similar to a stack buffer overflow > exploit. Under Linux, the extended signal handler that is set with > sigaction is called with three arguments: the signal, a siginfo_t* and > a ucontext_t* as the third. > > The third parameter is what we're interested in. Deep inside the > ucontext_t struct is uc.mcontext.gregs[REG_EIP], the address of the > instruction that caused the segfault. This is the location that > execution returns to when the signal handler returns. By overwriting > this location, we can turn a return into a function call. > > First, gregs[REG_EAX] = gregs[REG_EIP]; > > We can safely assume that the function that caused the segfault > doesn't really need its EAX anymore, so we can reuse it to reconstruct > a proper stackframe to throw from later. > > Second, gregs[REG_EIP] = cast(void*) &sigsegv_userspace_handler; > > Note that the naked attribute was not used. If used, it can make this > code slightly easier. > > extern(C) void sigsegv_userspace_handler() { > // done implicitly > // asm { push ebp; } > // asm { mov ebp, esp; } > asm { mov ebx, [esp]; } // backup the pushed ebp > asm { mov [esp], eax; } // replace it with the correct return address > // which was originally left out due to the > // irregular way we entered this function (via a > ret). > asm { push ebx; } // recreate the pushed ebp > asm { mov ebp, esp; } // complete stackframe. > // originally, our stackframe (because we entered this function via a ret) > // was [ebp]. Now, it's [return address][ebp], as is proper for cdecl. > // at this point, we can safely throw > // (or invoke any other non-handler-safe function). > throw new SignalException("SIGSEGV"); > }
Nice!! So basically you allow the signal handler to return cleanly so that we're out of signal-handling context, but overwrite the return address so that instead of returning to where the signal happened, it gets diverted to a special handler that reconstructs a stack frame and then throws. Cool beans! The only drawback is, this only works on x86 Linux. I think it should be possible to make it work on non-x86 Linux by writing machine-specific code along the same principles. But I'm pretty sure it won't work for other unixen though. They'll probably need their own system-specific hacks. T -- If you compete with slaves, you become a slave. -- Norbert Wiener