<html>
<head>
<base href="https://bugs.llvm.org/">
</head>
<body><table border="1" cellspacing="0" cellpadding="8">
<tr>
<th>Bug ID</th>
<td><a class="bz_bug_link
bz_status_NEW "
title="NEW - libunwind: Segfault at the end of _Unwind_Backtrace"
href="https://bugs.llvm.org/show_bug.cgi?id=36005">36005</a>
</td>
</tr>
<tr>
<th>Summary</th>
<td>libunwind: Segfault at the end of _Unwind_Backtrace
</td>
</tr>
<tr>
<th>Product</th>
<td>Runtime Libraries
</td>
</tr>
<tr>
<th>Version</th>
<td>6.0
</td>
</tr>
<tr>
<th>Hardware</th>
<td>PC
</td>
</tr>
<tr>
<th>OS</th>
<td>Linux
</td>
</tr>
<tr>
<th>Status</th>
<td>NEW
</td>
</tr>
<tr>
<th>Severity</th>
<td>release blocker
</td>
</tr>
<tr>
<th>Priority</th>
<td>P
</td>
</tr>
<tr>
<th>Component</th>
<td>other
</td>
</tr>
<tr>
<th>Assignee</th>
<td>unassignedbugs@nondot.org
</td>
</tr>
<tr>
<th>Reporter</th>
<td>Andrew.Caldwell@metaswitch.com
</td>
</tr>
<tr>
<th>CC</th>
<td>llvm-bugs@lists.llvm.org
</td>
</tr></table>
<p>
<div>
<pre># 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) ()
<a href="https://github.com/llvm-mirror/libunwind/blob/release_39/src/AddressSpace.hpp#L131">https://github.com/llvm-mirror/libunwind/blob/release_39/src/AddressSpace.hpp#L131</a>
#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*) ()
<a href="https://github.com/llvm-mirror/libunwind/blob/release_39/src/DwarfParser.hpp#L184">https://github.com/llvm-mirror/libunwind/blob/release_39/src/DwarfParser.hpp#L184</a>
#2 0x0000000000fa983d in libunwind::UnwindCursor<libunwind::LocalAddressSpace,
libunwind::Registers_x86_64>::getInfoFromDwarfSection(unsigned long,
libunwind::UnwindInfoSections const&, unsigned int) ()
<a href="https://github.com/llvm-mirror/libunwind/blob/release_39/src/UnwindCursor.hpp#L900">https://github.com/llvm-mirror/libunwind/blob/release_39/src/UnwindCursor.hpp#L900</a>
#3 0x0000000000fa923d in libunwind::UnwindCursor<libunwind::LocalAddressSpace,
libunwind::Registers_x86_64>::setInfoBasedOnIPRegister(bool) ()
<a href="https://github.com/llvm-mirror/libunwind/blob/release_39/src/UnwindCursor.hpp#L1241">https://github.com/llvm-mirror/libunwind/blob/release_39/src/UnwindCursor.hpp#L1241</a>
#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
<a href="https://github.com/llvm-mirror/libunwind/blob/release_39/src/AddressSpace.hpp#L390-L441">https://github.com/llvm-mirror/libunwind/blob/release_39/src/AddressSpace.hpp#L390-L441</a>
(called from
<a href="https://github.com/llvm-mirror/libunwind/blob/release_39/src/UnwindCursor.hpp#L1214">https://github.com/llvm-mirror/libunwind/blob/release_39/src/UnwindCursor.hpp#L1214</a>).
* 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
<a href="https://github.com/llvm-mirror/libunwind/blob/release_39/src/AddressSpace.hpp#L434">https://github.com/llvm-mirror/libunwind/blob/release_39/src/AddressSpace.hpp#L434</a>
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?</pre>
</div>
</p>
<hr>
<span>You are receiving this mail because:</span>
<ul>
<li>You are on the CC list for the bug.</li>
</ul>
</body>
</html>