From 305705207e0831e6387706065e59f91296ba0d0a Mon Sep 17 00:00:00 2001 From: Jan Kratochvil Date: Fri, 3 Jan 2014 22:06:12 +0100 Subject: Detect infinite backtraces. libdwfl/ 2014-01-03 Jan Kratochvil 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 * 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 --- libdwfl/dwfl_frame.c | 3 +- libdwfl/frame_unwind.c | 46 ++++++++++++++------- libdwfl/libdwflP.h | 7 +++- tests/Makefile.am | 8 ++-- tests/run-cfaloop.sh | 22 ++++++++++ tests/testfilecfaloop.S | 90 +++++++++++++++++++++++++++++++++++++++++ tests/testfilecfaloop.bz2 | Bin 0 -> 640 bytes tests/testfilecfaloop.c | 22 ++++++++++ tests/testfilecfaloop.core.bz2 | Bin 0 -> 5765 bytes 9 files changed, 177 insertions(+), 21 deletions(-) create mode 100755 tests/run-cfaloop.sh create mode 100644 tests/testfilecfaloop.S create mode 100644 tests/testfilecfaloop.bz2 create mode 100644 tests/testfilecfaloop.c create mode 100644 tests/testfilecfaloop.core.bz2 diff --git a/libdwfl/dwfl_frame.c b/libdwfl/dwfl_frame.c index 28008e90..89fd9205 100644 --- a/libdwfl/dwfl_frame.c +++ b/libdwfl/dwfl_frame.c @@ -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; diff --git a/libdwfl/frame_unwind.c b/libdwfl/frame_unwind.c index 3ce45479..d434c468 100644 --- a/libdwfl/frame_unwind.c +++ b/libdwfl/frame_unwind.c @@ -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, ®val, bias)) + else if (! expr_eval (state, reg_ops, reg_nops, ®val, bias, elfclass)) { /* PPC32 vDSO has various invalid operations, ignore them. The register will look as unset causing an error later, if used. diff --git a/libdwfl/libdwflP.h b/libdwfl/libdwflP.h index 63615a8a..5f6bbcdd 100644 --- a/libdwfl/libdwflP.h +++ b/libdwfl/libdwflP.h @@ -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 diff --git a/tests/Makefile.am b/tests/Makefile.am index 52eb50aa..ac83d9c4 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -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 index 00000000..afad5dba --- /dev/null +++ b/tests/run-cfaloop.sh @@ -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 . + +. $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 index 00000000..f4108a27 --- /dev/null +++ b/tests/testfilecfaloop.S @@ -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 . */ + + .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 index 00000000..ab30fcd0 Binary files /dev/null and b/tests/testfilecfaloop.bz2 differ diff --git a/tests/testfilecfaloop.c b/tests/testfilecfaloop.c new file mode 100644 index 00000000..14a2fca8 --- /dev/null +++ b/tests/testfilecfaloop.c @@ -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 . */ + +// 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 index 00000000..923d5e56 Binary files /dev/null and b/tests/testfilecfaloop.core.bz2 differ -- cgit v1.2.1