Skip to content

Commit 72914bc

Browse files
committed
fix(cp): setuid/setgid/sticky bits
1 parent 9b5a9b7 commit 72914bc

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
)
@@ -2391,6 +2395,7 @@ fn calculate_dest_permissions(
23912395
options.attributes.mode,
23922396
false,
23932397
dest_metadata.is_none(),
2398+
false,
23942399
source_metadata.permissions().mode(),
23952400
orig_umask,
23962401
)
@@ -2684,51 +2689,52 @@ fn is_stream(metadata: &Metadata) -> bool {
26842689
}
26852690

26862691
#[cfg(unix)]
2692+
#[allow(clippy::unnecessary_cast)]
26872693
/// Calculate the final permissions mode.
2688-
///
2689-
/// If the dir/file was not created, then returns `None` except when preserving mode in which case
2690-
/// the source mode is returned.
2691-
///
2692-
/// Otherwise, see [`handle_created_mode`].
26932694
fn handle_mode(
26942695
mode: Preserve,
26952696
is_dir: bool,
26962697
was_created: bool,
2698+
failed_to_set_ownership: bool,
26972699
source_mode: u32,
26982700
umask: u32,
26992701
) -> Option<u32> {
2700-
if was_created {
2701-
Some(handle_created_mode(mode, is_dir, source_mode, umask))
2702-
} else {
2703-
match mode {
2704-
Preserve::Yes { required: true } => Some(source_mode),
2705-
_ => None,
2706-
}
2707-
}
2708-
}
2709-
2710-
#[cfg(unix)]
2711-
#[inline]
2712-
/// Calculate the final permissions mode when the dir/file is newly created.
2713-
///
2714-
/// If no preserving mode, return 0o777(dir)/0o666(file) & !umask.
2715-
/// If default, return source_mode & 0o777 & !umask.
2716-
/// If preserving mode, return source_mode & 0o777.
2717-
fn handle_created_mode(mode: Preserve, is_dir: bool, source_mode: u32, umask: u32) -> u32 {
2718-
use libc::{S_IRGRP, S_IROTH, S_IRUSR, S_IRWXG, S_IRWXO, S_IRWXU, S_IWGRP, S_IWOTH, S_IWUSR};
2719-
#[allow(clippy::unnecessary_cast)]
2720-
const MODE_RW_UGO: u32 = (S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH) as u32;
2721-
#[allow(clippy::unnecessary_cast)]
2722-
const S_IRWXUGO: u32 = (S_IRWXU | S_IRWXG | S_IRWXO) as u32;
2702+
match (was_created, mode) {
2703+
// If preserving mode, return Some source_mode.
2704+
(_, Preserve::Yes { .. }) => {
2705+
use libc::{S_ISGID, S_ISUID};
2706+
const UID_GID_MASK: u32 = !(S_ISGID | S_ISUID) as u32;
2707+
2708+
Some(if failed_to_set_ownership {
2709+
// "Additionally, the -p option explicitly requires that all set-user-ID and
2710+
// set-group-ID permissions be discarded if any of the owner or group IDs cannot be
2711+
// set. This is to keep users from unintentionally giving away special privilege
2712+
// when copying programs."
2713+
source_mode & UID_GID_MASK
2714+
} else {
2715+
source_mode
2716+
})
2717+
}
2718+
// If was not created (destination existed), return None
2719+
(false, _) => None,
2720+
// If was created (destination did not exist), return Some
2721+
// If no preserving mode, return 0o777(dir)/0o666(file) & !umask.
2722+
// If default, return source_mode & 0o777 & !umask.
2723+
(true, Preserve::No { explicit }) => {
2724+
use libc::{
2725+
S_IRGRP, S_IROTH, S_IRUSR, S_IRWXG, S_IRWXO, S_IRWXU, S_IWGRP, S_IWOTH, S_IWUSR,
2726+
};
2727+
const MODE_RW_UGO: u32 =
2728+
(S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH) as u32;
2729+
const S_IRWXUGO: u32 = (S_IRWXU | S_IRWXG | S_IRWXO) as u32;
27232730

2724-
if let Preserve::No { explicit } = mode {
2725-
(if explicit {
2726-
if is_dir { S_IRWXUGO } else { MODE_RW_UGO }
2727-
} else {
2728-
source_mode & S_IRWXUGO
2729-
}) & !umask
2730-
} else {
2731-
source_mode & S_IRWXUGO
2731+
let mode = if explicit {
2732+
if is_dir { S_IRWXUGO } else { MODE_RW_UGO }
2733+
} else {
2734+
source_mode & S_IRWXUGO
2735+
};
2736+
Some(mode & !umask)
2737+
}
27322738
}
27332739
}
27342740

0 commit comments

Comments
 (0)