March 14, 2012
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
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
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
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
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
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
Does it recover from

void function() f=null;
f();
March 15, 2012
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
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.
1 2 3 4
Next ›   Last »