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

Don Clugston dac at nospam.com
Wed Mar 14 13:08:29 PDT 2012


On 13/03/12 11:09, FeepingCreature wrote:
> Note: I worked out this method for my own language, Neat, but the basic approach should be portable to D's exceptions as well.
>
> 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");
> }

I didn't realize that was possible. Very interesting.
As it stands, though, that's got some pretty serious issues.

You are on the stack of the function that was called, but you don't know 
for sure that it is a valid stack.

asm {
     push EBX;
     mov EBX, ESP;
     mov ESP, 0;    // Look ma, no stack!

     mov int ptr [ESP], 0; // segfault -- null pointer exception

     mov ESP, EBX;
     pop EBX;
}

Now, your user space handler will cause another segfault when it does 
the mov [ESP], 0. I think that gives you an infinite loop.

I think the idea would work, if you had some guarantee that the stack 
pointer was valid. Then, call a separate handler if it is not.
The primary 'trick' in Windows SEH is that it goes to great lengths to 
verify that the stack is valid. I'm not sure that in Linux user space 
you have enough information to verify it. But maybe you do. At least, 
you should be able to check that it's in memory which is owned by your 
process.

Would be awesome if it is possible.



More information about the Digitalmars-d mailing list