Skip to main content

onelf_sysroot/
path.rs

1//! Paths read inside a sysroot as if it were the root filesystem.
2//!
3//! A rootfs is full of absolute symlinks, `lib64 -> /usr/lib` and the
4//! like, and they name the sysroot's own files. The packer's kernel
5//! follows them to the packer's files instead, so every path read out of
6//! a sysroot is resolved here first: an absolute target starts again at
7//! the sysroot root, and `..` stops there.
8
9use std::ffi::OsString;
10use std::io;
11use std::path::{Component, Path, PathBuf};
12
13/// More links than this on one path is taken for a loop, as the kernel
14/// does.
15const MAX_LINKS: usize = 40;
16
17/// `rel` inside `root` with every symlink followed in sysroot terms,
18/// returned as `root` joined with a path that holds no symlink. A
19/// component that does not exist ends the resolution, and the rest is
20/// appended as written, so the result exists exactly when `rel` does in
21/// the sysroot.
22pub fn in_root(root: &Path, rel: &Path) -> io::Result<PathBuf> {
23    resolve(root, rel, true)
24}
25
26/// As [`in_root`], with the last component left as it is: a symlink there
27/// is the link itself rather than what it names.
28pub 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
65/// Push the components of `path` so the first is popped first.
66fn 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}