[PATCH] Segfault while unwinding an invalid function pointer
Pete Eberlein
eberlein@linux.vnet.ibm.com
Thu Jan 31 08:48:00 GMT 2008
Hello, I am resubmitting this patch for review. To recap, when the Backtrace
function is called from a signal handler as a result of invalid function pointer
call, the unwinding code will itself raise segv. This was first reported in
http://gcc.gnu.org/ml/gcc/2007-06/msg00329.html
This fix is for x86_64 and uses the mincore function determine if a memory range
is "safe" before attempting to read it, so that the MD_FALLBACK_FRAME_STATE_FOR
function will not segfault. If the memory range is invalid, it is determined to
be a invalid function pointer call and the cfa is adjusted accordingly.
Here is a test case to demonstrate the problem and verify the fix:
#include <unistd.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <execinfo.h>
void (*fp)(void) = (void *)0xffffff;
int rc ;
struct sigaction newact; /* Action */
struct sigaction oldact; /* Old action */
void foo( void )
{
char buf[16];
buf[0] = '\n';
buf[1] = '\0';
printf( "Calling invalid function pointer.%s", buf );
fp();
}
void catch_segv(int signum)
{
int i, cnt;
void *syms[100];
char buf[80];
char* *strs;
rc = sigaction (SIGSEGV, &oldact, NULL);
rc = sigaction (SIGBUS, &oldact, NULL);
cnt = backtrace( syms, 100 );
strs = backtrace_symbols( syms, 100 );
for ( i = 0 ; i < cnt ; i++ )
{
snprintf( buf, sizeof(buf), "%d\t%lx\t%s\n",
i,
(unsigned long)syms[i],
strs[i] );
puts( buf );
}
free(strs);
printf("PASS\n");
exit(1);
}
int main( void )
{
newact.sa_handler = catch_segv;
sigemptyset (&newact.sa_mask);
newact.sa_flags = 0;
rc = sigaction (SIGSEGV, &newact, &oldact);
rc = sigaction (SIGBUS, &newact, &oldact);
printf( "rc = %d, calling foo\n", rc );
foo();
return 0;
}
--
Pete Eberlein
IBM Linux Technology Center
Linux on Power Toolchain
2008-01-30 Pete Eberlein <eberlein@us.ibm.com>
* linux-unwind.h (x86_64_fallback_frame_state): Handle
invalid function pointer address and change cfa.
Index: gcc/config/i386/linux-unwind.h
===================================================================
--- gcc/config/i386/linux-unwind.h (revision 131968)
+++ gcc/config/i386/linux-unwind.h (working copy)
@@ -37,6 +37,9 @@
#include <signal.h>
#include <sys/ucontext.h>
+#include <unistd.h>
+#include <sys/mman.h>
+
#define MD_FALLBACK_FRAME_STATE_FOR x86_64_fallback_frame_state
static _Unwind_Reason_Code
@@ -46,7 +49,51 @@
unsigned char *pc = context->ra;
struct sigcontext *sc;
long new_cfa;
+ int mem_invalid, saved_errno;
+ char dummy[2];
+ unsigned long start;
+ /* check if memory is readable (using mincore) */
+ saved_errno = errno;
+ start = ((unsigned long)pc) & ~(sysconf(_SC_PAGE_SIZE) - 1);
+ mem_invalid = mincore((void*)start, (unsigned long)(pc+9-start), dummy);
+ if (errno != ENOMEM && errno != EINVAL)
+ mem_invalid = 0;
+ errno = saved_errno;
+
+ if (mem_invalid)
+ {
+ /* memory was invalid */
+
+ fs->regs.cfa_how = CFA_REG_OFFSET;
+ /* Register 7 is rsp */
+ fs->regs.cfa_reg = 7;
+ fs->regs.cfa_offset = 8;
+
+ fs->regs.reg[0].how = REG_UNSAVED;
+ fs->regs.reg[1].how = REG_UNSAVED;
+ fs->regs.reg[2].how = REG_UNSAVED;
+ fs->regs.reg[3].how = REG_UNSAVED;
+ fs->regs.reg[4].how = REG_UNSAVED;
+ fs->regs.reg[5].how = REG_UNSAVED;
+ fs->regs.reg[6].how = REG_UNSAVED;
+ fs->regs.reg[8].how = REG_UNSAVED;
+ fs->regs.reg[9].how = REG_UNSAVED;
+ fs->regs.reg[10].how = REG_UNSAVED;
+ fs->regs.reg[11].how = REG_UNSAVED;
+ fs->regs.reg[12].how = REG_UNSAVED;
+ fs->regs.reg[13].how = REG_UNSAVED;
+ fs->regs.reg[14].how = REG_UNSAVED;
+ fs->regs.reg[15].how = REG_UNSAVED;
+ fs->regs.reg[16].how = REG_SAVED_OFFSET;
+ fs->regs.reg[16].loc.offset = -8;
+
+ fs->retaddr_column = 16;
+ fs->signal_frame = 0;
+
+ return _URC_NO_REASON;
+ }
+
/* movq __NR_rt_sigreturn, %rax ; syscall */
if (*(unsigned char *)(pc+0) == 0x48
&& *(unsigned long *)(pc+1) == 0x050f0000000fc0c7)
More information about the Gcc-patches
mailing list