use std::fmt::Result as FmtResult;
use std::fmt::{Display, Formatter};
use std::fs::File;
use std::io::{BufRead, BufReader};
use std::io::{Error as IOError, Lines};
use std::path::PathBuf;
use std::str::FromStr;
use nix::libc::{PROT_EXEC, PROT_READ, PROT_WRITE};
use crate::error::{CouldNotParseMappingFile, MappingError};
use crate::process::{Pid, TargetProcess};
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct PermissionsType<T> {
pub readable: T,
pub writable: T,
pub executable: T,
pub private: T,
}
pub type Permissions = PermissionsType<bool>;
impl FromStr for Permissions {
type Err = CouldNotParseMappingFile;
fn from_str(s: &str) -> Result<Self, Self::Err> {
if s.len() == 4 {
let chars: Vec<char> = s.chars().collect();
let readable = chars[0] == 'r';
let writable = chars[1] == 'w';
let executable = chars[2] == 'x';
let private = chars[3] == 'p';
Ok(Self {
readable,
writable,
executable,
private,
})
} else {
Err(Self::Err::Permissions)
}
}
}
impl Display for Permissions {
fn fmt(&self, fmt: &mut Formatter<'_>) -> FmtResult {
let mut positives = Vec::with_capacity(4);
if self.readable {
positives.push("readable");
}
if self.writable {
positives.push("writable");
}
if self.executable {
positives.push("executable");
}
if self.private {
positives.push("private");
}
write!(fmt, "{}", positives.join(", "))
}
}
impl Permissions {
#[must_use]
pub const fn to_i32(&self) -> i32 {
let mut res = 0;
if self.readable {
res |= PROT_READ;
}
if self.writable {
res |= PROT_WRITE;
}
if self.executable {
res |= PROT_EXEC;
}
res
}
#[must_use]
pub fn matches(&self, matcher: &PermissionMatcher) -> bool {
matcher.readable.map_or(true, |v| self.readable == v)
&& matcher.writable.map_or(true, |v| self.writable == v)
&& matcher.executable.map_or(true, |v| self.executable == v)
&& matcher.private.map_or(true, |v| self.private == v)
}
}
pub type PermissionMatcher = PermissionsType<Option<bool>>;
impl Display for PermissionMatcher {
fn fmt(&self, fmt: &mut Formatter<'_>) -> FmtResult {
fn dispatch<'a>(
positive: &mut Vec<&'a str>,
negative: &mut Vec<&'a str>,
value: Option<bool>,
disp: &'a str,
) {
if value == Some(true) {
positive.push(disp);
} else if value == Some(false) {
negative.push(disp);
}
}
let mut positive = Vec::with_capacity(4);
let mut negative = Vec::with_capacity(4);
dispatch(&mut positive, &mut negative, self.readable, "readable");
dispatch(&mut positive, &mut negative, self.writable, "writable");
dispatch(&mut positive, &mut negative, self.executable, "executable");
dispatch(&mut positive, &mut negative, self.private, "private");
let positive_msg = positive.join(", ");
let negative_msg = if negative.is_empty() {
String::new()
} else {
format!(" but not {}", negative.join(", "))
};
write!(fmt, "{positive_msg}{negative_msg}")
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Device {
pub major: u8,
pub minor: u8,
}
impl FromStr for Device {
type Err = CouldNotParseMappingFile;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let (major, minor) =
s.split_once(':')
.ok_or(Self::Err::Device)
.and_then(|(maj, min)| {
let major = u8::from_str_radix(maj, 16).or(Err(Self::Err::Device))?;
let minor = u8::from_str_radix(min, 16).or(Err(Self::Err::Device))?;
Ok((major, minor))
})?;
Ok(Self { major, minor })
}
}
#[derive(Debug, PartialEq)]
enum Pathname {
Stack { tid: usize },
Vdso,
Vvar,
Vsyscall,
Heap,
File(PathBuf),
Anonymous,
}
impl FromStr for Pathname {
type Err = CouldNotParseMappingFile;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(if s.starts_with("[stack") {
if !s.ends_with(']') {
Self::File(PathBuf::from(s))
} else if s.len() == 7 {
Self::Stack { tid: 0 }
} else {
let slice = &s[7..s.len() - 1];
let tid = slice.parse::<usize>().or(Err(Self::Err::StackTID))?;
Self::Stack { tid }
}
} else {
match s {
"[stack]" => Self::Stack { tid: 0 },
"" => Self::Anonymous,
"[vdso]" => Self::Vdso,
"[vvar]" => Self::Vvar,
"[vsyscall]" => Self::Vsyscall,
"[heap]" => Self::Heap,
s => Self::File(PathBuf::from(s)),
}
})
}
}
#[derive(Debug, PartialEq, Eq)]
#[allow(clippy::module_name_repetitions)]
pub enum Type {
Anonymous,
Vdso,
Vvar,
Vsyscall,
Heap,
Stack { tid: usize },
File {
device: Device,
inode: usize,
offset: usize,
path: PathBuf,
},
}
impl Type {
#[allow(clippy::missing_const_for_fn)]
fn from_pathname(pathname: Pathname, device: Device, inode: usize, offset: usize) -> Self {
match pathname {
Pathname::Anonymous => Self::Anonymous,
Pathname::Heap => Self::Heap,
Pathname::Stack { tid } => Self::Stack { tid },
Pathname::Vdso => Self::Vdso,
Pathname::Vvar => Self::Vvar,
Pathname::Vsyscall => Self::Vsyscall,
Pathname::File(buf) => Self::File {
device,
inode,
offset,
path: buf,
},
}
}
}
#[derive(Debug, PartialEq, Eq)]
pub struct Mapping {
pub start: usize,
pub end: usize,
pub permissions: Permissions,
pub mapping_type: Type,
}
fn mapping_file_lines(pid: Pid) -> Result<Lines<BufReader<File>>, IOError> {
let filename = format!("/proc/{pid}/maps");
let file = BufReader::new(File::open(filename)?);
Ok(file.lines())
}
impl FromStr for Mapping {
type Err = CouldNotParseMappingFile;
fn from_str(line: &str) -> Result<Self, Self::Err> {
let mut words = line.split_whitespace();
let range = Self::Err::from_option(words.next())?;
let permissions = Permissions::from_str(Self::Err::from_option(words.next())?)?;
let offset = usize::from_str_radix(Self::Err::from_option(words.next())?, 16)
.or(Err(Self::Err::Offset))?;
let device = Device::from_str(Self::Err::from_option(words.next())?)?;
let inode = Self::Err::from_option(words.next())?
.parse::<usize>()
.or(Err(Self::Err::Inode))?;
let pathname = Pathname::from_str(words.collect::<Vec<_>>().join(" ").as_str())?;
let (start, end) = range
.split_once('-')
.ok_or(Self::Err::AddressRange)
.and_then(|(s, e)| {
let start = usize::from_str_radix(s, 16).or(Err(Self::Err::StartAddress))?;
let end = usize::from_str_radix(e, 16).or(Err(Self::Err::StartAddress))?;
Ok((start, end))
})?;
Ok(Self {
start,
end,
permissions,
mapping_type: Type::from_pathname(pathname, device, inode, offset),
})
}
}
impl Mapping {
#[must_use]
pub const fn is_anonymous(&self) -> bool {
matches!(self.mapping_type, Type::Anonymous)
}
#[must_use]
pub const fn is_vdso(&self) -> bool {
matches!(self.mapping_type, Type::Vdso)
}
#[must_use]
pub const fn is_vvar(&self) -> bool {
matches!(self.mapping_type, Type::Vvar)
}
#[must_use]
pub const fn is_vsyscall(&self) -> bool {
matches!(self.mapping_type, Type::Vsyscall)
}
#[must_use]
pub const fn is_heap(&self) -> bool {
matches!(self.mapping_type, Type::Heap)
}
#[must_use]
pub const fn is_stack(&self) -> bool {
matches!(self.mapping_type, Type::Stack { .. })
}
#[must_use]
pub const fn is_file(&self) -> bool {
matches!(self.mapping_type, Type::File { .. })
}
}
#[allow(clippy::module_name_repetitions)]
pub struct MemoryMapping(pub Vec<Mapping>);
impl MemoryMapping {
pub fn find<P>(&self, predicate: P) -> Option<&Mapping>
where
P: Fn(&Mapping) -> bool,
{
self.0.iter().find(|mapping| predicate(mapping))
}
}
#[allow(clippy::module_name_repetitions)]
pub fn memory_mapping(process: &TargetProcess) -> Result<MemoryMapping, MappingError> {
let vec = mapping_file_lines(process.pid())?
.map(|line| Mapping::from_str(&line?).map_err(MappingError::from))
.collect::<Result<Vec<Mapping>, _>>()?;
Ok(MemoryMapping(vec))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parse_permissions() {
let perm = Permissions::from_str("");
assert_eq!(perm, Err(CouldNotParseMappingFile::Permissions));
let perm = Permissions::from_str("r---");
assert_eq!(
perm,
Ok(Permissions {
readable: true,
writable: false,
executable: false,
private: false
})
);
let perm = Permissions::from_str("-wx-");
assert_eq!(
perm,
Ok(Permissions {
readable: false,
writable: true,
executable: true,
private: false
})
);
let perm = Permissions::from_str("r-x-");
assert_eq!(
perm,
Ok(Permissions {
readable: true,
writable: false,
executable: true,
private: false
})
);
let perm = Permissions::from_str("r-xp");
assert_eq!(
perm,
Ok(Permissions {
readable: true,
writable: false,
executable: true,
private: true
})
);
}
#[test]
fn parse_pathname() {
let stack = Pathname::from_str("[stack]");
assert_eq!(stack, Ok(Pathname::Stack { tid: 0 }));
let stack = Pathname::from_str("[stack");
assert_eq!(stack, Ok(Pathname::File(PathBuf::from("[stack"))));
let stack = Pathname::from_str("[stack:890]");
assert_eq!(stack, Ok(Pathname::Stack { tid: 890 }));
let vdso = Pathname::from_str("[vdso]");
assert_eq!(vdso, Ok(Pathname::Vdso));
let heap = Pathname::from_str("[heap]");
assert_eq!(heap, Ok(Pathname::Heap));
let anon = Pathname::from_str("");
assert_eq!(anon, Ok(Pathname::Anonymous));
let file = Pathname::from_str("/lib/somelib.so");
assert_eq!(file, Ok(Pathname::File(PathBuf::from("/lib/somelib.so"))));
}
#[test]
fn parse_device() {
let device = Device::from_str("fd:04");
assert_eq!(
device,
Ok(Device {
major: 0xfd,
minor: 0x04
})
);
}
#[test]
fn parse_line() {
let line = "7f7663254000-7f7663255000 r--p 00000000 fe:00 10495632 /usr/lib/ld-linux-x86-64.so.2";
let res = Mapping::from_str(line);
assert_eq!(
res,
Ok(Mapping {
start: 0x7f76_6325_4000,
end: 0x7f76_6325_5000,
permissions: Permissions {
readable: true,
writable: false,
executable: false,
private: true
},
mapping_type: Type::File {
offset: 0,
device: Device {
major: 0xfe,
minor: 0
},
inode: 10_495_632,
path: PathBuf::from("/usr/lib/ld-linux-x86-64.so.2"),
}
})
);
}
}