From 324a6000a89b174f1c5c8fb888eee571adce864c Mon Sep 17 00:00:00 2001 From: Philip Craig Date: Mon, 5 Aug 2024 15:43:15 +1000 Subject: [PATCH] read/cfi: fix hack for zero length entries in .debug_frame 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. --- src/read/cfi.rs | 104 +++++++++++++++++++++++++++++------------------- 1 file changed, 63 insertions(+), 41 deletions(-) diff --git a/src/read/cfi.rs b/src/read/cfi.rs index 9941f72c..f14f6447 100644 --- a/src/read/cfi.rs +++ b/src/read/cfi.rs @@ -601,9 +601,8 @@ pub trait _UnwindSectionPrivate { /// 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; @@ -791,7 +790,7 @@ impl _UnwindSectionPrivate for DebugFrame { &self.section } - fn length_value_is_end_of_entries(_: R::Offset) -> bool { + fn has_zero_terminator() -> bool { false } @@ -835,8 +834,8 @@ impl _UnwindSectionPrivate for EhFrame { &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 { @@ -1013,20 +1012,30 @@ where { /// Advance the iterator to the next entry. pub fn next(&mut self) -> Result>> { - 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)), } } } @@ -1069,23 +1078,11 @@ where R: Reader, Section: UnwindSection, { - 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()); @@ -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(§ion, 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(§ion, 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(§ion, LittleEndian).cie_from_offset(&bases, EhFrameOffset(0)), + debug_frame.cie_from_offset(&bases, DebugFrameOffset(0)), Err(Error::NoEntryAtGivenOffset) ); }