use std::{
fs,
os::unix::fs as unixfs,
path::{Path, PathBuf},
};
use crate::{
syscalls,
tests::common::{self as tests_common, MountType},
};
use anyhow::{Context, Error};
use rustix::{
fs::{self as rustix_fs, AtFlags, OFlags, CWD},
mount::MountFlags,
};
use tempfile::TempDir;
macro_rules! create_inode {
(@do $path:expr, chmod $mode:expr) => {
rustix_fs::chmodat(CWD, $path, $mode.into(), AtFlags::empty())
.with_context(|| format!("chmod 0o{:o} {}", $mode, $path.display()))?;
};
(@do $path:expr, chown $uid:literal : $gid:literal) => {
rustix_fs::chownat(
CWD,
$path,
Some(::rustix::process::Uid::from_raw($uid)),
Some(::rustix::process::Gid::from_raw($gid)),
AtFlags::SYMLINK_NOFOLLOW,
)
.with_context(|| format!("chown {}:{} {}", $uid, $gid, $path.display()))?;
};
(@do $path:expr, chown $uid:literal :) => {
rustix_fs::chownat(
CWD,
$path,
Some(::rustix::process::Uid::from_raw($uid)),
None,
AtFlags::SYMLINK_NOFOLLOW,
)
.with_context(|| format!("chown {}:<none> {}", $uid, $path.display()))?;
};
(@do $path:expr, chown : $gid:literal) => {
rustix_fs::chownat(
CWD,
$path,
None,
Some(::rustix::process::Gid::from_raw($gid)),
AtFlags::SYMLINK_NOFOLLOW,
)
.with_context(|| format!("chown <none>:{} {}", $gid, $path.display()))?;
};
($path:expr => dir $(,{$($extra:tt)*})*) => {
rustix_fs::mkdir($path, 0o755.into())
.with_context(|| format!("mkdir {}", $path.display()))?;
$(
create_inode!(@do $path, $($extra)*);
)*
};
($path:expr => file $(,{$($extra:tt)*})*) => {
rustix_fs::open($path, OFlags::CREATE, 0o644.into())
.with_context(|| format!("mkfile {}", $path.display()))?;
$(
create_inode!(@do $path, $($extra)*);
)*
};
($path:expr => fifo $(, {$($extra:tt)*})*) => {
syscalls::mknodat(rustix_fs::CWD, $path, libc::S_IFIFO | 0o644, 0)
.with_context(|| format!("mkfifo {}", $path.display()))?;
$(
create_inode!(@do $path, $($extra)*);
)*
};
($path:expr => sock $(,{$($extra:tt)*})*) => {
syscalls::mknodat(rustix_fs::CWD, $path, libc::S_IFSOCK | 0o644, 0)
.with_context(|| format!("mksock {}", $path.display()))?;
$(
create_inode!(@do $path, $($extra)*);
)*
};
($path:expr => symlink -> $target:expr $(,{$($extra:tt)*})*) => {
unixfs::symlink($target, $path)
.with_context(|| format!("symlink {} -> {}", $path.display(), $target))?;
$(
create_inode!(@do $path, $($extra)*);
)*
};
($path:expr => hardlink -> $target:expr) => {
fs::hard_link($target, $path)
.with_context(|| format!("hardlink {} -> {}", $path.display(), $target))?;
};
}
macro_rules! create_tree {
($($subpath:expr => $(#[$meta:meta])* ($($inner:tt)*));+ $(;)*) => {
{
let root = TempDir::new()?;
$(
$(#[$meta])*
{
let root_dir: &Path = root.as_ref();
let subpath = $subpath;
let path = root_dir.join(subpath.trim_start_matches('/'));
if let Some(parent) = path.parent() {
fs::create_dir_all(parent).with_context(|| format!("mkdirall {}", path.display()))?;
}
create_inode!(&path => $($inner)*);
}
)*
root
}
}
}
pub(crate) fn create_basic_tree() -> Result<TempDir, Error> {
Ok(create_tree! {
"a" => (dir);
"b/c/d/e/f" => (dir);
"b/c/file" => (file);
"e" => (symlink -> "/b/c/d/e");
"b-file" => (symlink -> "b/c/file");
"root-link1" => (symlink -> "/");
"root-link2" => (symlink -> "/..");
"root-link3" => (symlink -> "/../../../../..");
"escape-link1" => (symlink -> "../../../../../../../../../../target");
"escape-link2" => (symlink -> "/../../../../../../../../../../target");
"b/fifo" => (fifo);
"b/sock" => (sock);
"a-fake1" => (symlink -> "a/fake");
"a-fake2" => (symlink -> "a/fake/foo/bar/..");
"a-fake3" => (symlink -> "a/fake/../../b");
"c/a-fake1" => (symlink -> "/a/fake");
"c/a-fake2" => (symlink -> "/a/fake/foo/bar/..");
"c/a-fake3" => (symlink -> "/a/fake/../../b");
"target" => (dir);
"link1/target_abs" => (symlink -> "/target");
"link1/target_rel" => (symlink -> "../target");
"link2/link1_abs" => (symlink -> "/link1");
"link2/link1_rel" => (symlink -> "../link1");
"link3/target_abs" => (symlink -> "/link2/link1_rel/target_rel");
"link3/target_rel" => (symlink -> "../link2/link1_rel/target_rel");
"link3/deep_dangling1" => (symlink -> "../link2/link1_rel/target_rel/nonexist");
"link3/deep_dangling2" => (symlink -> "../link2/link1_abs/target_abs/nonexist");
"dangling/a" => (symlink -> "b/c");
"dangling/b/c" => (symlink -> "../c");
"dangling/c" => (symlink -> "d/e");
"dangling/d/e" => (symlink -> "../e");
"dangling/e" => (symlink -> "f/../g");
"dangling/f" => (dir);
"dangling/g" => (symlink -> "h/i/j/nonexistent");
"dangling/h/i/j" => (dir);
"dangling-file/a" => (symlink -> "b/c");
"dangling-file/b/c" => (symlink -> "../c");
"dangling-file/c" => (symlink -> "d/e");
"dangling-file/d/e" => (symlink -> "../e");
"dangling-file/e" => (symlink -> "f/../g");
"dangling-file/f" => (dir);
"dangling-file/g" => (symlink -> "h/i/j/file/foo");
"dangling-file/h/i/j/file" => (file);
"loop/basic-loop1" => (symlink -> "basic-loop1");
"loop/basic-loop2" => (symlink -> "/loop/basic-loop2");
"loop/basic-loop3" => (symlink -> "../loop/basic-loop3");
"loop/a/link" => (symlink -> "../b/link");
"loop/b/link" => (symlink -> "/loop/c/link");
"loop/c/link" => (symlink -> "/loop/d/link");
"loop/d" => (symlink -> "e");
"loop/e/link" => (symlink -> "../a/link");
"loop/link" => (symlink -> "a/link");
"nosymfollow/goodlink" => (symlink -> "/");
"nosymfollow/badlink" => (symlink -> "/"); "nosymfollow/nosymdir/dir/badlink" => (symlink -> "/"); "nosymfollow/nosymdir/dir/goodlink" => (symlink -> "/");
"nosymfollow/nosymdir/dir/foo/yessymdir/bar/goodlink" => (symlink -> "/");
"tmpfs-self" => (dir, {chmod 0o1777});
"tmpfs-self/file" => (file);
"tmpfs-self/link-self" => (symlink -> "file");
"tmpfs-self/link-otheruid" => #[cfg(feature = "_test_as_root")] (symlink -> "file", {chown 12345:});
"tmpfs-self/link-othergid" => #[cfg(feature = "_test_as_root")] (symlink -> "file", {chown :12345});
"tmpfs-self/link-other" => #[cfg(feature = "_test_as_root")] (symlink -> "file", {chown 12345:12345});
"tmpfs-other" => #[cfg(feature = "_test_as_root")] (dir, {chown 12345:12345}, {chmod 0o1777});
"tmpfs-other/file" => #[cfg(feature = "_test_as_root")] (file);
"tmpfs-other/link-self" => #[cfg(feature = "_test_as_root")] (symlink -> "file");
"tmpfs-other/link-selfuid" => #[cfg(feature = "_test_as_root")] (symlink -> "file", {chown :11111});
"tmpfs-other/link-owner" => #[cfg(feature = "_test_as_root")] (symlink -> "file", {chown 12345:12345});
"tmpfs-other/link-otheruid" => #[cfg(feature = "_test_as_root")] (symlink -> "file", {chown 11111:12345});
"tmpfs-other/link-othergid" => #[cfg(feature = "_test_as_root")] (symlink -> "file", {chown 12345:11111});
"tmpfs-other/link-other" => #[cfg(feature = "_test_as_root")] (symlink -> "file", {chown 11111:11111});
"setgid-self" => (dir, {chmod 0o7777});
"setgid-other" => #[cfg(feature = "_test_as_root")] (dir, {chown 12345:12345}, {chmod 0o7777});
"deep-rmdir" => (dir);
"deep-rmdir/a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p/q/r/s/t/u/v/w/x/y/z" => (file);
"deep-rmdir/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p/q/r/s/t/u/v/w/x/y/z" => (dir);
"deep-rmdir/c/d/e/f/g/h/i/j/k/l/m/n/o/p/q/r/s/t/u/v/w/x/y/z" => (file);
"deep-rmdir/d/e/f/g/h/i/j/k/l/m/n/o/p/q/r/s/t/u/v/w/x/y/z" => (symlink -> "/");
"deep-rmdir/aa/bb/foo/bar/baz" => (file);
"deep-rmdir/aa/cc/foo/bar/baz" => (file);
"deep-rmdir/aa/dd/foo/bar/baz" => (file);
"deep-rmdir/aa/ee/foo/bar/baz" => (file);
"deep-rmdir/aa/ff/foo/bar/baz" => (file);
"deep-rmdir/aa/gg/foo/bar/baz" => (file);
"deep-rmdir/aa/hh/foo/bar/baz" => (file);
"deep-rmdir/aa/ii/foo/bar/baz" => (file);
"deep-rmdir/aa/jj/foo/bar/baz" => (file);
"deep-rmdir/aa/kk/foo/bar/baz" => (file);
"deep-rmdir/aa/ll/foo/bar/baz" => (file);
"deep-rmdir/aa/mm/foo/bar/baz" => (file);
"deep-rmdir/aa/nn/foo/bar/baz" => (file);
"deep-rmdir/aa/oo/foo/bar/baz" => (file);
"deep-rmdir/aa/pp/foo/bar/baz" => (file);
"deep-rmdir/aa/qq/foo/bar/baz" => (file);
"deep-rmdir/aa/rr/foo/bar/baz" => (file);
"deep-rmdir/aa/ss/foo/bar/baz" => (file);
"deep-rmdir/aa/tt/foo/bar/baz" => (file);
"deep-rmdir/aa/uu/foo/bar/baz" => (file);
"deep-rmdir/aa/vv/foo/bar/baz" => (file);
"deep-rmdir/aa/ww/foo/bar/baz" => (file);
"deep-rmdir/aa/xx/foo/bar/baz" => (file);
"deep-rmdir/aa/yy/foo/bar/baz" => (file);
"deep-rmdir/aa/zz/foo/bar/baz" => (file);
"deep-rmdir/bb/bb/foo/bar/baz" => (file);
"deep-rmdir/bb/cc/foo/bar/baz" => (file);
"deep-rmdir/bb/dd/foo/bar/baz" => (file);
"deep-rmdir/bb/ee/foo/bar/baz" => (file);
"deep-rmdir/bb/ff/foo/bar/baz" => (file);
"deep-rmdir/bb/gg/foo/bar/baz" => (file);
"deep-rmdir/bb/hh/foo/bar/baz" => (file);
"deep-rmdir/bb/ii/foo/bar/baz" => (file);
"deep-rmdir/bb/jj/foo/bar/baz" => (file);
"deep-rmdir/bb/kk/foo/bar/baz" => (file);
"deep-rmdir/bb/ll/foo/bar/baz" => (file);
"deep-rmdir/bb/mm/foo/bar/baz" => (file);
"deep-rmdir/bb/nn/foo/bar/baz" => (file);
"deep-rmdir/bb/oo/foo/bar/baz" => (file);
"deep-rmdir/bb/pp/foo/bar/baz" => (file);
"deep-rmdir/bb/qq/foo/bar/baz" => (file);
"deep-rmdir/bb/rr/foo/bar/baz" => (file);
"deep-rmdir/bb/ss/foo/bar/baz" => (file);
"deep-rmdir/bb/tt/foo/bar/baz" => (file);
"deep-rmdir/bb/uu/foo/bar/baz" => (file);
"deep-rmdir/bb/vv/foo/bar/baz" => (file);
"deep-rmdir/bb/ww/foo/bar/baz" => (file);
"deep-rmdir/bb/xx/foo/bar/baz" => (file);
"deep-rmdir/bb/yy/foo/bar/baz" => (file);
"deep-rmdir/bb/zz/foo/bar/baz" => (file);
"deep-rmdir/cc/bb/foo/bar/baz" => (file);
"deep-rmdir/cc/cc/foo/bar/baz" => (file);
"deep-rmdir/cc/dd/foo/bar/baz" => (file);
"deep-rmdir/cc/ee/foo/bar/baz" => (file);
"deep-rmdir/cc/ff/foo/bar/baz" => (file);
"deep-rmdir/cc/gg/foo/bar/baz" => (file);
"deep-rmdir/cc/hh/foo/bar/baz" => (file);
"deep-rmdir/cc/ii/foo/bar/baz" => (file);
"deep-rmdir/cc/jj/foo/bar/baz" => (file);
"deep-rmdir/cc/kk/foo/bar/baz" => (file);
"deep-rmdir/cc/ll/foo/bar/baz" => (file);
"deep-rmdir/cc/mm/foo/bar/baz" => (file);
"deep-rmdir/cc/nn/foo/bar/baz" => (file);
"deep-rmdir/cc/oo/foo/bar/baz" => (file);
"deep-rmdir/cc/pp/foo/bar/baz" => (file);
"deep-rmdir/cc/qq/foo/bar/baz" => (file);
"deep-rmdir/cc/rr/foo/bar/baz" => (file);
"deep-rmdir/cc/ss/foo/bar/baz" => (file);
"deep-rmdir/cc/tt/foo/bar/baz" => (file);
"deep-rmdir/cc/uu/foo/bar/baz" => (file);
"deep-rmdir/cc/vv/foo/bar/baz" => (file);
"deep-rmdir/cc/ww/foo/bar/baz" => (file);
"deep-rmdir/cc/xx/foo/bar/baz" => (file);
"deep-rmdir/cc/yy/foo/bar/baz" => (file);
"deep-rmdir/cc/zz/foo/bar/baz" => (file);
})
}
pub(crate) fn mask_nosymfollow(root: &Path) -> Result<(), Error> {
let root_prefix = root.to_path_buf();
tests_common::mount(
root_prefix.join("nosymfollow/badlink"),
MountType::RebindWithFlags {
flags: tests_common::NOSYMFOLLOW,
},
)?;
tests_common::mount(
root_prefix.join("nosymfollow/nosymdir/dir"),
MountType::RebindWithFlags {
flags: tests_common::NOSYMFOLLOW,
},
)?;
tests_common::mount(
root_prefix.join("nosymfollow/nosymdir/dir/goodlink"),
MountType::RebindWithFlags {
flags: MountFlags::empty(),
},
)?;
tests_common::mount(
root_prefix.join("nosymfollow/nosymdir/dir/foo/yessymdir"),
MountType::RebindWithFlags {
flags: MountFlags::empty(),
},
)?;
Ok(())
}
pub(crate) fn create_race_tree() -> Result<(TempDir, PathBuf), Error> {
let tmpdir = create_tree! {
"root" => (dir);
"root/a/b/c/d" => (dir);
"root/b-link" => (symlink -> "../b/../b/../b/../b/../b/../b/../b/../b/../b/../b/../b/../b/../b");
"root/c-link" => (symlink -> "../../b/c/../../b/c/../../b/c/../../b/c/../../b/c/../../b/c/../../b/c/../../b/c/../../b/c");
"root/bad-link1" => (symlink -> "/non/exist");
"root/bad-link2" => (symlink -> "/file/non/exist");
"root/etc/passwd" => (file);
"root/etc-target/passwd" => (file);
"root/etc-attack-rel-link" => (symlink -> "../../../../../../../../../../../../../../../../../../etc");
"root/etc-attack-abs-link" => (symlink -> "/../../../../../../../../../../../../../../../../../../etc");
"root/passwd-attack-rel-link" => (symlink -> "../../../../../../../../../../../../../../../../../../etc/passwd");
"root/passwd-attack-abs-link" => (symlink -> "/../../../../../../../../../../../../../../../../../../etc/passwd");
"root/file" => (file);
"outsideroot" => (dir);
};
let root: PathBuf = [tmpdir.as_ref(), Path::new("root")].iter().collect();
Ok((tmpdir, root))
}