use std::fs;
use std::sync::{LazyLock, RwLock};
#[derive(Clone, Copy)]
enum Perm {
Readable,
Writable,
}
pub(super) fn range_readable(start: *const u8, len: usize) -> bool {
range_in_perm_vma(start as usize, len, Perm::Readable)
}
pub(super) fn range_writable(start: *const u8, len: usize) -> bool {
range_in_perm_vma(start as usize, len, Perm::Writable)
}
static MAPS_CACHE: LazyLock<RwLock<Option<String>>> = LazyLock::new(|| RwLock::new(None));
fn range_in_perm_vma(start: usize, len: usize, need: Perm) -> bool {
if len == 0 {
return true;
}
let Some(end) = start.checked_add(len) else {
return false;
};
if let Some(hit) = MAPS_CACHE.read().ok().and_then(|guard| {
guard
.as_deref()
.and_then(|maps| check_range_in_maps(maps, start, end, need))
}) {
return hit;
}
let maps = match fs::read_to_string("/proc/self/maps") {
Ok(maps) => maps,
Err(e) => {
log::warn!("vmem: 读取 /proc/self/maps 失败({e}),跳过区间校验");
return true;
}
};
let result = check_range_in_maps(&maps, start, end, need).unwrap_or(false);
if let Ok(mut guard) = MAPS_CACHE.write() {
*guard = Some(maps);
}
result
}
fn check_range_in_maps(maps: &str, start: usize, end: usize, need: Perm) -> Option<bool> {
for line in maps.lines() {
let Some((addr, rest)) = line.split_once(' ') else {
continue;
};
let Some((vma_start, vma_end)) = addr.split_once('-') else {
continue;
};
let (Ok(vma_start), Ok(vma_end)) = (
usize::from_str_radix(vma_start, 16),
usize::from_str_radix(vma_end, 16),
) else {
continue;
};
if vma_start <= start && end <= vma_end {
let perms = rest.split(' ').next().unwrap_or("");
return Some(vma_perm_ok(perms, need));
}
}
None
}
fn vma_perm_ok(perms: &str, need: Perm) -> bool {
let b = perms.as_bytes();
match need {
Perm::Readable => b.first() == Some(&b'r'),
Perm::Writable => b.get(1) == Some(&b'w'),
}
}
#[cfg(test)]
mod vmem_tests {
use super::*;
#[test]
fn test_stack_range_readable_writable() {
let buf = [0u8; 8];
assert!(range_readable(buf.as_ptr(), buf.len()));
assert!(range_writable(buf.as_ptr(), buf.len()));
}
#[test]
fn test_heap_range_readable_writable() {
let buf = vec![0u8; 100 * 1024];
assert!(range_readable(buf.as_ptr(), buf.len()));
assert!(range_writable(buf.as_ptr(), buf.len()));
}
#[test]
fn test_code_range_readable_not_writable() {
let p = test_code_range_readable_not_writable as *const u8;
assert!(range_readable(p, 1));
assert!(!range_writable(p, 1));
}
#[test]
fn test_zero_len_valid() {
assert!(range_readable(std::ptr::null(), 0));
assert!(range_writable(std::ptr::null(), 0));
}
#[test]
fn test_len_overflow_rejected() {
let buf = [0u8; 8];
assert!(!range_readable(buf.as_ptr(), usize::MAX));
assert!(!range_writable(buf.as_ptr(), usize::MAX));
}
#[test]
fn test_cross_vma_rejected() {
let buf = [0u8; 8];
let len = 512 * 1024 * 1024;
assert!(!range_readable(buf.as_ptr(), len));
}
}