#![deny(unsafe_code)]
use std::{
ffi::CStr,
fmt,
io::BufReader,
num::NonZeroUsize,
os::{
fd::{AsFd, AsRawFd},
unix::ffi::OsStrExt,
},
ptr::NonNull,
};
use bitflags::bitflags;
use memchr::memchr;
use nix::{errno::Errno, unistd::Pid};
use procfs_core::{
process::{MMPermissions, MMapPath, MemoryMaps, SmapsRollup},
FromBufRead,
};
use serde::{ser::SerializeMap, Serialize, Serializer};
use super::util::safe_open_proc;
use crate::{
config::*,
confine::SydMemoryMap,
cookie::{CookieIdx, SYSCOOKIE_POOL},
err::{proc_errno, proc_error_to_errno},
fd::SafeOwnedFd,
imap::SydIndexSet,
path::{XPathBuf, PATH_MAX},
XPath,
};
bitflags! {
#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
pub struct ProcmapQueryFlags: u64 {
const VMA_READABLE = 0x01;
const VMA_WRITABLE = 0x02;
const VMA_EXECUTABLE = 0x04;
const VMA_SHARED = 0x08;
const COVERING_OR_NEXT_VMA = 0x10;
const FILE_BACKED_VMA = 0x20;
}
}
impl fmt::Display for ProcmapQueryFlags {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.contains(Self::VMA_READABLE) {
f.write_str("r")?;
} else {
f.write_str("-")?;
}
if self.contains(Self::VMA_WRITABLE) {
f.write_str("w")?;
} else {
f.write_str("-")?;
}
if self.contains(Self::VMA_EXECUTABLE) {
f.write_str("x")?;
} else {
f.write_str("-")?;
}
if self.contains(Self::VMA_SHARED) {
f.write_str("s")?;
} else {
f.write_str("p")?;
}
Ok(())
}
}
impl Serialize for ProcmapQueryFlags {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.collect_str(self)
}
}
impl From<MMPermissions> for ProcmapQueryFlags {
fn from(perms: MMPermissions) -> Self {
let mut flags = Self::empty();
if perms.contains(MMPermissions::READ) {
flags.insert(Self::VMA_READABLE);
}
if perms.contains(MMPermissions::WRITE) {
flags.insert(Self::VMA_WRITABLE);
}
if perms.contains(MMPermissions::EXECUTE) {
flags.insert(Self::VMA_EXECUTABLE);
}
if perms.contains(MMPermissions::SHARED) {
flags.insert(Self::VMA_SHARED);
}
flags
}
}
#[repr(C)]
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub struct ProcmapQuery {
pub size: u64,
pub query_flags: u64,
pub query_addr: u64,
pub vma_start: u64,
pub vma_end: u64,
pub vma_flags: u64,
pub vma_page_size: u64,
pub vma_offset: u64,
pub inode: u64,
pub dev_major: u32,
pub dev_minor: u32,
pub vma_name_size: u32,
pub build_id_size: u32,
pub vma_name_addr: u64,
pub build_id_addr: u64,
}
impl Default for ProcmapQuery {
fn default() -> Self {
Self {
size: size_of::<Self>() as u64,
query_flags: 0,
query_addr: 0,
vma_start: 0,
vma_end: 0,
vma_flags: 0,
vma_page_size: 0,
vma_offset: 0,
inode: 0,
dev_major: 0,
dev_minor: 0,
vma_name_size: 0,
build_id_size: 0,
vma_name_addr: 0,
build_id_addr: 0,
}
}
}
pub const PROCFS_IOCTL_MAGIC: u32 = b'f' as u32;
pub const PROCMAP_QUERY: libc::c_ulong =
libc::_IOWR::<ProcmapQuery>(PROCFS_IOCTL_MAGIC, 17) as libc::c_ulong;
pub fn procmap_query<Fd: AsFd>(
fd: Fd,
flags: ProcmapQueryFlags,
query_addr: u64,
name_buf: Option<&mut [u8]>,
build_id_buf: Option<&mut [u8]>,
) -> Result<ProcmapQuery, Errno> {
let mut q = ProcmapQuery {
query_addr,
query_flags: flags.bits(),
..Default::default()
};
if let Some(buf) = name_buf {
q.vma_name_size = u32::try_from(buf.len()).or(Err(Errno::EINVAL))?;
q.vma_name_addr = buf.as_mut_ptr() as u64;
}
if let Some(buf) = build_id_buf {
q.build_id_size = u32::try_from(buf.len()).or(Err(Errno::EINVAL))?;
q.build_id_addr = buf.as_mut_ptr() as u64;
}
#[expect(unsafe_code)]
Errno::result(unsafe {
libc::syscall(
libc::SYS_ioctl,
fd.as_fd().as_raw_fd(),
PROCMAP_QUERY,
&mut q,
SYSCOOKIE_POOL.get(CookieIdx::ProcmapQueryArg3),
SYSCOOKIE_POOL.get(CookieIdx::ProcmapQueryArg4),
SYSCOOKIE_POOL.get(CookieIdx::ProcmapQueryArg5),
)
})
.map(|_| q)
}
pub fn proc_maps(pid: Pid) -> Result<Vec<SydMemoryMap>, Errno> {
proc_maps_read(proc_maps_open(pid)?)
}
pub fn proc_maps_open(pid: Pid) -> Result<SafeOwnedFd, Errno> {
let mut path = XPathBuf::from_pid(pid)?;
path.try_push(b"maps")?;
safe_open_proc(&path).map_err(proc_errno)
}
pub fn proc_maps_read(fd: SafeOwnedFd) -> Result<Vec<SydMemoryMap>, Errno> {
let maps = MemoryMaps::from_buf_read(BufReader::new(fd))
.map_err(|err| proc_error_to_errno(&err).unwrap_or(Errno::ENOSYS))
.map_err(proc_errno)?;
let mut vec = Vec::new();
vec.try_reserve_exact(maps.0.len()).or(Err(Errno::ENOMEM))?;
for map in maps.0 {
vec.push(SydMemoryMap(map));
}
Ok(vec)
}
pub fn proc_smaps(pid: Pid) -> Result<Vec<SydMemoryMap>, Errno> {
let mut path = XPathBuf::from_pid(pid)?;
path.try_push(b"smaps")?;
let reader = safe_open_proc(&path)
.map(BufReader::new)
.map_err(proc_errno)?;
let maps = MemoryMaps::from_buf_read(reader)
.map_err(|err| proc_error_to_errno(&err).unwrap_or(Errno::ENOSYS))
.map_err(proc_errno)?;
let mut vec = Vec::new();
vec.try_reserve_exact(maps.0.len()).or(Err(Errno::ENOMEM))?;
for map in maps.0 {
vec.push(SydMemoryMap(map));
}
Ok(vec)
}
pub fn proc_smaps_rollup(pid: Pid) -> Result<SydMemoryMap, Errno> {
let mut path = XPathBuf::from_pid(pid)?;
path.try_push(b"smaps_rollup")?;
let reader = safe_open_proc(&path)
.map(BufReader::new)
.map_err(proc_errno)?;
SmapsRollup::from_buf_read(reader)
.map_err(|err| proc_error_to_errno(&err).unwrap_or(Errno::ENOSYS))
.map_err(proc_errno)
.and_then(|maps| {
maps.memory_map_rollup
.into_iter()
.next()
.map(SydMemoryMap)
.ok_or(Errno::ENOMEM)
})
}
pub fn proc_mem(pid: Pid) -> Result<u64, Errno> {
let map = proc_smaps_rollup(pid)?;
let mut sum = 0u64;
for key in ["Pss", "Private_Dirty", "Shared_Dirty"] {
let val = map.0.extension.map.get(key).copied().unwrap_or(0);
sum = sum.saturating_add(val);
}
Ok(sum)
}
#[derive(Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub struct SydExecMap {
pub path: XPathBuf,
pub inode: u64,
pub dev_major: i32,
pub dev_minor: i32,
}
pub fn proc_executables(pid: Pid) -> Result<Vec<SydExecMap>, Errno> {
if *crate::config::HAVE_PROCMAP_QUERY {
return procmap_query_executables(pid);
}
let maps = proc_maps(pid)?;
let mut set: SydIndexSet<SydExecMap> = SydIndexSet::default();
for map in maps {
if let MMapPath::Path(path) = map.0.pathname {
if map.0.perms.contains(MMPermissions::EXECUTE) {
let exe = SydExecMap {
path: path.into(),
inode: map.0.inode,
dev_major: map.0.dev.0,
dev_minor: map.0.dev.1,
};
set.try_insert(exe)?;
}
}
}
if set.is_empty() {
return Err(Errno::ENOEXEC);
}
let mut vec = Vec::new();
vec.try_reserve_exact(set.len()).or(Err(Errno::ENOMEM))?;
for exe in set {
vec.push(exe);
}
Ok(vec)
}
pub fn procmap_query_executables(pid: Pid) -> Result<Vec<SydExecMap>, Errno> {
let maps = proc_maps_open(pid)?;
let flags = ProcmapQueryFlags::COVERING_OR_NEXT_VMA
| ProcmapQueryFlags::FILE_BACKED_VMA
| ProcmapQueryFlags::VMA_EXECUTABLE;
let mut path = [0u8; PATH_MAX];
let mut addr = 0u64;
let mut set: SydIndexSet<SydExecMap> = SydIndexSet::default();
loop {
match procmap_query(&maps, flags, addr, Some(&mut path), None) {
Ok(q) => {
let name_len = q.vma_name_size as usize;
if name_len == 0 {
addr = q.vma_end;
continue;
}
let path = CStr::from_bytes_with_nul(&path[..name_len])
.map(|cstr| cstr.to_bytes())
.or(Err(Errno::EINVAL))
.and_then(XPathBuf::try_from)?;
#[expect(clippy::cast_possible_wrap)]
let exe = SydExecMap {
path,
inode: q.inode,
dev_major: q.dev_major as i32,
dev_minor: q.dev_minor as i32,
};
set.try_insert(exe)?;
addr = q.vma_end;
}
Err(Errno::ENOENT) => break,
Err(errno) => return Err(errno),
}
}
if set.is_empty() {
return Err(Errno::ENOEXEC);
}
let mut vec = Vec::new();
vec.try_reserve_exact(set.len()).or(Err(Errno::ENOMEM))?;
for exe in set {
vec.push(exe);
}
Ok(vec)
}
pub struct Vma {
ptr: NonNull<libc::c_void>,
len: NonZeroUsize,
flags: ProcmapQueryFlags,
name: [u8; PATH_MAX],
}
impl Vma {
fn new(
ptr: NonNull<libc::c_void>,
len: NonZeroUsize,
flags: ProcmapQueryFlags,
name: [u8; PATH_MAX],
) -> Self {
Self {
ptr,
len,
flags,
name,
}
}
pub fn addr(&self) -> usize {
self.ptr.as_ptr() as usize
}
pub fn as_ptr(&self) -> NonNull<libc::c_void> {
self.ptr
}
pub fn len(&self) -> NonZeroUsize {
self.len
}
pub fn flags(&self) -> ProcmapQueryFlags {
self.flags
}
pub fn name(&self) -> &XPath {
XPath::from_bytes(self.name_bytes())
}
pub fn name_bytes(&self) -> &[u8] {
let len = memchr(0, &self.name).unwrap_or(PATH_MAX);
&self.name[..len]
}
}
impl TryFrom<SydMemoryMap> for Vma {
type Error = Errno;
fn try_from(map: SydMemoryMap) -> Result<Self, Self::Error> {
let map = map.0;
let (start, end) = (map.address.0, map.address.1);
let ptr = NonNull::new(start as *mut _).ok_or(Errno::EINVAL)?;
let len = end
.checked_sub(start)
.ok_or(Errno::EINVAL)
.map(usize::try_from)?
.or(Err(Errno::EINVAL))
.map(NonZeroUsize::new)?
.ok_or(Errno::EINVAL)?;
let mut name = [0u8; PATH_MAX];
match &map.pathname {
MMapPath::Path(path) => {
let bytes = path.as_os_str().as_bytes();
let len = bytes.len().min(PATH_MAX);
name[..len].copy_from_slice(&bytes[..len]);
}
MMapPath::Heap => {
name[..7].copy_from_slice(b"[heap]\0");
}
MMapPath::Stack => {
name[..8].copy_from_slice(b"[stack]\0");
}
MMapPath::TStack(tid) => {
use std::io::Write;
let _ = write!(&mut name[..], "[stack:{tid}]\0");
}
MMapPath::Vdso => {
name[..7].copy_from_slice(b"[vdso]\0");
}
MMapPath::Vvar => {
name[..7].copy_from_slice(b"[vvar]\0");
}
MMapPath::Vsyscall => {
name[..11].copy_from_slice(b"[vsyscall]\0");
}
MMapPath::Rollup | MMapPath::Vsys(_) | MMapPath::Other(_) | MMapPath::Anonymous => {}
}
Ok(Self::new(ptr, len, map.perms.into(), name))
}
}
impl TryFrom<(ProcmapQuery, [u8; PATH_MAX])> for Vma {
type Error = Errno;
fn try_from((q, name): (ProcmapQuery, [u8; PATH_MAX])) -> Result<Self, Self::Error> {
let ptr = NonNull::new(q.vma_start as *mut _).ok_or(Errno::EINVAL)?;
let len = q
.vma_end
.checked_sub(q.vma_start)
.ok_or(Errno::EINVAL)
.map(usize::try_from)?
.or(Err(Errno::EINVAL))
.map(NonZeroUsize::new)?
.ok_or(Errno::EINVAL)?;
let flags = ProcmapQueryFlags::from_bits_retain(q.vma_flags);
Ok(Self::new(ptr, len, flags, name))
}
}
impl fmt::Display for Vma {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let start = self.addr();
let end = start.saturating_add(self.len().get());
let flags = self.flags();
let name = self.name();
write!(f, "{start:x}-{end:x} {flags} {name}")
}
}
impl Serialize for Vma {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let start = self.addr();
let end = start.saturating_add(self.len().get());
let mut map = serializer.serialize_map(Some(3))?;
map.serialize_entry("addr", &[start, end])?;
map.serialize_entry("perm", &self.flags())?;
map.serialize_entry("name", &self.name())?;
map.end()
}
}
pub fn proc_get_vma(pid: Pid, addr: u64) -> Result<Vma, Errno> {
if *HAVE_PROCMAP_QUERY {
return procmap_query_get_vma(pid, addr);
}
let maps = proc_maps(pid)?;
for map in maps {
let (start, end) = (map.0.address.0, map.0.address.1);
if (start..end).contains(&addr) {
return Vma::try_from(map);
}
}
Err(Errno::ENOENT)
}
fn procmap_query_get_vma(pid: Pid, addr: u64) -> Result<Vma, Errno> {
let maps = proc_maps_open(pid)?;
let flags = ProcmapQueryFlags::COVERING_OR_NEXT_VMA;
let mut name = [0u8; PATH_MAX];
let q = procmap_query(&maps, flags, addr, Some(&mut name), None)?;
Vma::try_from((q, name))
}
pub fn proc_find_vma(pid: Pid, flags: ProcmapQueryFlags) -> Result<Vec<Vma>, Errno> {
if *HAVE_PROCMAP_QUERY {
return procmap_query_find_vma(pid, flags);
}
let maps = proc_maps(pid)?;
let mut vmas = Vec::new();
for map in maps {
let perms = ProcmapQueryFlags::from(map.0.perms);
if flags.contains(ProcmapQueryFlags::VMA_READABLE)
&& !perms.contains(ProcmapQueryFlags::VMA_READABLE)
{
continue;
}
if flags.contains(ProcmapQueryFlags::VMA_WRITABLE)
&& !perms.contains(ProcmapQueryFlags::VMA_WRITABLE)
{
continue;
}
if flags.contains(ProcmapQueryFlags::VMA_EXECUTABLE)
&& !perms.contains(ProcmapQueryFlags::VMA_EXECUTABLE)
{
continue;
}
if flags.contains(ProcmapQueryFlags::VMA_SHARED)
&& !perms.contains(ProcmapQueryFlags::VMA_SHARED)
{
continue;
}
if flags.contains(ProcmapQueryFlags::FILE_BACKED_VMA)
&& !matches!(map.0.pathname, MMapPath::Path(_))
{
continue;
}
vmas.push(Vma::try_from(map)?);
}
Ok(vmas)
}
pub fn procmap_query_find_vma(pid: Pid, flags: ProcmapQueryFlags) -> Result<Vec<Vma>, Errno> {
let maps = proc_maps_open(pid)?;
let query_flags = flags | ProcmapQueryFlags::COVERING_OR_NEXT_VMA;
let mut vmas = Vec::new();
let mut addr = 0u64;
loop {
let mut name = [0u8; PATH_MAX];
match procmap_query(&maps, query_flags, addr, Some(&mut name), None) {
Ok(q) => {
vmas.push(Vma::try_from((q, name))?);
addr = q.vma_end;
}
Err(Errno::ENOENT) => break,
Err(errno) => return Err(errno),
}
}
Ok(vmas)
}
pub fn proc_get_fault(pid: Pid, addr: u64, len: usize) -> Result<usize, Errno> {
ProcMaps::new(pid)?.get_fault(addr, len)
}
pub enum ProcMaps {
Query(SafeOwnedFd),
Maps(Vec<SydMemoryMap>),
}
impl ProcMaps {
pub fn new(pid: Pid) -> Result<Self, Errno> {
if *HAVE_PROCMAP_QUERY {
Ok(Self::Query(proc_maps_open(pid)?))
} else {
Ok(Self::Maps(proc_maps(pid)?))
}
}
pub fn get_fault(&self, addr: u64, len: usize) -> Result<usize, Errno> {
let lim = addr.saturating_add(u64::try_from(len).or(Err(Errno::EOVERFLOW))?);
let mut end = addr;
match self {
Self::Query(maps) => {
while end < lim {
match procmap_query(maps, ProcmapQueryFlags::VMA_WRITABLE, end, None, None) {
Ok(q) => end = q.vma_end.min(lim),
Err(Errno::ENOENT) => break,
Err(errno) => return Err(errno),
}
}
}
Self::Maps(maps) => {
for map in maps {
let (start, stop) = (map.0.address.0, map.0.address.1);
if end >= lim || start > end {
break;
}
if stop <= end {
continue;
}
if !map.0.perms.contains(MMPermissions::WRITE) {
break;
}
end = stop.min(lim);
}
}
}
#[expect(clippy::arithmetic_side_effects)]
usize::try_from(lim - end).or(Err(Errno::EOVERFLOW))
}
}
#[cfg(test)]
mod tests {
use std::{fs::File, os::unix::fs::PermissionsExt};
use super::{super::util::tests::setup, *};
macro_rules! skip_if_procmap_query_not_supported {
() => {
if !*crate::config::HAVE_PROCMAP_QUERY {
eprintln!("skipping: PROCMAP_QUERY not supported on this kernel (requires >=6.11)");
return;
}
};
}
#[test]
fn test_procmap_query_flags_1() {
let flags = ProcmapQueryFlags::VMA_READABLE
| ProcmapQueryFlags::VMA_WRITABLE
| ProcmapQueryFlags::VMA_EXECUTABLE;
assert_eq!(format!("{flags}"), "rwxp");
}
#[test]
fn test_procmap_query_flags_2() {
let flags = ProcmapQueryFlags::empty();
assert_eq!(format!("{flags}"), "---p");
}
#[test]
fn test_procmap_query_flags_3() {
let flags = ProcmapQueryFlags::VMA_READABLE | ProcmapQueryFlags::VMA_SHARED;
assert_eq!(format!("{flags}"), "r--s");
}
#[test]
fn test_procmap_query_flags_4() {
let flags = ProcmapQueryFlags::VMA_READABLE
| ProcmapQueryFlags::VMA_WRITABLE
| ProcmapQueryFlags::VMA_EXECUTABLE
| ProcmapQueryFlags::VMA_SHARED;
assert_eq!(format!("{flags}"), "rwxs");
}
#[test]
fn test_procmap_query_flags_5() {
let perms = MMPermissions::READ | MMPermissions::WRITE;
let flags = ProcmapQueryFlags::from(perms);
assert!(flags.contains(ProcmapQueryFlags::VMA_READABLE));
assert!(flags.contains(ProcmapQueryFlags::VMA_WRITABLE));
assert!(!flags.contains(ProcmapQueryFlags::VMA_EXECUTABLE));
assert!(!flags.contains(ProcmapQueryFlags::VMA_SHARED));
}
#[test]
fn test_procmap_query_flags_6() {
let perms = MMPermissions::READ | MMPermissions::EXECUTE | MMPermissions::SHARED;
let flags = ProcmapQueryFlags::from(perms);
assert!(flags.contains(ProcmapQueryFlags::VMA_READABLE));
assert!(flags.contains(ProcmapQueryFlags::VMA_EXECUTABLE));
assert!(flags.contains(ProcmapQueryFlags::VMA_SHARED));
}
#[test]
fn test_procmap_query_flags_7() {
let perms = MMPermissions::empty();
let flags = ProcmapQueryFlags::from(perms);
assert!(flags.is_empty());
}
#[test]
fn test_procmap_query_flags_8() {
let flags = ProcmapQueryFlags::VMA_READABLE | ProcmapQueryFlags::VMA_WRITABLE;
let json = serde_json::to_string(&flags).unwrap();
assert_eq!(json, "\"rw-p\"");
}
#[test]
fn test_procmap_query_1() {
let q = ProcmapQuery::default();
assert_eq!(q.size as usize, size_of::<ProcmapQuery>());
assert_eq!(q.query_flags, 0);
assert_eq!(q.query_addr, 0);
assert_eq!(q.vma_start, 0);
assert_eq!(q.vma_end, 0);
assert_eq!(q.vma_flags, 0);
assert_eq!(q.vma_page_size, 0);
assert_eq!(q.vma_offset, 0);
assert_eq!(q.inode, 0);
assert_eq!(q.dev_major, 0);
assert_eq!(q.dev_minor, 0);
assert_eq!(q.vma_name_size, 0);
assert_eq!(q.build_id_size, 0);
assert_eq!(q.vma_name_addr, 0);
assert_eq!(q.build_id_addr, 0);
}
#[test]
fn test_procmap_query_2() {
skip_if_procmap_query_not_supported!();
let maps = File::open("/proc/self/maps").unwrap();
let q = procmap_query(
&maps,
ProcmapQueryFlags::COVERING_OR_NEXT_VMA | ProcmapQueryFlags::VMA_READABLE,
0,
None,
None,
)
.expect("basic query failed");
assert!(q.vma_start < q.vma_end);
let perms = ProcmapQueryFlags::from_bits_truncate(q.vma_flags);
assert!(perms.contains(ProcmapQueryFlags::VMA_READABLE));
assert_eq!(q.vma_name_addr, 0);
assert_eq!(q.vma_name_size, 0);
}
#[test]
fn test_procmap_query_3() {
skip_if_procmap_query_not_supported!();
let maps = File::open("/proc/self/maps").unwrap();
let mut buf = [0u8; PATH_MAX];
let q = procmap_query(
&maps,
ProcmapQueryFlags::COVERING_OR_NEXT_VMA,
0,
Some(&mut buf),
None,
)
.expect("query with name buffer failed");
assert_eq!(q.vma_name_size as usize <= PATH_MAX, true);
assert_ne!(q.vma_name_addr, 0);
let slice = &buf[..q.vma_name_size as usize];
let cstr = CStr::from_bytes_with_nul(slice).expect("vma name buffer not NUL terminated");
assert!(!cstr.to_bytes().is_empty(), "empty VMA name");
}
#[test]
fn test_procmap_query_4() {
skip_if_procmap_query_not_supported!();
let maps = File::open("/proc/self/maps").unwrap();
let mut name_buf = [0u8; 512];
let mut build_buf = [0u8; 64];
let q = procmap_query(
&maps,
ProcmapQueryFlags::COVERING_OR_NEXT_VMA | ProcmapQueryFlags::FILE_BACKED_VMA,
0,
Some(&mut name_buf),
Some(&mut build_buf),
)
.expect("query with both buffers failed");
assert!(q.build_id_size as usize <= build_buf.len());
let slice = &name_buf[..q.vma_name_size as usize];
let _ = CStr::from_bytes_with_nul(slice).expect("invalid VMA name");
}
#[test]
fn test_procmap_query_5() {
skip_if_procmap_query_not_supported!();
let maps = File::open("/proc/self/maps").unwrap();
let flags = ProcmapQueryFlags::COVERING_OR_NEXT_VMA | ProcmapQueryFlags::FILE_BACKED_VMA;
let mut addr = 0;
let mut buf = [0u8; PATH_MAX];
let mut seen = 0;
loop {
match procmap_query(&maps, flags, addr, Some(&mut buf), None) {
Ok(q) => {
assert!(q.vma_start < q.vma_end);
seen += 1;
addr = q.vma_end;
}
Err(Errno::ENOENT) => break,
Err(errno) => panic!("unexpected error during iteration: {errno}"),
}
}
assert!(seen > 0, "expected to see at least one VMA!");
}
#[test]
fn test_procmap_query_6() {
skip_if_procmap_query_not_supported!();
let maps = File::open("/proc/self/maps").unwrap();
let flags = ProcmapQueryFlags::COVERING_OR_NEXT_VMA
| ProcmapQueryFlags::FILE_BACKED_VMA
| ProcmapQueryFlags::VMA_EXECUTABLE;
let mut addr = 0;
let mut buf = [0u8; PATH_MAX];
let mut found_exec = false;
loop {
match procmap_query(&maps, flags, addr, Some(&mut buf), None) {
Ok(q) => {
let perms = ProcmapQueryFlags::from_bits_truncate(q.vma_flags);
assert!(perms.contains(ProcmapQueryFlags::VMA_EXECUTABLE));
found_exec = true;
addr = q.vma_end;
}
Err(Errno::ENOENT) => break,
Err(errno) => panic!("unexpected error: {errno}"),
}
}
assert!(found_exec, "no executable VMAs found!");
}
#[test]
fn test_procmap_query_7() {
skip_if_procmap_query_not_supported!();
let maps = File::open("/proc/self/maps").unwrap();
let mut build_buf = [0u8; 64];
let q = procmap_query(
&maps,
ProcmapQueryFlags::COVERING_OR_NEXT_VMA,
0,
None,
Some(&mut build_buf),
)
.expect("query build-id only failed");
assert_eq!(q.vma_name_addr, 0);
assert_eq!(q.vma_name_size, 0);
assert!(q.build_id_size as usize <= build_buf.len());
if q.build_id_size > 0 {
let _ = &build_buf[..q.build_id_size as usize];
}
}
#[test]
fn test_procmap_query_8() {
skip_if_procmap_query_not_supported!();
let maps = File::open("/proc/self/maps").unwrap();
let q = procmap_query(
&maps,
ProcmapQueryFlags::COVERING_OR_NEXT_VMA,
0,
None,
None,
)
.expect("basic query failed");
assert!(q.vma_page_size > 0);
let vma_len = q.vma_end - q.vma_start;
assert!(q.vma_offset <= vma_len);
}
#[test]
fn test_procmap_query_9() {
skip_if_procmap_query_not_supported!();
let maps = File::open("/proc/self/maps").unwrap();
let flags = ProcmapQueryFlags::COVERING_OR_NEXT_VMA;
let mut addr = 0;
let mut seen_addrs = Vec::new();
for _ in 0..2 {
let q = procmap_query(&maps, flags, addr, None, None).expect("query iteration failed");
seen_addrs.push(q.vma_start);
addr = q.vma_end;
}
assert_eq!(seen_addrs.len(), 2);
assert!(seen_addrs[0] < seen_addrs[1], "VMAs did not advance!");
}
#[test]
fn test_procmap_query_10() {
skip_if_procmap_query_not_supported!();
let maps = File::open("/proc/self/maps").unwrap();
let mut buf = [0u8; 1];
let err = procmap_query(
&maps,
ProcmapQueryFlags::COVERING_OR_NEXT_VMA | ProcmapQueryFlags::FILE_BACKED_VMA,
0,
Some(&mut buf),
None,
)
.unwrap_err();
assert_eq!(err, Errno::ENAMETOOLONG);
}
#[test]
fn test_procmap_query_11() {
skip_if_procmap_query_not_supported!();
let maps = File::open("/proc/self/maps").unwrap();
let result = procmap_query(
&maps,
ProcmapQueryFlags::COVERING_OR_NEXT_VMA,
u64::MAX,
None,
None,
);
assert_eq!(result.unwrap_err(), Errno::ENOENT);
}
#[test]
fn test_procmap_query_12() {
skip_if_procmap_query_not_supported!();
let maps = File::open("/proc/self/maps").unwrap();
let q = procmap_query(
&maps,
ProcmapQueryFlags::COVERING_OR_NEXT_VMA,
0,
None,
None,
)
.unwrap();
assert_eq!(q.size as usize, size_of::<ProcmapQuery>());
}
#[test]
fn test_procmap_query_13() {
skip_if_procmap_query_not_supported!();
let maps = File::open("/proc/self/maps").unwrap();
let flags = ProcmapQueryFlags::COVERING_OR_NEXT_VMA
| ProcmapQueryFlags::FILE_BACKED_VMA
| ProcmapQueryFlags::VMA_WRITABLE;
let mut addr = 0;
let mut count = 0;
let mut buf = [0u8; 256];
while let Ok(q) = procmap_query(&maps, flags, addr, Some(&mut buf), None) {
let perms = ProcmapQueryFlags::from_bits_truncate(q.vma_flags);
assert!(perms.contains(ProcmapQueryFlags::VMA_WRITABLE));
count += 1;
addr = q.vma_end;
}
assert!(count > 0, "expected at least one writable VMA");
}
#[test]
fn test_procmap_query_14() {
skip_if_procmap_query_not_supported!();
let maps = File::open("/proc/self/maps").unwrap();
let flags = ProcmapQueryFlags::COVERING_OR_NEXT_VMA | ProcmapQueryFlags::FILE_BACKED_VMA;
let mut buf = [0u8; 512];
let q = procmap_query(&maps, flags, 0, Some(&mut buf), None).unwrap();
assert!(
q.inode != 0,
"expected inode of a file-backed VMA to be non-zero"
);
}
#[test]
fn test_proc_executables_1() {
if !setup() {
return;
}
let list = proc_executables(Pid::this()).expect("expected executables");
assert!(!list.is_empty(), "no executables found for self");
}
#[test]
fn test_proc_executables_2() {
if !setup() {
return;
}
let bins = proc_executables(Pid::this()).unwrap();
let mut seen: SydIndexSet<XPathBuf> = SydIndexSet::default();
for bin in &bins {
let path = &bin.path;
assert!(
seen.try_insert(path.try_clone().unwrap()).unwrap(),
"duplicate path {path}!"
);
}
let collected: Vec<_> = seen.into_iter().collect();
let returned: Vec<_> = bins
.iter()
.map(|bin| bin.path.try_clone().unwrap())
.collect();
assert_eq!(collected, returned);
}
#[test]
fn test_proc_executables_3() {
if !setup() {
return;
}
let bins = proc_executables(Pid::this()).unwrap();
for (idx, bin) in bins.into_iter().enumerate() {
let path = bin.path;
let md = std::fs::metadata(&path).expect("path does not exist");
if idx == 0 {
let perms = md.permissions().mode();
assert!(
perms & 0o111 != 0,
"file {path} is not executable (mode {perms:o})",
);
}
}
}
#[test]
fn test_proc_find_vma() {
if !setup() {
return;
}
let pid = Pid::this();
for flags in [
ProcmapQueryFlags::VMA_READABLE,
ProcmapQueryFlags::VMA_EXECUTABLE,
ProcmapQueryFlags::VMA_READABLE | ProcmapQueryFlags::VMA_EXECUTABLE,
] {
let vmas = proc_find_vma(pid, flags).unwrap();
assert!(!vmas.is_empty());
for vma in vmas {
assert!(vma.flags().contains(flags));
}
}
}
}