use std::ffi::OsString;
use std::io;
use std::path::{Component, Path, PathBuf};
const MAX_LINKS: usize = 40;
pub fn in_root(root: &Path, rel: &Path) -> io::Result<PathBuf> {
resolve(root, rel, true)
}
pub fn entry_in_root(root: &Path, rel: &Path) -> io::Result<PathBuf> {
resolve(root, rel, false)
}
fn resolve(root: &Path, rel: &Path, follow_last: bool) -> io::Result<PathBuf> {
let mut pending: Vec<OsString> = Vec::new();
queue(&mut pending, rel);
let mut out = PathBuf::new();
let mut links = 0;
while let Some(part) = pending.pop() {
if part == ".." {
out.pop();
continue;
}
out.push(&part);
if pending.is_empty() && !follow_last {
break;
}
let Ok(target) = std::fs::read_link(root.join(&out)) else {
continue;
};
links += 1;
if links > MAX_LINKS {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
format!("{}: too many levels of symbolic links", rel.display()),
));
}
out.pop();
if target.is_absolute() {
out.clear();
}
queue(&mut pending, &target);
}
Ok(root.join(out))
}
fn queue(pending: &mut Vec<OsString>, path: &Path) {
for c in path.components().rev() {
match c {
Component::Normal(name) => pending.push(name.to_os_string()),
Component::ParentDir => pending.push("..".into()),
Component::CurDir | Component::RootDir | Component::Prefix(_) => {}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::db::fixture::temp_root;
use std::os::unix::fs::symlink;
#[test]
fn links_resolve_inside_the_root_and_never_leave_it() {
let root = temp_root("inroot");
std::fs::create_dir_all(root.join("usr/lib")).unwrap();
std::fs::write(root.join("usr/lib/libfoo.so.1.2"), b"elf").unwrap();
symlink("/usr/lib/libfoo.so.1.2", root.join("usr/lib/libfoo.so.1")).unwrap();
symlink("/usr/lib", root.join("lib64")).unwrap();
symlink("/home", root.join("usr/share")).unwrap();
symlink("../../../../../..", root.join("usr/lib/up")).unwrap();
symlink("loop", root.join("loop")).unwrap();
assert_eq!(
in_root(&root, Path::new("usr/lib/libfoo.so.1")).unwrap(),
root.join("usr/lib/libfoo.so.1.2")
);
assert_eq!(
in_root(&root, Path::new("lib64/libfoo.so.1")).unwrap(),
root.join("usr/lib/libfoo.so.1.2")
);
assert_eq!(
entry_in_root(&root, Path::new("lib64/libfoo.so.1")).unwrap(),
root.join("usr/lib/libfoo.so.1")
);
assert_eq!(
in_root(&root, Path::new("usr/share/me/.ssh/id_ed25519")).unwrap(),
root.join("home/me/.ssh/id_ed25519")
);
assert_eq!(
in_root(&root, Path::new("/../../etc/passwd")).unwrap(),
root.join("etc/passwd")
);
assert_eq!(
in_root(&root, Path::new("usr/lib/up/etc/passwd")).unwrap(),
root.join("etc/passwd")
);
assert!(in_root(&root, Path::new("loop")).is_err());
}
}