Skip to content

Commit

Permalink
read/cfi: fix hack for zero length entries in .debug_frame
Browse files Browse the repository at this point in the history
There were two problems with this hack:
- returned an error for zero length entries at the end of the section
- returned an entry for cie_from_offset/fde_from_offset when the
  requested offset is the zero length entry

Fix by moving the zero length handling into the iterator.
  • Loading branch information
philipc committed Aug 5, 2024
1 parent 9599d7a commit 324a600
Showing 1 changed file with 63 additions and 41 deletions.
104 changes: 63 additions & 41 deletions src/read/cfi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -601,9 +601,8 @@ pub trait _UnwindSectionPrivate<R: Reader> {
/// Get the underlying section data.
fn section(&self) -> &R;

/// Returns true if the given length value should be considered an
/// end-of-entries sentinel.
fn length_value_is_end_of_entries(length: R::Offset) -> bool;
/// Returns true if the section allows a zero terminator.
fn has_zero_terminator() -> bool;

/// Return true if the given offset if the CIE sentinel, false otherwise.
fn is_cie(format: Format, id: u64) -> bool;
Expand Down Expand Up @@ -791,7 +790,7 @@ impl<R: Reader> _UnwindSectionPrivate<R> for DebugFrame<R> {
&self.section
}

fn length_value_is_end_of_entries(_: R::Offset) -> bool {
fn has_zero_terminator() -> bool {
false
}

Expand Down Expand Up @@ -835,8 +834,8 @@ impl<R: Reader> _UnwindSectionPrivate<R> for EhFrame<R> {
&self.section
}

fn length_value_is_end_of_entries(length: R::Offset) -> bool {
length.into_u64() == 0
fn has_zero_terminator() -> bool {
true
}

fn is_cie(_: Format, id: u64) -> bool {
Expand Down Expand Up @@ -1013,20 +1012,30 @@ where
{
/// Advance the iterator to the next entry.
pub fn next(&mut self) -> Result<Option<CieOrFde<'bases, Section, R>>> {
if self.input.is_empty() {
return Ok(None);
}

match parse_cfi_entry(self.bases, &self.section, &mut self.input) {
Err(e) => {
self.input.empty();
Err(e)
loop {
if self.input.is_empty() {
return Ok(None);
}
Ok(None) => {
self.input.empty();
Ok(None)

match parse_cfi_entry(self.bases, &self.section, &mut self.input) {
Ok(Some(entry)) => return Ok(Some(entry)),
Err(e) => {
self.input.empty();
return Err(e);
}
Ok(None) => {
if Section::has_zero_terminator() {
self.input.empty();
return Ok(None);
}

// Hack: If we get to here, then we're reading `.debug_frame` and
// encountered a length of 0. This is a compiler or linker bug
// (originally seen for NASM, fixed in 2.15rc9).
// Skip this value and try again.
continue;
}
}
Ok(Some(entry)) => Ok(Some(entry)),
}
}
}
Expand Down Expand Up @@ -1069,23 +1078,11 @@ where
R: Reader,
Section: UnwindSection<R>,
{
let (offset, length, format) = loop {
let offset = input.offset_from(section.section());
let (length, format) = input.read_initial_length()?;

if Section::length_value_is_end_of_entries(length) {
return Ok(None);
}

// Hack: skip zero padding inserted by buggy compilers/linkers.
// We require that the padding is a multiple of 32-bits, otherwise
// there is no reliable way to determine when the padding ends. This
// should be okay since CFI entries must be aligned to the address size.

if length.into_u64() != 0 || format != Format::Dwarf32 {
break (offset, length, format);
}
};
let offset = input.offset_from(section.section());
let (length, format) = input.read_initial_length()?;
if length.into_u64() == 0 {
return Ok(None);
}

let mut rest = input.split(length)?;
let cie_offset_base = rest.offset_from(section.section());
Expand Down Expand Up @@ -6686,18 +6683,43 @@ mod tests {

#[test]
fn test_eh_frame_stops_at_zero_length() {
let section = Section::with_endian(Endian::Little).L32(0);
let section = section.get_contents().unwrap();
let rest = &mut EndianSlice::new(&section, LittleEndian);
let mut cie = make_test_cie();
let kind = eh_frame_le();
let section = Section::with_endian(Endian::Little)
.L32(0)
.cie(kind, None, &mut cie)
.L32(0);
let contents = section.get_contents().unwrap();
let eh_frame = kind.section(&contents);
let bases = Default::default();

let mut entries = eh_frame.entries(&bases);
assert_eq!(entries.next(), Ok(None));

assert_eq!(
parse_cfi_entry(&bases, &EhFrame::new(&section, LittleEndian), rest),
Ok(None)
eh_frame.cie_from_offset(&bases, EhFrameOffset(0)),
Err(Error::NoEntryAtGivenOffset)
);
}

#[test]
fn test_debug_frame_skips_zero_length() {
let mut cie = make_test_cie();
let kind = debug_frame_le();
let section = Section::with_endian(Endian::Little)
.L32(0)
.cie(kind, None, &mut cie)
.L32(0);
let contents = section.get_contents().unwrap();
let debug_frame = kind.section(&contents);
let bases = Default::default();

let mut entries = debug_frame.entries(&bases);
assert_eq!(entries.next(), Ok(Some(CieOrFde::Cie(cie))));
assert_eq!(entries.next(), Ok(None));

assert_eq!(
EhFrame::new(&section, LittleEndian).cie_from_offset(&bases, EhFrameOffset(0)),
debug_frame.cie_from_offset(&bases, DebugFrameOffset(0)),
Err(Error::NoEntryAtGivenOffset)
);
}
Expand Down

0 comments on commit 324a600

Please sign in to comment.