diff --git a/cap-primitives/src/fs/manually/open.rs b/cap-primitives/src/fs/manually/open.rs index 0bda7514..20a4b546 100644 --- a/cap-primitives/src/fs/manually/open.rs +++ b/cap-primitives/src/fs/manually/open.rs @@ -222,23 +222,12 @@ impl<'start> Context<'start> { dir_options() }; - // If the last path component ended in a slash, re-add the slash, - // as Rust's `Path` will have removed it, and we need it to get the - // same behavior from the OS. - let use_path: Cow = if self.components.is_empty() && self.trailing_slash { - let mut p = one.to_os_string(); - p.push("/"); - Cow::Owned(p) - } else { - Cow::Borrowed(one) - }; - let dir_required = self.dir_required || use_options.dir_required; #[allow(clippy::redundant_clone)] match open_unchecked( &self.base, - use_path.as_ref(), + one.as_ref(), use_options .clone() .follow(FollowSymlinks::No) diff --git a/tests/fs_additional.rs b/tests/fs_additional.rs index bc220df0..967a3cab 100644 --- a/tests/fs_additional.rs +++ b/tests/fs_additional.rs @@ -5,6 +5,7 @@ #[macro_use] mod sys_common; +use cap_std::ambient_authority; use cap_std::fs::{Dir, DirBuilder, OpenOptions}; use cap_std::time::SystemClock; use std::io::{self, Read, Write}; @@ -1412,3 +1413,99 @@ fn statat_slash() { ); } } + +/// Test interactions between symlinks and trailing slashes. +#[test] +fn trailing_slash_symlink() { + let tmpdir = tmpdir(); + + check!(tmpdir.create_dir("sandbox")); + check!(symlink_dir("../outside", &tmpdir, "sandbox/hidden")); + check!(symlink_dir("hidden/", &tmpdir, "sandbox/indirect")); + + let sandbox = check!(tmpdir.open_dir("sandbox")); + + for path in ["hidden", "hidden/", "indirect", "indirect/"] { + error!( + sandbox.open_dir(path), + "a path led outside of the filesystem" + ); + error!( + sandbox.read_dir(path), + "a path led outside of the filesystem" + ); + error!( + sandbox.canonicalize(path), + "a path led outside of the filesystem" + ); + } +} + +/// Similar to `trailing_slash_symlink`, but populates the test directory +/// outside the sandbox, so it can cover more cases. +#[test] +fn trailing_slash_symlink_more() { + let tmpdir = tempfile::tempdir().unwrap(); + + check!(std::fs::create_dir(tmpdir.path().join("sandbox"))); + #[cfg(unix)] + { + check!(std::os::unix::fs::symlink( + "../outside", + tmpdir.path().join("sandbox/hidden") + )); + check!(std::os::unix::fs::symlink( + "hidden/", + tmpdir.path().join("sandbox/indirect") + )); + check!(std::os::unix::fs::symlink( + "/.", + tmpdir.path().join("sandbox/root_link") + )); + } + #[cfg(windows)] + { + check!(std::os::windows::fs::symlink_dir( + "../outside", + tmpdir.path().join("sandbox/hidden") + )); + check!(std::os::windows::fs::symlink_dir( + "hidden/", + tmpdir.path().join("sandbox/indirect") + )); + check!(std::os::windows::fs::symlink_dir( + "/.", + tmpdir.path().join("sandbox/root_link") + )); + } + #[cfg(not(any(unix, windows)))] + { + compile_error!("not implemented yet"); + } + + let tmpdir = check!(Dir::open_ambient_dir(tmpdir.path(), ambient_authority())); + + let sandbox = check!(tmpdir.open_dir("sandbox")); + + for path in [ + "hidden", + "hidden/", + "indirect", + "indirect/", + "root_link", + "root_link/", + ] { + error!( + sandbox.open_dir(path), + "a path led outside of the filesystem" + ); + error!( + sandbox.read_dir(path), + "a path led outside of the filesystem" + ); + error!( + sandbox.canonicalize(path), + "a path led outside of the filesystem" + ); + } +}