From eaf2a71851f539f9cea949006cbd72b62308d949 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Mon, 19 Oct 2026 09:33:01 +0200 Subject: [PATCH 1/2] fs::Dir tests: ensure remove and rename do not follow symlinks --- library/std/src/fs/tests.rs | 39 +++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/library/std/src/fs/tests.rs b/library/std/src/fs/tests.rs index 605ad25270a63..15b81210928d4 100644 --- a/library/std/src/fs/tests.rs +++ b/library/std/src/fs/tests.rs @@ -3250,6 +3250,14 @@ fn test_dir_remove_file() { check!(f.flush()); drop(f); let dir = check!(Dir::open(tmpdir.path())); + + if got_symlink_permission(&tmpdir) { + // Removing a symlink should remove the link, not the destination. + check!(symlink_file("foo.txt", &tmpdir.join("link"))); + check!(dir.remove_file("link")); + assert!(check!(dir.metadata("foo.txt")).is_file()); // file still exists + } + check!(dir.remove_file("foo.txt")); assert!(!matches!(exists(tmpdir.join("foo.txt")), Ok(true))); } @@ -3266,6 +3274,14 @@ fn test_dir_rename_file() { let mut buf = [0u8; 3]; check!(f.read_exact(&mut buf)); assert_eq!(b"bar", &buf); + + // Renaming a symlink should rename the link, not the destination. + if got_symlink_permission(&tmpdir) { + check!(symlink_file("baz.txt", &tmpdir.join("link"))); + check!(dir.rename("link", &dir, "link-renamed")); + assert!(check!(dir.symlink_metadata("baz.txt")).is_file()); // file still exists + assert!(check!(dir.symlink_metadata("link-renamed")).is_symlink()); // symlink has new name + } } // FIXME: re-enable once QNX fixes TOCTOU bug for fs::remove_dir @@ -3277,6 +3293,21 @@ fn test_dir_remove_dir() { let tmpdir = tmpdir(); check!(fs::create_dir(tmpdir.join("foo"))); let dir = check!(Dir::open(tmpdir.path())); + + if got_symlink_permission(&tmpdir) { + // Removing a symlink should remove the link, not the destination. + check!(symlink_dir("foo", &tmpdir.join("link"))); + if cfg!(windows) { + // directory symlinks need `remove_dir` on Windows + dir.remove_file("link").unwrap_err(); + check!(dir.remove_dir("link")); + } else { + dir.remove_dir("link").unwrap_err(); + check!(dir.remove_file("link")); + } + assert!(check!(dir.metadata("foo")).is_dir()); // dir still exists + } + check!(dir.remove_dir("foo")); assert!(!matches!(exists(tmpdir.join("foo")), Ok(true))); } @@ -3289,6 +3320,14 @@ fn test_dir_rename_dir() { check!(dir.rename("foo", &dir, "baz")); let m = check!(tmpdir.join("baz").metadata()); assert!(m.is_dir()); + + // Renaming a symlink should rename the link, not the destination. + if got_symlink_permission(&tmpdir) { + check!(symlink_dir("baz", &tmpdir.join("link"))); + check!(dir.rename("link", &dir, "link-renamed")); + assert!(check!(dir.symlink_metadata("baz")).is_dir()); // dir still exists + assert!(check!(dir.symlink_metadata("link-renamed")).is_symlink()); // symlink has new name + } } #[test] From 811ebf70afd33068a39de321b79d2854e8c80a26 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Mon, 19 Oct 2026 09:35:35 +0200 Subject: [PATCH 2/2] windows: fix Dir::{remove_*,rename} symlink behavior --- library/std/src/sys/fs/windows/dir.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/library/std/src/sys/fs/windows/dir.rs b/library/std/src/sys/fs/windows/dir.rs index 60f8c2522de26..4a6ea4f20f19d 100644 --- a/library/std/src/sys/fs/windows/dir.rs +++ b/library/std/src/sys/fs/windows/dir.rs @@ -157,7 +157,8 @@ impl Dir { fn remove_native(&self, path: &[u16], dir: bool) -> io::Result<()> { let mut opts = OpenOptions::new(); opts.access_mode(c::DELETE); - let create_opt = if dir { c::FILE_DIRECTORY_FILE } else { c::FILE_NON_DIRECTORY_FILE }; + let mut create_opt = if dir { c::FILE_DIRECTORY_FILE } else { c::FILE_NON_DIRECTORY_FILE }; + create_opt |= c::FILE_OPEN_REPARSE_POINT; let handle = self.open_file_native(path, &opts, create_opt)?; File::from_inner(handle).delete().io_result() } @@ -165,9 +166,8 @@ impl Dir { fn rename_native(&self, from: &[u16], to_dir: &Self, to: &[u16]) -> io::Result<()> { let mut opts = OpenOptions::new(); opts.access_mode(c::DELETE); - // FIXME: custom_flags is ignored by `open_file_native`! - opts.custom_flags(c::FILE_FLAG_OPEN_REPARSE_POINT | c::FILE_FLAG_BACKUP_SEMANTICS); - let handle = self.open_file_native(from, &opts, /* create_opt */ 0)?; + let handle = + self.open_file_native(from, &opts, /* create_opt */ c::FILE_OPEN_REPARSE_POINT)?; // Calculate the layout of the `FILE_RENAME_INFORMATION` we pass to `NtSetInformationFile` // This is a dynamically sized struct so we need to get the position of the last field to calculate the actual size. const too_long_err: io::Error =