Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 17 additions & 1 deletion src/uu/cp/src/cp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1708,7 +1708,23 @@ pub(crate) fn copy_attributes(
// do nothing, since every symbolic link has the same
// permissions.
if !dest.is_symlink() {
fs::set_permissions(dest, source_metadata.permissions())
// gnu compatibility: cp strips both setuid & setgid bits if preserving either ownership or group fails
let mut perms = source_metadata.permissions();
#[cfg(unix)]
{
use std::os::unix::fs::MetadataExt;

let dest_metadata = fs::symlink_metadata(dest)
.map_err(|e| CpError::IoErrContext(e, context.to_owned()))?;

if dest_metadata.uid() != source_metadata.uid()
|| dest_metadata.gid() != source_metadata.gid()
{
let mode = perms.mode() & !0o6000;
perms.set_mode(mode);
}
}
fs::set_permissions(dest, perms)
.map_err(|e| CpError::IoErrContext(e, context.to_owned()))?;
// FIXME: Implement this for windows as well
#[cfg(feature = "feat_acl")]
Expand Down
95 changes: 95 additions & 0 deletions tests/by-util/test_cp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7400,3 +7400,98 @@
.no_stderr()
.stdout_is(output);
}

#[test]
#[cfg(unix)]
fn test_cp_strip_uid_gid_preserve_ownership_fails() {
let scene = TestScenario::new(util_name!());
let at = &scene.fixtures;
let ucmd = &mut scene.ucmd();

let src = "src";
let dest_dir = "dir";
let dest = "dir/dest";

// Test must be run as root (or with `sudo -E`)
if scene.cmd("whoami").run().stdout_str() != "root\n" {
return;
}

at.touch(src);
at.set_mode(src, 0o6755);

at.mkdir(dest_dir);
at.set_mode(dest_dir, 0o777);

// Need to drop privileges for preserving ownership to fail
unsafe {
libc::seteuid(1000);

Check failure on line 7428 in tests/by-util/test_cp.rs

View workflow job for this annotation

GitHub Actions / Style/spelling (ubuntu-latest, feat_os_unix)

ERROR: `cspell`: Unknown word 'seteuid' (file:'tests/by-util/test_cp.rs', line:7428)
}

ucmd.arg("-p").arg(src).arg(dest).succeeds();

// When preserving ownership fails, setuid and setgid bits should be stripped
let dest_metadata = at.metadata(dest);
assert_eq!(
dest_metadata.mode() & 0o6755,
0o755,
"setuid or setgid not stripped"
);
}

#[test]
#[cfg(unix)]
fn test_cp_strip_uid_gid_preserve_group_fails() {
let scene = TestScenario::new(util_name!());
let at = &scene.fixtures;
let ucmd = &mut scene.ucmd();

let dir = "dir";
let src = "dir/src";
let dest = "dir/dest";

// Test must be run as root (or with `sudo -E`)
if scene.cmd("whoami").run().stdout_str() != "root\n" {
return;
}

at.mkdir(dir);
at.set_mode(dir, 0o777);

// Drop privileges before creating file
// Will need root to set group later
let orig_euid = unsafe { libc::geteuid() };

Check failure on line 7463 in tests/by-util/test_cp.rs

View workflow job for this annotation

GitHub Actions / Style/spelling (ubuntu-latest, feat_os_unix)

ERROR: `cspell`: Unknown word 'euid' (file:'tests/by-util/test_cp.rs', line:7463)
let orig_egid = unsafe { libc::getegid() };

Check failure on line 7464 in tests/by-util/test_cp.rs

View workflow job for this annotation

GitHub Actions / Style/spelling (ubuntu-latest, feat_os_unix)

ERROR: `cspell`: Unknown word 'egid' (file:'tests/by-util/test_cp.rs', line:7464)

unsafe {
libc::seteuid(1000);

Check failure on line 7467 in tests/by-util/test_cp.rs

View workflow job for this annotation

GitHub Actions / Style/spelling (ubuntu-latest, feat_os_unix)

ERROR: `cspell`: Unknown word 'seteuid' (file:'tests/by-util/test_cp.rs', line:7467)
libc::setegid(1000);

Check failure on line 7468 in tests/by-util/test_cp.rs

View workflow job for this annotation

GitHub Actions / Style/spelling (ubuntu-latest, feat_os_unix)

ERROR: `cspell`: Unknown word 'setegid' (file:'tests/by-util/test_cp.rs', line:7468)
}

at.touch(src);
at.set_mode(src, 0o6755);

// Escalate privileges to set group
unsafe {
libc::seteuid(orig_euid);

Check failure on line 7476 in tests/by-util/test_cp.rs

View workflow job for this annotation

GitHub Actions / Style/spelling (ubuntu-latest, feat_os_unix)

ERROR: `cspell`: Unknown word 'euid' (file:'tests/by-util/test_cp.rs', line:7476)

Check failure on line 7476 in tests/by-util/test_cp.rs

View workflow job for this annotation

GitHub Actions / Style/spelling (ubuntu-latest, feat_os_unix)

ERROR: `cspell`: Unknown word 'seteuid' (file:'tests/by-util/test_cp.rs', line:7476)
libc::setegid(orig_egid);

Check failure on line 7477 in tests/by-util/test_cp.rs

View workflow job for this annotation

GitHub Actions / Style/spelling (ubuntu-latest, feat_os_unix)

ERROR: `cspell`: Unknown word 'egid' (file:'tests/by-util/test_cp.rs', line:7477)

Check failure on line 7477 in tests/by-util/test_cp.rs

View workflow job for this annotation

GitHub Actions / Style/spelling (ubuntu-latest, feat_os_unix)

ERROR: `cspell`: Unknown word 'setegid' (file:'tests/by-util/test_cp.rs', line:7477)
}

scene.cmd("chgrp").arg("0").arg(src).succeeds();

// Drop again before running cp
unsafe {
libc::seteuid(1000);

Check failure on line 7484 in tests/by-util/test_cp.rs

View workflow job for this annotation

GitHub Actions / Style/spelling (ubuntu-latest, feat_os_unix)

ERROR: `cspell`: Unknown word 'seteuid' (file:'tests/by-util/test_cp.rs', line:7484)
libc::setegid(1000);
}

ucmd.arg("-p").arg(src).arg(dest).succeeds();

// When preserving group fails, setuid and setgid bits should be stripped
let dest_metadata = at.metadata(dest);
assert_eq!(
dest_metadata.mode() & 0o6755,
0o755,
"setuid or setgid not stripped"
);
}
Loading