public inbox for gcc-patches@gcc.gnu.org
 help / color / mirror / Atom feed
* [PATCH] Segfault while unwinding an invalid function pointer
@ 2008-04-11  4:23 Pete Eberlein
  0 siblings, 0 replies; 13+ messages in thread
From: Pete Eberlein @ 2008-04-11  4:23 UTC (permalink / raw)
  To: gcc-patches; +Cc: jakub, bergner

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
I last submitted this patch for review in
http://gcc.gnu.org/ml/gcc-patches/2008-01/msg01474.html
but this was during a regression patch cycle.  If this patch is
accepted, would Peter Bergner mind checking it in?

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.

I tried porting the patch to POWER, the only other architecture I have
access to, but was unable to reproduce the segfault during backtrace.
Somehow the signal frame is causing the invalid address to be skipped.

Here is a test case to demonstrate the problem and verify the fix:
(This example uses a non-hard-coded address for the invalid function
pointer using mprotect'ed memory.)

#include <unistd.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <execinfo.h>
#include <sys/mman.h>
#include <malloc.h>

void (*fp)(void);

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 )
{
   int result;
   
   fp = (void*) memalign(sysconf(_SC_PAGESIZE),4096);
   result = mprotect(fp, 4096, PROT_NONE);
   if (result) perror("mprotect");

   printf( "fp = %llx  (mprotect returned %d)\n", fp, result );

   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 for Power Toolchain


2008-04-10  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 134151)
+++ gcc/config/i386/linux-unwind.h      (working copy)
@@ -39,6 +39,10 @@
 
 #define MD_FALLBACK_FRAME_STATE_FOR x86_64_fallback_frame_state
 
+#include <unistd.h>
+#include <sys/mman.h>
+
+
 static _Unwind_Reason_Code
 x86_64_fallback_frame_state (struct _Unwind_Context *context,
                             _Unwind_FrameState *fs)
@@ -46,7 +50,52 @@
   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)


^ permalink raw reply	[flat|nested] 13+ messages in thread
[parent not found: <47A0F9FA.9000903@linux.vnet.ibm.com.suse.lists.egcs-patches>]
* [PATCH] Segfault while unwinding an invalid function pointer
@ 2008-01-31  8:48 Pete Eberlein
  2008-01-31  9:27 ` Jakub Jelinek
  0 siblings, 1 reply; 13+ messages in thread
From: Pete Eberlein @ 2008-01-31  8:48 UTC (permalink / raw)
  To: gcc-patches; +Cc: jakub, drow, bergner

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)



^ permalink raw reply	[flat|nested] 13+ messages in thread
* [PATCH] Segfault while unwinding an invalid function pointer
@ 2007-10-10 20:39 Pete Eberlein
  0 siblings, 0 replies; 13+ messages in thread
From: Pete Eberlein @ 2007-10-10 20:39 UTC (permalink / raw)
  To: gcc-patches

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 adds a function that will 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.  The function which determines if a 
memory range is "safe" does not install a signal handler; instead it 
writes the memory range to a file and checks the result of bytes written 
to see if the memory was valid.  Since the writes happen in kernel 
space, no segfault will be generated.

-- 
Pete Eberlein
IBM Linux Technology Center
Linux on Power Toolchain


2007-10-10  Pete Eberlein  <eberlein@us.ibm.com>

	* linux-unwind.h (unwind_memory_range_check): New function
	that determines if a memory range is readable.
	* 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 129215)
+++ gcc/config/i386/linux-unwind.h      (working copy)
@@ -39,6 +39,65 @@

  #define MD_FALLBACK_FRAME_STATE_FOR x86_64_fallback_frame_state

+#include <unistd.h>
+#include <stdlib.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+
+#define tmp_path "/tmp/null"
+
+static int
+unwind_memory_range_check (void *addr, size_t len)
+{
+  int rc;
+  int result = 0;
+  int tmp_null_f = -1;
+  int write_len = 0;
+  mode_t mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH;
+
+  errno = 0;
+  if (tmp_null_f == -1)
+  {
+    tmp_null_f = open (tmp_path, (O_WRONLY | O_CREAT | O_TRUNC), mode);
+    if (tmp_null_f == -1) {
+      perror (" open (" tmp_path ") failed\n");
+      exit (1);
+    }
+  }
+  if (tmp_null_f != -1)
+  {
+    if ((write_len + len) > 4096)
+    {
+      if (write_len > 4096)
+      {
+        errno = 0;
+        rc = ftruncate (tmp_null_f, 0);
+        if (rc == -1)
+        {
+          perror ("ftruncate failed\n");
+        }
+      }
+      write_len = 0;
+      errno = 0;
+      rc = lseek (tmp_null_f, 0, SEEK_SET);
+
+      if (rc == -1)
+      {
+        perror ("lseek failed\n");
+      }
+    }
+    write_len += len;
+
+    rc = write (tmp_null_f, addr, len);
+    result = (rc == len);
+
+    close (tmp_null_f);
+    unlink (tmp_path);
+
+  }
+  return result;
+}
+
  static _Unwind_Reason_Code
  x86_64_fallback_frame_state (struct _Unwind_Context *context,
                              _Unwind_FrameState *fs)
@@ -47,6 +106,39 @@
    struct sigcontext *sc;
    long new_cfa;

+  if (!unwind_memory_range_check(pc, 5))
+    {
+      /* 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)

^ permalink raw reply	[flat|nested] 13+ messages in thread

end of thread, other threads:[~2008-04-11  0:36 UTC | newest]

Thread overview: 13+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
     [not found] <OF41DBA6D6.22A62FDD-ON8825738B.00651D50-8825738B.006528AE@us.ibm.com>
     [not found] ` <4730D908.8090401@linux.vnet.ibm.com>
2007-11-06 21:18   ` [PATCH] Segfault while unwinding an invalid function pointer Pete Eberlein
2007-11-16 22:25     ` Pete Eberlein
2008-04-11  4:23 Pete Eberlein
     [not found] <47A0F9FA.9000903@linux.vnet.ibm.com.suse.lists.egcs-patches>
2008-01-31 13:06 ` Andi Kleen
2008-01-31 13:23   ` Jakub Jelinek
2008-01-31 13:36     ` Andi Kleen
2008-01-31 14:24       ` Andrew Haley
2008-01-31 16:19         ` Andi Kleen
2008-01-31 17:21           ` Jakub Jelinek
2008-01-31 20:42             ` Daniel Jacobowitz
  -- strict thread matches above, loose matches on Subject: below --
2008-01-31  8:48 Pete Eberlein
2008-01-31  9:27 ` Jakub Jelinek
2007-10-10 20:39 Pete Eberlein

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).