diff --git a/src/linux/maps_reader.rs b/src/linux/maps_reader.rs index 4d0d3b5a..6e96d0b2 100644 --- a/src/linux/maps_reader.rs +++ b/src/linux/maps_reader.rs @@ -303,7 +303,44 @@ impl MappingInfo { Ok(soname.to_string()) } - pub fn get_mapping_effective_path_and_name(&self) -> Result<(PathBuf, String)> { + /// Attempts to retrieve the .so version of the elf via its filename as a + /// `(major, minor, release)` triplet + fn elf_file_so_version(&self) -> (u32, u32, u32) { + const DEF: (u32, u32, u32) = (0, 0, 0); + let Some(so_name) = self.name.as_deref() else { + return DEF; + }; + let Some(filename) = std::path::Path::new(so_name).file_name() else { + return DEF; + }; + + // Avoid an allocation unless the string contains non-utf8 + let filename = filename.to_string_lossy(); + + let Some((_, version)) = filename.split_once(".so.") else { + return DEF; + }; + + let mut triplet = [0, 0, 0]; + + for (so, trip) in version.split('.').zip(triplet.iter_mut()) { + // In some cases the release/patch version is alphanumeric (eg. '2rc5'), + // so try to parse as much as we can rather than completely ignoring + for digit in so + .chars() + .filter_map(|c: char| c.is_ascii_digit().then_some(c as u8 - b'0')) + { + *trip *= 10; + *trip += digit as u32; + } + } + + (triplet[0], triplet[1], triplet[2]) + } + + pub fn get_mapping_effective_path_name_and_version( + &self, + ) -> Result<(PathBuf, String, (u32, u32, u32))> { let mut file_path = PathBuf::from(self.name.clone().unwrap_or_default()); // Tools such as minidump_stackwalk use the name of the module to look up @@ -321,7 +358,7 @@ impl MappingInfo { .file_name() .map(|s| s.to_string_lossy().into_owned()) .unwrap_or_default(); - return Ok((file_path, file_name)); + return Ok((file_path, file_name, self.elf_file_so_version())); }; if self.is_executable() && self.offset != 0 { @@ -337,7 +374,7 @@ impl MappingInfo { file_path.set_file_name(&file_name); } - Ok((file_path, file_name)) + Ok((file_path, file_name, self.elf_file_so_version())) } pub fn is_contained_in(&self, user_mapping_list: &MappingList) -> bool { @@ -628,13 +665,48 @@ a4840000-a4873000 rw-p 09021000 08:12 393449 /data/app/org.mozilla.firefox-1 ); assert_eq!(mappings.len(), 1); - let (file_path, file_name) = mappings[0] - .get_mapping_effective_path_and_name() + let (file_path, file_name, _version) = mappings[0] + .get_mapping_effective_path_name_and_version() .expect("Couldn't get effective name for mapping"); assert_eq!(file_name, "libmozgtk.so"); assert_eq!(file_path, PathBuf::from("/home/martin/Documents/mozilla/devel/mozilla-central/obj/widget/gtk/mozgtk/gtk3/libmozgtk.so")); } + #[test] + fn test_elf_file_so_version() { + let mappings = get_mappings_for( + "\ +7f877ab9f000-7f877aba0000 rw-p 0001f000 00:1b 100457459 /home/alex/bin/firefox/libmozsandbox.so +7f877ae65000-7f877ae68000 rw-p 00265000 00:1b 90432393 /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.32 +7f877ae76000-7f877ae77000 rw-p 0000a000 00:1b 90443112 /usr/lib/x86_64-linux-gnu/libcairo-gobject.so.2.11800.0 +7f877ae7c000-7f877ae8c000 r--p 00000000 00:1b 93439971 /usr/lib/x86_64-linux-gnu/libm.so.6 +7f877af70000-7f877af71000 rw-p 00003000 00:1b 93439980 /usr/lib/x86_64-linux-gnu/libpthread.so.0 +7f877af78000-7f877af79000 rw-p 00005000 00:1b 90423049 /usr/lib/x86_64-linux-gnu/libgmodule-2.0.so.0.7800.0 +7f877ae7c000-7f877ae8c000 rw-p 00000000 00:1b 93439971 /usr/lib/x86_64-linux-gnu/libabsl_time_zone.so.20220623.0.0 +7f877ae7c000-7f877ae8c000 rw-p 00000000 00:1b 93439971 /usr/lib/x86_64-linux-gnu/libdbus-1.so.3.34.2rc5 +7f877ae7c000-7f877ae8c000 rw-p 00000000 00:1b 93439971 /usr/lib/x86_64-linux-gnu/libtoto.so.AAA", + 0x7ffe091bf000, + ); + assert_eq!(mappings.len(), 9); + + let expected = [ + (0, 0, 0), + (6, 0, 32), + (2, 11800, 0), + (6, 0, 0), + (0, 0, 0), + (0, 7800, 0), + (20220623, 0, 0), + (3, 34, 25), + (0, 0, 0), + ]; + + for (i, (map, exp)) in mappings.into_iter().zip(expected).enumerate() { + let version = map.elf_file_so_version(); + assert_eq!(version, exp, "{i}"); + } + } + #[test] fn test_whitespaces_in_name() { let mappings = get_mappings_for( diff --git a/src/linux/sections/mappings.rs b/src/linux/sections/mappings.rs index de19c540..a92739ea 100644 --- a/src/linux/sections/mappings.rs +++ b/src/linux/sections/mappings.rs @@ -83,16 +83,24 @@ fn fill_raw_module( sig_section.location() }; - let (file_path, _) = mapping - .get_mapping_effective_path_and_name() + let (file_path, _, (major, minor, release)) = mapping + .get_mapping_effective_path_name_and_version() .map_err(|e| errors::SectionMappingsError::GetEffectivePathError(mapping.clone(), e))?; let name_header = write_string_to_location(buffer, file_path.to_string_lossy().as_ref())?; - Ok(MDRawModule { + let mut raw_module = MDRawModule { base_of_image: mapping.start_address as u64, size_of_image: mapping.size as u32, cv_record, module_name_rva: name_header.rva, ..Default::default() - }) + }; + + raw_module.version_info.signature = format::VS_FFI_SIGNATURE; + raw_module.version_info.struct_version = format::VS_FFI_STRUCVERSION; + raw_module.version_info.file_version_hi = major; + raw_module.version_info.file_version_lo = minor; + raw_module.version_info.product_version_hi = release; + + Ok(raw_module) }