diff --git a/Cargo.toml b/Cargo.toml index d35a1129..53a00280 100755 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,6 +18,7 @@ indexmap = { version = "1.6", optional = true } wasmparser = { version = "0.102.0", optional = true } memchr = { version = "2.4.1", default-features = false } hashbrown = { version = "0.13.1", features = ["ahash"], default-features = false, optional = true } +ruzstd = { version = "0.3.1", optional = true } # Internal feature, only used when building as part of libstd, not part of the # stable interface of this crate. @@ -48,7 +49,7 @@ write = ["write_std", "coff", "elf", "macho", "pe", "xcoff"] std = ["memchr/std"] # Enable decompression of compressed sections. # This feature is not required if you want to do your own decompression. -compression = ["flate2", "std"] +compression = ["flate2", "ruzstd", "std"] # Treat all types as unaligned. # Normally types use the alignment required by the specifications, but # sometimes files do not strictly follow the specifications. diff --git a/crates/examples/src/readobj/elf.rs b/crates/examples/src/readobj/elf.rs index d5550137..3aaefb3c 100644 --- a/crates/examples/src/readobj/elf.rs +++ b/crates/examples/src/readobj/elf.rs @@ -262,6 +262,15 @@ fn print_section_headers( p.field_hex("AddressAlign", section.sh_addralign(endian).into()); p.field_hex("EntrySize", section.sh_entsize(endian).into()); + if let Some(Some((compression, _, _))) = section.compression(endian, data).print_err(p) + { + p.group("CompressionHeader", |p| { + p.field_enum("Type", compression.ch_type(endian), FLAGS_ELFCOMPRESS); + p.field_hex("Size", compression.ch_size(endian).into()); + p.field_hex("AddressAlign", compression.ch_addralign(endian).into()); + }); + } + match section.sh_type(endian) { SHT_SYMTAB | SHT_DYNSYM => { print_section_symbols(p, endian, data, elf, sections, index, section) @@ -1315,6 +1324,7 @@ static FLAGS_SHF_PARISC: &[Flag] = &flags!(SHF_PARISC_SHORT, SHF_PARISC_HUG static FLAGS_SHF_ALPHA: &[Flag] = &flags!(SHF_ALPHA_GPREL); static FLAGS_SHF_ARM: &[Flag] = &flags!(SHF_ARM_ENTRYSECT, SHF_ARM_COMDEF); static FLAGS_SHF_IA_64: &[Flag] = &flags!(SHF_IA_64_SHORT, SHF_IA_64_NORECOV); +static FLAGS_ELFCOMPRESS: &[Flag] = &flags!(ELFCOMPRESS_ZLIB, ELFCOMPRESS_ZSTD); static FLAGS_STT: &[Flag] = &flags!( STT_NOTYPE, STT_OBJECT, diff --git a/crates/examples/testfiles/elf/base-debug-zlib.o.readobj.compression b/crates/examples/testfiles/elf/base-debug-zlib.o.readobj.compression new file mode 100644 index 00000000..83c8b84b --- /dev/null +++ b/crates/examples/testfiles/elf/base-debug-zlib.o.readobj.compression @@ -0,0 +1,19 @@ +SectionHeader { + Index: 6 + Name: ".debug_info" (0x3E) + Type: SHT_PROGBITS (0x1) + Flags: 0x800 + SHF_COMPRESSED (0x800) + Address: 0x0 + Offset: 0x70 + Size: 0x74 + Link: 0 + Info: 0 + AddressAlign: 0x8 + EntrySize: 0x0 + CompressionHeader { + Type: ELFCOMPRESS_ZLIB (0x1) + Size: 0xAF + AddressAlign: 0x1 + } +} diff --git a/crates/examples/testfiles/elf/base-debug-zstd.o.readobj.compression b/crates/examples/testfiles/elf/base-debug-zstd.o.readobj.compression new file mode 100644 index 00000000..a28b85af --- /dev/null +++ b/crates/examples/testfiles/elf/base-debug-zstd.o.readobj.compression @@ -0,0 +1,19 @@ +SectionHeader { + Index: 6 + Name: ".debug_info" (0x3E) + Type: SHT_PROGBITS (0x1) + Flags: 0x800 + SHF_COMPRESSED (0x800) + Address: 0x0 + Offset: 0x70 + Size: 0x84 + Link: 0 + Info: 0 + AddressAlign: 0x8 + EntrySize: 0x0 + CompressionHeader { + Type: ELFCOMPRESS_ZSTD (0x2) + Size: 0xAF + AddressAlign: 0x1 + } +} diff --git a/src/elf.rs b/src/elf.rs index c67d6e64..38bc42e7 100644 --- a/src/elf.rs +++ b/src/elf.rs @@ -819,6 +819,8 @@ pub struct CompressionHeader64 { /// ZLIB/DEFLATE algorithm. pub const ELFCOMPRESS_ZLIB: u32 = 1; +/// Zstandard algorithm. +pub const ELFCOMPRESS_ZSTD: u32 = 2; /// Start of OS-specific compression types. pub const ELFCOMPRESS_LOOS: u32 = 0x6000_0000; /// End of OS-specific compression types. diff --git a/src/read/elf/section.rs b/src/read/elf/section.rs index ce7a3d40..3b103dd3 100644 --- a/src/read/elf/section.rs +++ b/src/read/elf/section.rs @@ -381,32 +381,24 @@ impl<'data, 'file, Elf: FileHeader, R: ReadRef<'data>> ElfSection<'data, 'file, fn maybe_compressed(&self) -> read::Result> { let endian = self.file.endian; - if (self.section.sh_flags(endian).into() & u64::from(elf::SHF_COMPRESSED)) == 0 { - return Ok(None); - } - let (section_offset, section_size) = self - .section - .file_range(endian) - .read_error("Invalid ELF compressed section type")?; - let mut offset = section_offset; - let header = self - .file - .data - .read::(&mut offset) - .read_error("Invalid ELF compressed section offset")?; - if header.ch_type(endian) != elf::ELFCOMPRESS_ZLIB { - return Err(Error("Unsupported ELF compression type")); + if let Some((header, offset, compressed_size)) = + self.section.compression(endian, self.file.data)? + { + let format = match header.ch_type(endian) { + elf::ELFCOMPRESS_ZLIB => CompressionFormat::Zlib, + elf::ELFCOMPRESS_ZSTD => CompressionFormat::Zstandard, + _ => return Err(Error("Unsupported ELF compression type")), + }; + let uncompressed_size = header.ch_size(endian).into(); + Ok(Some(CompressedFileRange { + format, + offset, + compressed_size, + uncompressed_size, + })) + } else { + Ok(None) } - let uncompressed_size = header.ch_size(endian).into(); - let compressed_size = section_size - .checked_sub(offset - section_offset) - .read_error("Invalid ELF compressed section size")?; - Ok(Some(CompressedFileRange { - format: CompressionFormat::Zlib, - offset, - compressed_size, - uncompressed_size, - })) } /// Try GNU-style "ZLIB" header decompression. @@ -1006,6 +998,40 @@ pub trait SectionHeader: Debug + Pod { let data = self.data(endian, data)?; AttributesSection::new(endian, data) } + + /// Parse the compression header if present. + /// + /// Returns the header, and the offset and size of the compressed section data + /// in the file. + /// + /// Returns `Ok(None)` if the section flags do not have `SHF_COMPRESSED`. + /// Returns `Err` for invalid values. + fn compression<'data, R: ReadRef<'data>>( + &self, + endian: Self::Endian, + data: R, + ) -> read::Result< + Option<( + &'data ::CompressionHeader, + u64, + u64, + )>, + > { + if (self.sh_flags(endian).into() & u64::from(elf::SHF_COMPRESSED)) == 0 { + return Ok(None); + } + let (section_offset, section_size) = self + .file_range(endian) + .read_error("Invalid ELF compressed section type")?; + let mut offset = section_offset; + let header = data + .read::<::CompressionHeader>(&mut offset) + .read_error("Invalid ELF compressed section offset")?; + let compressed_size = section_size + .checked_sub(offset - section_offset) + .read_error("Invalid ELF compressed section size")?; + Ok(Some((header, offset, compressed_size))) + } } impl SectionHeader for elf::SectionHeader32 { diff --git a/src/read/mod.rs b/src/read/mod.rs index d91ba7fa..8a37dcd3 100644 --- a/src/read/mod.rs +++ b/src/read/mod.rs @@ -632,6 +632,10 @@ pub enum CompressionFormat { /// /// Used for ELF compression and GNU compressed debug information. Zlib, + /// Zstandard. + /// + /// Used for ELF compression. + Zstandard, } /// A range in a file that may be compressed. @@ -731,6 +735,25 @@ impl<'data> CompressedData<'data> { .read_error("Invalid zlib compressed data")?; Ok(Cow::Owned(decompressed)) } + #[cfg(feature = "compression")] + CompressionFormat::Zstandard => { + use core::convert::TryInto; + use std::io::Read; + let size = self + .uncompressed_size + .try_into() + .ok() + .read_error("Uncompressed data size is too large.")?; + let mut decompressed = Vec::with_capacity(size); + let mut decoder = ruzstd::StreamingDecoder::new(self.data) + .ok() + .read_error("Invalid zstd compressed data")?; + decoder + .read_to_end(&mut decompressed) + .ok() + .read_error("Invalid zstd compressed data")?; + Ok(Cow::Owned(decompressed)) + } _ => Err(Error("Unsupported compressed data.")), } } diff --git a/testfiles b/testfiles index 46e8c9af..f48e59cc 160000 --- a/testfiles +++ b/testfiles @@ -1 +1 @@ -Subproject commit 46e8c9afc1ef757de0248e1acdd6699cd12433b1 +Subproject commit f48e59ccb44bb2117c6dbfe43305637438e4f3a6