chown: -h on symlink to directory operates on link, not target (#12522)

closes: #11887
This commit is contained in:
Lorenzo Rossi
2026-05-30 14:29:58 +02:00
committed by GitHub
parent 66d9ae61c6
commit ed15a26a6a
2 changed files with 48 additions and 1 deletions
+2 -1
View File
@@ -309,7 +309,8 @@ impl ChownExecutor {
let ret = if self.matched(meta.uid(), meta.gid()) {
// Use safe syscalls for root directory to prevent TOCTOU attacks on Linux
#[cfg(target_os = "linux")]
let chown_result = if path.is_dir() {
// We cannot check path.is_dir() here, as this would resolve symlinks
let chown_result = if meta.is_dir() {
// For directories on Linux, use safe traversal from the start
match DirFd::open(path, SymlinkBehavior::Follow) {
Ok(dir_fd) => self
+46
View File
@@ -4,6 +4,7 @@
// file that was distributed with this source code.
// spell-checker:ignore (words) agroupthatdoesntexist auserthatdoesntexist cuuser groupname notexisting passgrp
use std::os::unix::fs::MetadataExt;
use uutests::util::{CmdResult, TestScenario, is_ci, run_ucmd_as_root};
use uutests::util_name;
use uutests::{at_and_ucmd, new_ucmd};
@@ -897,3 +898,48 @@ fn test_chown_reference_file() {
.stderr_contains("ownership of 'b' retained as")
.no_stdout();
}
#[test]
#[cfg(unix)]
fn test_chown_no_dereference_symlink_to_dir() {
let scene = TestScenario::new(util_name!());
let at = &scene.fixtures;
let result = scene.cmd("whoami").run();
if skipping_test_is_okay(&result, "whoami: cannot find name for user ID") {
return;
}
let user_name = String::from(result.stdout_str().trim());
assert!(!user_name.is_empty());
at.mkdir("dir");
at.symlink_dir("dir", "link_to_dir");
let link_meta_before = std::fs::symlink_metadata(at.plus("link_to_dir")).unwrap();
let link_ctime_before = (link_meta_before.ctime(), link_meta_before.ctime_nsec());
let dir_meta_before = std::fs::metadata(at.plus("dir")).unwrap();
let dir_ctime_before = (dir_meta_before.ctime(), dir_meta_before.ctime_nsec());
scene
.ucmd()
.arg("--no-dereference")
.arg(&user_name)
.arg("link_to_dir")
.succeeds();
let link_meta_after = std::fs::symlink_metadata(at.plus("link_to_dir")).unwrap();
let link_ctime_after = (link_meta_after.ctime(), link_meta_after.ctime_nsec());
let dir_meta_after = std::fs::metadata(at.plus("dir")).unwrap();
let dir_ctime_after = (dir_meta_after.ctime(), dir_meta_after.ctime_nsec());
assert_ne!(
link_ctime_before, link_ctime_after,
"link's ctime should have advanced"
);
assert_eq!(
dir_ctime_before, dir_ctime_after,
"dir's ctime should not have changed"
);
}