Skip to content

Commit 2daf958

Browse files
committed
cp: strip setuid & setgid when preserving ownership or group fails
1 parent 87ba3af commit 2daf958

File tree

2 files changed

+112
-1
lines changed

2 files changed

+112
-1
lines changed

src/uu/cp/src/cp.rs

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1708,7 +1708,23 @@ pub(crate) fn copy_attributes(
17081708
// do nothing, since every symbolic link has the same
17091709
// permissions.
17101710
if !dest.is_symlink() {
1711-
fs::set_permissions(dest, source_metadata.permissions())
1711+
// gnu compatibility: cp strips both setuid & setgid bits if preserving either ownership or group fails
1712+
let mut perms = source_metadata.permissions();
1713+
#[cfg(unix)]
1714+
{
1715+
use std::os::unix::fs::MetadataExt;
1716+
1717+
let dest_metadata = fs::symlink_metadata(dest)
1718+
.map_err(|e| CpError::IoErrContext(e, context.to_owned()))?;
1719+
1720+
if dest_metadata.uid() != source_metadata.uid()
1721+
|| dest_metadata.gid() != source_metadata.gid()
1722+
{
1723+
let mode = perms.mode() & !0o6000;
1724+
perms.set_mode(mode)
1725+
}
1726+
}
1727+
fs::set_permissions(dest, perms)
17121728
.map_err(|e| CpError::IoErrContext(e, context.to_owned()))?;
17131729
// FIXME: Implement this for windows as well
17141730
#[cfg(feature = "feat_acl")]

tests/by-util/test_cp.rs

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7400,3 +7400,98 @@ fn test_cp_recurse_verbose_output_with_symlink_already_exists() {
74007400
.no_stderr()
74017401
.stdout_is(output);
74027402
}
7403+
7404+
#[test]
7405+
#[cfg(unix)]
7406+
fn test_cp_strip_uid_gid_preserve_ownership_fails() {
7407+
let scene = TestScenario::new(util_name!());
7408+
let at = &scene.fixtures;
7409+
let ucmd = &mut scene.ucmd();
7410+
7411+
let src = "src";
7412+
let dest_dir = "dir";
7413+
let dest = "dir/dest";
7414+
7415+
// Test must be run as root (or with `sudo -E`)
7416+
if scene.cmd("whoami").run().stdout_str() != "root\n" {
7417+
return;
7418+
}
7419+
7420+
at.touch(src);
7421+
at.set_mode(src, 0o6755);
7422+
7423+
at.mkdir(dest_dir);
7424+
at.set_mode(dest_dir, 0o777);
7425+
7426+
// Need to drop privileges for preserving ownership to fail
7427+
unsafe {
7428+
libc::seteuid(1000);
7429+
}
7430+
7431+
ucmd.arg("-p").arg(src).arg(dest).succeeds();
7432+
7433+
// When preserving ownership fails, setuid and setgid bits should be stripped
7434+
let dest_metadata = at.metadata(&dest);
7435+
assert_eq!(
7436+
dest_metadata.mode() & 0o6755,
7437+
0o755,
7438+
"setuid or setgid not stripped"
7439+
);
7440+
}
7441+
7442+
#[test]
7443+
#[cfg(unix)]
7444+
fn test_cp_strip_uid_gid_preserve_group_fails() {
7445+
let scene = TestScenario::new(util_name!());
7446+
let at = &scene.fixtures;
7447+
let ucmd = &mut scene.ucmd();
7448+
7449+
let dir = "dir";
7450+
let src = "dir/src";
7451+
let dest = "dir/dest";
7452+
7453+
// Test must be run as root (or with `sudo -E`)
7454+
if scene.cmd("whoami").run().stdout_str() != "root\n" {
7455+
return;
7456+
}
7457+
7458+
at.mkdir(dir);
7459+
at.set_mode(dir, 0o777);
7460+
7461+
// Drop privileges before creating file
7462+
// Will need root to set group later
7463+
let orig_euid = unsafe { libc::geteuid() };
7464+
let orig_egid = unsafe { libc::getegid() };
7465+
7466+
unsafe {
7467+
libc::seteuid(1000);
7468+
libc::setegid(1000);
7469+
}
7470+
7471+
at.touch(src);
7472+
at.set_mode(src, 0o6755);
7473+
7474+
// Escalate privileges to set group
7475+
unsafe {
7476+
libc::seteuid(orig_euid);
7477+
libc::setegid(orig_egid);
7478+
}
7479+
7480+
scene.cmd("chgrp").arg("0").arg(src).succeeds();
7481+
7482+
// Drop again before running cp
7483+
unsafe {
7484+
libc::seteuid(1000);
7485+
libc::setegid(1000);
7486+
}
7487+
7488+
ucmd.arg("-p").arg(src).arg(dest).succeeds();
7489+
7490+
// When preserving group fails, setuid and setgid bits should be stripped
7491+
let dest_metadata = at.metadata(&dest);
7492+
assert_eq!(
7493+
dest_metadata.mode() & 0o6755,
7494+
0o755,
7495+
"setuid or setgid not stripped"
7496+
);
7497+
}

0 commit comments

Comments
 (0)