#[cfg(target_os = "linux")]
use super::fd::FdError;
use std::path::PathBuf;
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub(crate) struct KboxlikeMapPerms {
pub(crate) read: bool,
pub(crate) write: bool,
pub(crate) exec: bool,
pub(crate) shared: bool,
}
impl KboxlikeMapPerms {
pub(crate) fn prot(self) -> i32 {
let mut prot = 0;
if self.read {
prot |= libc::PROT_READ;
}
if self.write {
prot |= libc::PROT_WRITE;
}
if self.exec {
prot |= libc::PROT_EXEC;
}
prot
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub(crate) struct KboxlikeMapEntry {
pub(crate) start: u64,
pub(crate) end: u64,
pub(crate) perms: KboxlikeMapPerms,
pub(crate) file_offset: u64,
pub(crate) dev: String,
pub(crate) inode: u64,
pub(crate) path: Option<String>,
}
impl KboxlikeMapEntry {
pub(crate) fn len(&self) -> u64 {
self.end - self.start
}
pub(crate) fn is_kernel_synthetic(&self) -> bool {
matches!(
self.path.as_deref(),
Some("[vdso]") | Some("[vvar]") | Some("[vvar_vclock]") | Some("[vsyscall]")
)
}
pub(crate) fn is_dump_candidate(&self) -> bool {
self.end > self.start
&& self.perms.read
&& (!self.is_kernel_synthetic() || self.path.as_deref() == Some("[vdso]"))
}
pub(crate) fn file_path(&self) -> Option<PathBuf> {
let path = self.path.as_deref()?;
if path.starts_with('[') {
return None;
}
Some(PathBuf::from(path.trim_end_matches(" (deleted)")))
}
}
pub(crate) fn parse_proc_maps(raw: &str) -> Vec<KboxlikeMapEntry> {
raw.lines().filter_map(parse_proc_maps_line).collect()
}
#[cfg(target_os = "linux")]
pub(crate) fn read_proc_maps_for_pid(host_pid: i32) -> Result<Vec<KboxlikeMapEntry>, FdError> {
if host_pid <= 0 {
return Err(FdError::TraceeInstallFailed("invalid tracee pid"));
}
let raw = std::fs::read_to_string(format!("/proc/{host_pid}/maps"))
.map_err(|_| FdError::TraceeInstallFailed("read tracee maps failed"))?;
Ok(parse_proc_maps(&raw))
}
fn parse_proc_maps_line(line: &str) -> Option<KboxlikeMapEntry> {
let mut fields = line.split_whitespace();
let range = fields.next()?;
let perms = parse_perms(fields.next()?)?;
let file_offset = u64::from_str_radix(fields.next()?, 16).ok()?;
let dev = fields.next()?.to_owned();
let inode = fields.next()?.parse::<u64>().ok()?;
let mut range_parts = range.splitn(2, '-');
let start = u64::from_str_radix(range_parts.next()?, 16).ok()?;
let end = u64::from_str_radix(range_parts.next()?, 16).ok()?;
if end <= start {
return None;
}
let path = fields.collect::<Vec<_>>();
let path = if path.is_empty() {
None
} else {
Some(path.join(" "))
};
Some(KboxlikeMapEntry {
start,
end,
perms,
file_offset,
dev,
inode,
path,
})
}
fn parse_perms(raw: &str) -> Option<KboxlikeMapPerms> {
let bytes = raw.as_bytes();
if bytes.len() < 4 {
return None;
}
Some(KboxlikeMapPerms {
read: bytes[0] == b'r',
write: bytes[1] == b'w',
exec: bytes[2] == b'x',
shared: bytes[3] == b's',
})
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parses_proc_maps_entries_with_full_metadata() {
let raw = "\
00400000-00452000 r-xp 00000000 08:02 131073 /usr/bin/chrome\n\
00652000-00653000 r--p 00052000 08:02 131073 /usr/bin/chrome\n\
7f0000000000-7f0000021000 rw-p 00000000 00:00 0 [heap]\n\
7ffc00000000-7ffc00021000 rw-p 00000000 00:00 0 [stack]\n\
7ffc12345000-7ffc12347000 r--p 00000000 00:00 0 [vvar]\n\
7ffc12347000-7ffc12349000 r-xp 00000000 00:00 0 [vdso]\n";
let maps = parse_proc_maps(raw);
assert_eq!(maps.len(), 6);
assert_eq!(maps[0].start, 0x0040_0000);
assert_eq!(maps[0].end, 0x0045_2000);
assert_eq!(
maps[0].perms,
KboxlikeMapPerms {
read: true,
write: false,
exec: true,
shared: false,
}
);
assert_eq!(maps[0].file_offset, 0);
assert_eq!(maps[0].dev, "08:02");
assert_eq!(maps[0].inode, 131073);
assert_eq!(maps[0].file_path(), Some(PathBuf::from("/usr/bin/chrome")));
assert_eq!(maps[2].path.as_deref(), Some("[heap]"));
assert!(maps[2].is_dump_candidate());
assert!(maps[4].is_kernel_synthetic());
assert!(!maps[4].is_dump_candidate());
assert!(maps[5].is_kernel_synthetic());
assert!(maps[5].is_dump_candidate());
}
#[test]
fn parses_deleted_and_space_containing_paths_without_panicking() {
let raw = "\
7f1000000000-7f1000001000 rw-s 00000000 00:01 999 /memfd:with spaces (deleted)\n\
bad line\n\
1000-1000 r--p 00000000 00:00 0 [zero]\n\
2000-3000 ---p 00000000 00:00 0\n";
let maps = parse_proc_maps(raw);
assert_eq!(maps.len(), 2);
assert_eq!(
maps[0].path.as_deref(),
Some("/memfd:with spaces (deleted)")
);
assert_eq!(
maps[0].file_path(),
Some(PathBuf::from("/memfd:with spaces"))
);
assert!(maps[0].perms.shared);
assert_eq!(maps[1].path, None);
assert!(!maps[1].is_dump_candidate());
}
#[test]
fn perms_convert_to_mmap_prot_bits() {
let perms = KboxlikeMapPerms {
read: true,
write: true,
exec: false,
shared: false,
};
assert_eq!(perms.prot(), libc::PROT_READ | libc::PROT_WRITE);
}
}