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}