[llvm-bugs] [Bug 36005] New: libunwind: Segfault at the end of _Unwind_Backtrace
via llvm-bugs
llvm-bugs at lists.llvm.org
Thu Jan 18 10:05:19 PST 2018
https://bugs.llvm.org/show_bug.cgi?id=36005
Bug ID: 36005
Summary: libunwind: Segfault at the end of _Unwind_Backtrace
Product: Runtime Libraries
Version: 6.0
Hardware: PC
OS: Linux
Status: NEW
Severity: release blocker
Priority: P
Component: other
Assignee: unassignedbugs at nondot.org
Reporter: Andrew.Caldwell at metaswitch.com
CC: llvm-bugs at lists.llvm.org
# Headline
libunwind miscalculates the length of the .eh_frame section and sometimes reads
off the end of the containing segment, leading to segfaults and sadness.
# Discovery
We discovered this in a rust project at our company, the program would segfault
deep in _Unwind_Backtrace but only in a few specific circumstances:
* Compiling against the MUSL libc, which implies:
* Static linking of all libraries
* Using a static build of libunwind from LLVM (version 3.9)
* Only one of our codebases
* Attempts to reproduce in a more controlled environment have all failed
* The codebase in question is **big** resulting in an 84Mb binary
# Backtrace (courtesy of GDB + line number links reverse engineered... Rustc
builds libunwind without debug symbols)
```
#0 0x0000000000fa733b in libunwind::LocalAddressSpace::get32(unsigned long) ()
https://github.com/llvm-mirror/libunwind/blob/release_39/src/AddressSpace.hpp#L131
#1 0x0000000000faa0a2 in
libunwind::CFI_Parser<libunwind::LocalAddressSpace>::findFDE(libunwind::LocalAddressSpace&,
unsigned long, unsigned long, unsigned int, unsigned long,
libunwind::CFI_Parser<libunwind::LocalAddressSpace>::FDE_Info*,
libunwind::CFI_Parser<libunwind::LocalAddressSpace>::CIE_Info*) ()
https://github.com/llvm-mirror/libunwind/blob/release_39/src/DwarfParser.hpp#L184
#2 0x0000000000fa983d in libunwind::UnwindCursor<libunwind::LocalAddressSpace,
libunwind::Registers_x86_64>::getInfoFromDwarfSection(unsigned long,
libunwind::UnwindInfoSections const&, unsigned int) ()
https://github.com/llvm-mirror/libunwind/blob/release_39/src/UnwindCursor.hpp#L900
#3 0x0000000000fa923d in libunwind::UnwindCursor<libunwind::LocalAddressSpace,
libunwind::Registers_x86_64>::setInfoBasedOnIPRegister(bool) ()
https://github.com/llvm-mirror/libunwind/blob/release_39/src/UnwindCursor.hpp#L1241
#4 0x0000000000fa8fff in libunwind::UnwindCursor<libunwind::LocalAddressSpace,
libunwind::Registers_x86_64>::step() ()
#5 0x0000000000fa820e in unw_step ()
#6 0x0000000000fa6ac0 in _Unwind_Backtrace ()
```
# Analysis
* Looking at frame #1 it appears that, although `p < ehSectionEnd`, it points
into invalid memory, playing around suggests that `ehSectionEnd` is massively
beyond the range of readable memory.
* Tracking back, we see that `ehSectionEnd` is `sects.dwarf_section_length`
which is populated by the code at
https://github.com/llvm-mirror/libunwind/blob/release_39/src/AddressSpace.hpp#L390-L441
(called from
https://github.com/llvm-mirror/libunwind/blob/release_39/src/UnwindCursor.hpp#L1214).
* This code is a little weird, and I'm not 100% it's doing anything sensible,
but I've assumed it's doing the following:
* Searching for a `LOAD` segment that contains the address we're interested
in and remembering this segment's base address and length
* Searching for a `GNU_EH_FRAME` segment, parsing it (assuming it contains
exactly one `.eh_frame_ptr` section, which I guess is the spec for a segment of
this type) to find the location of the `.eh_frame` section
* Assuming that the `.eh_frame` points into the LOAD segment we found (not
sure this is guaranteed, but maybe?)
* Storing off the `.eh_frame` section's length by assuming it's the `LOAD`
segment's `memsz`
* This last bullet is where I think the bug lies, the `.eh_frame` is not always
(never?) the first section in the `LOAD` segment, so the length of that section
is strictly less than the size of the `LOAD` segment
# Proposed fix
Change
https://github.com/llvm-mirror/libunwind/blob/release_39/src/AddressSpace.hpp#L434
to:
cbdata->sects->dwarf_section_length = object_length -
(cbdata->sects->dwarf_section - cbdata->sects->dso_base);
This is better than the existing code (since the span from
`cbdata->sects->dwarf_section` to `cbdata->sects->dwarf_section +
cbdata->sects->dwarf_section_length` is all contained in the `LOAD` segment and
is thus valid memory, but it's still not perfectly correct (as that span
contains any sections after the `.eh_frame` section in the `LOAD` segment).
I'm not sure there is a way to determine the end of a `.eh_frame` section,
otherwise I'd recommend using that here. OTOH this change prevented the
segfault I was seeing, without affecting backtrace generation.
# Affected versions
I found this on version 3.9, but the issue is still present in version 6.0 and
seems to have been around since at least version 3.7 (and all versions in
between).
# Platform
Linux Mint 18.1 Serena (based on Ubuntu 16.04 Xenial Xerus) on x86_64. Also
seen on Centos 7 on x86_64. Segfaulting code compiled with Rustc version
1.22.0, 1.22.1, 1.23.0, 1.24.0-beta or 1.25.0-nightly.
# Open Questions
* Why doesn't this always crash?
* Why do we only see this in `musl`-targeting Rust builds?
* Is it simply that musl binaries are statically linked and thus bigger
and this means the section layout is different?
* Is there a way to spot the end of the `.eh_frame` section?
* Is it always followed by a `.eh_frame_ptr` section? Can we distinguish
the two?
* Is there a "this section is XXX long" or "this section contains XXX
records header?
--
You are receiving this mail because:
You are on the CC list for the bug.
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.llvm.org/pipermail/llvm-bugs/attachments/20180118/36fb8662/attachment-0001.html>
More information about the llvm-bugs
mailing list