-
Notifications
You must be signed in to change notification settings - Fork 190
elf: fix MIPS64 little-endian relocation parsing #519
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 2 commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -266,6 +266,28 @@ pub mod reloc64 { | |
| (sym << 32) + typ | ||
| } | ||
|
|
||
| /// Convert a MIPS64 little-endian `r_info` value to the standard ELF64 format. | ||
| /// | ||
| /// MIPS64 ELF uses a non-standard relocation info layout: | ||
| /// - `r_sym` (32 bits) | `r_ssym` (8 bits) | `r_type3` (8 bits) | `r_type2` (8 bits) | `r_type` (8 bits) | ||
| /// | ||
| /// On little-endian systems, when this struct is read as a single `u64`, the byte order | ||
| /// causes the fields to be scrambled compared to the standard `(sym << 32) | type` layout. | ||
| /// This function rearranges the bytes so that the standard [`r_sym`] and [`r_type`] functions | ||
| /// return correct values. | ||
| /// | ||
| /// See the [MIPS64 ELF ABI](https://web.archive.org/web/20231012215433/https://techpubs.jurassic.nl/manuals/hdwr/developer/Mpro_n32_ABI/sgi_html/sgidoc/books/Mpro_n32_ABI/sgi_html/ch06.html) | ||
| /// and [LLVM's implementation](https://github.com/llvm/llvm-project/blob/119bf57ab6de49a3e61b9200c917a6d30ac6f0ad/llvm/include/llvm/Object/ELFTypes.h#L435-L444) | ||
| /// for reference. | ||
| #[inline(always)] | ||
| pub fn mips64el_r_info(info: u64) -> u64 { | ||
| (info << 32) | ||
| | ((info >> 8) & 0xff000000) | ||
| | ((info >> 24) & 0x00ff0000) | ||
| | ((info >> 40) & 0x0000ff00) | ||
| | ((info >> 56) & 0x000000ff) | ||
| } | ||
|
|
||
| elf_rela_std_impl!(u64, i64); | ||
| } | ||
|
|
||
|
|
@@ -296,57 +318,86 @@ if_alloc! { | |
| impl Reloc { | ||
| pub fn size(is_rela: bool, ctx: Ctx) -> usize { | ||
| use scroll::ctx::SizeWith; | ||
| Reloc::size_with(&(is_rela, ctx)) | ||
| Reloc::size_with(&RelocCtx::new(is_rela, ctx)) | ||
| } | ||
|
|
||
| /// Fix up `r_sym` and `r_type` for MIPS64 little-endian binaries. | ||
| /// | ||
| /// MIPS64 ELF uses a non-standard relocation info layout that causes | ||
| /// `r_sym` and `r_type` to be incorrectly extracted on little-endian systems. | ||
| /// This method reconstructs the original `r_info`, applies the MIPS64 LE | ||
| /// byte transformation, and re-extracts the correct values. | ||
| fn fixup_mips64el(&mut self) { | ||
| let r_info = ((self.r_sym as u64) << 32) | (self.r_type as u64); | ||
| let fixed = reloc64::mips64el_r_info(r_info); | ||
| self.r_sym = reloc64::r_sym(fixed) as usize; | ||
| self.r_type = reloc64::r_type(fixed); | ||
| } | ||
| } | ||
|
|
||
| type RelocCtx = (bool, Ctx); | ||
| #[derive(Clone, Copy, Debug, Default)] | ||
| struct RelocCtx { | ||
| is_rela: bool, | ||
| is_mips64el: bool, | ||
| ctx: Ctx, | ||
| } | ||
|
|
||
| impl RelocCtx { | ||
| fn new(is_rela: bool, ctx: Ctx) -> Self { | ||
messense marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| RelocCtx { is_rela, is_mips64el: false, ctx } | ||
| } | ||
| } | ||
|
|
||
| impl ctx::SizeWith<RelocCtx> for Reloc { | ||
| fn size_with( &(is_rela, Ctx { container, .. }): &RelocCtx) -> usize { | ||
| match container { | ||
| fn size_with(rctx: &RelocCtx) -> usize { | ||
| match rctx.ctx.container { | ||
| Container::Little => { | ||
| if is_rela { reloc32::SIZEOF_RELA } else { reloc32::SIZEOF_REL } | ||
| if rctx.is_rela { reloc32::SIZEOF_RELA } else { reloc32::SIZEOF_REL } | ||
| }, | ||
| Container::Big => { | ||
| if is_rela { reloc64::SIZEOF_RELA } else { reloc64::SIZEOF_REL } | ||
| if rctx.is_rela { reloc64::SIZEOF_RELA } else { reloc64::SIZEOF_REL } | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
||
| impl<'a> ctx::TryFromCtx<'a, RelocCtx> for Reloc { | ||
| type Error = crate::error::Error; | ||
| fn try_from_ctx(bytes: &'a [u8], (is_rela, Ctx { container, le }): RelocCtx) -> result::Result<(Self, usize), Self::Error> { | ||
| fn try_from_ctx(bytes: &'a [u8], rctx: RelocCtx) -> result::Result<(Self, usize), Self::Error> { | ||
| use scroll::Pread; | ||
| let reloc = match container { | ||
| let Ctx { container, le } = rctx.ctx; | ||
| let (mut reloc, size): (Reloc, usize) = match container { | ||
| Container::Little => { | ||
| if is_rela { | ||
| if rctx.is_rela { | ||
| (bytes.pread_with::<reloc32::Rela>(0, le)?.into(), reloc32::SIZEOF_RELA) | ||
| } else { | ||
| (bytes.pread_with::<reloc32::Rel>(0, le)?.into(), reloc32::SIZEOF_REL) | ||
| } | ||
| }, | ||
| Container::Big => { | ||
| if is_rela { | ||
| if rctx.is_rela { | ||
| (bytes.pread_with::<reloc64::Rela>(0, le)?.into(), reloc64::SIZEOF_RELA) | ||
| } else { | ||
| (bytes.pread_with::<reloc64::Rel>(0, le)?.into(), reloc64::SIZEOF_REL) | ||
| } | ||
| } | ||
| }; | ||
| Ok(reloc) | ||
| if rctx.is_mips64el { | ||
| reloc.fixup_mips64el(); | ||
| } | ||
| Ok((reloc, size)) | ||
| } | ||
| } | ||
|
|
||
| impl ctx::TryIntoCtx<RelocCtx> for Reloc { | ||
| type Error = crate::error::Error; | ||
| /// Writes the relocation into `bytes` | ||
| fn try_into_ctx(self, bytes: &mut [u8], (is_rela, Ctx {container, le}): RelocCtx) -> result::Result<usize, Self::Error> { | ||
| fn try_into_ctx(self, bytes: &mut [u8], rctx: RelocCtx) -> result::Result<usize, Self::Error> { | ||
messense marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| use scroll::Pwrite; | ||
| let Ctx { container, le } = rctx.ctx; | ||
| match container { | ||
| Container::Little => { | ||
| if is_rela { | ||
| if rctx.is_rela { | ||
| let rela: reloc32::Rela = self.into(); | ||
| Ok(bytes.pwrite_with(rela, 0, le)?) | ||
| } else { | ||
|
|
@@ -355,7 +406,7 @@ if_alloc! { | |
| } | ||
| }, | ||
| Container::Big => { | ||
| if is_rela { | ||
| if rctx.is_rela { | ||
| let rela: reloc64::Rela = self.into(); | ||
| Ok(bytes.pwrite_with(rela, 0, le)?) | ||
| } else { | ||
|
|
@@ -369,9 +420,9 @@ if_alloc! { | |
|
|
||
| impl ctx::IntoCtx<(bool, Ctx)> for Reloc { | ||
messense marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| /// Writes the relocation into `bytes` | ||
| fn into_ctx(self, bytes: &mut [u8], ctx: RelocCtx) { | ||
| fn into_ctx(self, bytes: &mut [u8], (is_rela, ctx): (bool, Ctx)) { | ||
| use scroll::Pwrite; | ||
| bytes.pwrite_with(self, 0, ctx).unwrap(); | ||
| bytes.pwrite_with(self, 0, RelocCtx { is_rela, is_mips64el: false, ctx }).unwrap(); | ||
| } | ||
| } | ||
|
|
||
|
|
@@ -411,7 +462,23 @@ if_alloc! { | |
| impl<'a> RelocSection<'a> { | ||
| #[cfg(feature = "endian_fd")] | ||
| /// Parse a REL or RELA section of size `filesz` from `offset`. | ||
| /// | ||
| /// **Note:** This method does not apply the MIPS64 little-endian relocation | ||
| /// fixup. If you are parsing a MIPS64 LE binary, use [`Elf::parse`] or | ||
| /// [`Elf::parse_with_opts`] instead, which automatically detect MIPS64 LE | ||
| /// and apply the necessary `r_info` byte transformation. | ||
| pub fn parse(bytes: &'a [u8], offset: usize, filesz: usize, is_rela: bool, ctx: Ctx) -> crate::error::Result<RelocSection<'a>> { | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. if we want to make this a breaking change I think the better api here is to have caller construct the reloc ctx, so we'd make pub fn parse(bytes: &'a [u8], offset: usize, filesz: usize, ctx: RelocCtx) -> crate::error::Result<RelocSection<'a>> I don't think we should do this though in this patch, if this change isn't already breaking (which it doesn't appear to be, even with suggestion about doing the mips logic in the Ctx) |
||
| Self::parse_inner(bytes, offset, filesz, is_rela, ctx, false) | ||
| } | ||
|
|
||
| #[cfg(feature = "endian_fd")] | ||
| /// Parse a REL or RELA section of size `filesz` from `offset`, with MIPS64 | ||
| /// little-endian relocation info handling. | ||
| /// | ||
| /// When `is_mips64el` is `true`, the MIPS64 little-endian byte transformation | ||
| /// is applied to the `r_info` field of each relocation entry, which corrects | ||
| /// the `r_sym` and `r_type` extraction for MIPS64 LE binaries. | ||
| pub(crate) fn parse_inner(bytes: &'a [u8], offset: usize, filesz: usize, is_rela: bool, ctx: Ctx, is_mips64el: bool) -> crate::error::Result<RelocSection<'a>> { | ||
| // TODO: better error message when too large (see symtab implementation) | ||
| let bytes = if filesz != 0 { | ||
| bytes.pread_with::<&'a [u8]>(offset, filesz)? | ||
|
|
@@ -420,9 +487,9 @@ if_alloc! { | |
| }; | ||
|
|
||
| Ok(RelocSection { | ||
| bytes: bytes, | ||
| bytes, | ||
| count: filesz / Reloc::size(is_rela, ctx), | ||
| ctx: (is_rela, ctx), | ||
| ctx: RelocCtx { is_rela, is_mips64el, ctx }, | ||
| start: offset, | ||
| end: offset + filesz, | ||
| }) | ||
|
|
@@ -518,3 +585,130 @@ if_alloc! { | |
| } | ||
| } | ||
| } // end if_alloc | ||
|
|
||
| #[cfg(test)] | ||
| mod tests { | ||
| use super::reloc64; | ||
|
|
||
| #[test] | ||
| fn test_mips64el_r_info() { | ||
| // Test case from issue #274: a MIPS64 LE binary with r_info bytes | ||
| // [00 00 00 00 00 00 12 03] which, read as LE u64, gives 0x0312000000000000. | ||
| // | ||
| // Without the fix: | ||
| // r_sym = 0x0312000000000000 >> 32 = 0x03120000 = 51511296 (WRONG) | ||
| // r_type = 0x0312000000000000 & 0xFFFFFFFF = 0 (WRONG) | ||
| // | ||
| // The actual MIPS64 struct contains: | ||
| // r_sym = 0, r_ssym = 0, r_type3 = 0, r_type2 = 0x12 (R_MIPS_64), r_type = 0x03 (R_MIPS_REL32) | ||
| let info: u64 = 0x0312000000000000; | ||
| let fixed = reloc64::mips64el_r_info(info); | ||
| assert_eq!(reloc64::r_sym(fixed), 0, "r_sym should be 0"); | ||
| assert_eq!( | ||
| reloc64::r_type(fixed), | ||
| 0x00001203, | ||
| "r_type should contain composite MIPS64 type" | ||
| ); | ||
| assert_eq!( | ||
| reloc64::r_type(fixed) & 0xFF, | ||
| 3, | ||
| "primary r_type should be R_MIPS_REL32 (3)" | ||
| ); | ||
| assert_eq!( | ||
| (reloc64::r_type(fixed) >> 8) & 0xFF, | ||
| 0x12, | ||
| "r_type2 should be R_MIPS_64 (18)" | ||
| ); | ||
| } | ||
|
|
||
| #[test] | ||
| fn test_mips64el_r_info_with_sym() { | ||
| // Test case from issue #274: last reloc entry with sym=0x27 | ||
| // Raw bytes in file: [27 00 00 00 00 00 12 03] | ||
| // As LE u64: 0x0312000000000027 | ||
| let info: u64 = 0x0312000000000027; | ||
| let fixed = reloc64::mips64el_r_info(info); | ||
| assert_eq!(reloc64::r_sym(fixed), 0x27, "r_sym should be 0x27 (39)"); | ||
| assert_eq!( | ||
| reloc64::r_type(fixed) & 0xFF, | ||
| 3, | ||
| "primary r_type should be R_MIPS_REL32 (3)" | ||
| ); | ||
| } | ||
|
|
||
| #[test] | ||
| fn test_mips64el_r_info_zero() { | ||
| // All-zero r_info should remain all-zero | ||
| let info: u64 = 0; | ||
| let fixed = reloc64::mips64el_r_info(info); | ||
| assert_eq!(reloc64::r_sym(fixed), 0); | ||
| assert_eq!(reloc64::r_type(fixed), 0); | ||
| } | ||
|
|
||
| #[test] | ||
| fn test_standard_r_sym_r_type_unchanged() { | ||
| // Ensure the standard r_sym/r_type functions still work for non-MIPS | ||
| let info: u64 = (42u64 << 32) | 7u64; | ||
| assert_eq!(reloc64::r_sym(info), 42); | ||
| assert_eq!(reloc64::r_type(info), 7); | ||
| } | ||
|
|
||
| /// Test that RelocSection correctly applies MIPS64 LE fixup when parsing | ||
| /// raw relocation bytes through the full pipeline. | ||
| #[test] | ||
| #[cfg(feature = "endian_fd")] | ||
| fn test_mips64el_reloc_section_parse() { | ||
| use super::RelocSection; | ||
| use crate::container::{Container, Ctx}; | ||
|
|
||
| let ctx = Ctx::new(Container::Big, scroll::Endian::Little); | ||
|
|
||
| // Construct raw bytes for a REL entry (r_offset + r_info, each 8 bytes). | ||
| // r_offset = 0x150f0 (LE bytes: f0 50 01 00 00 00 00 00) | ||
| // r_info as MIPS64 struct: r_sym=0, r_ssym=0, r_type3=0, r_type2=0x12, r_type=0x03 | ||
| // In file bytes: 00 00 00 00 00 00 12 03 | ||
| let rel_bytes: Vec<u8> = vec![ | ||
| // r_offset (LE u64 = 0x150f0) | ||
| 0xf0, 0x50, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, // r_info (MIPS64 LE layout) | ||
| 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x12, 0x03, | ||
| ]; | ||
|
|
||
| // Prepend enough zeros so the offset parameter works | ||
| let mut bytes = vec![0u8; 64]; | ||
| let offset = bytes.len(); | ||
| bytes.extend_from_slice(&rel_bytes); | ||
|
|
||
| // Parse without MIPS64 fixup - should give wrong values | ||
| let section_no_fix = | ||
| RelocSection::parse(&bytes, offset, rel_bytes.len(), false, ctx).unwrap(); | ||
| let reloc_no_fix = section_no_fix.get(0).unwrap(); | ||
| assert_eq!(reloc_no_fix.r_offset, 0x150f0); | ||
| // Without fixup, r_sym is garbage (51511296) and r_type is wrong (0) | ||
| assert_eq!( | ||
| reloc_no_fix.r_sym, 51511296, | ||
| "Without fixup, r_sym should be 51511296 (wrong)" | ||
| ); | ||
| assert_eq!( | ||
| reloc_no_fix.r_type, 0, | ||
| "Without fixup, r_type should be 0 (wrong)" | ||
| ); | ||
|
|
||
| // Parse with MIPS64 fixup - should give correct values | ||
| let section_fixed = | ||
| RelocSection::parse_inner(&bytes, offset, rel_bytes.len(), false, ctx, true).unwrap(); | ||
| let reloc_fixed = section_fixed.get(0).unwrap(); | ||
| assert_eq!(reloc_fixed.r_offset, 0x150f0); | ||
| assert_eq!(reloc_fixed.r_sym, 0, "With fixup, r_sym should be 0"); | ||
| assert_eq!( | ||
| reloc_fixed.r_type & 0xFF, | ||
| 3, | ||
| "With fixup, primary r_type should be R_MIPS_REL32 (3)" | ||
| ); | ||
|
|
||
| // Also test iteration | ||
| let relocs: Vec<_> = section_fixed.iter().collect(); | ||
| assert_eq!(relocs.len(), 1); | ||
| assert_eq!(relocs[0].r_sym, 0); | ||
| assert_eq!(relocs[0].r_type & 0xFF, 3); | ||
| } | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.