1use std::ffi::OsString;
10use std::io;
11use std::path::{Component, Path, PathBuf};
12
13const MAX_LINKS: usize = 40;
16
17pub fn in_root(root: &Path, rel: &Path) -> io::Result<PathBuf> {
23 resolve(root, rel, true)
24}
25
26pub fn entry_in_root(root: &Path, rel: &Path) -> io::Result<PathBuf> {
29 resolve(root, rel, false)
30}
31
32fn resolve(root: &Path, rel: &Path, follow_last: bool) -> io::Result<PathBuf> {
33 let mut pending: Vec<OsString> = Vec::new();
34 queue(&mut pending, rel);
35 let mut out = PathBuf::new();
36 let mut links = 0;
37 while let Some(part) = pending.pop() {
38 if part == ".." {
39 out.pop();
40 continue;
41 }
42 out.push(&part);
43 if pending.is_empty() && !follow_last {
44 break;
45 }
46 let Ok(target) = std::fs::read_link(root.join(&out)) else {
47 continue;
48 };
49 links += 1;
50 if links > MAX_LINKS {
51 return Err(io::Error::new(
52 io::ErrorKind::InvalidData,
53 format!("{}: too many levels of symbolic links", rel.display()),
54 ));
55 }
56 out.pop();
57 if target.is_absolute() {
58 out.clear();
59 }
60 queue(&mut pending, &target);
61 }
62 Ok(root.join(out))
63}
64
65fn queue(pending: &mut Vec<OsString>, path: &Path) {
67 for c in path.components().rev() {
68 match c {
69 Component::Normal(name) => pending.push(name.to_os_string()),
70 Component::ParentDir => pending.push("..".into()),
71 Component::CurDir | Component::RootDir | Component::Prefix(_) => {}
72 }
73 }
74}
75
76#[cfg(test)]
77mod tests {
78 use super::*;
79 use crate::db::fixture::temp_root;
80 use std::os::unix::fs::symlink;
81
82 #[test]
83 fn links_resolve_inside_the_root_and_never_leave_it() {
84 let root = temp_root("inroot");
85 std::fs::create_dir_all(root.join("usr/lib")).unwrap();
86 std::fs::write(root.join("usr/lib/libfoo.so.1.2"), b"elf").unwrap();
87 symlink("/usr/lib/libfoo.so.1.2", root.join("usr/lib/libfoo.so.1")).unwrap();
88 symlink("/usr/lib", root.join("lib64")).unwrap();
89 symlink("/home", root.join("usr/share")).unwrap();
90 symlink("../../../../../..", root.join("usr/lib/up")).unwrap();
91 symlink("loop", root.join("loop")).unwrap();
92
93 assert_eq!(
94 in_root(&root, Path::new("usr/lib/libfoo.so.1")).unwrap(),
95 root.join("usr/lib/libfoo.so.1.2")
96 );
97 assert_eq!(
98 in_root(&root, Path::new("lib64/libfoo.so.1")).unwrap(),
99 root.join("usr/lib/libfoo.so.1.2")
100 );
101 assert_eq!(
102 entry_in_root(&root, Path::new("lib64/libfoo.so.1")).unwrap(),
103 root.join("usr/lib/libfoo.so.1")
104 );
105 assert_eq!(
106 in_root(&root, Path::new("usr/share/me/.ssh/id_ed25519")).unwrap(),
107 root.join("home/me/.ssh/id_ed25519")
108 );
109 assert_eq!(
110 in_root(&root, Path::new("/../../etc/passwd")).unwrap(),
111 root.join("etc/passwd")
112 );
113 assert_eq!(
114 in_root(&root, Path::new("usr/lib/up/etc/passwd")).unwrap(),
115 root.join("etc/passwd")
116 );
117 assert!(in_root(&root, Path::new("loop")).is_err());
118 }
119}