allow for spaces in path
All checks were successful
CI / lint (push) Successful in 16s
CI / fuzz (push) Successful in 1m42s
CI / test (push) Successful in 20s

This commit is contained in:
Kamal Tufekcic 2026-07-27 11:36:21 +03:00
commit 63b65952ba

View file

@ -20,6 +20,23 @@ pub struct LiveProcess {
executable: Vec<(u64, u64)>, // r-x regions — where valid code/vtable-slot targets must land executable: Vec<(u64, u64)>, // r-x regions — where valid code/vtable-slot targets must land
} }
/// The pathname field of a `/proc/<pid>/maps` line — everything after the fifth whitespace-delimited field.
/// It must NOT be read as "whatever follows the last whitespace": a mapped path may contain spaces (Steam's
/// default install directory is `.../common/Counter-Strike Global Offensive/...`), and taking the last field
/// yields a fragment that fails the leading-`/` test, so the library is silently dropped from the map and
/// every lookup for it then reports it as not mapped. Empty for an anonymous mapping.
fn maps_path(line: &str) -> &str {
let mut rest = line;
for _ in 0..5 {
rest = rest.trim_start();
match rest.find(char::is_whitespace) {
Some(i) => rest = &rest[i..],
None => return "",
}
}
rest.trim_start()
}
impl LiveProcess { impl LiveProcess {
pub fn attach(pid: u32) -> Result<Self> { pub fn attach(pid: u32) -> Result<Self> {
let maps = std::fs::read_to_string(format!("/proc/{pid}/maps")) let maps = std::fs::read_to_string(format!("/proc/{pid}/maps"))
@ -35,7 +52,7 @@ impl LiveProcess {
None => continue, None => continue,
}; };
let perms = rest.split(' ').next().unwrap_or(""); let perms = rest.split(' ').next().unwrap_or("");
let path = line.rsplit_once(char::is_whitespace).map_or("", |(_, p)| p); let path = maps_path(line);
let Some((start, end)) = range.split_once('-').and_then(|(a, b)| { let Some((start, end)) = range.split_once('-').and_then(|(a, b)| {
Some(( Some((
u64::from_str_radix(a, 16).ok()?, u64::from_str_radix(a, 16).ok()?,
@ -308,3 +325,39 @@ pub fn call_remote(pid: i32, func: u64, args: &[u64]) -> Result<CallResult> {
fn errno() -> i32 { fn errno() -> i32 {
unsafe { *libc::__errno_location() } unsafe { *libc::__errno_location() }
} }
#[cfg(test)]
mod tests {
use super::maps_path;
#[test]
fn maps_path_survives_spaces_in_the_mapped_path() {
// Steam's default install directory contains spaces; taking the last whitespace-delimited field
// yields "Offensive/..." which fails the leading-`/` test, so the library silently vanishes from
// the map and the live oracle reports it as not mapped.
let spaced = "7f1a2b000000-7f1a2c000000 r-xp 00000000 08:01 12345 \
/home/cs2/.steam/SteamApps/common/Counter-Strike Global Offensive/game/csgo/bin/linuxsteamrt64/libserver.so";
assert_eq!(
maps_path(spaced),
"/home/cs2/.steam/SteamApps/common/Counter-Strike Global Offensive/game/csgo/bin/linuxsteamrt64/libserver.so"
);
// The no-space case must be unchanged.
let plain = "7f1a2b000000-7f1a2c000000 r-xp 00000000 08:01 12345 /home/snake/game/csgo/bin/linuxsteamrt64/libserver.so";
assert_eq!(
maps_path(plain),
"/home/snake/game/csgo/bin/linuxsteamrt64/libserver.so"
);
// An anonymous mapping has no pathname — it must read as EMPTY, since that is what marks the
// writable heap regions the instance scan walks.
assert_eq!(
maps_path("24557800000-24597800000 rw-p 00000000 00:00 0 "),
""
);
assert_eq!(
maps_path("29618000-29639000 rw-p 00000000 00:00 0 [heap]"),
"[heap]"
);
}
}