Skip to content

Commit c022fbc

Browse files
committed
fix(cp): setuid/setgid/sticky bits
1 parent 124d55a commit c022fbc

1 file changed

Lines changed: 43 additions & 37 deletions

File tree

src/uu/cp/src/cp.rs

Lines changed: 43 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1693,7 +1693,7 @@ impl OverwriteMode {
16931693
/// Note: ENOTSUP/EOPNOTSUPP errors are silently ignored when not required, as per GNU cp
16941694
/// documentation: "Try to preserve SELinux security context and extended attributes (xattr),
16951695
/// but ignore any failure to do that and print no corresponding diagnostic."
1696-
fn handle_preserve<F: Fn() -> CopyResult<()>>(p: Preserve, f: F) -> CopyResult<()> {
1696+
fn handle_preserve<F: FnMut() -> CopyResult<()>>(p: Preserve, mut f: F) -> CopyResult<()> {
16971697
match p {
16981698
Preserve::No { .. } => {}
16991699
Preserve::Yes { required } => {
@@ -1789,6 +1789,8 @@ pub(crate) fn copy_attributes(
17891789
let source_metadata = fs::symlink_metadata(source)
17901790
.map_err(|e| CpError::IoErrContext(e, context_for(source, dest)))?;
17911791

1792+
let mut failed_to_set_ownership = false;
1793+
17921794
// Ownership must be changed first to avoid interfering with mode change.
17931795
#[cfg(unix)]
17941796
handle_preserve(attributes.ownership, || -> CopyResult<()> {
@@ -1821,6 +1823,7 @@ pub(crate) fn copy_attributes(
18211823
// gnu compatibility: cp doesn't report an error if it fails to set the ownership,
18221824
// and will fall back to changing only the gid if possible.
18231825
if try_chown(Some(dest_uid)).is_err() {
1826+
failed_to_set_ownership = true;
18241827
let _ = try_chown(None);
18251828
}
18261829
Ok(())
@@ -1872,6 +1875,7 @@ pub(crate) fn copy_attributes(
18721875
attributes.mode,
18731876
dest.is_dir(),
18741877
was_created,
1878+
failed_to_set_ownership,
18751879
source_metadata.permissions().mode(),
18761880
orig_umask,
18771881
)
@@ -2401,6 +2405,7 @@ fn calculate_dest_permissions(
24012405
options.attributes.mode,
24022406
false,
24032407
dest_metadata.is_none(),
2408+
false,
24042409
source_metadata.permissions().mode(),
24052410
orig_umask,
24062411
)
@@ -2694,51 +2699,52 @@ fn is_stream(metadata: &Metadata) -> bool {
26942699
}
26952700

26962701
#[cfg(unix)]
2702+
#[allow(clippy::unnecessary_cast)]
26972703
/// Calculate the final permissions mode.
2698-
///
2699-
/// If the dir/file was not created, then returns `None` except when preserving mode in which case
2700-
/// the source mode is returned.
2701-
///
2702-
/// Otherwise, see [`handle_created_mode`].
27032704
fn handle_mode(
27042705
mode: Preserve,
27052706
is_dir: bool,
27062707
was_created: bool,
2708+
failed_to_set_ownership: bool,
27072709
source_mode: u32,
27082710
umask: u32,
27092711
) -> Option<u32> {
2710-
if was_created {
2711-
Some(handle_created_mode(mode, is_dir, source_mode, umask))
2712-
} else {
2713-
match mode {
2714-
Preserve::Yes { required: true } => Some(source_mode),
2715-
_ => None,
2716-
}
2717-
}
2718-
}
2719-
2720-
#[cfg(unix)]
2721-
#[inline]
2722-
/// Calculate the final permissions mode when the dir/file is newly created.
2723-
///
2724-
/// If no preserving mode, return 0o777(dir)/0o666(file) & !umask.
2725-
/// If default, return source_mode & 0o777 & !umask.
2726-
/// If preserving mode, return source_mode & 0o777.
2727-
fn handle_created_mode(mode: Preserve, is_dir: bool, source_mode: u32, umask: u32) -> u32 {
2728-
use libc::{S_IRGRP, S_IROTH, S_IRUSR, S_IRWXG, S_IRWXO, S_IRWXU, S_IWGRP, S_IWOTH, S_IWUSR};
2729-
#[allow(clippy::unnecessary_cast)]
2730-
const MODE_RW_UGO: u32 = (S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH) as u32;
2731-
#[allow(clippy::unnecessary_cast)]
2732-
const S_IRWXUGO: u32 = (S_IRWXU | S_IRWXG | S_IRWXO) as u32;
2712+
match (was_created, mode) {
2713+
// If preserving mode, return Some source_mode.
2714+
(_, Preserve::Yes { .. }) => {
2715+
use libc::{S_ISGID, S_ISUID};
2716+
const UID_GID_MASK: u32 = !(S_ISGID | S_ISUID) as u32;
2717+
2718+
Some(if failed_to_set_ownership {
2719+
// "Additionally, the -p option explicitly requires that all set-user-ID and
2720+
// set-group-ID permissions be discarded if any of the owner or group IDs cannot be
2721+
// set. This is to keep users from unintentionally giving away special privilege
2722+
// when copying programs."
2723+
source_mode & UID_GID_MASK
2724+
} else {
2725+
source_mode
2726+
})
2727+
}
2728+
// If was not created (destination existed), return None
2729+
(false, _) => None,
2730+
// If was created (destination did not exist), return Some
2731+
// If no preserving mode, return 0o777(dir)/0o666(file) & !umask.
2732+
// If default, return source_mode & 0o777 & !umask.
2733+
(true, Preserve::No { explicit }) => {
2734+
use libc::{
2735+
S_IRGRP, S_IROTH, S_IRUSR, S_IRWXG, S_IRWXO, S_IRWXU, S_IWGRP, S_IWOTH, S_IWUSR,
2736+
};
2737+
const MODE_RW_UGO: u32 =
2738+
(S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH) as u32;
2739+
const S_IRWXUGO: u32 = (S_IRWXU | S_IRWXG | S_IRWXO) as u32;
27332740

2734-
if let Preserve::No { explicit } = mode {
2735-
(if explicit {
2736-
if is_dir { S_IRWXUGO } else { MODE_RW_UGO }
2737-
} else {
2738-
source_mode & S_IRWXUGO
2739-
}) & !umask
2740-
} else {
2741-
source_mode & S_IRWXUGO
2741+
let mode = if explicit {
2742+
if is_dir { S_IRWXUGO } else { MODE_RW_UGO }
2743+
} else {
2744+
source_mode & S_IRWXUGO
2745+
};
2746+
Some(mode & !umask)
2747+
}
27422748
}
27432749
}
27442750

0 commit comments

Comments
 (0)