[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