steroid/mapping.rs
1//! The mapping module provides convenient data types to manipulate the memory mapping of a process.
2//!
3//! ## Memory mapping
4//!
5//! The mapping of a process' memory can be visualized with the file `/proc/{pid}/maps` that lies in
6//! the [`procfs(5)`] filesystem of Linux. This file looks like the following example obtained
7//! running `/usr/bin/cat`:
8//!
9//! ```text
10//! 5652a293f000-5652a2941000 r--p 00000000 fe:00 10486180 /usr/bin/cat
11//! 5652a2941000-5652a2945000 r-xp 00002000 fe:00 10486180 /usr/bin/cat
12//! 5652a2945000-5652a2947000 r--p 00006000 fe:00 10486180 /usr/bin/cat
13//! 5652a2947000-5652a2948000 r--p 00007000 fe:00 10486180 /usr/bin/cat
14//! 5652a2948000-5652a2949000 rw-p 00008000 fe:00 10486180 /usr/bin/cat
15//! 5652a42c6000-5652a42e7000 rw-p 00000000 00:00 0 [heap]
16//! 7fb5b9a92000-7fb5b9d7d000 r--p 00000000 fe:00 10496617 /usr/lib/locale/locale-archive
17//! 7fb5b9d7d000-7fb5b9d80000 rw-p 00000000 00:00 0
18//! 7fb5b9d80000-7fb5b9da2000 r--p 00000000 fe:00 10495643 /usr/lib/libc.so.6
19//! 7fb5b9da2000-7fb5b9efc000 r-xp 00022000 fe:00 10495643 /usr/lib/libc.so.6
20//! 7fb5b9efc000-7fb5b9f54000 r--p 0017c000 fe:00 10495643 /usr/lib/libc.so.6
21//! 7fb5b9f54000-7fb5b9f58000 r--p 001d4000 fe:00 10495643 /usr/lib/libc.so.6
22//! 7fb5b9f58000-7fb5b9f5a000 rw-p 001d8000 fe:00 10495643 /usr/lib/libc.so.6
23//! 7fb5b9f5a000-7fb5b9f69000 rw-p 00000000 00:00 0
24//! 7fb5b9f81000-7fb5b9fa3000 rw-p 00000000 00:00 0
25//! 7fb5b9fa3000-7fb5b9fa4000 r--p 00000000 fe:00 10495632 /usr/lib/ld-linux-x86-64.so.2
26//! 7fb5b9fa4000-7fb5b9fca000 r-xp 00001000 fe:00 10495632 /usr/lib/ld-linux-x86-64.so.2
27//! 7fb5b9fca000-7fb5b9fd4000 r--p 00027000 fe:00 10495632 /usr/lib/ld-linux-x86-64.so.2
28//! 7fb5b9fd4000-7fb5b9fd6000 r--p 00031000 fe:00 10495632 /usr/lib/ld-linux-x86-64.so.2
29//! 7fb5b9fd6000-7fb5b9fd8000 rw-p 00033000 fe:00 10495632 /usr/lib/ld-linux-x86-64.so.2
30//! 7ffd2bb97000-7ffd2bbb9000 rw-p 00000000 00:00 0 [stack]
31//! 7ffd2bbe0000-7ffd2bbe4000 r--p 00000000 00:00 0 [vvar]
32//! 7ffd2bbe4000-7ffd2bbe6000 r-xp 00000000 00:00 0 [vdso]
33//! ffffffffff600000-ffffffffff601000 --xp 00000000 00:00 0 [vsyscall]
34//! ```
35//!
36//! The values of the first column correspond to the start and end addresses of the memory mapping,
37//! which means the address range at which the memory is accessible in the process' address
38//! space.
39//!
40//! The second column is the permissions of the memory mapping. For instance, one may not
41//! want to make the program's code writable however it must be executable. It may be
42//! private (the "p" in the permissions) or shared. If a mapping is private, it is copy-on-write and
43//! modifies only the process' mapping of the memory. If it is shared, all the processes that map
44//! this piece of memory will be affected.
45//!
46//! The third column is the offset in the source file at which the mapping starts.
47//!
48//! The fourth column is the device in which the file lives. It is given in the form of
49//! `major:minor` pair of IDs.
50//!
51//! The fifth column is the inode of the file in the filesystem.
52//!
53//! Finally the last column is what is mapped. It may be a file (represented by its path), part of
54//! the heap (represented by `[heap]`), the stack of one of the process' threads (represented by
55//! `[stack:{tid}]` or `[stack]` for the main thread), the vdso (represented by `[vdso]`), the vvar
56//! (represented by `[vvar]`), the legacy vsyscall (represented by `[vsyscall]`) or an anonymous
57//! mapping. An anonymous mapping is a mapping of some memory allocated by the process to store
58//! data.
59//!
60//! ## Convenient data types
61//!
62//! This module provides the user with a convenient API to manipulate this data. The structure
63//! [`MemoryMapping`] gives a high-level representation of the memory mapping of the whole
64//! process. It is a collection of [`Mapping`] that each represent one line of the maps file. It is
65//! possible to get a [`MemoryMapping`] using the function [`memory_mapping`]:
66//!
67//! ```
68//! # use anyhow::Error;
69//! # use steroid::process::spawn_process;
70//! # use steroid::run::Executing;
71//! # use steroid::mapping::memory_mapping;
72//! # let mut process = spawn_process("/bin/ls", ["-l"]).unwrap();
73//! # let mut ctrl = process.wait()?.assume_alive()?;
74//! let memory = memory_mapping(ctrl.process())?;
75//! # Ok::<(), Error>(())
76//! ```
77//!
78//! It is possible to get useful information using the API of types such as [`Mapping`]:
79//!
80//! ```
81//! # use std::path::PathBuf;
82//! # use anyhow::Error;
83//! # use steroid::process::spawn_process;
84//! # use steroid::run::{Executing};
85//! # use steroid::breakpoint::{breakpoint, Mode};
86//! # use steroid::mapping::{Type, memory_mapping};
87//! # let mut path_buf = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
88//! # path_buf.push("resources/test/say_hello_no_pie");
89//! # let process = spawn_process::<_, _, &str>(path_buf, vec![])?;
90//! # let mut ctrl_start = process.wait()?.assume_alive()?;
91//! # breakpoint(&mut ctrl_start, 0x40113c, Mode::OneShot)?;
92//! # let process = ctrl_start.resume()?;
93//! # let mut ctrl = process.wait()?.assume_alive()?;
94//! # let memory = memory_mapping(ctrl.process())?;
95//! let c_lib_range = memory
96//! .find(|mapping| match &mapping.mapping_type {
97//! Type::File { path, .. } => {
98//! let filename = path.to_str().unwrap();
99//! mapping.permissions.executable && filename.contains("libc.so")
100//! }
101//! _ => false,
102//! })
103//! .map(|mapping| (mapping.start, mapping.end))
104//! .unwrap();
105//! # Ok::<(), Error>(())
106//! ```
107//!
108//! [`procfs(5)`]: https://man7.org/linux/man-pages/man5/proc.5.html
109use std::fmt::Result as FmtResult;
110use std::fmt::{Display, Formatter};
111use std::fs::File;
112use std::io::{BufRead, BufReader};
113use std::io::{Error as IOError, Lines};
114use std::path::PathBuf;
115use std::str::FromStr;
116
117use nix::libc::{PROT_EXEC, PROT_READ, PROT_WRITE};
118
119use crate::error::{CouldNotParseMappingFile, MappingError};
120use crate::process::{Pid, TargetProcess};
121
122/// Base generic type to express memory mapping permissions.
123#[derive(Clone, Debug, PartialEq, Eq)]
124pub struct PermissionsType<T> {
125 pub readable: T,
126 pub writable: T,
127 pub executable: T,
128 /// A private mapping means that it is copy-on-write, the mapped file is not modified by the
129 /// process, only its copy mapped in memory. A "non-private" mapping is a shared mapping. See
130 /// [`mmap(2)`].
131 ///
132 /// [`mmap(2)`]: https://man7.org/linux/man-pages/man2/mmap.2.html
133 pub private: T,
134}
135
136/// Permissions of a memory mapping.
137pub type Permissions = PermissionsType<bool>;
138
139impl FromStr for Permissions {
140 type Err = CouldNotParseMappingFile;
141
142 fn from_str(s: &str) -> Result<Self, Self::Err> {
143 if s.len() == 4 {
144 let chars: Vec<char> = s.chars().collect();
145
146 let readable = chars[0] == 'r';
147 let writable = chars[1] == 'w';
148 let executable = chars[2] == 'x';
149 let private = chars[3] == 'p';
150
151 Ok(Self {
152 readable,
153 writable,
154 executable,
155 private,
156 })
157 } else {
158 Err(Self::Err::Permissions)
159 }
160 }
161}
162
163impl Display for Permissions {
164 fn fmt(&self, fmt: &mut Formatter<'_>) -> FmtResult {
165 let mut positives = Vec::with_capacity(4);
166 if self.readable {
167 positives.push("readable");
168 }
169 if self.writable {
170 positives.push("writable");
171 }
172 if self.executable {
173 positives.push("executable");
174 }
175 if self.private {
176 positives.push("private");
177 }
178
179 write!(fmt, "{}", positives.join(", "))
180 }
181}
182
183impl Permissions {
184 #[must_use]
185 pub const fn to_i32(&self) -> i32 {
186 let mut res = 0;
187 if self.readable {
188 res |= PROT_READ;
189 }
190 if self.writable {
191 res |= PROT_WRITE;
192 }
193 if self.executable {
194 res |= PROT_EXEC;
195 }
196
197 res
198 }
199
200 #[must_use]
201 pub fn matches(&self, matcher: &PermissionMatcher) -> bool {
202 matcher.readable.map_or(true, |v| self.readable == v)
203 && matcher.writable.map_or(true, |v| self.writable == v)
204 && matcher.executable.map_or(true, |v| self.executable == v)
205 && matcher.private.map_or(true, |v| self.private == v)
206 }
207}
208
209/// Type used in errors to represent subsets of permissions that were expected.
210///
211/// For instance, if a mapping was expected to be readable but not writable, the corresponding
212/// matcher would be:
213///
214/// ```
215/// # use steroid::mapping::PermissionMatcher;
216/// #
217/// PermissionMatcher {
218/// readable: Some(true),
219/// writable: Some(false),
220/// executable: None,
221/// private: None,
222/// }
223/// # ; ()
224/// ```
225pub type PermissionMatcher = PermissionsType<Option<bool>>;
226
227impl Display for PermissionMatcher {
228 fn fmt(&self, fmt: &mut Formatter<'_>) -> FmtResult {
229 fn dispatch<'a>(
230 positive: &mut Vec<&'a str>,
231 negative: &mut Vec<&'a str>,
232 value: Option<bool>,
233 disp: &'a str,
234 ) {
235 if value == Some(true) {
236 positive.push(disp);
237 } else if value == Some(false) {
238 negative.push(disp);
239 }
240 }
241
242 let mut positive = Vec::with_capacity(4);
243 let mut negative = Vec::with_capacity(4);
244
245 dispatch(&mut positive, &mut negative, self.readable, "readable");
246 dispatch(&mut positive, &mut negative, self.writable, "writable");
247 dispatch(&mut positive, &mut negative, self.executable, "executable");
248 dispatch(&mut positive, &mut negative, self.private, "private");
249
250 let positive_msg = positive.join(", ");
251 let negative_msg = if negative.is_empty() {
252 String::new()
253 } else {
254 format!(" but not {}", negative.join(", "))
255 };
256
257 write!(fmt, "{positive_msg}{negative_msg}")
258 }
259}
260
261/// Device on which the mapped file is stored, in major:minor form.
262#[derive(Clone, Debug, PartialEq, Eq)]
263pub struct Device {
264 pub major: u8,
265 pub minor: u8,
266}
267
268impl FromStr for Device {
269 type Err = CouldNotParseMappingFile;
270
271 fn from_str(s: &str) -> Result<Self, Self::Err> {
272 let (major, minor) =
273 s.split_once(':')
274 .ok_or(Self::Err::Device)
275 .and_then(|(maj, min)| {
276 let major = u8::from_str_radix(maj, 16).or(Err(Self::Err::Device))?;
277 let minor = u8::from_str_radix(min, 16).or(Err(Self::Err::Device))?;
278
279 Ok((major, minor))
280 })?;
281
282 Ok(Self { major, minor })
283 }
284}
285
286#[derive(Debug, PartialEq)]
287enum Pathname {
288 Stack { tid: usize },
289 Vdso,
290 Vvar,
291 Vsyscall,
292 Heap,
293 File(PathBuf),
294 Anonymous,
295}
296
297impl FromStr for Pathname {
298 type Err = CouldNotParseMappingFile;
299
300 fn from_str(s: &str) -> Result<Self, Self::Err> {
301 Ok(if s.starts_with("[stack") {
302 if !s.ends_with(']') {
303 Self::File(PathBuf::from(s))
304 } else if s.len() == 7 {
305 Self::Stack { tid: 0 }
306 } else {
307 let slice = &s[7..s.len() - 1];
308 let tid = slice.parse::<usize>().or(Err(Self::Err::StackTID))?;
309 Self::Stack { tid }
310 }
311 } else {
312 match s {
313 "[stack]" => Self::Stack { tid: 0 },
314 "" => Self::Anonymous,
315 "[vdso]" => Self::Vdso,
316 "[vvar]" => Self::Vvar,
317 "[vsyscall]" => Self::Vsyscall,
318 "[heap]" => Self::Heap,
319 s => Self::File(PathBuf::from(s)),
320 }
321 })
322 }
323}
324
325/// Type of a memory mapping.
326#[derive(Debug, PartialEq, Eq)]
327#[allow(clippy::module_name_repetitions)]
328pub enum Type {
329 /// Anonymous mapping of memory, typically performed by the process via [`mmap(2)`]
330 ///
331 /// [`mmap(2)`]: https://man7.org/linux/man-pages/man2/mmap.2.html
332 Anonymous,
333 /// Mapping of the [`vdso(7)`]
334 ///
335 /// [`vdso(7)`]: https://man7.org/linux/man-pages/man7/vdso.7.html
336 Vdso,
337 /// Mirror mapping of kernel variables required by system calls exported by the kernel. See
338 /// [`this article`] for further information
339 ///
340 /// [`this article`]: https://lwn.net/Articles/615809/
341 Vvar,
342 /// Defunct mapping of some kernel code for specific system calls that did not require elevated
343 /// privileges
344 Vsyscall,
345 /// Mapping of a part of the heap of the process
346 Heap,
347 /// Mapping of a stack of the process, the TID is the thread ID in the process
348 Stack { tid: usize },
349 /// Mapping of a file in the process' address space
350 File {
351 device: Device,
352 inode: usize,
353 offset: usize,
354 path: PathBuf,
355 },
356}
357
358impl Type {
359 #[allow(clippy::missing_const_for_fn)]
360 fn from_pathname(pathname: Pathname, device: Device, inode: usize, offset: usize) -> Self {
361 match pathname {
362 Pathname::Anonymous => Self::Anonymous,
363 Pathname::Heap => Self::Heap,
364 Pathname::Stack { tid } => Self::Stack { tid },
365 Pathname::Vdso => Self::Vdso,
366 Pathname::Vvar => Self::Vvar,
367 Pathname::Vsyscall => Self::Vsyscall,
368 Pathname::File(buf) => Self::File {
369 device,
370 inode,
371 offset,
372 path: buf,
373 },
374 }
375 }
376}
377
378/// One memory mapping in a process. A mapping has a start and end address in the process' address
379/// space, it has permissions, so that the user cannot mess with the memory. The [`Type`] of a
380/// mapping corresponds to the type of mapping, whether it is one of the process' stacks, heap, the
381/// vdso, a file (like a library) or even an anonymous mapping.
382#[derive(Debug, PartialEq, Eq)]
383pub struct Mapping {
384 pub start: usize,
385 pub end: usize,
386 pub permissions: Permissions,
387 pub mapping_type: Type,
388}
389
390fn mapping_file_lines(pid: Pid) -> Result<Lines<BufReader<File>>, IOError> {
391 let filename = format!("/proc/{pid}/maps");
392 let file = BufReader::new(File::open(filename)?);
393
394 Ok(file.lines())
395}
396
397impl FromStr for Mapping {
398 type Err = CouldNotParseMappingFile;
399
400 fn from_str(line: &str) -> Result<Self, Self::Err> {
401 let mut words = line.split_whitespace();
402 let range = Self::Err::from_option(words.next())?;
403 let permissions = Permissions::from_str(Self::Err::from_option(words.next())?)?;
404 let offset = usize::from_str_radix(Self::Err::from_option(words.next())?, 16)
405 .or(Err(Self::Err::Offset))?;
406 let device = Device::from_str(Self::Err::from_option(words.next())?)?;
407 let inode = Self::Err::from_option(words.next())?
408 .parse::<usize>()
409 .or(Err(Self::Err::Inode))?;
410
411 let pathname = Pathname::from_str(words.collect::<Vec<_>>().join(" ").as_str())?;
412
413 let (start, end) = range
414 .split_once('-')
415 .ok_or(Self::Err::AddressRange)
416 .and_then(|(s, e)| {
417 let start = usize::from_str_radix(s, 16).or(Err(Self::Err::StartAddress))?;
418 let end = usize::from_str_radix(e, 16).or(Err(Self::Err::StartAddress))?;
419 Ok((start, end))
420 })?;
421
422 Ok(Self {
423 start,
424 end,
425 permissions,
426 mapping_type: Type::from_pathname(pathname, device, inode, offset),
427 })
428 }
429}
430
431impl Mapping {
432 /// Check if the mapping is an anonymous mapping of memory, see [`mmap(2)`].
433 ///
434 /// [`mmap(2)`]: https://man7.org/linux/man-pages/man2/mmap.2.html
435 #[must_use]
436 pub const fn is_anonymous(&self) -> bool {
437 matches!(self.mapping_type, Type::Anonymous)
438 }
439
440 /// Check if the mapping corresponds to the VDSO, see [`vdso(7)`].
441 ///
442 /// [`vdso(7)`]: https://man7.org/linux/man-pages/man7/vdso.7.html
443 #[must_use]
444 pub const fn is_vdso(&self) -> bool {
445 matches!(self.mapping_type, Type::Vdso)
446 }
447
448 /// Check if the mapping is the vvar.
449 #[must_use]
450 pub const fn is_vvar(&self) -> bool {
451 matches!(self.mapping_type, Type::Vvar)
452 }
453
454 /// Check if the mapping is the vsyscall.
455 #[must_use]
456 pub const fn is_vsyscall(&self) -> bool {
457 matches!(self.mapping_type, Type::Vsyscall)
458 }
459
460 /// Check if the mapping is part of the process heap.
461 #[must_use]
462 pub const fn is_heap(&self) -> bool {
463 matches!(self.mapping_type, Type::Heap)
464 }
465
466 /// Check if the mapping is a stack of the process.
467 #[must_use]
468 pub const fn is_stack(&self) -> bool {
469 matches!(self.mapping_type, Type::Stack { .. })
470 }
471
472 /// Check if the mapping is a mapping of a file.
473 #[must_use]
474 pub const fn is_file(&self) -> bool {
475 matches!(self.mapping_type, Type::File { .. })
476 }
477}
478
479/// Convienient structure over a [`Vec`] of [`Mapping`] that represents the whole memory mapping of a
480/// process. This structure provides the user with some convienient methods to manipulate the memory
481/// mapping more easily.
482#[allow(clippy::module_name_repetitions)]
483pub struct MemoryMapping(pub Vec<Mapping>);
484
485impl MemoryMapping {
486 /// Look for a mapping in the memory mapping that accepts the given predicate.
487 ///
488 /// ```
489 /// # use anyhow::Error;
490 /// # use steroid::process::spawn_process;
491 /// # use steroid::run::Executing;
492 /// # use steroid::mapping::memory_mapping;
493 /// # let mut process = spawn_process("/bin/ls", ["-l"])?;
494 /// # let mut ctrl = process.wait()?.assume_alive()?;
495 /// let memory = memory_mapping(ctrl.process())?;
496 /// let mapping = memory.find(|m| m.is_stack());
497 /// assert!(mapping.is_some());
498 /// # Ok::<(), Error>(())
499 /// ```
500 pub fn find<P>(&self, predicate: P) -> Option<&Mapping>
501 where
502 P: Fn(&Mapping) -> bool,
503 {
504 self.0.iter().find(|mapping| predicate(mapping))
505 }
506}
507
508/// Get the memory mapping of the given process. The memory mapping will tell the user what pages
509/// are mapped, their permissions, the file they may be mapping, etc. This function is relatively
510/// safe and only returns an error when the memory map file `/proc/{pid}/maps` is not accessible or
511/// could not be parsed correctly, which in theory should never happen.
512///
513/// # Errors
514///
515/// If any error occurs during the parsing of the maps file, the function will fail and return a
516/// [`MappingError`].
517#[allow(clippy::module_name_repetitions)]
518pub fn memory_mapping(process: &TargetProcess) -> Result<MemoryMapping, MappingError> {
519 let vec = mapping_file_lines(process.pid())?
520 .map(|line| Mapping::from_str(&line?).map_err(MappingError::from))
521 .collect::<Result<Vec<Mapping>, _>>()?;
522 Ok(MemoryMapping(vec))
523}
524
525#[cfg(test)]
526mod tests {
527 use super::*;
528
529 #[test]
530 fn parse_permissions() {
531 let perm = Permissions::from_str("");
532 assert_eq!(perm, Err(CouldNotParseMappingFile::Permissions));
533
534 let perm = Permissions::from_str("r---");
535 assert_eq!(
536 perm,
537 Ok(Permissions {
538 readable: true,
539 writable: false,
540 executable: false,
541 private: false
542 })
543 );
544
545 let perm = Permissions::from_str("-wx-");
546 assert_eq!(
547 perm,
548 Ok(Permissions {
549 readable: false,
550 writable: true,
551 executable: true,
552 private: false
553 })
554 );
555
556 let perm = Permissions::from_str("r-x-");
557 assert_eq!(
558 perm,
559 Ok(Permissions {
560 readable: true,
561 writable: false,
562 executable: true,
563 private: false
564 })
565 );
566
567 let perm = Permissions::from_str("r-xp");
568 assert_eq!(
569 perm,
570 Ok(Permissions {
571 readable: true,
572 writable: false,
573 executable: true,
574 private: true
575 })
576 );
577 }
578
579 #[test]
580 fn parse_pathname() {
581 let stack = Pathname::from_str("[stack]");
582 assert_eq!(stack, Ok(Pathname::Stack { tid: 0 }));
583 let stack = Pathname::from_str("[stack");
584 assert_eq!(stack, Ok(Pathname::File(PathBuf::from("[stack"))));
585 let stack = Pathname::from_str("[stack:890]");
586 assert_eq!(stack, Ok(Pathname::Stack { tid: 890 }));
587
588 let vdso = Pathname::from_str("[vdso]");
589 assert_eq!(vdso, Ok(Pathname::Vdso));
590
591 let heap = Pathname::from_str("[heap]");
592 assert_eq!(heap, Ok(Pathname::Heap));
593
594 let anon = Pathname::from_str("");
595 assert_eq!(anon, Ok(Pathname::Anonymous));
596
597 let file = Pathname::from_str("/lib/somelib.so");
598 assert_eq!(file, Ok(Pathname::File(PathBuf::from("/lib/somelib.so"))));
599 }
600
601 #[test]
602 fn parse_device() {
603 let device = Device::from_str("fd:04");
604 assert_eq!(
605 device,
606 Ok(Device {
607 major: 0xfd,
608 minor: 0x04
609 })
610 );
611 }
612
613 #[test]
614 fn parse_line() {
615 let line = "7f7663254000-7f7663255000 r--p 00000000 fe:00 10495632 /usr/lib/ld-linux-x86-64.so.2";
616 let res = Mapping::from_str(line);
617 assert_eq!(
618 res,
619 Ok(Mapping {
620 start: 0x7f76_6325_4000,
621 end: 0x7f76_6325_5000,
622 permissions: Permissions {
623 readable: true,
624 writable: false,
625 executable: false,
626 private: true
627 },
628 mapping_type: Type::File {
629 offset: 0,
630 device: Device {
631 major: 0xfe,
632 minor: 0
633 },
634 inode: 10_495_632,
635 path: PathBuf::from("/usr/lib/ld-linux-x86-64.so.2"),
636 }
637 })
638 );
639 }
640}