March 14, 2012 Re: Turning a SIGSEGV into a regular function call under Linux, allowing throw | ||||
---|---|---|---|---|
| ||||
Posted in reply to Steven Schveighoffer | Le 14/03/2012 21:53, Steven Schveighoffer a écrit :
> On Wed, 14 Mar 2012 16:45:49 -0400, Don Clugston <dac@nospam.com> wrote:
>
>> On 14/03/12 21:31, Steven Schveighoffer wrote:
>>> On Wed, 14 Mar 2012 16:08:29 -0400, Don Clugston <dac@nospam.com> wrote:
>>>
>>>> Now, your user space handler will cause another segfault when it does
>>>> the mov [ESP], 0. I think that gives you an infinite loop.
>>>
>>> SEGFAULT inside a SEGV signal handler aborts the program (no way to turn
>>> this off IIRC).
>>>
>>> -Steve
>>
>> But you're not inside the signal handler when it happens. You returned.
>
> Then how does the signal handler do anything? I mean, doesn't it need a
> stack? Or does it just affect register variables? Most signal handlers
> are normal functions, and isn't there some usage of the stack to save
> registers?
>
> It seems there should be a way to turn off the signal handler during the
> time when you are suspicous of the stack being the culprit, then
> re-engage the signal handler before throwing the error.
>
> -Steve
The address of the instruction being executed is hijacked, so, instead of resuming normal operation after the signal handler exit, it get into the throwing handler.
This is a very nice trick !
|
March 14, 2012 Re: Turning a SIGSEGV into a regular function call under Linux, allowing throw | ||||
---|---|---|---|---|
| ||||
Posted in reply to Sean Kelly | Le 14/03/2012 21:59, Sean Kelly a écrit :
> On Mar 14, 2012, at 1:54 PM, FeepingCreature wrote:
>>
>> I think that case is sufficiently rare that it'd have to count somewhere between "act of god" and "outright developer malice". The assumption that the stack frame is valid is, I'd say, safe to make in the vast majority of cases. You pretty much have to actively try to break it, for no clearly discernible reason.
>
> The prevalence of buffer overflow attacks might suggest otherwise.
And as a stack overflow is likely to create a SEGFAULT too, we are doomed !
|
March 14, 2012 Re: Turning a SIGSEGV into a regular function call under Linux, allowing throw | ||||
---|---|---|---|---|
| ||||
Posted in reply to deadalnix | On Wed, 14 Mar 2012 17:25:28 -0400, deadalnix <deadalnix@gmail.com> wrote:
> Le 14/03/2012 21:53, Steven Schveighoffer a écrit :
>> On Wed, 14 Mar 2012 16:45:49 -0400, Don Clugston <dac@nospam.com> wrote:
>>
>>> On 14/03/12 21:31, Steven Schveighoffer wrote:
>>>> On Wed, 14 Mar 2012 16:08:29 -0400, Don Clugston <dac@nospam.com> wrote:
>>>>
>>>>> Now, your user space handler will cause another segfault when it does
>>>>> the mov [ESP], 0. I think that gives you an infinite loop.
>>>>
>>>> SEGFAULT inside a SEGV signal handler aborts the program (no way to turn
>>>> this off IIRC).
>>>>
>>>> -Steve
>>>
>>> But you're not inside the signal handler when it happens. You returned.
>>
>> Then how does the signal handler do anything? I mean, doesn't it need a
>> stack? Or does it just affect register variables? Most signal handlers
>> are normal functions, and isn't there some usage of the stack to save
>> registers?
>>
>> It seems there should be a way to turn off the signal handler during the
>> time when you are suspicous of the stack being the culprit, then
>> re-engage the signal handler before throwing the error.
>>
>> -Steve
>
> The address of the instruction being executed is hijacked, so, instead of resuming normal operation after the signal handler exit, it get into the throwing handler.
>
> This is a very nice trick !
I get that. What I was saying is, I thought even the signal handler uses the stack (thereby it would abort if invalid). And even if it doesn't, simply accessing the stack by loading it into a register should be sufficient to "test" and see if the stack is valid to use (i.e. cause another SEGV inside the signal handler forcing an abort so we don't have an infinite loop).
I honestly don't know enough to really be discussing, but it seems like a really neat idea, and I grasp how it works. I just don't know all the particulars of signal calling conventions.
-Steve
|
March 14, 2012 Re: Turning a SIGSEGV into a regular function call under Linux, allowing throw | ||||
---|---|---|---|---|
| ||||
Posted in reply to Sean Kelly | On 14/03/12 21:59, Sean Kelly wrote:
> On Mar 14, 2012, at 1:54 PM, FeepingCreature wrote:
>>
>> I think that case is sufficiently rare that it'd have to count somewhere between "act of god" and "outright developer malice". The assumption that the stack frame is valid is, I'd say, safe to make in the vast majority of cases. You pretty much have to actively try to break it, for no clearly discernible reason.
>
> The prevalence of buffer overflow attacks might suggest otherwise.
void foo()
{
bar();
}
void bar()
{
int y;
int *p = &y;
p[1] = 0;
}
The assignment to p[1]=0 clobbers the location where EBP was pushed.
Then:
mov ESP, EBP; // ESP is OK
pop EBP; // EBP is now 0
ret;
now return to foo, where we get:
call bar;
-> mov ESP, EBP; // ESP is now 0
pop EBP; // segfault
ret
Unfortunately it's not difficult to corrupt ESP.
|
March 14, 2012 Re: Turning a SIGSEGV into a regular function call under Linux, allowing throw | ||||
---|---|---|---|---|
| ||||
Posted in reply to Steven Schveighoffer | On Wed, Mar 14, 2012 at 05:35:04PM -0400, Steven Schveighoffer wrote: [...] > I get that. What I was saying is, I thought even the signal handler uses the stack (thereby it would abort if invalid). And even if it doesn't, simply accessing the stack by loading it into a register should be sufficient to "test" and see if the stack is valid to use (i.e. cause another SEGV inside the signal handler forcing an abort so we don't have an infinite loop). That's a good idea. So the signal handler reads the top of the stack into EAX (since we're already overwriting EAX anyway), and if the stack is invalid, that will segfault and abort the program. If that doesn't abort, then assume the stack is valid and proceed with the hack to divert the return address to the throwing handler. However, this still assumes that ESP is either valid or null. If the segfault was caused by, say, an exploit attempt, then ESP may be non-null but not pointing to a valid stack either. It's conceivable that someone might try to exploit a D program by crafting a bad stack and pointing ESP at it, then triggering a segfault intentionally. (The bad stack could, for example, contain strange stack frames that causes the stack unwinder to do something unintended, like execute arbitrary code.) I don't know how to solve this, though. T -- There is no gravity. The earth sucks. |
March 15, 2012 Re: Turning a SIGSEGV into a regular function call under Linux, allowing throw | ||||
---|---|---|---|---|
| ||||
Posted in reply to FeepingCreature | Here is a proof of concept of how we can recover from segfault. This isn't perfect as it doesn't protect everything (like floating point registers). This is mostly because I can't find the precise documentation about what must be saved or not. The handler call a naked function that will set up a stack simulation a standard call, and then call a D function. This function recieve as parameter the memory address that cause the segfault. We can do whatever we want in the D function, at this point we have a clean stack. Then, if the function call return the standard way, things are set back and the code triggering the segfault ran again. In the example below, I use memory protection to trigger the segfault. In the handler, I remove the memory protection, so the program can continue its execution. The code is based on Vladimir Panteleev's prototype. import core.sys.posix.signal; import core.sys.posix.ucontext; import std.stdio; // Missing details from Druntime version(X86_64) { enum { REG_R8 = 0, REG_R9, REG_R10, REG_R11, REG_R12, REG_R13, REG_R14, REG_R15, REG_RDI, REG_RSI, REG_RBP, REG_RBX, REG_RDX, REG_RAX, REG_RCX, REG_RSP, REG_RIP, REG_EFL, REG_CSGSFS, /* Actually short cs, gs, fs, __pad0. */ REG_ERR, REG_TRAPNO, REG_OLDMASK, REG_CR2 } } else version (X86) { enum { REG_GS = 0, REG_FS, REG_ES, REG_DS, REG_EDI, REG_ESI, REG_EBP, REG_ESP, REG_EBX, REG_EDX, REG_ECX, REG_EAX, REG_TRAPNO, REG_ERR, REG_EIP, REG_CS, REG_EFL, REG_UESP, REG_SS } } // Init shared static this() { sigaction_t action; action.sa_sigaction = &handleSignal; action.sa_flags = SA_SIGINFO; sigaction(SIGSEGV, &action, null); } // Sighandler space alias typeof({ucontext_t uc; return uc.uc_mcontext.gregs[0];}()) REG_TYPE; static REG_TYPE saved_EAX, saved_EDX; extern(C) void handleSignal(int signum, siginfo_t* info, void* contextPtr) { auto context = cast(ucontext_t*)contextPtr; // Save registers into global thread local, to allow recovery. saved_EAX = context.uc_mcontext.gregs[REG_EAX]; saved_EDX = context.uc_mcontext.gregs[REG_EDX]; // Hijack current context so we call our handler. context.uc_mcontext.gregs[REG_EAX] = cast(REG_TYPE) info._sifields._sigfault.si_addr; context.uc_mcontext.gregs[REG_EDX] = context.uc_mcontext.gregs[REG_EIP]; context.uc_mcontext.gregs[REG_EIP] = cast(REG_TYPE) &sigsegv_userspace_handler; } // User space // This function must be called with faulting address in EAX and original EIP in EDX. void sigsegv_userspace_handler() { asm { naked; push EDX; // return address (original EIP). push EBP; // old ebp mov EBP, ESP; push ECX; // ECX is a trash register and must be preserved as local variable. // Parameter address is already set as EAX. call sigsegv_userspace_process; // Restore register values and return. call restore_registers; pop ECX; // Return pop EBP; ret; } } // The return value is stored in EAX and EDX, so this function restore the correct value for theses registers. REG_TYPE[2] restore_registers() { return [saved_EAX, saved_EDX]; } // User space handler class SignalError : Error { this(string msg) { super(msg); } } extern(C) int mprotect(void*, size_t, int); void sigsegv_userspace_process(void* address) { import std.stdio; writeln("Handler starting."); writeln("SEGFAULT triggered at address : ", address); // Dirty trick to get stack trace, for debug purpose. try { throw new SignalError("SIGSEGV"); } catch(SignalError se) { writeln(se.toString()); } // Allow write access to memory. So when we return the operation causing SEGFAULT will succeed. import core.sys.posix.sys.mman; mprotect(address, 4096, PROT_READ|PROT_WRITE); writeln("Handler ending."); // throw new SignalError("SIGSEGV"); } // Demonstration void foo(void* x) { *(cast(int*) x) = 1; } void main() { import core.sys.posix.sys.mman; import std.stdio; void* x = mmap(cast(void*) 0x12340000, 4096, PROT_NONE, MAP_PRIVATE|MAP_ANON, -1, 0); if(x == cast(void*) 0x12340000) { writeln("Try to write at ", x); foo(x); } else { write("Can't mmap :("); } assert(*(cast(int*) x) == 1); writeln("Value successfully written ! SIGSEGV recovered !"); } |
March 15, 2012 Re: Turning a SIGSEGV into a regular function call under Linux, allowing throw | ||||
---|---|---|---|---|
| ||||
Posted in reply to FeepingCreature | Does it recover from void function() f=null; f(); |
March 15, 2012 Re: Turning a SIGSEGV into a regular function call under Linux, allowing throw | ||||
---|---|---|---|---|
| ||||
Posted in reply to Kagamin | On 03/15/12 16:16, Kagamin wrote:
> Does it recover from
>
> void function() f=null;
> f();
Not as currently written, no. It should be possible to detect this case and get a proper stackframe back, though.
|
March 17, 2012 Re: Turning a SIGSEGV into a regular function call under Linux, allowing throw | ||||
---|---|---|---|---|
| ||||
Posted in reply to FeepingCreature | Le 15/03/2012 21:20, FeepingCreature a écrit :
> On 03/15/12 16:16, Kagamin wrote:
>> Does it recover from
>>
>> void function() f=null;
>> f();
>
> Not as currently written, no. It should be possible to detect this case and get a proper stackframe back, though.
It is supported as written in my sample code. I have do do another one for x86_64.
|
Copyright © 1999-2021 by the D Language Foundation