1use {
2 super::{
3 auxv::AuxvType,
4 module_reader::ModuleReaderError,
5 process_inspection::{self, ProcessInspector},
6 serializers::*,
7 },
8 crate::serializers::*,
9 byteorder::{NativeEndian, ReadBytesExt},
10 procfs_core::{
11 FromRead,
12 process::{MMPermissions, MMapPath, MemoryMaps},
13 },
14 std::{
15 ffi::{OsStr, OsString},
16 mem::size_of,
17 os::unix::ffi::{OsStrExt, OsStringExt},
18 path::{Path, PathBuf},
19 },
20};
21
22pub const LINUX_GATE_LIBRARY_NAME: &str = "linux-gate.so";
23pub const DELETED_SUFFIX: &[u8] = b" (deleted)";
24
25type Result<T> = std::result::Result<T, MapsReaderError>;
26
27#[derive(Debug, PartialEq, Eq, Clone, serde::Serialize)]
28pub struct SystemMappingInfo {
29 pub start_address: usize,
30 pub end_address: usize,
31}
32
33#[derive(Debug, PartialEq, Eq, Clone, serde::Serialize)]
36pub struct MappingInfo {
37 pub start_address: usize,
43 pub size: usize,
44 pub system_mapping_info: SystemMappingInfo,
49 pub offset: usize, pub permissions: MMPermissions, pub name: Option<OsString>,
52 }
54
55#[derive(Debug)]
56pub struct MappingEntry {
57 pub mapping: MappingInfo,
58 pub identifier: Vec<u8>,
59}
60
61pub type MappingList = Vec<MappingEntry>;
63
64#[derive(thiserror::Error, Debug, serde::Serialize)]
65pub enum MapsReaderError {
66 #[error("failed to read /proc/<pid>/maps")]
67 ReadFileFailed(#[source] process_inspection::Error),
68 #[error("Couldn't parse as ELF file")]
69 ELFParsingFailed(
70 #[from]
71 #[serde(serialize_with = "serialize_goblin_error")]
72 goblin::error::Error,
73 ),
74 #[error("error reading soname from file")]
75 ReadSoNameFromFileFailed(#[source] ModuleReaderError),
76 #[error("failed to memory map file")]
77 MemoryMapFileFailed(#[source] ModuleReaderError),
78 #[error("No soname found (filename: {})", .0.to_string_lossy())]
79 NoSoName(OsString, #[source] ModuleReaderError),
80
81 #[error("Map entry malformed: No {0} found")]
83 MapEntryMalformed(&'static str),
84 #[error("Couldn't parse address")]
85 UnparsableInteger(
86 #[from]
87 #[serde(skip)]
88 std::num::ParseIntError,
89 ),
90 #[error("Linux gate location doesn't fit in the required integer type")]
91 LinuxGateNotConvertable(
92 #[from]
93 #[serde(skip)]
94 std::num::TryFromIntError,
95 ),
96
97 #[error("Not safe to open mapping {}", .0.to_string_lossy())]
99 NotSafeToOpenMapping(OsString),
100 #[error("IO Error")]
101 FileError(
102 #[from]
103 #[serde(serialize_with = "serialize_io_error")]
104 std::io::Error,
105 ),
106 #[error("Mmapped file empty or not an ELF file")]
107 MmapSanityCheckFailed,
108 #[error("Symlink does not match ({0} vs. {1})")]
109 SymlinkError(std::path::PathBuf, std::path::PathBuf),
110 #[error("Failed to parse memory maps file")]
111 ParsingError(
112 #[from]
113 #[serde(serialize_with = "serialize_proc_error")]
114 procfs_core::ProcError,
115 ),
116}
117
118fn is_mapping_a_path(pathname: Option<&OsStr>) -> bool {
119 match pathname {
120 Some(x) => x.as_bytes().contains(&b'/'),
121 None => false,
122 }
123}
124
125fn sanitize_path(pathname: OsString) -> OsString {
129 if let Some(bytes) = pathname.as_bytes().strip_suffix(DELETED_SUFFIX) {
130 OsString::from_vec(bytes.to_owned())
131 } else {
132 pathname
133 }
134}
135
136impl MappingInfo {
137 pub fn for_pid(
139 process_inspector: &ProcessInspector,
140 pid: i32,
141 linux_gate_loc: Option<AuxvType>,
142 ) -> Result<Vec<Self>> {
143 let maps_path = format!("/proc/{}/maps", pid);
144 let maps_file = process_inspector
145 .read_file(&maps_path)
146 .map_err(MapsReaderError::ReadFileFailed)?;
147 let maps = MemoryMaps::from_read(maps_file)?;
148 Self::aggregate(maps, linux_gate_loc)
149 }
150
151 pub fn name_is_path(&self) -> bool {
153 is_mapping_a_path(self.name.as_deref())
154 }
155
156 pub fn is_empty_page(&self) -> bool {
157 (self.offset == 0) && (self.permissions == MMPermissions::PRIVATE) && self.name.is_none()
158 }
159
160 pub fn end_address(&self) -> usize {
161 self.start_address + self.size
162 }
163
164 pub fn aggregate(
165 memory_maps: MemoryMaps,
166 linux_gate_loc: Option<AuxvType>,
167 ) -> Result<Vec<Self>> {
168 let mut infos = Vec::<Self>::new();
169
170 for mm in memory_maps {
171 let start_address: usize = mm.address.0.try_into()?;
172 let end_address: usize = mm.address.1.try_into()?;
173 let mut offset: usize = mm.offset.try_into()?;
174
175 let mut pathname: Option<OsString> = match mm.pathname {
176 MMapPath::Path(p) => Some(sanitize_path(p.into())),
177 MMapPath::Heap => Some("[heap]".into()),
178 MMapPath::Stack => Some("[stack]".into()),
179 MMapPath::TStack(i) => Some(format!("[stack:{i}]").into()),
180 MMapPath::Vdso => Some("[vdso]".into()),
181 MMapPath::Vvar => Some("[vvar]".into()),
182 MMapPath::Vsyscall => Some("[vsyscall]".into()),
183 MMapPath::Rollup => Some("[rollup]".into()),
184 MMapPath::Vsys(i) => Some(format!("/SYSV{i:x}").into()),
185 MMapPath::Other(n) => Some(format!("[{n}]").into()),
186 MMapPath::Anonymous => None,
187 };
188
189 let is_path = is_mapping_a_path(pathname.as_deref());
190
191 if let Some(linux_gate_loc) = linux_gate_loc.map(|u| usize::try_from(u).unwrap())
192 && (!is_path && (start_address == linux_gate_loc))
193 {
194 pathname = Some(LINUX_GATE_LIBRARY_NAME.into());
195 offset = 0;
196 }
197
198 if let Some(prev_module) = infos.last_mut() {
199 if (start_address == prev_module.end_address())
200 && pathname.is_some()
201 && (pathname == prev_module.name)
202 {
203 prev_module.system_mapping_info.end_address = end_address;
206 prev_module.size = end_address - prev_module.start_address;
207 prev_module.permissions |= mm.perms;
208 continue;
209 } else if (start_address == prev_module.end_address())
210 && prev_module.is_executable()
211 && prev_module.name_is_path()
212 && ((offset == 0) || (offset == prev_module.end_address()))
213 && (mm.perms == MMPermissions::PRIVATE)
214 {
215 prev_module.size = end_address - prev_module.start_address;
220 continue;
221 }
222 }
223
224 if let Some(previous_modules) = infos.rchunks_exact_mut(2).next() {
228 let empty_page = if let Some(prev_module) = previous_modules.last() {
229 let prev_prev_module = previous_modules.first().unwrap();
230 prev_prev_module.name_is_path()
231 && (prev_prev_module.end_address() == prev_module.start_address)
232 && prev_module.is_empty_page()
233 && (prev_module.end_address() == start_address)
234 } else {
235 false
236 };
237
238 if empty_page {
239 let prev_prev_module = previous_modules.first_mut().unwrap();
240
241 if pathname == prev_prev_module.name {
242 prev_prev_module.system_mapping_info.end_address = end_address;
243 prev_prev_module.size = end_address - prev_prev_module.start_address;
244 prev_prev_module.permissions |= mm.perms;
245 infos.pop();
246 continue;
247 }
248 }
249 }
250
251 infos.push(MappingInfo {
252 start_address,
253 size: end_address - start_address,
254 system_mapping_info: SystemMappingInfo {
255 start_address,
256 end_address,
257 },
258 offset,
259 permissions: mm.perms,
260 name: pathname,
261 });
262 }
263 Ok(infos)
264 }
265
266 pub fn stack_has_pointer_to_mapping(&self, stack_copy: &[u8], sp_offset: usize) -> bool {
267 let low_addr = self.system_mapping_info.start_address;
273 let high_addr = self.system_mapping_info.end_address;
274 let mut offset = (sp_offset + size_of::<usize>() - 1) & !(size_of::<usize>() - 1);
275 while offset <= stack_copy.len() - size_of::<usize>() {
276 let addr = match std::mem::size_of::<usize>() {
277 4 => stack_copy[offset..]
278 .as_ref()
279 .read_u32::<NativeEndian>()
280 .map(|u| u as usize),
281 8 => stack_copy[offset..]
282 .as_ref()
283 .read_u64::<NativeEndian>()
284 .map(|u| u as usize),
285 x => panic!("Unexpected type width: {x}"),
286 };
287 if let Ok(addr) = addr {
288 if low_addr <= addr && addr <= high_addr {
289 return true;
290 }
291 offset += size_of::<usize>();
292 } else {
293 break;
294 }
295 }
296 false
297 }
298
299 fn so_name(&self, process_inspector: &ProcessInspector) -> Result<String> {
302 let path = Path::new(self.name.as_deref().unwrap_or_default());
303 super::module_reader::read_soname_from_file(process_inspector, path, self.offset)
304 .map_err(MapsReaderError::ReadSoNameFromFileFailed)
305 }
306
307 #[inline]
308 fn so_version(&self) -> Option<SoVersion> {
309 SoVersion::parse(self.name.as_deref()?)
310 }
311
312 pub fn get_mapping_effective_path_name_and_version(
313 &self,
314 process_inspector: &ProcessInspector,
315 soname: Option<String>,
316 ) -> Result<(PathBuf, String, Option<SoVersion>)> {
317 let mut file_path = PathBuf::from(self.name.clone().unwrap_or_default());
318
319 let Some(file_name) = soname.or_else(|| self.so_name(process_inspector).ok()) else {
326 let file_name = file_path
329 .file_name()
330 .map(|s| s.to_string_lossy().into_owned())
331 .unwrap_or_default();
332
333 return Ok((file_path, file_name, self.so_version()));
334 };
335
336 if self.is_executable() && self.offset != 0 {
337 file_path.push(&file_name);
344 } else {
345 file_path.set_file_name(&file_name);
347 }
348
349 Ok((file_path, file_name, self.so_version()))
350 }
351
352 pub fn is_contained_in(&self, user_mapping_list: &MappingList) -> bool {
353 for user in user_mapping_list {
354 if self.start_address >= user.mapping.start_address
357 && (self.start_address + self.size)
358 <= (user.mapping.start_address + user.mapping.size)
359 {
360 return true;
361 }
362 }
363 false
364 }
365
366 pub fn is_interesting(&self) -> bool {
367 self.name.is_some() &&
369 (self.offset == 0 || self.is_executable()) &&
372 self.size >= 4096
374 }
375
376 pub fn contains_address(&self, address: usize) -> bool {
377 self.system_mapping_info.start_address <= address
378 && address < self.system_mapping_info.end_address
379 }
380
381 pub fn is_executable(&self) -> bool {
382 self.permissions.contains(MMPermissions::EXECUTE)
383 }
384
385 pub fn is_readable(&self) -> bool {
386 self.permissions.contains(MMPermissions::READ)
387 }
388
389 pub fn is_writable(&self) -> bool {
390 self.permissions.contains(MMPermissions::WRITE)
391 }
392}
393
394#[cfg_attr(test, derive(Debug))]
403pub struct SoVersion {
404 pub major: u32,
408 pub minor: u32,
412 pub patch: u32,
416 pub prerelease: u32,
420}
421
422impl SoVersion {
423 fn parse(so_path: &OsStr) -> Option<Self> {
425 let filename = std::path::Path::new(so_path).file_name()?;
426
427 let filename = filename.to_string_lossy();
429
430 let (_, version) = filename.split_once(".so.")?;
431
432 let mut sov = Self {
433 major: 0,
434 minor: 0,
435 patch: 0,
436 prerelease: 0,
437 };
438
439 let comps = [
440 &mut sov.major,
441 &mut sov.minor,
442 &mut sov.patch,
443 &mut sov.prerelease,
444 ];
445
446 for (i, comp) in version.split('.').enumerate() {
447 if i <= 1 {
448 *comps[i] = comp.parse().unwrap_or_default();
449 } else if i >= 4 {
450 break;
451 } else {
452 if let Some(pend) = comp.find(|c: char| !c.is_ascii_digit()) {
455 if let Ok(patch) = comp[..pend].parse() {
456 *comps[i] = patch;
457 }
458
459 if i >= comps.len() - 1 {
460 break;
461 }
462 if let Some(pre) = comp.rfind(|c: char| !c.is_ascii_digit())
463 && let Ok(pre) = comp[pre + 1..].parse()
464 {
465 *comps[i + 1] = pre;
466 break;
467 }
468 } else {
469 *comps[i] = comp.parse().unwrap_or_default();
470 }
471 }
472 }
473
474 Some(sov)
475 }
476}
477
478#[cfg(test)]
479impl PartialEq<(u32, u32, u32, u32)> for SoVersion {
480 fn eq(&self, o: &(u32, u32, u32, u32)) -> bool {
481 self.major == o.0 && self.minor == o.1 && self.patch == o.2 && self.prerelease == o.3
482 }
483}
484
485#[cfg(test)]
486#[cfg(target_pointer_width = "64")] mod tests {
488 use super::*;
489 use procfs_core::FromRead;
490
491 fn get_mappings_for(map: &str, linux_gate_loc: u64) -> Vec<MappingInfo> {
492 MappingInfo::aggregate(
493 MemoryMaps::from_read(map.as_bytes()).expect("failed to read mapping info"),
494 Some(linux_gate_loc),
495 )
496 .unwrap_or_default()
497 }
498
499 const LINES: &str = "\
5005597483fc000-5597483fe000 r--p 00000000 00:31 4750073 /usr/bin/cat
5015597483fe000-559748402000 r-xp 00002000 00:31 4750073 /usr/bin/cat
502559748402000-559748404000 r--p 00006000 00:31 4750073 /usr/bin/cat
503559748404000-559748405000 r--p 00007000 00:31 4750073 /usr/bin/cat
504559748405000-559748406000 rw-p 00008000 00:31 4750073 /usr/bin/cat
505559749b0e000-559749b2f000 rw-p 00000000 00:00 0 [heap]
5067efd968d3000-7efd968f5000 rw-p 00000000 00:00 0
5077efd968f5000-7efd9694a000 r--p 00000000 00:31 5004638 /usr/lib/locale/en_US.utf8/LC_CTYPE
5087efd9694a000-7efd96bc2000 r--p 00000000 00:31 5004373 /usr/lib/locale/en_US.utf8/LC_COLLATE
5097efd96bc2000-7efd96bc4000 rw-p 00000000 00:00 0
5107efd96bc4000-7efd96bea000 r--p 00000000 00:31 4996104 /lib64/libc-2.32.so
5117efd96bea000-7efd96d39000 r-xp 00026000 00:31 4996104 /lib64/libc-2.32.so
5127efd96d39000-7efd96d85000 r--p 00175000 00:31 4996104 /lib64/libc-2.32.so
5137efd96d85000-7efd96d86000 ---p 001c1000 00:31 4996104 /lib64/libc-2.32.so
5147efd96d86000-7efd96d89000 r--p 001c1000 00:31 4996104 /lib64/libc-2.32.so
5157efd96d89000-7efd96d8c000 rw-p 001c4000 00:31 4996104 /lib64/libc-2.32.so
5167efd96d8c000-7efd96d92000 ---p 00000000 00:00 0
5177efd96da0000-7efd96da1000 r--p 00000000 00:31 5004379 /usr/lib/locale/en_US.utf8/LC_NUMERIC
5187efd96da1000-7efd96da2000 r--p 00000000 00:31 5004382 /usr/lib/locale/en_US.utf8/LC_TIME
5197efd96da2000-7efd96da3000 r--p 00000000 00:31 5004377 /usr/lib/locale/en_US.utf8/LC_MONETARY
5207efd96da3000-7efd96da4000 r--p 00000000 00:31 5004376 /usr/lib/locale/en_US.utf8/LC_MESSAGES/SYS_LC_MESSAGES
5217efd96da4000-7efd96da5000 r--p 00000000 00:31 5004380 /usr/lib/locale/en_US.utf8/LC_PAPER
5227efd96da5000-7efd96da6000 r--p 00000000 00:31 5004378 /usr/lib/locale/en_US.utf8/LC_NAME
5237efd96da6000-7efd96da7000 r--p 00000000 00:31 5004372 /usr/lib/locale/en_US.utf8/LC_ADDRESS
5247efd96da7000-7efd96da8000 r--p 00000000 00:31 5004381 /usr/lib/locale/en_US.utf8/LC_TELEPHONE
5257efd96da8000-7efd96da9000 r--p 00000000 00:31 5004375 /usr/lib/locale/en_US.utf8/LC_MEASUREMENT
5267efd96da9000-7efd96db0000 r--s 00000000 00:31 5004639 /usr/lib64/gconv/gconv-modules.cache
5277efd96db0000-7efd96db1000 r--p 00000000 00:31 5004374 /usr/lib/locale/en_US.utf8/LC_IDENTIFICATION
5287efd96db1000-7efd96db2000 r--p 00000000 00:31 4996100 /lib64/ld-2.32.so
5297efd96db2000-7efd96dd3000 r-xp 00001000 00:31 4996100 /lib64/ld-2.32.so
5307efd96dd3000-7efd96ddc000 r--p 00022000 00:31 4996100 /lib64/ld-2.32.so
5317efd96ddc000-7efd96ddd000 r--p 0002a000 00:31 4996100 /lib64/ld-2.32.so
5327efd96ddd000-7efd96ddf000 rw-p 0002b000 00:31 4996100 /lib64/ld-2.32.so
5337ffc6dfda000-7ffc6dffb000 rw-p 00000000 00:00 0 [stack]
5347ffc6e0f3000-7ffc6e0f7000 r--p 00000000 00:00 0 [vvar]
5357ffc6e0f7000-7ffc6e0f9000 r-xp 00000000 00:00 0 [vdso]
536ffffffffff600000-ffffffffff601000 --xp 00000000 00:00 0 [vsyscall]";
537 const LINUX_GATE_LOC: u64 = 0x7ffc6e0f7000;
538
539 fn get_all_mappings() -> Vec<MappingInfo> {
540 get_mappings_for(LINES, LINUX_GATE_LOC)
541 }
542
543 #[test]
544 fn test_merged() {
545 let mappings = get_mappings_for(
547 "\
5485597483fc000-5597483fe000 r--p 00000000 00:31 4750073 /usr/bin/cat
5495597483fe000-559748402000 r-xp 00002000 00:31 4750073 /usr/bin/cat
550559748402000-559748404000 r--p 00006000 00:31 4750073 /usr/bin/cat
551559748404000-559748405000 r--p 00007000 00:31 4750073 /usr/bin/cat
552559748405000-559748406000 rw-p 00008000 00:31 4750073 /usr/bin/cat
553559749b0e000-559749b2f000 rw-p 00000000 00:00 0 [heap]
5547efd968d3000-7efd968f5000 rw-p 00000000 00:00 0 ",
555 0x7ffc6e0f7000,
556 );
557
558 assert_eq!(mappings.len(), 3);
559 let cat_map = MappingInfo {
560 start_address: 0x5597483fc000,
561 size: 40960,
562 system_mapping_info: SystemMappingInfo {
563 start_address: 0x5597483fc000,
564 end_address: 0x559748406000,
565 },
566 offset: 0,
567 permissions: MMPermissions::READ
568 | MMPermissions::WRITE
569 | MMPermissions::EXECUTE
570 | MMPermissions::PRIVATE,
571 name: Some("/usr/bin/cat".into()),
572 };
573
574 assert_eq!(mappings[0], cat_map);
575
576 let heap_map = MappingInfo {
577 start_address: 0x559749b0e000,
578 size: 135168,
579 system_mapping_info: SystemMappingInfo {
580 start_address: 0x559749b0e000,
581 end_address: 0x559749b2f000,
582 },
583 offset: 0,
584 permissions: MMPermissions::READ | MMPermissions::WRITE | MMPermissions::PRIVATE,
585 name: Some("[heap]".into()),
586 };
587
588 assert_eq!(mappings[1], heap_map);
589
590 let empty_map = MappingInfo {
591 start_address: 0x7efd968d3000,
592 size: 139264,
593 system_mapping_info: SystemMappingInfo {
594 start_address: 0x7efd968d3000,
595 end_address: 0x7efd968f5000,
596 },
597 offset: 0,
598 permissions: MMPermissions::READ | MMPermissions::WRITE | MMPermissions::PRIVATE,
599 name: None,
600 };
601
602 assert_eq!(mappings[2], empty_map);
603 }
604
605 #[test]
606 fn test_linux_gate_parsing() {
607 let mappings = get_all_mappings();
608
609 let gate_map = MappingInfo {
610 start_address: 0x7ffc6e0f7000,
611 size: 8192,
612 system_mapping_info: SystemMappingInfo {
613 start_address: 0x7ffc6e0f7000,
614 end_address: 0x7ffc6e0f9000,
615 },
616 offset: 0,
617 permissions: MMPermissions::READ | MMPermissions::EXECUTE | MMPermissions::PRIVATE,
618 name: Some("linux-gate.so".into()),
619 };
620
621 assert_eq!(mappings[21], gate_map);
622 }
623
624 #[test]
625 fn test_reading_all() {
626 let mappings = get_all_mappings();
627
628 let found_items: Vec<Option<OsString>> = vec![
629 Some("/usr/bin/cat".into()),
630 Some("[heap]".into()),
631 None,
632 Some("/usr/lib/locale/en_US.utf8/LC_CTYPE".into()),
633 Some("/usr/lib/locale/en_US.utf8/LC_COLLATE".into()),
634 None,
635 Some("/lib64/libc-2.32.so".into()),
636 Some("/usr/lib/locale/en_US.utf8/LC_NUMERIC".into()),
641 Some("/usr/lib/locale/en_US.utf8/LC_TIME".into()),
642 Some("/usr/lib/locale/en_US.utf8/LC_MONETARY".into()),
643 Some("/usr/lib/locale/en_US.utf8/LC_MESSAGES/SYS_LC_MESSAGES".into()),
644 Some("/usr/lib/locale/en_US.utf8/LC_PAPER".into()),
645 Some("/usr/lib/locale/en_US.utf8/LC_NAME".into()),
646 Some("/usr/lib/locale/en_US.utf8/LC_ADDRESS".into()),
647 Some("/usr/lib/locale/en_US.utf8/LC_TELEPHONE".into()),
648 Some("/usr/lib/locale/en_US.utf8/LC_MEASUREMENT".into()),
649 Some("/usr/lib64/gconv/gconv-modules.cache".into()),
650 Some("/usr/lib/locale/en_US.utf8/LC_IDENTIFICATION".into()),
651 Some("/lib64/ld-2.32.so".into()),
652 Some("[stack]".into()),
653 Some("[vvar]".into()),
654 Some("linux-gate.so".into()),
656 Some("[vsyscall]".into()),
657 ];
658
659 assert_eq!(
660 mappings.iter().map(|x| x.name.clone()).collect::<Vec<_>>(),
661 found_items
662 );
663 }
664
665 #[test]
666 fn test_merged_reserved_mappings() {
667 let mappings = get_all_mappings();
668
669 let gate_map = MappingInfo {
670 start_address: 0x7efd96bc4000,
671 size: 1892352, system_mapping_info: SystemMappingInfo {
673 start_address: 0x7efd96bc4000,
674 end_address: 0x7efd96d8c000, },
676 offset: 0,
677 permissions: MMPermissions::READ
678 | MMPermissions::WRITE
679 | MMPermissions::EXECUTE
680 | MMPermissions::PRIVATE,
681 name: Some("/lib64/libc-2.32.so".into()),
682 };
683
684 assert_eq!(mappings[6], gate_map);
685 }
686
687 #[test]
688 fn test_merged_reserved_mappings_within_module() {
689 let mappings = get_mappings_for(
690 "\
6919b4a0000-9b931000 r--p 00000000 08:12 393449 /data/app/org.mozilla.firefox-1/lib/x86/libxul.so
6929b931000-9bcae000 ---p 00000000 00:00 0
6939bcae000-a116b000 r-xp 00490000 08:12 393449 /data/app/org.mozilla.firefox-1/lib/x86/libxul.so
694a116b000-a4562000 r--p 0594d000 08:12 393449 /data/app/org.mozilla.firefox-1/lib/x86/libxul.so
695a4562000-a4563000 ---p 00000000 00:00 0
696a4563000-a4840000 r--p 08d44000 08:12 393449 /data/app/org.mozilla.firefox-1/lib/x86/libxul.so
697a4840000-a4873000 rw-p 09021000 08:12 393449 /data/app/org.mozilla.firefox-1/lib/x86/libxul.so",
698 0xa4876000,
699 );
700
701 let gate_map = MappingInfo {
702 start_address: 0x9b4a0000,
703 size: 155004928, system_mapping_info: SystemMappingInfo {
705 start_address: 0x9b4a0000,
706 end_address: 0xa4873000,
707 },
708 offset: 0,
709 permissions: MMPermissions::READ
710 | MMPermissions::WRITE
711 | MMPermissions::EXECUTE
712 | MMPermissions::PRIVATE,
713 name: Some("/data/app/org.mozilla.firefox-1/lib/x86/libxul.so".into()),
714 };
715
716 assert_eq!(mappings[0], gate_map);
717 }
718
719 #[test]
720 fn test_get_mapping_effective_name() {
721 let mappings = get_mappings_for(
722 "\
7237f0b97b6f000-7f0b97b70000 r--p 00000000 00:3e 27136458 /home/martin/Documents/mozilla/devel/mozilla-central/obj/widget/gtk/mozgtk/gtk3/libmozgtk.so
7247f0b97b70000-7f0b97b71000 r-xp 00000000 00:3e 27136458 /home/martin/Documents/mozilla/devel/mozilla-central/obj/widget/gtk/mozgtk/gtk3/libmozgtk.so
7257f0b97b71000-7f0b97b73000 r--p 00000000 00:3e 27136458 /home/martin/Documents/mozilla/devel/mozilla-central/obj/widget/gtk/mozgtk/gtk3/libmozgtk.so
7267f0b97b73000-7f0b97b74000 rw-p 00001000 00:3e 27136458 /home/martin/Documents/mozilla/devel/mozilla-central/obj/widget/gtk/mozgtk/gtk3/libmozgtk.so",
727 0x7ffe091bf000,
728 );
729 assert_eq!(mappings.len(), 1);
730
731 let process_inspector = ProcessInspector::local(0);
732
733 let (file_path, file_name, _version) = mappings[0]
734 .get_mapping_effective_path_name_and_version(&process_inspector, None)
735 .expect("Couldn't get effective name for mapping");
736 assert_eq!(file_name, "libmozgtk.so");
737 assert_eq!(
738 file_path,
739 PathBuf::from(
740 "/home/martin/Documents/mozilla/devel/mozilla-central/obj/widget/gtk/mozgtk/gtk3/libmozgtk.so"
741 )
742 );
743 }
744
745 #[test]
746 fn test_elf_file_so_version() {
747 #[rustfmt::skip]
748 let test_cases = [
749 ("/usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.32", (6, 0, 32, 0)),
750 ("/usr/lib/x86_64-linux-gnu/libcairo-gobject.so.2.11800.0", (2, 11800, 0, 0)),
751 ("/usr/lib/x86_64-linux-gnu/libm.so.6", (6, 0, 0, 0)),
752 ("/usr/lib/x86_64-linux-gnu/libpthread.so.0", (0, 0, 0, 0)),
753 ("/usr/lib/x86_64-linux-gnu/libgmodule-2.0.so.0.7800.0", (0, 7800, 0, 0)),
754 ("/usr/lib/x86_64-linux-gnu/libabsl_time_zone.so.20220623.0.0", (20220623, 0, 0, 0)),
755 ("/usr/lib/x86_64-linux-gnu/libdbus-1.so.3.34.2rc5", (3, 34, 2, 5)),
756 ("/usr/lib/x86_64-linux-gnu/libdbus-1.so.3.34.2rc", (3, 34, 2, 0)),
757 ("/usr/lib/x86_64-linux-gnu/libdbus-1.so.3.34.rc5", (3, 34, 0, 5)),
758 ("/usr/lib/x86_64-linux-gnu/libtoto.so.AAA", (0, 0, 0, 0)),
759 ("/usr/lib/x86_64-linux-gnu/libsemver-1.so.1.2.alpha.1", (1, 2, 0, 1)),
760 ("/usr/lib/x86_64-linux-gnu/libboop.so.1.2.3.4.5", (1, 2, 3, 4)),
761 ("/usr/lib/x86_64-linux-gnu/libboop.so.1.2.3pre4.5", (1, 2, 3, 4)),
762 ];
763
764 assert!(SoVersion::parse(OsStr::new("/home/alex/bin/firefox/libmozsandbox.so")).is_none());
765
766 for (path, expected) in test_cases {
767 let actual = SoVersion::parse(OsStr::new(path)).unwrap();
768 assert_eq!(actual, expected);
769 }
770 }
771
772 #[test]
773 fn test_whitespaces_in_name() {
774 let mappings = get_mappings_for(
775 "\
77610000000-20000000 r--p 00000000 00:3e 27136458 libmoz gtk.so
77730000000-40000000 r--p 00000000 00:3e 27136458 \"libmoz gtk.so (deleted)\"
77830000000-40000000 r--p 00000000 00:3e 27136458 ",
779 0x7ffe091bf000,
780 );
781
782 assert_eq!(mappings.len(), 3);
783 assert_eq!(mappings[0].name, Some("libmoz gtk.so".into()));
784 assert_eq!(
785 mappings[1].name,
786 Some("\"libmoz gtk.so (deleted)\"".into())
787 );
788 assert_eq!(mappings[2].name, None);
789 }
790}