This is the mail archive of the gcc-patches@gcc.gnu.org mailing list for the GCC project.


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]
Other format: [Raw text]

[PATCH] Segfault while unwinding an invalid function pointer


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)




Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]