From 63b65952ba8a7fc2f0b99002b343d87554f5e79b Mon Sep 17 00:00:00 2001 From: Kamal Tufekcic Date: Mon, 27 Jul 2026 11:36:21 +0300 Subject: [PATCH] allow for spaces in path --- src/live.rs | 55 ++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 54 insertions(+), 1 deletion(-) diff --git a/src/live.rs b/src/live.rs index 1433ce0..5c135c3 100644 --- a/src/live.rs +++ b/src/live.rs @@ -20,6 +20,23 @@ pub struct LiveProcess { executable: Vec<(u64, u64)>, // r-x regions — where valid code/vtable-slot targets must land } +/// The pathname field of a `/proc//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 { pub fn attach(pid: u32) -> Result { let maps = std::fs::read_to_string(format!("/proc/{pid}/maps")) @@ -35,7 +52,7 @@ impl LiveProcess { None => continue, }; 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)| { Some(( u64::from_str_radix(a, 16).ok()?, @@ -308,3 +325,39 @@ pub fn call_remote(pid: i32, func: u64, args: &[u64]) -> Result { fn errno() -> i32 { 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]" + ); + } +}