From b25e32f9476f54304411c08edcc809fc39a96abf Mon Sep 17 00:00:00 2001
From: Gabriele Svelto
Date: Tue, 19 Sep 2023 09:07:42 +0200
Subject: [PATCH] Merge empty mappings within a library so they form a single
contiguous range
This issue was observed in Firefox, see bug 1837471. This fixes #88
---
src/linux/maps_reader.rs | 105 +++++++++++++++++++++++++++++++--------
1 file changed, 84 insertions(+), 21 deletions(-)
diff --git a/src/linux/maps_reader.rs b/src/linux/maps_reader.rs
index 760d1fdf..4d0d3b5a 100644
--- a/src/linux/maps_reader.rs
+++ b/src/linux/maps_reader.rs
@@ -70,6 +70,14 @@ impl MappingInfo {
is_mapping_a_path(self.name.as_deref())
}
+ pub fn is_empty_page(&self) -> bool {
+ (self.offset == 0) && (self.permissions == MMPermissions::PRIVATE) && self.name.is_none()
+ }
+
+ pub fn end_address(&self) -> usize {
+ self.start_address + self.size
+ }
+
pub fn aggregate(memory_maps: MemoryMaps, linux_gate_loc: AuxvType) -> Result> {
let mut infos = Vec::::new();
@@ -99,31 +107,54 @@ impl MappingInfo {
offset = 0;
}
- // Merge adjacent mappings into one module, assuming they're a single
- // library mapped by the dynamic linker.
- if let Some(module) = infos.last_mut() {
- if pathname.is_some() {
- if (start_address == module.start_address + module.size)
- && (pathname == module.name)
- {
- module.system_mapping_info.end_address = end_address;
- module.size = end_address - module.start_address;
- module.permissions |= mm.perms;
- continue;
- }
- } else {
+ if let Some(prev_module) = infos.last_mut() {
+ if (start_address == prev_module.end_address())
+ && pathname.is_some()
+ && (pathname == prev_module.name)
+ {
+ // Merge adjacent mappings into one module, assuming they're a single
+ // library mapped by the dynamic linker.
+ prev_module.system_mapping_info.end_address = end_address;
+ prev_module.size = end_address - prev_module.start_address;
+ prev_module.permissions |= mm.perms;
+ continue;
+ } else if (start_address == prev_module.end_address())
+ && prev_module.is_executable()
+ && prev_module.name_is_path()
+ && ((offset == 0) || (offset == prev_module.end_address()))
+ && (mm.perms == MMPermissions::PRIVATE)
+ {
// Also merge mappings that result from address ranges that the
// linker reserved but which a loaded library did not use. These
// appear as an anonymous private mapping with no access flags set
// and which directly follow an executable mapping.
- let module_end_address = module.start_address + module.size;
- if (start_address == module_end_address)
- && module.is_executable()
- && is_mapping_a_path(module.name.as_deref())
- && (offset == 0 || offset == module_end_address)
- && mm.perms == MMPermissions::PRIVATE
- {
- module.size = end_address - module.start_address;
+ prev_module.size = end_address - prev_module.start_address;
+ continue;
+ }
+ }
+
+ // Sometimes the unused ranges reserved but the linker appear within the library.
+ // If we detect an empty page that is adjacent to two mappings of the same library
+ // we fold the three mappings together.
+ if let Some(previous_modules) = infos.rchunks_exact_mut(2).next() {
+ let empty_page = if let Some(prev_module) = previous_modules.last() {
+ let prev_prev_module = previous_modules.first().unwrap();
+ prev_prev_module.name_is_path()
+ && (prev_prev_module.end_address() == prev_module.start_address)
+ && prev_module.is_empty_page()
+ && (prev_module.end_address() == start_address)
+ } else {
+ false
+ };
+
+ if empty_page {
+ let prev_prev_module = previous_modules.first_mut().unwrap();
+
+ if pathname == prev_prev_module.name {
+ prev_prev_module.system_mapping_info.end_address = end_address;
+ prev_prev_module.size = end_address - prev_prev_module.start_address;
+ prev_prev_module.permissions |= mm.perms;
+ infos.pop();
continue;
}
}
@@ -553,6 +584,38 @@ ffffffffff600000-ffffffffff601000 --xp 00000000 00:00 0 [vsysca
assert_eq!(mappings[6], gate_map);
}
+ #[test]
+ fn test_merged_reserved_mappings_within_module() {
+ let mappings = get_mappings_for(
+ "\
+9b4a0000-9b931000 r--p 00000000 08:12 393449 /data/app/org.mozilla.firefox-1/lib/x86/libxul.so
+9b931000-9bcae000 ---p 00000000 00:00 0
+9bcae000-a116b000 r-xp 00490000 08:12 393449 /data/app/org.mozilla.firefox-1/lib/x86/libxul.so
+a116b000-a4562000 r--p 0594d000 08:12 393449 /data/app/org.mozilla.firefox-1/lib/x86/libxul.so
+a4562000-a4563000 ---p 00000000 00:00 0
+a4563000-a4840000 r--p 08d44000 08:12 393449 /data/app/org.mozilla.firefox-1/lib/x86/libxul.so
+a4840000-a4873000 rw-p 09021000 08:12 393449 /data/app/org.mozilla.firefox-1/lib/x86/libxul.so",
+ 0xa4876000,
+ );
+
+ let gate_map = MappingInfo {
+ start_address: 0x9b4a0000,
+ size: 155004928, // Merged the anonymous area after in this mapping, so its bigger..
+ system_mapping_info: SystemMappingInfo {
+ start_address: 0x9b4a0000,
+ end_address: 0xa4873000,
+ },
+ offset: 0,
+ permissions: MMPermissions::READ
+ | MMPermissions::WRITE
+ | MMPermissions::EXECUTE
+ | MMPermissions::PRIVATE,
+ name: Some("/data/app/org.mozilla.firefox-1/lib/x86/libxul.so".into()),
+ };
+
+ assert_eq!(mappings[0], gate_map);
+ }
+
#[test]
fn test_get_mapping_effective_name() {
let mappings = get_mappings_for(