use std::path::{Path, PathBuf};
use std::sync::{OnceLock, RwLock};
#[cfg(unix)]
const SYSTEM_BIN_DIRS: &[&str] = &[
"/usr/bin",
"/usr/local/bin",
"/usr/local/sbin",
"/usr/sbin",
"/bin",
"/sbin",
"/opt/homebrew/bin", "/opt/homebrew/sbin",
];
#[cfg(windows)]
const SYSTEM_BIN_DIRS: &[&str] = &[
"C:\\Windows\\System32",
"C:\\Windows",
"C:\\Windows\\System32\\WindowsPowerShell\\v1.0",
"C:\\Program Files\\Git\\cmd",
"C:\\Program Files\\Git\\bin",
];
#[cfg(unix)]
const EXE_SUFFIXES: &[&str] = &[""];
#[cfg(windows)]
const EXE_SUFFIXES: &[&str] = &[".exe", ".com", ".bat", ".cmd"];
static EXTRA_TRUSTED_BIN_DIRS: OnceLock<RwLock<Vec<PathBuf>>> = OnceLock::new();
pub fn set_extra_trusted_dirs(dirs: Vec<PathBuf>) {
let mut filtered = Vec::with_capacity(dirs.len());
for dir in dirs {
if dir.is_absolute() {
filtered.push(dir);
} else {
tracing::warn!(
dir = %dir.display(),
"ignoring relative trusted binary directory; only absolute paths are trusted"
);
}
}
let lock = EXTRA_TRUSTED_BIN_DIRS.get_or_init(|| RwLock::new(Vec::new()));
match lock.write() {
Ok(mut guard) => *guard = filtered,
Err(poisoned) => {
let mut guard = poisoned.into_inner();
*guard = filtered;
}
}
}
fn configured_trusted_dirs() -> Vec<PathBuf> {
let Some(lock) = EXTRA_TRUSTED_BIN_DIRS.get() else {
return Vec::new();
};
match lock.read() {
Ok(guard) => guard.clone(),
Err(poisoned) => poisoned.into_inner().clone(),
}
}
fn trusted_dirs() -> Vec<PathBuf> {
let mut dirs: Vec<PathBuf> = SYSTEM_BIN_DIRS.iter().map(PathBuf::from).collect();
dirs.extend(configured_trusted_dirs());
dirs
}
pub fn resolve_safe_bin(name: &str) -> Option<PathBuf> {
if name.contains('/') || name.contains('\\') {
let p = PathBuf::from(name);
if p.is_absolute() && in_trusted_dir(&p) && is_safe_target(&p) {
return Some(p);
}
return None;
}
for dir in trusted_dirs() {
for suffix in EXE_SUFFIXES {
let candidate = dir.join(format!("{name}{suffix}"));
if is_safe_target(&candidate) {
return Some(candidate);
}
}
}
None
}
#[cfg(unix)]
fn is_safe_target(candidate: &Path) -> bool {
use std::os::unix::fs::MetadataExt;
let Ok(meta) = std::fs::metadata(candidate) else {
return false; };
if !meta.is_file() {
return false;
}
let euid = unsafe { libc::geteuid() };
let owner = meta.uid();
owner == 0 || owner == euid
}
#[cfg(not(unix))]
fn is_safe_target(candidate: &Path) -> bool {
candidate.is_file()
}
fn in_trusted_dir(p: &Path) -> bool {
let parent = match p.parent() {
Some(p) => p,
None => return false,
};
trusted_dirs().iter().any(|dir| parent == dir.as_path())
}