Turning a SIGSEGV into a regular function call under Linux, allowing throw

H. S. Teoh hsteoh at quickfur.ath.cx
Tue Mar 13 16:59:06 PDT 2012


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


More information about the Digitalmars-d mailing list