diff --git a/src/uu/cp/src/cp.rs b/src/uu/cp/src/cp.rs index b333d4a78..45e9b69ac 100644 --- a/src/uu/cp/src/cp.rs +++ b/src/uu/cp/src/cp.rs @@ -1663,6 +1663,7 @@ pub(crate) fn copy_attributes( let context = &*format!("{} -> {}", source.quote(), dest.quote()); let source_metadata = fs::symlink_metadata(source).map_err(|e| CpError::IoErrContext(e, context.to_owned()))?; + #[cfg(unix)] let mut ownership_failed = false; // Ownership must be changed first to avoid interfering with mode change. @@ -1710,10 +1711,33 @@ pub(crate) fn copy_attributes( // do nothing, since every symbolic link has the same // permissions. if !dest.is_symlink() { - let mut permissions = source_metadata.permissions(); - if ownership_failed { - permissions.set_mode(permissions.mode() & !(libc::S_ISUID | libc::S_ISGID)); - } + let permissions = source_metadata.permissions(); + + #[cfg(unix)] + let permissions = { + let mut perms = permissions; + if ownership_failed { + #[cfg(not(any( + target_os = "android", + target_os = "macos", + target_os = "freebsd", + target_os = "redox", + )))] + let mask = libc::S_ISUID | libc::S_ISGID; + + #[cfg(any( + target_os = "android", + target_os = "macos", + target_os = "freebsd", + target_os = "redox", + ))] + let mask = (libc::S_ISUID | libc::S_ISGID) as u32; + + perms.set_mode(perms.mode() & !mask); + } + perms + }; + fs::set_permissions(dest, permissions) .map_err(|e| CpError::IoErrContext(e, context.to_owned()))?; // FIXME: Implement this for windows as well diff --git a/tests/by-util/test_cp.rs b/tests/by-util/test_cp.rs index 2563e533a..6e9505542 100644 --- a/tests/by-util/test_cp.rs +++ b/tests/by-util/test_cp.rs @@ -7400,3 +7400,38 @@ fn test_cp_recurse_verbose_output_with_symlink_already_exists() { .no_stderr() .stdout_is(output); } + +#[test] +#[cfg(unix)] +fn test_cp_preserve_strip_setuid() { + let (at, mut ucmd) = at_and_ucmd!(); + let source = "/bin/sh"; + let destination = "sh_copy"; + + ucmd.arg("-p").arg(source).arg(destination).succeeds(); + + use std::os::unix::fs::PermissionsExt; + let mode = at.metadata(destination).permissions().mode(); + + #[cfg(not(any( + target_os = "android", + target_os = "macos", + target_os = "freebsd", + target_os = "redox", + )))] + let mask = libc::S_ISUID | libc::S_ISGID; + + #[cfg(any( + target_os = "android", + target_os = "macos", + target_os = "freebsd", + target_os = "redox", + ))] + let mask = (libc::S_ISUID | libc::S_ISGID) as u32; + + assert_eq!( + mode & mask, + 0, + "SetUID/SetGID need to be stripped if ownership preservation fails." + ); +}