VYPR
Medium severity4.4NVD Advisory· Published Apr 22, 2026· Updated May 4, 2026

CVE-2026-35358

CVE-2026-35358

Description

The cp utility in uutils coreutils, when performing recursive copies (-R), incorrectly treats character and block device nodes as stream sources rather than preserving them. Because the implementation reads bytes into regular files at the destination instead of using mknod, device semantics are destroyed (e.g., /dev/null becomes a regular file). This behavior can lead to runtime denial of service through disk exhaustion or process hangs when reading from unbounded device nodes.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
coreutilscrates.io
< 0.7.00.7.0

Affected products

2
  • Uutils/Coreutilsreferences2 versions
    (expand)+ 1 more
    • (no CPE)
    • cpe:2.3:a:uutils:coreutils:*:*:*:*:*:rust:*:*range: <0.7.0

Patches

1
e6a3bb596f14

cp: handle special files (#11163)

https://github.com/uutils/coreutilsVictor ProkhorovMar 4, 2026via ghsa
6 files changed · +179 53
  • Cargo.lock+1 0 modified
    @@ -3293,6 +3293,7 @@ dependencies = [
      "fluent",
      "indicatif",
      "libc",
    + "nix",
      "selinux",
      "tempfile",
      "thiserror 2.0.18",
    
  • src/uu/cp/Cargo.toml+1 0 modified
    @@ -41,6 +41,7 @@ fluent = { workspace = true }
     
     [target.'cfg(unix)'.dependencies]
     exacl = { workspace = true, optional = true }
    +nix = { workspace = true, features = ["fs"] }
     
     [[bin]]
     name = "cp"
    
  • src/uu/cp/locales/en-US.ftl+1 0 modified
    @@ -87,6 +87,7 @@ cp-error-selinux-get-context = failed to get security context of { $path }
     cp-error-selinux-error = SELinux error: { $error }
     cp-error-selinux-context-conflict = cannot combine --context (-Z) with --preserve=context
     cp-error-cannot-create-fifo = cannot create fifo { $path }: File exists
    +cp-error-cannot-create-special-file = cannot create special file { $path }: { $error }
     cp-error-invalid-attribute = invalid attribute { $value }
     cp-error-failed-to-create-whole-tree = failed to create whole tree
     cp-error-failed-to-create-directory = Failed to create directory: { $error }
    
  • src/uu/cp/locales/fr-FR.ftl+1 0 modified
    @@ -87,6 +87,7 @@ cp-error-selinux-get-context = échec de l'obtention du contexte de sécurité d
     cp-error-selinux-error = Erreur SELinux : { $error }
     cp-error-selinux-context-conflict = impossible de combiner --context (-Z) avec --preserve=context
     cp-error-cannot-create-fifo = impossible de créer le fifo { $path } : Le fichier existe
    +cp-error-cannot-create-special-file = impossible de créer le fichier spécial { $path } : { $error }
     cp-error-invalid-attribute = attribut invalide { $value }
     cp-error-failed-to-create-whole-tree = échec de la création de l'arborescence complète
     cp-error-failed-to-create-directory = Échec de la création du répertoire : { $error }
    
  • src/uu/cp/src/cp.rs+46 53 modified
    @@ -2,15 +2,15 @@
     //
     // For the full copyright and license information, please view the LICENSE
     // file that was distributed with this source code.
    -// spell-checker:ignore (ToDO) copydir ficlone fiemap ftruncate linkgs lstat nlink nlinks pathbuf pwrite reflink strs xattrs symlinked deduplicated advcpmv nushell IRWXG IRWXO IRWXU IRWXUGO IRWXU IRWXG IRWXO IRWXUGO
    +// spell-checker:ignore (ToDO) copydir ficlone fiemap ftruncate linkgs lstat nlink nlinks pathbuf pwrite reflink strs xattrs symlinked deduplicated advcpmv nushell IRWXG IRWXO IRWXU IRWXUGO IRWXU IRWXG IRWXO IRWXUGO sflag
     
     use std::cmp::Ordering;
     use std::collections::{HashMap, HashSet};
     use std::ffi::OsString;
     use std::fmt::Display;
     use std::fs::{self, Metadata, OpenOptions, Permissions};
     #[cfg(unix)]
    -use std::os::unix::fs::{FileTypeExt, PermissionsExt};
    +use std::os::unix::fs::{FileTypeExt, MetadataExt, PermissionsExt};
     #[cfg(unix)]
     use std::os::unix::net::UnixListener;
     use std::path::{Path, PathBuf, StripPrefixError};
    @@ -22,6 +22,8 @@ use uucore::translate;
     use clap::{Arg, ArgAction, ArgMatches, Command, builder::ValueParser, value_parser};
     use filetime::FileTime;
     use indicatif::{ProgressBar, ProgressStyle};
    +#[cfg(unix)]
    +use nix::sys::stat::{Mode, SFlag, dev_t, mknod as nix_mknod, mode_t};
     use thiserror::Error;
     
     use platform::copy_on_write;
    @@ -2227,13 +2229,8 @@ fn handle_copy_mode(
         source_metadata: &Metadata,
         symlinked_files: &mut HashSet<FileInformation>,
         source_in_command_line: bool,
    -    source_is_fifo: bool,
    -    source_is_socket: bool,
         created_parent_dirs: &mut HashSet<PathBuf>,
    -    #[cfg(unix)] source_is_stream: bool,
     ) -> CopyResult<PerformedAction> {
    -    let source_is_symlink = source_metadata.is_symlink();
    -
         match options.copy_mode {
             CopyMode::Link => {
                 if dest.exists() {
    @@ -2267,13 +2264,9 @@ fn handle_copy_mode(
                     dest,
                     options,
                     context,
    -                source_is_symlink,
    -                source_is_fifo,
    -                source_is_socket,
    +                source_metadata,
                     symlinked_files,
                     created_parent_dirs,
    -                #[cfg(unix)]
    -                source_is_stream,
                 )?;
             }
             CopyMode::SymLink => {
    @@ -2291,13 +2284,9 @@ fn handle_copy_mode(
                                 dest,
                                 options,
                                 context,
    -                            source_is_symlink,
    -                            source_is_fifo,
    -                            source_is_socket,
    +                            source_metadata,
                                 symlinked_files,
                                 created_parent_dirs,
    -                            #[cfg(unix)]
    -                            source_is_stream,
                             )?;
                         }
                         UpdateMode::None => {
    @@ -2328,13 +2317,9 @@ fn handle_copy_mode(
                                 dest,
                                 options,
                                 context,
    -                            source_is_symlink,
    -                            source_is_fifo,
    -                            source_is_socket,
    +                            source_metadata,
                                 symlinked_files,
                                 created_parent_dirs,
    -                            #[cfg(unix)]
    -                            source_is_stream,
                             )?;
                         }
                     }
    @@ -2344,13 +2329,9 @@ fn handle_copy_mode(
                         dest,
                         options,
                         context,
    -                    source_is_symlink,
    -                    source_is_fifo,
    -                    source_is_socket,
    +                    source_metadata,
                         symlinked_files,
                         created_parent_dirs,
    -                    #[cfg(unix)]
    -                    source_is_stream,
                     )?;
                 }
             }
    @@ -2582,15 +2563,6 @@ fn copy_file(
             context,
         );
     
    -    #[cfg(unix)]
    -    let source_is_fifo = source_metadata.file_type().is_fifo();
    -    #[cfg(unix)]
    -    let source_is_socket = source_metadata.file_type().is_socket();
    -    #[cfg(not(unix))]
    -    let source_is_fifo = false;
    -    #[cfg(not(unix))]
    -    let source_is_socket = false;
    -
         let source_is_stream = is_stream(&source_metadata);
     
         let performed_action = handle_copy_mode(
    @@ -2601,11 +2573,7 @@ fn copy_file(
             &source_metadata,
             symlinked_files,
             source_in_command_line,
    -        source_is_fifo,
    -        source_is_socket,
             created_parent_dirs,
    -        #[cfg(unix)]
    -        source_is_stream,
         )?;
     
         if options.verbose && performed_action != PerformedAction::Skipped {
    @@ -2741,18 +2709,14 @@ fn handle_no_preserve_mode(options: &Options, org_mode: u32) -> u32 {
     
     /// Copy the file from `source` to `dest` either using the normal `fs::copy` or a
     /// copy-on-write scheme if --reflink is specified and the filesystem supports it.
    -#[allow(clippy::too_many_arguments)]
     fn copy_helper(
         source: &Path,
         dest: &Path,
         options: &Options,
         context: &str,
    -    source_is_symlink: bool,
    -    source_is_fifo: bool,
    -    source_is_socket: bool,
    +    source_metadata: &Metadata,
         symlinked_files: &mut HashSet<FileInformation>,
         created_parent_dirs: &mut HashSet<PathBuf>,
    -    #[cfg(unix)] source_is_stream: bool,
     ) -> CopyResult<()> {
         if options.parents {
             let parent = dest.parent().unwrap_or(dest);
    @@ -2765,13 +2729,21 @@ fn copy_helper(
             return Err(CpError::NotADirectory(dest.to_path_buf()));
         }
     
    -    if source_is_socket && options.recursive && !options.copy_contents {
    -        #[cfg(unix)]
    -        copy_socket(dest, options.overwrite, options.debug)?;
    -    } else if source_is_fifo && options.recursive && !options.copy_contents {
    -        #[cfg(unix)]
    -        copy_fifo(dest, options.overwrite, options.debug)?;
    -    } else if source_is_symlink {
    +    #[cfg(unix)]
    +    if options.recursive && !options.copy_contents {
    +        let ft = source_metadata.file_type();
    +        if ft.is_socket() {
    +            return copy_socket(dest, options.overwrite, options.debug);
    +        }
    +        if ft.is_fifo() {
    +            return copy_fifo(dest, options.overwrite, options.debug);
    +        }
    +        if ft.is_char_device() || ft.is_block_device() {
    +            return copy_node(dest, source_metadata, options.overwrite, options.debug);
    +        }
    +    }
    +
    +    if source_metadata.is_symlink() {
             copy_link(source, dest, symlinked_files, options)?;
         } else {
             let copy_debug = copy_on_write(
    @@ -2781,7 +2753,7 @@ fn copy_helper(
                 options.sparse_mode,
                 context,
                 #[cfg(unix)]
    -            source_is_stream,
    +            is_stream(source_metadata),
             )?;
     
             if !options.attributes_only && options.debug {
    @@ -2816,6 +2788,27 @@ fn copy_socket(dest: &Path, overwrite: OverwriteMode, debug: bool) -> CopyResult
         Ok(())
     }
     
    +#[cfg(unix)]
    +fn copy_node(
    +    dest: &Path,
    +    source_metadata: &Metadata,
    +    overwrite: OverwriteMode,
    +    debug: bool,
    +) -> CopyResult<()> {
    +    if dest.exists() {
    +        overwrite.verify(dest, debug)?;
    +        fs::remove_file(dest)?;
    +    }
    +    let sflag = if source_metadata.file_type().is_char_device() {
    +        SFlag::S_IFCHR
    +    } else {
    +        SFlag::S_IFBLK
    +    };
    +    let mode = Mode::from_bits_truncate(source_metadata.mode() as mode_t);
    +    nix_mknod(dest, sflag, mode, source_metadata.rdev() as dev_t)
    +        .map_err(|e| translate!("cp-error-cannot-create-special-file", "path" => dest.quote(), "error" => e.desc()).into())
    +}
    +
     fn copy_link(
         source: &Path,
         dest: &Path,
    
  • tests/by-util/test_cp.rs+129 0 modified
    @@ -5,6 +5,8 @@
     
     // spell-checker:ignore (flags) reflink (fs) tmpfs (linux) rlimit Rlim NOFILE clob btrfs neve ROOTDIR USERDIR outfile uufs xattrs
     // spell-checker:ignore bdfl hlsl IRWXO IRWXG nconfined matchpathcon libselinux-devel prwx doesnotexist reftests subdirs mksocket srwx
    +#[cfg(unix)]
    +use rstest::rstest;
     use uucore::display::Quotable;
     #[cfg(feature = "feat_selinux")]
     use uucore::selinux::get_getfattr_output;
    @@ -3171,6 +3173,133 @@ fn test_cp_fifo() {
         assert_eq!(permission, "prwx-wx--x".to_string());
     }
     
    +#[rstest]
    +#[case::recursive("-R")]
    +#[case::archive("-a")]
    +#[cfg(unix)]
    +fn test_cp_recursive_char_device(#[case] flag: &str) {
    +    use nix::sys::stat::{Mode, SFlag, mknod as nix_mknod};
    +    use std::os::unix::fs::{FileTypeExt, MetadataExt, PermissionsExt};
    +    use uutests::util::run_ucmd_as_root;
    +    let ts = TestScenario::new(util_name!());
    +    let at = &ts.fixtures;
    +    let mode = Mode::S_IRUSR | Mode::S_IWUSR | Mode::S_IRGRP;
    +    if nix_mknod(
    +        at.plus("null").as_path(),
    +        SFlag::S_IFCHR,
    +        mode,
    +        uucore::fs::makedev(1, 3),
    +    )
    +    .is_err()
    +    {
    +        print!("Test skipped; creating a char device node requires CAP_MKNOD");
    +        return;
    +    }
    +    if let Ok(result) = run_ucmd_as_root(&ts, &[flag, "null", "null2"]) {
    +        result.success();
    +        let null2_metadata = at.plus("null2").metadata().unwrap();
    +        assert!(null2_metadata.file_type().is_char_device());
    +        assert_eq!(
    +            null2_metadata.rdev(),
    +            at.plus("null").metadata().unwrap().rdev()
    +        );
    +        assert_eq!(
    +            null2_metadata.permissions().mode() & 0o777,
    +            mode.bits() as _
    +        );
    +    } else {
    +        print!("Test skipped; copying a char device node requires CAP_MKNOD");
    +    }
    +}
    +
    +#[rstest]
    +#[case::recursive("-R")]
    +#[case::archive("-a")]
    +#[cfg(unix)]
    +fn test_cp_recursive_char_device_no_permission(#[case] flag: &str) {
    +    new_ucmd!()
    +        .args(&[flag, "/dev/null", "null2"])
    +        .fails()
    +        .stderr_is("cp: cannot create special file 'null2': Operation not permitted\n");
    +}
    +
    +#[test]
    +#[cfg(unix)]
    +fn test_cp_recursive_char_device_copy_contents() {
    +    let (at, mut ucmd) = at_and_ucmd!();
    +    ucmd.args(&["-R", "--copy-contents", "/dev/null", "null2"])
    +        .succeeds()
    +        .no_stderr();
    +    assert!(at.plus("null2").metadata().unwrap().file_type().is_file());
    +    assert_eq!(at.read("null2"), "");
    +}
    +
    +#[test]
    +#[cfg(unix)]
    +fn test_cp_char_device() {
    +    let (at, mut ucmd) = at_and_ucmd!();
    +    ucmd.args(&["/dev/null", "null2"]).succeeds().no_stderr();
    +    assert!(at.plus("null2").metadata().unwrap().file_type().is_file());
    +    assert_eq!(at.read("null2"), "");
    +}
    +
    +#[rstest]
    +#[case::recursive("-R")]
    +#[case::archive("-a")]
    +#[cfg(unix)]
    +fn test_cp_recursive_block_device(#[case] flag: &str) {
    +    use nix::sys::stat::{Mode, SFlag, mknod as nix_mknod};
    +    use std::os::unix::fs::{FileTypeExt, MetadataExt};
    +    use uutests::util::run_ucmd_as_root;
    +    let ts = TestScenario::new(util_name!());
    +    let at = &ts.fixtures;
    +    if nix_mknod(
    +        at.plus("sda").as_path(),
    +        SFlag::S_IFBLK,
    +        Mode::S_IRUSR | Mode::S_IWUSR,
    +        uucore::fs::makedev(8, 0),
    +    )
    +    .is_err()
    +    {
    +        print!("Test skipped; creating a block device node requires CAP_MKNOD");
    +        return;
    +    }
    +    if let Ok(result) = run_ucmd_as_root(&ts, &[flag, "sda", "sda2"]) {
    +        result.success();
    +        let sda2_metadata = at.plus("sda2").metadata().unwrap();
    +        assert!(sda2_metadata.file_type().is_block_device());
    +        assert_eq!(
    +            sda2_metadata.rdev(),
    +            at.plus("sda").metadata().unwrap().rdev()
    +        );
    +    } else {
    +        print!("Test skipped; copying a block device node requires CAP_MKNOD");
    +    }
    +}
    +
    +#[test]
    +#[cfg(unix)]
    +fn test_cp_block_device_no_permission() {
    +    use nix::sys::stat::{Mode, SFlag, mknod as nix_mknod};
    +    let ts = TestScenario::new(util_name!());
    +    let at = &ts.fixtures;
    +    if nix_mknod(
    +        at.plus("sda").as_path(),
    +        SFlag::S_IFBLK,
    +        Mode::S_IRUSR | Mode::S_IWUSR,
    +        uucore::fs::makedev(8, 0),
    +    )
    +    .is_err()
    +    {
    +        print!("Test skipped; creating a block device node requires CAP_MKNOD");
    +        return;
    +    }
    +    ts.ucmd()
    +        .args(&["-R", "sda", "sda2"])
    +        .fails()
    +        .stderr_is("cp: cannot create special file 'sda2': Operation not permitted\n");
    +}
    +
     #[test]
     #[cfg(unix)]
     fn test_cp_socket() {
    

Vulnerability mechanics

Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

6

News mentions

0

No linked articles in our index yet.