|
From: Julian S. <js...@ac...> - 2006-02-11 02:15:23
|
In fact on further study I don't really understand how this is supposed
to work (safely), even natively.
> struct stack_layout
> {
> void *ret_addr;
> int i;
> char *str;
> char buf[16];
> unsigned long ebp;
> unsigned long eip;
> };
>
> void handler_new( int signo, siginfo_t* xx, void* uc)
> {
> ucontext_t* ctx = (ucontext_t*)uc;
> struct stack_layout* stack;
> printf("in handler2, setting EIP to %p\n", (void*)&diversion);
> stack = (struct stack_layout*)ctx->uc_mcontext.gregs[REG_ESP];
>
> stack--; /* push the stack_layout structure */
> VALGRIND_MAKE_WRITABLE(stack, sizeof(*stack));
> stack->ret_addr = (void *)0xdeadbabe; /* raise_func must not
> return */
> /* setting this to a reasonable value doesn't help
> stack->ret_addr = (unsigned long)&diversion;
> */
> stack->i = 12;
> stack->str = stack->buf;
> strcpy(stack->buf, "foo-bar");
>
> ctx->uc_mcontext.gregs[REG_EIP] = (unsigned long)&diversion;
> ctx->uc_mcontext.gregs[REG_ESP] = (unsigned long)stack;
> }
My understanding is:
- in main, the segfault happens
- kernel pushes a signal frame on the stack, saving the machine state
in it, and enters handler_new
- handler_new finds out what %esp was at the time of the fault
(stack = ctx->uc_mcontext.gregs[REG_ESP]).
My picture of the stack is now
------------
frame for main()
------------ <--- "stack"
kernel-constructed sigframe ...
...
kernel-constructed sigframe ...
- You then do "stack--", which moves "stack" down one frame unit. Now
it overlaps with the kernel-constructed sigframe.
- You write stuff in *stack, trashing part of the kernel-constructed frame.
- You set ctx->uc_mcontext.gregs[REG_EIP] and REG_ESP.
- handler_new returns. The machine state is restored from the partially
corrupted kernel-constructed sigframe. Execution resumes in diversion()
with the stack looking like this:
------------
frame for main()
------------
struct stack_layout
------------ <------- %esp
How do you know it is OK to overwrite the top of the
kernel-constructed signal frame with your struct stack_layout?
I suspect (if my analysis is right) that this could be a cause
of problems with V. V's signal delivery frames look different from
the kernel's one as they contain more information, and trashing
the top part of it will likely cause problems.
A perhaps safer approach is:
In the handler, do not write anything onto the stack and do not
change ctx->uc_mcontext.gregs[REG_ESP]. Instead copy all info you
need to construct the struct stack_layout, including the values
of ctx->uc_mcontext.gregs[REG_ESP] and [REG_EIP], to some safe place
(thread-local storage?). Set ctx->uc_mcontext.gregs[REG_EIP] to
point to a handwritten assembly function, and let the handler return.
You are now in your handwritten assembly function. Using the saved
info, construct the struct stack_layout, and jump the the EIP noted
in the saved info.
This relies on the ideas that
- overwriting the top end of the signal frame is likely to kill valgrind
- changing %ESP inside the signal handler is likely to cause memcheck to
emit lots of bogus messages, and these may be difficult to get rid of
(changing %ESP is really asking for trouble from memcheck :-)
My proposal avoids both problems.
J
|