From 9f4e130f1477b03bfd89d3a611a258c15ece2521 Mon Sep 17 00:00:00 2001
From: Alexandre Lissy
Date: Mon, 19 Feb 2024 14:06:05 +0100
Subject: [PATCH 1/2] Bug 1847098 - Add version number of so files
---
src/linux/maps_reader.rs | 110 +++++++++++++++++++++++++++++++--
src/linux/sections/mappings.rs | 16 +++--
2 files changed, 117 insertions(+), 9 deletions(-)
diff --git a/src/linux/maps_reader.rs b/src/linux/maps_reader.rs
index 4d0d3b5a..8e3cb1eb 100644
--- a/src/linux/maps_reader.rs
+++ b/src/linux/maps_reader.rs
@@ -303,7 +303,62 @@ impl MappingInfo {
Ok(soname.to_string())
}
- pub fn get_mapping_effective_path_and_name(&self) -> Result<(PathBuf, String)> {
+ fn elf_file_so_version(&self) -> (u32, u32, u32) {
+ // Report the so version from the filename as two u32 that will fit into
+ // version_info.file_version_{hi,lo}
+ let name = String::from(
+ self.name
+ .clone()
+ .expect("Failure to clone")
+ .to_str()
+ .unwrap(),
+ );
+
+ if !(name.contains(".so.")) {
+ return (0, 0, 0);
+ }
+
+ let filename = match name.split('/').last() {
+ Some(f) => f,
+ None => return (0, 0, 0),
+ };
+
+ match filename.split(".so.").last() {
+ Some(s) => {
+ let mut got_non_digit = false;
+ let vers: Vec = s
+ .split('.')
+ .collect::>()
+ .into_iter()
+ .map(|n| {
+ n.parse().unwrap_or_else(|_| {
+ got_non_digit = true;
+ 0
+ })
+ })
+ .collect::>();
+
+ if got_non_digit {
+ return (0, 0, 0);
+ }
+
+ let (major, minor, release) = match vers.len() {
+ 0 => (0, 0, 0),
+ 1 => (vers[0], 0, 0),
+ 2 => (vers[0], vers[1], 0),
+ 3 => (vers[0], vers[1], vers[2]),
+ _ => (vers[0], vers[1], vers[2]),
+ };
+
+ (major, minor, release)
+ }
+ None => (0, 0, 0),
+ }
+ }
+
+ 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 +376,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 +392,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 +683,58 @@ 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 version = mappings[0].elf_file_so_version();
+ assert_eq!(version, (0, 0, 0));
+
+ let version = mappings[1].elf_file_so_version();
+ assert_eq!(version, (6, 0, 32));
+
+ let version = mappings[2].elf_file_so_version();
+ assert_eq!(version, (2, 11800, 0));
+
+ let version = mappings[3].elf_file_so_version();
+ assert_eq!(version, (6, 0, 0));
+
+ let version = mappings[4].elf_file_so_version();
+ assert_eq!(version, (0, 0, 0));
+
+ let version = mappings[5].elf_file_so_version();
+ assert_eq!(version, (0, 7800, 0));
+
+ let version = mappings[6].elf_file_so_version();
+ assert_eq!(version, (20220623, 0, 0));
+
+ let version = mappings[7].elf_file_so_version();
+ assert_eq!(version, (0, 0, 0));
+
+ let version = mappings[8].elf_file_so_version();
+ assert_eq!(version, (0, 0, 0));
+ }
+
#[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)
}
From 733c2665595260b86850140bd4d7a7f2291976b8 Mon Sep 17 00:00:00 2001
From: Jake Shadle
Date: Fri, 23 Feb 2024 17:25:21 +0100
Subject: [PATCH 2/2] Simplify
The original code would have paniced on non-utf8 paths, and would fail to return any version components if a single one failed to be parsed as a number.
The new code will now gracefully fail if the filename (the rest of the path is inconsequential) is non-utf8, and now tries to handle as many version components as it can, including partial parsing of mixed alphanumeric components such as, eg 2rc5 (which was aready a test case) would be parsed as `25`
---
src/linux/maps_reader.rs | 108 +++++++++++++++------------------------
1 file changed, 40 insertions(+), 68 deletions(-)
diff --git a/src/linux/maps_reader.rs b/src/linux/maps_reader.rs
index 8e3cb1eb..6e96d0b2 100644
--- a/src/linux/maps_reader.rs
+++ b/src/linux/maps_reader.rs
@@ -303,57 +303,39 @@ impl MappingInfo {
Ok(soname.to_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) {
- // Report the so version from the filename as two u32 that will fit into
- // version_info.file_version_{hi,lo}
- let name = String::from(
- self.name
- .clone()
- .expect("Failure to clone")
- .to_str()
- .unwrap(),
- );
+ 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;
+ };
- if !(name.contains(".so.")) {
- return (0, 0, 0);
- }
+ // Avoid an allocation unless the string contains non-utf8
+ let filename = filename.to_string_lossy();
- let filename = match name.split('/').last() {
- Some(f) => f,
- None => return (0, 0, 0),
+ let Some((_, version)) = filename.split_once(".so.") else {
+ return DEF;
};
- match filename.split(".so.").last() {
- Some(s) => {
- let mut got_non_digit = false;
- let vers: Vec = s
- .split('.')
- .collect::>()
- .into_iter()
- .map(|n| {
- n.parse().unwrap_or_else(|_| {
- got_non_digit = true;
- 0
- })
- })
- .collect::>();
-
- if got_non_digit {
- return (0, 0, 0);
- }
+ let mut triplet = [0, 0, 0];
- let (major, minor, release) = match vers.len() {
- 0 => (0, 0, 0),
- 1 => (vers[0], 0, 0),
- 2 => (vers[0], vers[1], 0),
- 3 => (vers[0], vers[1], vers[2]),
- _ => (vers[0], vers[1], vers[2]),
- };
-
- (major, minor, release)
+ 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;
}
- None => (0, 0, 0),
}
+
+ (triplet[0], triplet[1], triplet[2])
}
pub fn get_mapping_effective_path_name_and_version(
@@ -707,32 +689,22 @@ a4840000-a4873000 rw-p 09021000 08:12 393449 /data/app/org.mozilla.firefox-1
);
assert_eq!(mappings.len(), 9);
- let version = mappings[0].elf_file_so_version();
- assert_eq!(version, (0, 0, 0));
-
- let version = mappings[1].elf_file_so_version();
- assert_eq!(version, (6, 0, 32));
-
- let version = mappings[2].elf_file_so_version();
- assert_eq!(version, (2, 11800, 0));
-
- let version = mappings[3].elf_file_so_version();
- assert_eq!(version, (6, 0, 0));
-
- let version = mappings[4].elf_file_so_version();
- assert_eq!(version, (0, 0, 0));
-
- let version = mappings[5].elf_file_so_version();
- assert_eq!(version, (0, 7800, 0));
-
- let version = mappings[6].elf_file_so_version();
- assert_eq!(version, (20220623, 0, 0));
-
- let version = mappings[7].elf_file_so_version();
- assert_eq!(version, (0, 0, 0));
+ 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),
+ ];
- let version = mappings[8].elf_file_so_version();
- assert_eq!(version, (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]