]> git.ipfire.org Git - thirdparty/elfutils.git/commitdiff
Detect infinite backtraces. jankratochvil/cfaloop
authorJan Kratochvil <jan.kratochvil@redhat.com>
Fri, 3 Jan 2014 21:06:12 +0000 (22:06 +0100)
committerJan Kratochvil <jan.kratochvil@redhat.com>
Fri, 3 Jan 2014 21:06:36 +0000 (22:06 +0100)
libdwfl/
2014-01-03  Jan Kratochvil  <jan.kratochvil@redhat.com>

Detect infinite backtraces.
* dwfl_frame.c (state_alloc): Initialize CFA.
* frame_unwind.c (expr_eval): Remove parameter frame, add parameter
elfclass.  Move elfclass to handle_cfi.  Replace recursive call by
state->unwound->CFA.
(new_unwound): Initialize CFA.
(handle_cfi): Move elfclass here.  Compute CFA.  Update expr_eval
caller parameters.
* libdwflP.h (DWFL_ERRORS): Add UNWIND_BAD_CFA.
(struct Dwfl_Frame): Add field cfa.

tests/
2014-01-03  Jan Kratochvil  <jan.kratochvil@redhat.com>

* Makefile.am (TESTS): Add run-cfaloop.sh.
(EXTRA_DIST): Add run-cfaloop.sh, testfilecfaloop.S,
testfilecfaloop.bz2, testfilecfaloop.c and testfilecfaloop.core.bz2.
* run-cfaloop.sh: New file.
* testfilecfaloop.S: New file.
* testfilecfaloop.bz2: New file.
* testfilecfaloop.c: New file.
* testfilecfaloop.core.bz2: New file.

Signed-off-by: Jan Kratochvil <jan.kratochvil@redhat.com>
libdwfl/dwfl_frame.c
libdwfl/frame_unwind.c
libdwfl/libdwflP.h
tests/Makefile.am
tests/run-cfaloop.sh [new file with mode: 0755]
tests/testfilecfaloop.S [new file with mode: 0644]
tests/testfilecfaloop.bz2 [new file with mode: 0644]
tests/testfilecfaloop.c [new file with mode: 0644]
tests/testfilecfaloop.core.bz2 [new file with mode: 0644]

index 28008e9033a59fbe8c78f6867b9233340f6699b5..89fd920593a86f1de4fa0783fdcffaf0bce9f212 100644 (file)
@@ -1,5 +1,5 @@
 /* Get Dwarf Frame state for target PID or core file.
-   Copyright (C) 2013 Red Hat, Inc.
+   Copyright (C) 2013-2014 Red Hat, Inc.
    This file is part of elfutils.
 
    This file is free software; you can redistribute it and/or modify
@@ -98,6 +98,7 @@ state_alloc (Dwfl_Thread *thread)
   state->thread = thread;
   state->signal_frame = false;
   state->initial_frame = true;
+  state->cfa = 0;
   state->pc_state = DWFL_FRAME_STATE_ERROR;
   memset (state->regs_set, 0, sizeof (state->regs_set));
   thread->unwound = state;
index 3ce45479b8c7a964f7ee514f12b1a2e4cfa31a4a..d434c46878314c1aedf7c05c1938862f843c70b8 100644 (file)
@@ -1,5 +1,5 @@
 /* Get previous frame state for an existing frame state.
-   Copyright (C) 2013 Red Hat, Inc.
+   Copyright (C) 2013-2014 Red Hat, Inc.
    This file is part of elfutils.
 
    This file is free software; you can redistribute it and/or modify
@@ -105,8 +105,8 @@ bra_compar (const void *key_voidp, const void *elem_voidp)
    DW_OP_call_frame_cfa is no longer permitted.  */
 
 static bool
-expr_eval (Dwfl_Frame *state, Dwarf_Frame *frame, const Dwarf_Op *ops,
-          size_t nops, Dwarf_Addr *result, Dwarf_Addr bias)
+expr_eval (Dwfl_Frame *state, const Dwarf_Op *ops, size_t nops,
+          Dwarf_Addr *result, Dwarf_Addr bias, const int elfclass)
 {
   Dwfl_Process *process = state->thread->process;
   if (nops == 0)
@@ -310,7 +310,6 @@ expr_eval (Dwfl_Frame *state, Dwarf_Frame *frame, const Dwarf_Op *ops,
            }
          if (op->atom == DW_OP_deref_size)
            {
-             const int elfclass = frame->cache->e_ident[EI_CLASS];
              const unsigned addr_bytes = elfclass == ELFCLASS32 ? 4 : 8;
              if (op->number > addr_bytes)
                {
@@ -450,16 +449,15 @@ expr_eval (Dwfl_Frame *state, Dwarf_Frame *frame, const Dwarf_Op *ops,
          break;
        /* DW_OP_* not listed in libgcc/unwind-dw2.c execute_stack_op:  */
        case DW_OP_call_frame_cfa:;
-         // Not used by CFI itself but it is synthetized by elfutils internation.
-         Dwarf_Op *cfa_ops;
-         size_t cfa_nops;
-         Dwarf_Addr cfa;
-         if (frame == NULL
-             || dwarf_frame_cfa (frame, &cfa_ops, &cfa_nops) != 0
-             || ! expr_eval (state, NULL, cfa_ops, cfa_nops, &cfa, bias)
-             || ! push (cfa))
-           {
-             __libdwfl_seterrno (DWFL_E_LIBDW);
+         if (state->unwound->cfa == 0)
+           {
+             /* DW_OP_call_frame_cfa is needed to compute CFA itself.  */
+             free (stack);
+             __libdwfl_seterrno (DWFL_E_INVALID_DWARF);
+             return false;
+           }
+         if (! push (state->unwound->cfa))
+           {
              free (stack);
              return false;
            }
@@ -510,6 +508,7 @@ new_unwound (Dwfl_Frame *state)
   unwound->unwound = NULL;
   unwound->signal_frame = false;
   unwound->initial_frame = false;
+  unwound->cfa = 0;
   unwound->pc_state = DWFL_FRAME_STATE_ERROR;
   memset (unwound->regs_set, 0, sizeof (unwound->regs_set));
 }
@@ -536,12 +535,29 @@ handle_cfi (Dwfl_Frame *state, Dwarf_Addr pc, Dwarf_CFI *cfi, Dwarf_Addr bias)
   Ebl *ebl = process->ebl;
   size_t nregs = ebl_frame_nregs (ebl);
   assert (nregs > 0);
+  const int elfclass = frame->cache->e_ident[EI_CLASS];
 
   /* The return register is special for setting the unwound->pc_state.  */
   unsigned ra = frame->fde->cie->return_address_register;
   bool ra_set = false;
   ebl_dwarf_to_regno (ebl, &ra);
 
+  // Set unwound->CFA.
+  Dwarf_Op *cfa_ops;
+  size_t cfa_nops;
+  if (dwarf_frame_cfa (frame, &cfa_ops, &cfa_nops) != 0
+      || ! expr_eval (state, cfa_ops, cfa_nops, &unwound->cfa, bias, elfclass))
+    {
+      __libdwfl_seterrno (DWFL_E_LIBDW);
+      return;
+    }
+  if (unwound->cfa == 0
+      || (! state->initial_frame && unwound->cfa <= state->cfa))
+    {
+      __libdwfl_seterrno (DWFL_E_UNWIND_BAD_CFA);
+      return;
+    }
+
   for (unsigned regno = 0; regno < nregs; regno++)
     {
       Dwarf_Op reg_ops_mem[3], *reg_ops;
@@ -574,7 +590,7 @@ handle_cfi (Dwfl_Frame *state, Dwarf_Addr pc, Dwarf_CFI *cfi, Dwarf_Addr bias)
              continue;
            }
        }
-      else if (! expr_eval (state, frame, reg_ops, reg_nops, &regval, bias))
+      else if (! expr_eval (state, reg_ops, reg_nops, &regval, bias, elfclass))
        {
          /* PPC32 vDSO has various invalid operations, ignore them.  The
             register will look as unset causing an error later, if used.
index 63615a8a586e91b7f23e487b3c80f6ad1ece11b9..5f6bbcddfeccab86d233053811a5fde9a0194feb 100644 (file)
@@ -1,5 +1,5 @@
 /* Internal definitions for libdwfl.
-   Copyright (C) 2005-2013 Red Hat, Inc.
+   Copyright (C) 2005-2014 Red Hat, Inc.
    This file is part of elfutils.
 
    This file is free software; you can redistribute it and/or modify
@@ -89,7 +89,8 @@ typedef struct Dwfl_Process Dwfl_Process;
   DWFL_ERROR (ATTACH_STATE_CONFLICT, N_("Dwfl already has attached state"))   \
   DWFL_ERROR (NO_ATTACH_STATE, N_("Dwfl has no attached state"))             \
   DWFL_ERROR (NO_UNWIND, N_("Unwinding not supported for this architecture")) \
-  DWFL_ERROR (INVALID_ARGUMENT, N_("Invalid argument"))
+  DWFL_ERROR (INVALID_ARGUMENT, N_("Invalid argument"))                              \
+  DWFL_ERROR (UNWIND_BAD_CFA, N_("Unwind not monotonous (corrupt stack?)"))
 
 #define DWFL_ERROR(name, text) DWFL_E_##name,
 typedef enum { DWFL_ERRORS DWFL_E_NUM } Dwfl_Error;
@@ -241,6 +242,8 @@ struct Dwfl_Frame
   Dwfl_Frame *unwound;
   bool signal_frame : 1;
   bool initial_frame : 1;
+  /* Used to catch infinite unwinding.  */
+  Dwarf_Addr cfa;
   enum
   {
     /* This structure is still being initialized or there was an error
index 52eb50aab93c27d57519b9968c2d1525b8f5e0f1..ac83d9c4944a82dbc802655a2d0ecb14225b89cd 100644 (file)
@@ -1,6 +1,6 @@
 ## Process this file with automake to create Makefile.in
 ##
-## Copyright (C) 1996-2013 Red Hat, Inc.
+## Copyright (C) 1996-2014 Red Hat, Inc.
 ## This file is part of elfutils.
 ##
 ## This file is free software; you can redistribute it and/or modify
@@ -107,7 +107,7 @@ TESTS = run-arextract.sh run-arsymtest.sh newfile test-nlist \
        run-backtrace-native-biarch.sh run-backtrace-native-core.sh \
        run-backtrace-native-core-biarch.sh run-backtrace-core-x86_64.sh \
        run-backtrace-core-i386.sh run-backtrace-core-ppc.sh \
-       run-backtrace-core-s390x.sh run-backtrace-core-s390.sh
+       run-backtrace-core-s390x.sh run-backtrace-core-s390.sh run-cfaloop.sh
 
 if !BIARCH
 export ELFUTILS_DISABLE_BIARCH = 1
@@ -256,7 +256,9 @@ EXTRA_DIST = run-arextract.sh run-arsymtest.sh \
             run-backtrace-core-ppc.sh testfile66.bz2 testfile66.core.bz2 \
             backtrace.s390x.core.bz2 backtrace.s390x.exec.bz2 \
             backtrace.s390.core.bz2 backtrace.s390.exec.bz2 \
-            run-backtrace-core-s390x.sh run-backtrace-core-s390.sh
+            run-backtrace-core-s390x.sh run-backtrace-core-s390.sh \
+            run-cfaloop.sh testfilecfaloop.S testfilecfaloop.bz2 \
+            testfilecfaloop.c testfilecfaloop.core.bz2
 
 if USE_VALGRIND
 valgrind_cmd='valgrind -q --error-exitcode=1 --run-libc-freeres=no'
diff --git a/tests/run-cfaloop.sh b/tests/run-cfaloop.sh
new file mode 100755 (executable)
index 0000000..afad5db
--- /dev/null
@@ -0,0 +1,22 @@
+#! /bin/bash
+# Copyright (C) 2014 Red Hat, Inc.
+# This file is part of elfutils.
+#
+# This file is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# elfutils is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+. $srcdir/test-subr.sh
+
+testfiles testfilecfaloop testfilecfaloop.core
+testrun ${abs_top_builddir}/src/stack -e testfilecfaloop --core=testfilecfaloop.core 2>&1 \
+       | grep -q 'Unwind not monotonous'
diff --git a/tests/testfilecfaloop.S b/tests/testfilecfaloop.S
new file mode 100644 (file)
index 0000000..f4108a2
--- /dev/null
@@ -0,0 +1,90 @@
+/* Test program for run-cfaloop.sh.
+   Copyright (C) 2014 Red Hat, Inc.
+   This file is part of elfutils.
+
+   This file is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 3 of the License, or
+   (at your option) any later version.
+
+   elfutils is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
+
+       .file   "testfilecfaloop.c"
+       .text
+       .p2align 4,,15
+       .globl  _start
+       .type   _start, @function
+_start:
+.LFB0:
+# BLOCK 2 freq:10000 seq:0
+# PRED: ENTRY [100.0%]  (FALLTHRU)
+       movq    $0, -8(%rsp)
+       movq    -8(%rsp), %rax
+       movl    $0, (%rax)
+# SUCC: EXIT [100.0%] 
+       ret
+.LFE0:
+       .size   _start, .-_start
+#APP
+       .section        .eh_frame,"a",@progbits
+.Lframe1:
+       .long   .LECIE1-.LSCIE1 # Length of Common Information Entry
+.LSCIE1:
+       .long   0       # CIE Identifier Tag
+       .byte   0x3     # CIE Version
+       .ascii "zR\0"   # CIE Augmentation
+       .uleb128 0x1    # CIE Code Alignment Factor
+       .sleb128 -8     # CIE Data Alignment Factor
+       .uleb128 0x10   # CIE RA Column
+       .uleb128 0x1    # Augmentation size
+       .byte   0x3     # FDE Encoding (udata4)
+       .byte   0xc     # DW_CFA_def_cfa
+       .uleb128 0x7
+       .uleb128 0x8
+       .byte   0x90    # DW_CFA_offset, column 0x10
+       .uleb128 0x1
+       .align 8
+.LECIE1:
+.LSFDE1:
+       .long   .LEFDE1-.LASFDE1        # FDE Length
+.LASFDE1:
+       .long   .LASFDE1-.Lframe1       # FDE CIE offset
+       .long   .LFB0   # FDE initial location
+       .long   .LFE0-.LFB0     # FDE address range
+       .uleb128 0      # Augmentation size
+// BEGIN inserted data { DW_CFA_same_value + ULEB128 register }
+#define REG(n)         \
+       .byte   0x8;    \
+       .uleb128 n;
+REG(0)
+REG(1)
+REG(2)
+REG(3)
+REG(4)
+REG(5)
+REG(6)
+REG(7)
+REG(8)
+REG(9)
+REG(10)
+REG(11)
+REG(12)
+REG(13)
+REG(14)
+REG(15)
+REG(16)
+       .byte   0xc     # DW_CFA_def_cfa
+       .uleb128 0x7
+       .uleb128 0x0
+// END inserted data
+       .align 8
+.LEFDE1:
+#NO_APP
+       .ident  "GCC: (GNU) 4.8.2 20131212 (Red Hat 4.8.2-7)"
+       .section        .note.GNU-stack,"",@progbits
diff --git a/tests/testfilecfaloop.bz2 b/tests/testfilecfaloop.bz2
new file mode 100644 (file)
index 0000000..ab30fcd
Binary files /dev/null and b/tests/testfilecfaloop.bz2 differ
diff --git a/tests/testfilecfaloop.c b/tests/testfilecfaloop.c
new file mode 100644 (file)
index 0000000..14a2fca
--- /dev/null
@@ -0,0 +1,22 @@
+/* Test program for run-cfaloop.sh.
+   Copyright (C) 2014 Red Hat, Inc.
+   This file is part of elfutils.
+
+   This file is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 3 of the License, or
+   (at your option) any later version.
+
+   elfutils is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
+
+// gcc -o testfilecfaloop.S testfilecfaloop.c -Wall -O2 -dA -fno-dwarf2-cfi-asm -nostdlib -S
+void _start(void) {
+  volatile int *volatile p = 0;
+  *p = 0;
+}
diff --git a/tests/testfilecfaloop.core.bz2 b/tests/testfilecfaloop.core.bz2
new file mode 100644 (file)
index 0000000..923d5e5
Binary files /dev/null and b/tests/testfilecfaloop.core.bz2 differ